本身一直想搭建一套本身的博客系統,今年年底終於有時間開發,在開發後臺cms系統時,使用antd組件庫,忽然一天問本身,組件庫是怎麼實現的,內部是怎麼運行的,打包部署是怎麼個原理,帶着這些疑問,我開始萌生了本身開發一套組件庫的想法。說幹就幹,本身花費了大概2個多月的時間完成了deer-ui
初版的組件開發,如今分享出來,記錄下開發過程踩過的坑。javascript
首先肯定需求,初期先調研了流行的組件庫,如antd
,vant
,zarm-web
,cuke-ui
,iview
等等,最後選定antd做爲ui參考。前端
docz
,styleguidist
,storebook
),最後選擇storebook
做爲展現網站,不只限於此,項目中還集成了docz
,styleguidist
打包,若有須要可實現。create-react-app
搭建了一個react環境,在example
文件夾下,使用npm run dev
,便可打開調試環境,引入編寫的組件便可.二、源碼中搭建了一套組件庫的文檔部署環境,使用命令npm run storybook
,便可進入文檔模式,引入編寫的組件便可.java
.storebook
storebook 的一些配置components
放置全部組件example
組件調試環境代碼scripts
發佈,打包的腳本文件stories
項目靜態文檔,負責 demo 演示使用webpack4完成組件庫的打包,組件庫用戶通常經過es module
,commonjs
以及script
腳本的方式引入,這就須要咱們的組件庫知足這些規範,一般使用webpack打包成umd
規範便可知足上面不一樣的引入。node
... mode: "production", entry: { [name]: ["./components/index.js"] }, output: { path: path.join(process.cwd(), "dist"), library: name, libraryTarget: "umd", umdNamedDefine: true, filename: "index.js", }, ...
打包的入口是components/index.js
文件,主要是導出組件react
export { default as Button } from './button'; export { default as Tabs } from './tabs'; export { default as Icon } from './icons';
最後打包後會在dist目錄下生成index.js
文件,爲全部打包後組件。webpack
其餘配置不一一詳述了,若有須要查看scripts
目錄。webpack
經過上述webpack
打包的組件庫代碼,是全量引入的,全部的組件代碼都打包在一塊兒。爲了減少引入組件庫大小,通常都採用按需加載。按需加載的核心是要單獨將每一個組件打包出來,經過import
方式單獨引入。git
經過 babel 打包js文件 "build:lib": "cross-env OUTPUT_MODULE=commonjs babel components -d lib" "build:es": "babel components -d es",
將各組件的樣式文件提取到lib
,es
文件對應的組件下面,css打包代碼以下,這塊是借鑑cuke-ui
的配置,經過gulp
流的方式,打包css文件。github
/** * @name gulpfile.js * @description 打包項目css依賴 */ const path = require("path"); const gulp = require("gulp"); const concat = require("gulp-concat"); const less = require("gulp-less"); 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 { name,browserList } = require("../package.json"); const DIR = { less: path.resolve(__dirname, "../components/**/*.less"), buildSrc: [ path.resolve(__dirname, "../components/**/style.less"), path.resolve(__dirname, "../components/**/index.less") ], lib: path.resolve(__dirname, "../lib"), es: path.resolve(__dirname, "../es"), dist: path.resolve(__dirname, "../dist") }; gulp.task("copyLess", () => { return gulp .src(DIR.less) .pipe(gulp.dest(DIR.lib)) .pipe(gulp.dest(DIR.es)); }); gulp.task("copyCss", () => { return gulp .src(DIR.buildSrc) .pipe(sourcemaps.init()) .pipe( less({ outputStyle: "compressed" }) ) .pipe(autoprefixer({ browsers: browserList })) .pipe(size()) .pipe(cssnano()) .pipe(gulp.dest(DIR.lib)) .pipe(gulp.dest(DIR.es)); }); gulp.task("dist", () => { return gulp .src(DIR.buildSrc) .pipe(sourcemaps.init()) .pipe( less({ outputStyle: "compressed" }) ) .pipe(autoprefixer({ browsers: browserList })) .pipe(concat(`${name}.css`)) .pipe(size()) .pipe(gulp.dest(DIR.dist)) .pipe(sourcemaps.write()) .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(size()) .pipe(gulp.dest(DIR.dist)); }); gulp.task("default", gulp.series(["copyLess", "copyCss", "dist"]));
文檔採用storybook
來搭建,文檔地址預覽,具體配置以下web
import React from "react"; import { configure, addDecorator,addParameters } from '@storybook/react'; const { name, repository, version } = require("../package.json") import { configureActions } from '@storybook/addon-actions'; import '@storybook/addon-console'; import '@storybook/addon-options/register'; import "../stories/style/index.less" import "../stories/style/code.less" function loadStories() { // 介紹 require('../stories/index'); //基礎組件 require('../stories/basis'); //數據展現 require('../stories/showData'); //操做反饋 require('../stories/feedback'); //交互組件 require('../stories/interaction'); //佈局組件 require('../stories/layout'); } configureActions({ depth: 100 }) //加載配置 addParameters({ options: { name: `${name} v${version}`, title: "Deer-ui", url: repository, showSearchBox: false, showPanel: false, enableShortcuts:false, isToolshown: false, selectedPanel: undefined, hierarchySeparator: null, hierarchyRootSeparator: null, showAddonPanel: false, }}) //中間content邊距 addDecorator(story => <div style={{ padding: "0 60px 50px" }}>{story()}</div>) configure(loadStories, module);
編寫組件文檔
storiesOf("操做反饋", module) .add("Spin 加載中", () => ( <div> <h4>基本使用</h4> <div style={{ marginBottom: "30px" }}> <Spin /> </div> <CodeView value={` import { Spin } from 'deer-ui' <Spin /> `} ></CodeView> ))
最後單獨給storybook
配置webpack.config.js
,注意配置格式和通常的webpack
有點區別
const webpack = require("webpack"); module.exports = async ({ config, mode }) => { config.resolve = { extensions: [".js", ".jsx", ".json", ".jsx"] }; config.module.rules.push({ test: /\.(js|jsx)?$/, loaders: [require.resolve("@storybook/source-loader")], enforce: "pre", exclude: /node_modules/ }); config.module.rules.push({ test: /\.scss$/, use: [ "style-loader", "css-loader", "postcss-loader", { loader: "sass-loader" } ] }, { test: /\.less$/, use: [ "style-loader", "css-loader", "postcss-loader", { loader: "less-loader", options: { javascriptEnabled: true }, } ] }); config.plugins.push( new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn|en-gb/) ); return config; };
項目框架已經搭建完成,在components
目錄下就能夠開發組件了,例如button組件,刪減版
...省略 class Button extends Component { return ( <div className={cls(prefixCls, {[`${prefixCls}-block`]:block})}> <button type="button" {...isDisabled} className={cls(`${prefixCls}-btn`,className,`${prefixCls}-btn-${type}` </button> </div> ); } } export default Button;
在index.js文件中導出button組件
export { default as Button } from './button';
開發完成組件庫後,經過把代碼發佈到npm倉庫便可,默認你們都會發布npm包,若是有不瞭解請參考掘金上有關npm問題。框架提供了自動化發佈命令,打包,發版,lint,日誌等功能
npm run pub:prod //自動完成css,js,es,lin,umd打包,自動生成changelog,發佈npm倉庫,爲修訂版版本號。1.0.* npm run pub:major //都會完成上述不一樣,惟一區別是,打的npm版本號不一樣,此命令是打主版本號,不常常用 *.0.0 npm run pub:minor //都會完成上述不一樣,惟一區別是,打的npm版本號不一樣,此命令是打次版本號,不常常用 1.*.0 "pub:prod": "npm run standard:patch && npm run build && npm publish --registry https://registry.npmjs.org && git push" //打版本號和發版
組件庫提供兩種發佈方式
一、npm run pub:docs 採用storybook的方式去發佈,該方式須要在package.json中配置帳號信息。 "storybook-deployer": { "gitUsername": "deer-ui", "gitEmail": "your email", "commitMessage": "docs: deploy docs" }, 2.npm run deploy //該命令會執行腳本deploy.sh文件,打包併發布組件庫文檔 #!/bin/bash # 確保腳本拋出遇到的錯誤 set -e echo "start build..." # 打包文檔 npm run build:docs echo "√ build success" # 進入生成的文件夾 cd .docs echo "start publish..." # 提交到 gh-pages git config --get remote.origin.url git init git config user.name "xxxx" git config user.email "xxxx" git add . git commit -m 'docs:publish' git push --force --quiet git@github.com:zhangboyang123/deer-ui.git master:gh-pages echo "√ publish success 🦌" cd -
Deer-ui
使用less
做爲樣式開發語言,並定義了一系列全局/組件的樣式變量,你能夠根據需求進行相應調整。
如下是一些最經常使用的通用變量,全部樣式變量能夠在 這裏 找到。
@primary-color: #31c27c; //全局色 @warning-color: #fca130; //警告色 @error-color: #f93e3e; //失敗色 @success-color: #35C613; //成功色 @info-color: #61affe; //信息展現色 @default-color: #d9d9d9; //默認色 @border-color: #e8e8e8; //邊框顏色 @border-radius: 4px; //邊框圓角 @font-size: 14px; //默認組件字體大小 @font-size-small: 12px; //小字體 @font-size-large: 16px; //大字體 @bg-color: #FAFAFA; //組件背景色 @font-color: rgba(0, 0, 0, .65); //字體顏色 @disabled-font-color: fade(@font-color, 30%); //禁用字體顏色
主題定製原理上是使用 less
提供的 modifyVars
的方式進行覆蓋變量。使用webpack
中配置less-loader
的options
。注意javascriptEnabled
要打開。
// webpack.config.js module.exports = { rules: [{ test: /\.less$/, use: [{ loader: 'style-loader', }, { loader: 'css-loader', // translates CSS into CommonJS }, { loader: 'less-loader', // compiles Less to CSS + options: { + modifyVars: { + 'primary-color': '#1DA57A', + 'info-color': '#1DA57A', + 'font-size': '12px', + // or + 'hack': `true; @import "your-less-file-path.less";`, // 或者引用本地樣式文件覆蓋 + }, + javascriptEnabled: true, + }, }], }], }
注意,定製主題後,less-loader
的處理範圍不能過濾掉 node_modules
下的 deer-ui
包。
import Button from 'deer-ui/es/button'; import 'deer-ui/es/button/style.less';
// 單獨使用在.babelrc.js中配置 module.exports = { plugins: [ ["import", { "libraryName": "deer-ui", "libraryDirectory": "es", "style":true },"deer-ui"], ] } // 多個組件庫,例如antd module.exports = { plugins: [ ["import", { "libraryName": "deer-ui", "libraryDirectory": "es", "style": true },'deer-ui'], ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": true },'antd'], ]
### 八、後續計劃
最後暢想下,使用ts完成組件庫的重構。
開發完組件庫,也許沒啥意義, 可是經過這個組件庫, 讓我學到了不少平時 接觸不到的知識點,有時看着很簡單的東西,本身動手會發現裏面有好多坑。整體來講,去年給本身定的小目標已經實現,今年繼續在前端的路上不停的折騰,正所謂,生命不息,coding不止😝。