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
複製代碼