内容简介:stories:storybook 主目录相当于一般项目下的 src 目录stories/pages目录,用于存放 storybook 所有相关页面,storybook 虽然有自己的 webpack 配置, 但是显然无法满足一些复杂的情况, 在 storybook 中可通过创建
- 初始化项目
git init npm init -y 复制代码
- 创建 .gitignore
node_modules coverage dist es lib package-lock.json 复制代码
二、 基于 storybook 搭建开发测试环境
2.1 项目快速搭建
-
快速搭建 storybook react环境
npx -p @storybook/cli sb init --type react 复制代码
- 当前目录介绍
stories:storybook 主目录相当于一般项目下的 src 目录 index.stories.js: storybook 的入口文件 .storybook: storybook 配置目录
├── .gitignore ├── package.json ├── package-lock.json ├── stories │ ├── index.stories.js └── .storybook ├── addons.js └── config.js 复制代码
2.2 目录架构调整
stories/pages目录,用于存放 storybook 所有相关页面, stories/config.js 作为页面配置在入口文件 index.stories.js 中进行引用并根据该配置渲染出所有相关页面,同时假设我们 storybook 有一级目录 基本
且目录下有 介绍
页面, 那么对应的添加 stories/pages/base/Introduce 目录,目录下 api.md 用于编写对应组件 api 文档(当然介绍页面并没有 api 文档),index.css 作为当前页面的样式文件,index.jsx 则作为当前页面的入口文件, subpage 则用于存放页面的子页面。
├── .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 复制代码
2.3 编写测试代码
- 编写 stories/pages/base/Introduce/index.jsx
import React from 'react'; import './index.css'; export default () => { return ( <div> react 组件介绍 </div> ); }; 复制代码
- 编写 stories/config.js
import Introduce from './pages/base/Introduce'; export default [ { title: '介绍', module: '基本', component: Introduce } ]; 复制代码
- 编写 stories/index.stories.js
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))); 复制代码
- 添加 npm 脚本
{ "scripts": { + "start": "npm run storybook", "storybook": "start-storybook -p 8080", "build-storybook": "build-storybook" }, } 复制代码
- 执行脚本
npm start
2.4 自定义 webpack 配置
storybook 虽然有自己的 webpack 配置, 但是显然无法满足一些复杂的情况, 在 storybook 中可通过创建 .storybook/webpack.config.js
来自定义 webpack 配置。
- 下载相关依赖
# 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'], }, } 复制代码
- 项目下新增 babel 配置文件
.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 配置文件
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" ] }) ] }; 复制代码
- 项目下新增 eslint 配置文件
.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 复制代码
三、 测试组件的创建和发布
3.1 components 目录设计
项目根目录下创建 components 目录用于存放组件库的所有组件, components 目录的结构则参考 antd 进行设计, 假设有组件 input-number 那么 components 可以是下面这样:
- index.js 作为组件库的入口文件
- assets/iconfont 存放字体图标文件
- assets/style 用于存放通用样式文件
- input-number 用于存放 input-number 组件的所有源码
- input-number/index.jsx 作为组件的入口文件
- input-number/style 用于存放组件的所有样式文件
- input-number/style/index.js 组件样式的入口文件(该组件引入组件所有需要的样式文件,之后配合 babel-plugin-import 实现按需加载功能 )
- __tests__ 用于存放组件的单元测试
├── components │ ├── assets │ │ ├── iconfont │ │ └── style │ ├── index.js │ └── input-number │ ├── index.jsx │ ├── style │ │ ├── index.js │ │ └── index.scss │ └── __tests__ .... 复制代码
3.2 测试组件 input-number 编写
- 编写
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'; 复制代码
- 在 storybook 中对组件进行测试
在 stories/pages/base/Introduceindex.jsx 引用 InputNumber 组件,并运行项目对组件进行测试
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> ); }; 复制代码
3.3 配置脚本对组件进行编译打包
对于 npm 包我们在发布时需要对所需要发布的文件进行简单的编译和打包, 对于我们的组件库我们常常需要将组件库编译为 ES 模块和 CommonJS 以及 UMD 模块。
ES 模块和 CommonJS 模块的编译方式大同小异, 都是通过 babel 针对组件库中 js 模块进行编译对于其他文件则只需进行简单拷贝,当然针对样式文件还需要额外编译一份 css 文件, 唯一区别的是打包后的 js 模块不同一个是 ES 模块一个是 CommonJS 模块,同时编译后的 ES 模块和 CommonJS 模块的目录结构需要满足 babel-plugin-import 原理,从而实现按需加载功能。
UMD 模块则是通过 webpack 针对 component 中入口文件进行完整的打包编译
3.3.1 通过 babel 对组件库中 js 模块进行编译
- 安装所需依赖
npm install cross-env @babel/cli -D 复制代码
- 编写 npm 脚本: 针对组件库中的 js 模块编译为 ES 模块和 CommonJS 模块
{ "scripts": { + "build:lib": "cross-env OUTPUT_MODULE=commonjs babel components -d lib --ignore **/__tests__", + "build:es": "babel components -d es --ignore **/__tests__" } } 复制代码
3.3.2 通过 gulp 对组件库中进行处理
- 安装所需依赖
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 复制代码
- 创建 scripts/gulpfile.js
/** * @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', )); 复制代码
- 添加 npm 脚本
{ "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__" } } 复制代码
3.3.3 通过 webpack 对组件库进行 UMD 模块 打包
- 相关依赖包安装
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 复制代码
- 创建 scripts/build.umd.js
/** * @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; 复制代码
- 添加 npm 脚本
{ "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__" } } 复制代码
3.4 修改 package.json
- private 设置项目是否是私有包
- files 设置在发布包时需要发布的文件和目录
- main 设置包的入口文件
- module 设置 npm 包的模块入口
- peerDependencies 设置 npm 包同等依赖包
{ + "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" + }, } 复制代码
3.4 组件发布
- 组件库编译
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/ 复制代码
四、 其他配置
4.1 commit 校验以及版本发布配置
- 依赖包安装
# 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 和 commitizen 配置
# 生成 commitlint 配置文件 echo "module.exports = {extends: ['@commitlint/config-angular']};" > commitlint.config.js # commitizen 初始化 commitizen init cz-conventional-changelog --save-dev --save-exact 复制代码
- 更新 package.json
{ "scripts": { + "commit": "git-cz", + "release": "standard-version" }, + "husky": { + "hooks": { + "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" + } + } } 复制代码
4.2 editorconfig 配置
- 项目下新增
.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 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 搭建CocosCreator组件库
- 可视化搭建平台的地图组件和日历组件方案选型
- Android组件化入门:一步步搭建组件化架构
- Android组件化框架搭建
- 【手牵手】搭建前端组件库(一)
- 使用storybook搭建私有组件库
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。