git init
npm init -y
复制代码
node_modules
coverage
dist
es
lib
package-lock.json
复制代码
参考文档javascript
快速搭建 storybook react环境css
npx -p @storybook/cli sb init --type react
复制代码
stories: storybook 主目录至关于通常项目下的 src 目录 index.stories.js: storybook 的入口文件 .storybook: storybook 配置目录java
├── .gitignore
├── package.json
├── package-lock.json
├── stories
│ ├── index.stories.js
└── .storybook
├── addons.js
└── config.js
复制代码
stories/pages 目录,用于存放 storybook 全部相关页面, stories/config.js 做为页面配置在入口文件 index.stories.js 中进行引用并根据该配置渲染出全部相关页面,同时假设咱们 storybook 有一级目录 基本
且目录下有 介绍
页面, 那么对应的添加 stories/pages/base/Introduce 目录,目录下 api.md 用于编写对应组件 api 文档(固然介绍页面并无 api 文档),index.css 做为当前页面的样式文件,index.jsx 则做为当前页面的入口文件, subpage 则用于存放页面的子页面。node
├── .gitignore
├── package.json
├── package-lock.json
├── stories
│ ├── config.js
│ ├── index.stories.js
│ └── pages
│ └── base
│ └── Introduce
│ ├── api.md
│ ├── index.css
│ ├── index.jsx
│ └── subpage
└── .storybook
├── addons.js
└── config.js
复制代码
import React from 'react';
import './index.css';
export default () => {
return (
<div> react 组件介绍 </div>
);
};
复制代码
import Introduce from './pages/base/Introduce';
export default [
{
title: '介绍',
module: '基本',
component: Introduce
}
];
复制代码
import React from 'react';
import { storiesOf } from '@storybook/react';
import config from './config';
config.forEach( v => (storiesOf(v.module, module).add(v.title, v.component)));
复制代码
{
"scripts": {
+ "start": "npm run storybook",
"storybook": "start-storybook -p 8080",
"build-storybook": "build-storybook"
},
}
复制代码
npm start
storybook 虽然有本身的 webpack 配置, 可是显然没法知足一些复杂的状况, 在 storybook 中可经过建立 .storybook/webpack.config.js
来自定义 webpack 配置。react
# 1. webpack 安装
npm install webpack webpack-cli -D
# 2. babel-loader 相关依赖包安装
npm install babel-loader @babel/core -D
# 3. babel 相关预设依赖包安装
npm install @babel/preset-env @babel/preset-react -D
# 4. babel 相关插件依赖包安装
npm install @babel/plugin-transform-runtime -D
npm install @babel/plugin-proposal-decorators -D
npm install @babel/plugin-transform-async-to-generator -D
# 5. eslint-loader 相关依赖
npm install eslint eslint-loader -D
# 6. eslint 相关插件安装
npm install babel-eslint eslint-plugin-babel eslint-plugin-react -D
# 7. 样式文件加载配置所需依赖
npm install style-loader css-loader sass-loader node-sass postcss-loader -D
# 8. postcss-loader 相关插件依赖包安装
npm install autoprefixer -D
# 9. 图片字体加载所需依赖
npm install url-loader file-loader -D
# 10. 文本文件加载所需依赖
npm install raw-loader -D
复制代码
.storybook/webpack.config.js
文件const path = require('path');
const webpack = require('webpack');
// 路径别名
const alias = {};
module.exports = {
mode: 'production',
module: {
rules: [
{ // js 模块打包
test: /\.(mjs|js|jsx)$/,
exclude: [ path.resolve(__dirname, 'node_modules') ],
use: ['babel-loader', 'eslint-loader']
}, { // 样式文件打包
test: /\.(css|scss)$/,
use: [
'style-loader', {
loader: 'css-loader',
options: {
sourceMap: false,
}
}, {
loader: 'postcss-loader',
options: { javascriptEnabled: true, sourceMap: false },
}, {
loader: 'sass-loader'
}
],
}, { // 文字图片打包
test: /\.(png|jpg|gif|woff|svg|eot|ttf)$/,
use: [{
loader: 'url-loader',
options: {
limit: 10 * 1000,
}
}]
}, { // 文本文件加载(后期可能须要引入 markdown 文件)
test: /\.(txt|md)$/,
use: 'raw-loader',
},
]
},
plugins: [
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn|en-gb/),
],
// 解析模块
resolve: {
alias,
// 自动解析肯定的扩展
extensions: ['.mjs', '.js', '.jsx'],
},
}
复制代码
.babelrc
{
"plugins": [
// 为api提供沙箱的垫片方案,不会污染全局的 api
["@babel/plugin-transform-runtime"],
// 修饰器
["@babel/plugin-proposal-decorators", { "legacy": true }],
// asyn await 支持
["@babel/plugin-transform-async-to-generator"]
],
"presets": ["@babel/preset-react", "@babel/preset-env"]
}
复制代码
postcss.config.js
module.exports = {
plugins: [
require("autoprefixer")({
browsers: [
"last 2 versions",
"Android >= 4.4",
"Firefox ESR",
"not ie < 9",
"ff >= 30",
"chrome >= 34",
"safari >= 6",
"opera >= 12.1",
"ios >= 6"
]
})
]
};
复制代码
.eslintrc.js
.eslintignore
// .eslintrc.js 配置文件
module.exports = {
parserOptions: {
ecmaVersion: 8,
sourceType: "module",
ecmaFeatures: {
jsx: true
}
},
parser: "babel-eslint",
plugins: ["babel", "react"],
extends: "eslint:recommended",
env: {
es6: true,
browser: true,
commonjs: true
},
globals: {
process: true,
describe: true,
it: true,
__dirname: true,
expect: true,
jest: true,
beforeAll: true,
afterEach: true
},
rules: {
"object-shorthand": "error",
"generator-star-spacing": ["error", "after"],
camelcase: ["error", { properties: "never" }],
eqeqeq: ["error", "smart"],
"linebreak-style": ["error", "unix"],
"new-cap": "error",
"no-array-constructor": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-param-reassign": "error",
"no-sequences": "error",
"no-shadow-restricted-names": "error",
"no-unneeded-ternary": "error",
"no-unused-expressions": "error",
"no-unused-vars": "off",
"no-use-before-define": ["error", "nofunc"],
"no-var": "error",
"prefer-arrow-callback": "error",
"prefer-spread": "error",
"prefer-template": "error",
"wrap-iife": ["error", "inside"],
yoda: ["error", "never"],
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"react/jsx-no-undef": ["error", { allowGlobals: true }],
"react/jsx-no-bind": ["error", { allowArrowFunctions: true }],
"react/jsx-key": "error",
"react/no-unknown-property": "error",
"react/no-string-refs": "error",
"react/no-direct-mutation-state": "error",
"no-console": "off"
}
};
复制代码
# .eslintignore 配置文件
tests
node_modules
*.bundle.js
*.js.map
.history
dist
.vscode
**/*.snap
复制代码
项目根目录下建立 components 目录用于存放组件库的全部组件, components 目录的结构则参考 antd 进行设计, 假设有组件 input-number 那么 components 能够是下面这样:webpack
├── components
│ ├── assets
│ │ ├── iconfont
│ │ └── style
│ ├── index.js
│ └── input-number
│ ├── index.jsx
│ ├── style
│ │ ├── index.js
│ │ └── index.scss
│ └── __tests__
....
复制代码
components/input-number/index.jsx
import React from 'react';
export default () => {
return (
<div className="qyrc-input-num"> 测试组件: input-number </div>
);
};
复制代码
components/input-number/style/index.scss
.qyrc-input-num {
color: #999;
}
复制代码
components/input-number/style/index.js
// 若是组件须要额外样式文件则须要一同引入
import './index.scss';
复制代码
components/index.js
导出组件export { default as InputNumber } from './input-number';
复制代码
在 stories/pages/base/Introduceindex.jsx 引用 InputNumber 组件,并运行项目对组件进行测试ios
import React from 'react';
import './index.css';
import './index.scss';
// 引入 InputNumber 组件和样式
import { InputNumber } from '../../../../components';
import '../../../../components/input-number/style/index';
export default () => {
return (
<div> react 组件介绍 <InputNumber /> </div>
);
};
复制代码
对于 npm 包咱们在发布时须要对所须要发布的文件进行简单的编译和打包, 对于咱们的组件库咱们经常须要将组件库编译为 ES 模块和 CommonJS 以及 UMD 模块。git
ES 模块和 CommonJS 模块的编译方式大同小异, 都是经过 babel 针对组件库中 js 模块进行编译对于其余文件则只需进行简单拷贝,固然针对样式文件还须要额外编译一份 css 文件, 惟一区别的是打包后的 js 模块不一样一个是 ES 模块一个是 CommonJS 模块,同时编译后的 ES 模块和 CommonJS 模块的目录结构须要知足 babel-plugin-import 原理,从而实现按需加载功能。es6
UMD 模块则是经过 webpack 针对 component 中入口文件进行完整的打包编译web
npm install cross-env @babel/cli -D
复制代码
{
"scripts": {
+ "build:lib": "cross-env OUTPUT_MODULE=commonjs babel components -d lib --ignore **/__tests__",
+ "build:es": "babel components -d es --ignore **/__tests__"
}
}
复制代码
npm install gulp -D
npm install gulp-sass -D
npm install gulp-concat -D
npm install gulp-autoprefixer -D
npm install gulp-cssnano -D
npm install gulp-filesize -D
npm install gulp-sourcemaps -D
npm install gulp-rename -D
npm install gulp-replace -D
复制代码
/** * @name gulpfile.js * @description 打包项目css依赖 * @description 参考 cuke-ui */
const fs = require('fs');
const path = require('path');
const gulp = require('gulp');
const concat = require('gulp-concat');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssnano = require('gulp-cssnano');
const size = require('gulp-filesize');
const sourcemaps = require('gulp-sourcemaps');
const rename = require('gulp-rename');
const replace = require('gulp-replace');
const { name } = require('../package.json');
const browserList = [
'last 2 versions',
'Android >= 4.0',
'Firefox ESR',
'not ie < 9'
];
const DIR = {
// 输入目录
scss: path.resolve(__dirname, '../components/**/*.scss'),
buildSrc: path.resolve(__dirname, '../components/**/style/*.scss'),
style: path.resolve(__dirname, '../components/**/style/index.js'),
// 输入目录
lib: path.resolve(__dirname, '../lib'),
es: path.resolve(__dirname, '../es'),
dist: path.resolve(__dirname, '../dist')
};
// 拷贝 scss 文件
gulp.task('copyScss', () => {
return gulp
.src(DIR.scss)
.pipe(gulp.dest(DIR.lib))
.pipe(gulp.dest(DIR.es));
});
// 对 scss 进行编译后拷贝
gulp.task('copyCss', () => {
return gulp
.src(DIR.scss)
.pipe(sourcemaps.init())
.pipe(sass())
.pipe(autoprefixer({ browsers: browserList }))
.pipe(size())
.pipe(cssnano())
.pipe(gulp.dest(DIR.lib))
.pipe(gulp.dest(DIR.es));
});
// 建立 style/css.js
gulp.task('createCss', () => {
return gulp
.src(DIR.style)
.pipe(replace(/\.scss/, '.css'))
.pipe(rename({ basename: 'css' }))
.pipe(gulp.dest(DIR.lib))
.pipe(gulp.dest(DIR.es));
});
// 编译打包全部组件的样式至 dis 目录
gulp.task('dist', () => {
return gulp
.src(DIR.buildSrc)
.pipe(sourcemaps.init())
.pipe(sass())
.pipe(autoprefixer({ browsers: browserList }))
.pipe(concat(`${name}.css`))
.pipe(size())
.pipe(gulp.dest(DIR.dist))
.pipe(sourcemaps.write())
.pipe(rename(`${name}.css.map`))
.pipe(size())
.pipe(gulp.dest(DIR.dist))
.pipe(cssnano())
.pipe(concat(`${name}.min.css`))
.pipe(size())
.pipe(gulp.dest(DIR.dist))
.pipe(sourcemaps.write())
.pipe(rename(`${name}.min.css.map`))
.pipe(size())
.pipe(gulp.dest(DIR.dist));
});
gulp.task('default', gulp.parallel(
'dist',
'copyCss',
'copyScss',
'createCss',
));
复制代码
{
"scripts": {
+ "build:css": "cd scripts && gulp",
"build:lib": "cross-env OUTPUT_MODULE=commonjs babel components -d lib --ignore **/__tests__",
"build:es": "babel components -d es --ignore **/__tests__"
}
}
复制代码
npm install uglifyjs-webpack-plugin -D
npm install optimize-css-assets-webpack-plugin -D
npm install mini-css-extract-plugin -D
npm install progress-bar-webpack-plugin -D
复制代码
/** * @name UMD 模块 打包 * @description 参考 cuke-ui * @description 输出目录 [dist] * CMD Node.js 环境 * AMD 浏览器环境 * UMD 两种环境均可以执行 */
const fs = require("fs");
const path = require("path");
const webpack = require("webpack");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const { version, name, description } = require("../package.json");
const LOGO = ` __ _ _______ __/ /_____ __ __(_) / ___/ / / / //_/ _ \\______/ / / / / / /__/ /_/ / ,< / __/_____/ /_/ / / \\___/\\__,_/_/|_|\\___/ \\__,_/_/ `
const config = {
mode: "production",
entry: {
[name]: ["./components/index.js"]
},
//umd 模式打包
output: {
library: name,
libraryTarget: "umd",
umdNamedDefine: true, // 是否将模块名称做为 AMD 输出的命名空间
path: path.join(process.cwd(), "dist"),
filename: "[name].min.js"
},
//react 和 react-dom 不打包
externals: {
react: {
root: "React",
commonjs2: "react",
commonjs: "react",
amd: "react"
},
"react-dom": {
root: "ReactDOM",
commonjs2: "react-dom",
commonjs: "react-dom",
amd: "react-dom"
}
},
resolve: {
enforceExtension: false,
extensions: [".js", ".jsx", ".json", ".less", ".css"]
},
module: {
rules: [
{
test: /\.js[x]?$/,
use: [
{
loader: "babel-loader"
}
],
exclude: "/node_modules/",
include: [path.resolve("components")]
},
{
test: /\.(le|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{ loader: "postcss-loader", options: { sourceMap: false } },
{
loader: "sass-loader",
options: {
sourceMap: false
}
}
]
},
{
test: /\.(jpg|jpeg|png|gif|cur|ico)$/,
use: [
{
loader: "file-loader",
options: {
name: "images/[name][hash:8].[ext]" //遇到图片 生成一个images文件夹 名字.后缀的图片
}
}
]
}
]
},
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
uglifyOptions: {
compress: {
drop_debugger: true,
drop_console: false
},
}
}),
new OptimizeCSSAssetsPlugin({
// 压缩css 与 ExtractTextPlugin 配合使用
cssProcessor: require("cssnano"),
cssProcessorOptions: { discardComments: { removeAll: true } }, // 移除全部注释
canPrint: true // 是否向控制台打印消息
})
],
noEmitOnErrors: true,
},
plugins: [
new ProgressBarPlugin(),
new MiniCssExtractPlugin({
filename: "[name].min.css"
}),
// 在打包的文件以前 加上版权说明
new webpack.BannerPlugin(` \n ${name} v${version} \n ${description} \n ${LOGO}\n`),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("production"),
__DEBUG__: false,
}),
new webpack.LoaderOptionsPlugin({
minimize: true
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
]
};
module.exports = config;
复制代码
{
"scripts": {
+ "build:publish": "npm run build:lib && npm run build:es && npm run build:css && npm run build:umd",
+ "build:umd": "webpack --config ./scripts/build.umd.js",
"build:css": "cd scripts && gulp",
"build:lib": "cross-env OUTPUT_MODULE=commonjs babel components -d lib --ignore **/__tests__",
"build:es": "babel components -d es --ignore **/__tests__"
}
}
复制代码
{
+ "private": false,
+ "files": [
+ "lib",
+ "es",
+ "dist",
+ "LICENSE"
+ ],
+ "main": "lib/index.js",
+ "module": "es/index.js",
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ },
}
复制代码
npm run build:publish
复制代码
# 1. 切换官方源头
npm config set registry http://registry.npmjs.org
# 2. 登陆 npm
npm login
# 3. 发布包
npm publish --access public
# 4. 若是须要则切换回淘宝源
npm config set registry https://registry.npm.taobao.org/
复制代码
# husky 包安装
npm install husky --save-dev
# commitlint 所需包安装
npm install @commitlint/config-angular @commitlint/cli --save-dev
# commitizen 包安装
npm install commitizen --save-dev
npm install commitizen -g
# standard-version 包安装
npm install standard-version --save-dev
复制代码
# 生成 commitlint 配置文件
echo "module.exports = {extends: ['@commitlint/config-angular']};" > commitlint.config.js
# commitizen 初始化
commitizen init cz-conventional-changelog --save-dev --save-exact
复制代码
{
"scripts": {
+ "commit": "git-cz",
+ "release": "standard-version"
},
+ "husky": {
+ "hooks": {
+ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
+ }
+ }
}
复制代码
.editorconfig
配置文件# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
复制代码