從Element-ui源碼聊搭建UI庫 系列二
這一篇是承接上一篇《從Element-ui源碼聊搭建UI庫 系列一》,
上一篇主要分析了做組件庫和閱讀 Element 源碼的目的。大致介紹源碼中的目錄結(jié)構(gòu),細(xì)講了 packages 目錄和 package.json,最后解析 script 腳本命令中的前兩者bootstrap 和 build:file 命令。這一篇 從 scripts 腳本命令解析繼續(xù)分析其余命令。
五、 scripts腳本命令解析
build:theme
"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
主要是處理樣式相關(guān)的腳本。拆開來分析下:
node build/bin/gen-cssfile
執(zhí)行該文件通過組件列表生成對應(yīng)的 css 文件和 theme-chalk/index.scss 文件,并將所有組件的樣式都導(dǎo)入。以后每次新增一個組件不用手動導(dǎo)入,執(zhí)行命令自動導(dǎo)入。
比如創(chuàng)建了新組件 aaa,執(zhí)行命令后生成 aaa.scss,并且 index.scss 多了 aaa。
來看下具體實(shí)現(xiàn):
var fs = require('fs');
var path = require('path');
var Components = require('../../components.json');
var themes = [
'theme-chalk'
];
Components = Object.keys(Components);
var basepath = path.resolve(__dirname, '../../packages/');
function fileExists(filePath) {
try {
return fs.statSync(filePath).isFile();
} catch (err) {
return false;
}
}
themes.forEach((theme) => {
var isSCSS = theme !== 'theme-default';
var indexContent = isSCSS ? '@import "./base.scss";\n' : '@import "./base.css";\n';
Components.forEach(function(key) {
// 導(dǎo)入的組件不在packages文件夾下,需要過濾
// 以下是option-group的代碼
// import ElOptionGroup from '../select/src/option-group';
// /* istanbul ignore next */
// ElOptionGroup.install = function(Vue) {
// Vue.component(ElOptionGroup.name, ElOptionGroup);
// };
// export default ElOptionGroup;
if (['icon', 'option', 'option-group'].indexOf(key) > -1) return;
var fileName = key + (isSCSS ? '.scss' : '.css');
// @import "./popconfirm.scss";
indexContent += '@import "./' + fileName + '";\n';
// 組裝組件css文件路徑 E:\element-master\element-master\packages\theme-chalk\src\popconfirm.scss
var filePath = path.resolve(basepath, theme, 'src', fileName);
// 文件不存在就創(chuàng)建遺漏的css文件
if (!fileExists(filePath)) {
fs.writeFileSync(filePath, '', 'utf8');
console.log(theme, ' 創(chuàng)建遺漏的 ', fileName, ' 文件');
}
});
// 往index.scss中寫入導(dǎo)入 如:@import "./base.scss";
fs.writeFileSync(path.resolve(basepath, theme, 'src', isSCSS ? 'index.scss' : 'index.css'), indexContent);
});
gulp build --gulpfile packages/theme-chalk/gulpfile.js
把所有的 scss 文件通過 gulp 編譯成 css。
打包和壓縮的工作平時一般交給 webpack 來做,但是基于工作流用 gulp 更加快捷和方便。
'use strict';
const {
series,
src,
dest
} = require('gulp');
// 編譯gulp工具
const sass = require('gulp-sass');
// 添加廠商前綴
const autoprefixer = require('gulp-autoprefixer');
// 壓縮css
const cssmin = require('gulp-cssmin');
// src下面的所有文件編譯到lib下
function compile() {
return src('./src/*.scss')
.pipe(sass.sync()) //把scss編譯成css
.pipe(autoprefixer({ //基于目標(biāo)瀏覽器版本,添加廠商前綴
browsers: ['ie > 9', 'last 2 versions'],
cascade: false
}))
.pipe(cssmin()) //壓縮css
// dest: 流會將 vinyl File保存到指定目錄下
.pipe(dest('./lib')); //輸出到lib下
}
// 讀取src下的fonts文件目錄輸出到lib下
function copyfont() {
return src('./src/fonts/**')
.pipe(cssmin())
.pipe(dest('./lib/fonts'));
}
// series: 接受可變數(shù)量的字符串(taskName)和/或函數(shù)(fn),并返回組合任務(wù)或函數(shù)的一個函數(shù)
exports.build = series(compile, copyfont);

執(zhí)行命令輸出 css 文件,放入 lib 目錄:

為什么需要編譯呢?
因?yàn)?element 在使用時有兩種引入方式:
全局引用:
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
引入了 lib\theme-chalk\index.css 文件
局部引入:
import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';
Vue.component(Button.name, Button);
Vue.component(Select.name, Select);
/* 或?qū)憺?br> * Vue.use(Button)
* Vue.use(Select)
*/
new Vue({
el: '#app',
render: h => h(App)
});
不需要引入 css 文件,只需引入對應(yīng)的 scss 文件。這就是為什么需要編譯 scss 的原因。
cp-cli packages/theme-chalk/lib lib/theme-chalk
cp-cli 是一個跨平臺的 copy 工具 將gulp build --gulpfile .\packages\theme-chalk\gulpfile.js編譯生成的 css 目錄(packages/theme-chalk/lib)復(fù)制到 lib/theme-chalk 下

方便全局引用,導(dǎo)入 css
import 'element-ui/lib/theme-chalk/index.css';
build:utils
"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js"
將工具函數(shù)通過 babel 轉(zhuǎn)譯后移動到 lib 下,方便項(xiàng)目中使用。

將 src 目錄下的內(nèi)容忽略 index.js 通過 babel 轉(zhuǎn)譯,然后移動到 lib 下

比如 mousewheel 文件
沒轉(zhuǎn)譯的:

轉(zhuǎn)譯后的:

build:umd
"build:umd": "node build/bin/build-locale.js"
執(zhí)行后生成 umd 模塊的語言包。
將 src/locale/lang 下的語言包都編譯到 lib/umd/locale 下。
var fs = require('fs');
// 導(dǎo)出文件
var save = require('file-save');
// 解析為絕對路徑
var resolve = require('path').resolve;
// 獲取擴(kuò)展名,返回path最后一部分 path.basename('/foo/bar/quux.html', '.html'); // 返回:‘quux’
var basename = require('path').basename;
var localePath = resolve(__dirname, '../../src/locale/lang');
// 讀取src/locale/lang下的列表
var fileList = fs.readdirSync(localePath);
// 通過babel轉(zhuǎn)譯
var transform = function (filename, name, cb) {
// https://babel.docschina.org/docs/en/6.26.3/babel-core/
require('babel-core').transformFile(resolve(localePath, filename), {
plugins: [
'add-module-exports',
['transform-es2015-modules-umd', {
loose: true
}]
],
moduleId: name
}, cb);
};
fileList
// 過濾js文件
.filter(function (file) {
return /\.js$/.test(file);
})
.forEach(function (file) {
var name = basename(file, '.js');
// 異步轉(zhuǎn)譯文件中的全部內(nèi)容
transform(file, name, function (err, result) {
if (err) {
console.error(err);
} else {
var code = result.code;
code = code
.replace('define(\'', 'define(\'element/locale/')
.replace('global.', 'global.ELEMENT.lang = global.ELEMENT.lang || {}; \n global.ELEMENT.lang.');
save(resolve(__dirname, '../../lib/umd/locale', file)).write(code);
console.log(file);
}
});
});
clean
"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage"
清除打包好后的文件
deploy:build
"deploy:build": "npm run build:file && cross-env NODE_ENV=production webpack --config build/webpack.demo.js && echo element.eleme.io>>examples/element-ui/CNAME"
npm run build:file 前文分析過了,主要構(gòu)建官網(wǎng)文件。接下來分析新的構(gòu)建腳本。
生產(chǎn)環(huán)境下構(gòu)建官網(wǎng)。
cross-env NODE_ENV=production webpack --config build/webpack.demo.js
生產(chǎn)環(huán)境下構(gòu)建官網(wǎng)。
官網(wǎng)項(xiàng)目的 webpack 配置:
// webpack第三方插件
// https://github.com/webpack-contrib
const path = require('path');
const webpack = require('webpack');
// 這個插件將CSS提取到單獨(dú)的文件中。它為每個包含CSS的JS文件創(chuàng)建一個CSS文件。它支持按需加載CSS和SourceMaps。
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const config = require('./config');
const isProd = process.env.NODE_ENV === 'production';
// "dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js",
const isPlay = !!process.env.PLAY_ENV;
const webpackConfig = {
mode: process.env.NODE_ENV,
entry: isProd ? {
docs: './examples/entry.js'
} : (isPlay ? './examples/play.js' : './examples/entry.js'),
output: {
path: path.resolve(process.cwd(), './examples/element-ui/'),
publicPath: process.env.CI_ENV || '',
filename: '[name].[hash:7].js',
chunkFilename: isProd ? '[name].[hash:7].js' : '[name].js'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: config.alias,
modules: ['node_modules']
},
// 開發(fā)服務(wù)器
// webpack-dev-server 可用于快速開發(fā)應(yīng)用程序
devServer: {
host: '0.0.0.0',
port: 8085,
publicPath: '/',
hot: true
},
performance: {
hints: false
},
stats: {
children: false
},
module: {
rules: [{
enforce: 'pre',
test: /\.(vue|jsx?)$/,
exclude: /node_modules/,
loader: 'eslint-loader'
},
{
test: /\.(jsx?|babel|es6)$/,
include: process.cwd(),
exclude: config.jsexclude,
loader: 'babel-loader'
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
},
{
test: /\.(scss|css)$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'sass-loader'
]
},
// 使用md-loader加載模塊
{
test: /\.md$/,
use: [{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
},
{
loader: path.resolve(__dirname, './md-loader/index.js')
}
]
},
{
test: /\.(svg|otf|ttf|woff2?|eot|gif|png|jpe?g)(\?\S*)?$/,
loader: 'url-loader',
// todo: 這種寫法有待調(diào)整
query: {
limit: 10000,
name: path.posix.join('static', '[name].[hash:7].[ext]')
}
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: './examples/index.tpl',
filename: './index.html',
favicon: './examples/favicon.ico'
}),
new CopyWebpackPlugin([{
from: 'examples/versions.json'
}]),
new ProgressBarPlugin(),
new VueLoaderPlugin(),
new webpack.DefinePlugin({
'process.env.FAAS_ENV': JSON.stringify(process.env.FAAS_ENV)
}),
new webpack.LoaderOptionsPlugin({
vue: {
compilerOptions: {
preserveWhitespace: false
}
}
})
],
optimization: {
minimizer: []
},
devtool: '#eval-source-map'
};
// 生產(chǎn)環(huán)境更換插件\優(yōu)化\擴(kuò)展等
if (isProd) {
webpackConfig.externals = {
vue: 'Vue',
'vue-router': 'VueRouter',
'highlight.js': 'hljs'
};
webpackConfig.plugins.push(
new MiniCssExtractPlugin({
filename: '[name].[contenthash:7].css'
})
);
webpackConfig.optimization.minimizer.push(
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false
}),
new OptimizeCSSAssetsPlugin({})
);
// https://webpack.js.org/configuration/optimization/#optimizationsplitchunks
webpackConfig.optimization.splitChunks = {
cacheGroups: {
vendor: {
test: /\/src\//,
name: 'element-ui',
chunks: 'all'
}
}
};
webpackConfig.devtool = false;
}
module.exports = webpackConfig;
deploy:extension
"deploy:extension": "cross-env NODE_ENV=production webpack --config build/webpack.extension.js"
在生產(chǎn)環(huán)境下構(gòu)建主題插件,主題編輯器的 chorme 插件項(xiàng)目的 webpack 配置,項(xiàng)目在 extension 目錄下。執(zhí)行命令后會在 extension 目錄下生成 dist 目錄,其中包含了 chorme 插件,在瀏覽器加載已解壓的擴(kuò)展程序就可以使用主題生成插件。
dev:extension
"dev:extension": "rimraf examples/extension/dist && cross-env NODE_ENV=development webpack --watch --config build/webpack.extension.js"
啟動主題插件的開發(fā)環(huán)境,可以進(jìn)行開發(fā)調(diào)試。
dev
"dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js"
首先 npm run bootstrap 是用來安裝依賴的。npm run build:file 在前面也有提到,主要用來自動化生成一些文件。主要是 node build/bin/build-entry.js,用于生成 Element 的入口 js:先是讀取根目錄的 components.json,這個 json 文件維護(hù)著 Element 所有的組件路徑映射關(guān)系,鍵為組件名,值為組件源碼的入口文件;然后遍歷鍵值,將所有組件進(jìn)行 import,對外暴露 install 方法,把所有 import 的組件通過 Vue.component(name, component) 方式注冊為全局組件,并且把一些彈窗類的組件掛載到 Vue 的原型鏈上。
在生成了入口文件的 src/index.js 之后就會運(yùn)行 webpack-dev-server。
啟動組件庫本地開發(fā)環(huán)境。在更改后可以熱更新官網(wǎng)。具體 webpack 配置見 webpack.demo.js。
dev:play
"dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js"
組件測試項(xiàng)目,在 examples/play/index.vue 中可以引入組件庫任意組件,也可以直接使用 dev 啟動的項(xiàng)目,在文檔中使用組件。
用于查看某個組件的效果。適用于組件按需加載的顯示效果。在 webpack.demo.js 通過環(huán)境變量配置輸入。
element-master\build\webpack.demo.js
const isPlay = !!process.env.PLAY_ENV;
……省略webpack具體配置
entry: isProd ? {
docs: './examples/entry.js'
} : (isPlay ? './examples/play.js' : './examples/entry.js'),
element-master\examples\play.js
import Vue from 'vue';
import Element from 'main/index.js';
import App from './play/index.vue';
import 'packages/theme-chalk/src/index.scss';
Vue.use(Element);
new Vue({ // eslint-disable-line
render: h => h(App)
}).$mount('#app');
element-master\examples\play\index.vue
<template>
<div style="margin: 20px;">
<el-input v-model="input" placeholder="請輸入內(nèi)容"></el-input>
</div>
</template>
<script>
export default {
data() {
return {
input: 'Hello Element UI!'
};
}
};
</script>
dist
"dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme"
打包組件庫
npm run clean && npm run build:file && npm run lint 都已經(jīng)解釋過了,分別是清除上一次打包產(chǎn)物、生成入口文件以及 i18n 文件和 eslint 檢測。
webpack --config build/webpack.conf.js
生成 umd 格式的 js 文件(index.js)
const path = require('path');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const TerserPlugin = require('terser-webpack-plugin');
const config = require('./config');
console.log(config)
module.exports = {
// 模式
mode: 'production',
// 入口
entry: {
app: ['./src/index.js']
},
// 輸出
output: {
path: path.resolve(process.cwd(), './lib'),
publicPath: '/dist/',
// 輸出的文件名
filename: 'index.js',
// 初始的chunk文件名稱
chunkFilename: '[id].js',
// library 暴露為 AMD 模塊。 在 AMD 或 CommonJS 的 require 之后可訪問(libraryTarget:'umd')
libraryTarget: 'umd',
// 入口的默認(rèn)導(dǎo)出將分配給 library target:
// if your entry has a default export of `MyDefaultModule`
// var MyDefaultModule = _entry_return_.default;
libraryExport: 'default',
// 輸出一個庫,為你的入口做導(dǎo)出。
library: 'ELEMENT',
// 會把 AMD 模塊命名為 UMD 構(gòu)建
umdNamedDefine: true,
// 為了使 UMD 構(gòu)建在瀏覽器和 Node.js 上均可用,應(yīng)將 output.globalObject 選項(xiàng)設(shè)置為 'this'。對于類似 web 的目標(biāo),默認(rèn)為 self。
globalObject: 'typeof self !== \'undefined\' ? self : this'
},
// 解析
resolve: {
// 能夠使用戶在引入模塊時不帶擴(kuò)展.嘗試按順序解析這些后綴名。如果有多個文件有相同的名字,但后綴名不同,webpack 會解析列在數(shù)組首位的后綴的文件 并跳過其余的后綴。
extensions: ['.js', '.vue', '.json'],
// 創(chuàng)建 import 或 require 的別名,來確保模塊引入變得更簡單。
alias: config.alias
},
// 外部擴(kuò)展
externals: {
vue: config.vue
},
// 優(yōu)化
optimization: {
// 允許你通過提供一個或多個定制過的 TerserPlugin 實(shí)例, 覆蓋默認(rèn)壓縮工具(minimizer)
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: false
}
}
})
]
},
// 性能
performance: {
// 不展示警告或錯誤提示。
// 官網(wǎng)推薦使用error,有助于防止把體積大的bundle部署到生產(chǎn)環(huán)境,從而影響網(wǎng)頁的性能
// 很奇怪這里要把它關(guān)閉
hints: false
},
// stats對象
stats: {
// 告知 stats 是否添加關(guān)于子模塊的信息。
children: false
},
// 模塊
module: {
// 使用babel-loader和vue-loader
rules: [
{
test: /\.(jsx?|babel|es6)$/,
include: process.cwd(),
exclude: config.jsexclude,
loader: 'babel-loader'
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
}
]
},
// 插件
plugins: [
new ProgressBarPlugin(),
new VueLoaderPlugin()
]
};
webpack --config build/webpack.common.js
生成 commonjs 格式的 js 文件(element-ui.common.js),require 時默認(rèn)加載的是這個文件。
libraryTarget: 'commonjs2'
與 webpack.conf.js 不同在于輸出 output 的 libraryExport。
前者暴露的是 commonjs2,后者暴露的是 umd。
webpack --config build/webpack.component.js
與前兩者的 index.js 入口不同,以 components.json 為入口,將每一個組件打包生成一個文件,用于按需加載。
npm run build:utils && npm run build:umd && npm run build:theme 也已經(jīng)講過,分別是轉(zhuǎn)譯工具方法、轉(zhuǎn)譯語言包、生成樣式文件。
lint
"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet"
eslint 校驗(yàn) src 和 build 目錄下的文件。
pub
"pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh"
npm run bootstrap 下載依賴。
sh build/git-release.sh:主要是檢測 dev 分支是否沖突。
#!/usr/bin/env sh
# 切換到dev分支
git checkout dev
# 檢測本地是否有未提交文件
if test -n "$(git status --porcelain)"; then
# 輸出日志
echo 'Unclean working tree. Commit or stash changes first.' >&2;
exit 128;
fi
# 檢測本地分支是否有誤
if ! git fetch --quiet 2>/dev/null; then
# 輸出日志
echo 'There was a problem fetching your branch. Run `git fetch` to see more...' >&2;
exit 128;
fi
# 檢測是否有最新提交
if test "0" != "$(git rev-list --count --left-only @'{u}'...HEAD)"; then
# 輸出日志
echo 'Remote history differ. Please pull changes.' >&2;
exit 128;
fi
# 輸出日志
echo 'No conflicts.' >&2;
sh build/release.sh 腳本完成了以下工作:
合并 dev 分支到 master 修改樣式包和組件庫的版本號 發(fā)布樣式包和組件庫 提交 master 和 dev 分支到遠(yuǎn)程倉庫
該腳本在發(fā)布組件庫時可以使用,特別是其中自動更改版本號的功能(每次 publish 時都忘改版本號)。這里提交代碼到遠(yuǎn)程倉庫的日志很簡單。
#!/usr/bin/env sh
set -e
# 切換到master
git checkout master
# 合并dev分支
git merge dev
# npx: 使用本地已安裝的可執(zhí)行工具,而不需要配置 scripts
VERSION=`npx select-version-cli`
# 更新版本號
read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r
echo # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]
then
# 輸出:壓縮版本
echo "Releasing $VERSION ..."
# build
# 編譯打包
VERSION=$VERSION npm run dist
# ssr test
node test/ssr/require.test.js
# 發(fā)布到npm
# publish theme
# 輸出:壓縮theme-chalk版本
echo "Releasing theme-chalk $VERSION ..."
cd packages/theme-chalk
# 更改主題包的版本信息
npm version $VERSION --message "[release] $VERSION"
# 如果是beta版本則打個beta標(biāo)簽
if [[ $VERSION =~ "beta" ]]
then
npm publish --tag beta
else
npm publish
fi
cd ../..
# commit
git add -A
git commit -m "[build] $VERSION"
# 更改組件庫的版本信息
npm version $VERSION --message "[release] $VERSION"
# publish
# 發(fā)布到遠(yuǎn)程倉庫
git push eleme master
git push eleme refs/tags/v$VERSION
git checkout dev
git rebase master
git push eleme dev
# 發(fā)布組件庫
if [[ $VERSION =~ "beta" ]]
then
npm publish --tag beta
else
npm publish
fi
fi
node build/bin/gen-indices.js 生成目錄,支持搜索:
'use strict';
// 生成目錄
const fs = require('fs');
const path = require('path');
// 是一個托管的全文、數(shù)字和分面搜索引擎,能夠從第一次擊鍵交付實(shí)時結(jié)果。
const algoliasearch = require('algoliasearch');
// 將Unicode str轉(zhuǎn)換為段字符串,確保在URL或文件名中使用它是安全的。
// https://www.npmjs.com/package/transliteration?activeTab=readme
// demo:
// slugify('你好,世界');
// // ni-hao-shi-jie
const slugify = require('transliteration').slugify;
// 密鑰
const key = require('./algolia-key');
const client = algoliasearch('4C63BTGP6S', key);
const langs = {
'zh-CN': 'element-zh',
'en-US': 'element-en',
'es': 'element-es',
'fr-FR': 'element-fr'
};
// 四種語言
['zh-CN', 'en-US', 'es', 'fr-FR'].forEach(lang => {
const indexName = langs[lang];
const index = client.initIndex(indexName);
index.clearIndex(err => {
if (err) return;
// 讀取/examples/docs/中的文件
fs.readdir(path.resolve(__dirname, `../../examples/docs/${ lang }`), (err, files) => {
if (err) return;
let indices = [];
files.forEach(file => {
console.log(file)
const component = file.replace('.md', '');
const content = fs.readFileSync(path.resolve(__dirname, `../../examples/docs/${ lang }/${ file }`), 'utf8');
const matches = content
.replace(/:::[\s\S]*?:::/g, '')
.replace(/```[\s\S]*?```/g, '')
.match(/#{2,4}[^#]*/g)
.map(match => match.replace(/\n+/g, '\n').split('\n').filter(part => !!part))
.map(match => {
const length = match.length;
if (length > 2) {
const desc = match.slice(1, length).join('');
return [match[0], desc];
}
return match;
});
indices = indices.concat(matches.map(match => {
const isComponent = match[0].indexOf('###') < 0;
const title = match[0].replace(/#{2,4}/, '').trim();
const index = { component, title };
index.ranking = isComponent ? 2 : 1;
index.anchor = slugify(title);
index.content = (match[1] || title).replace(/<[^>]+>/g, '');
return index;
}));
});
index.addObjects(indices, (err, res) => {
console.log(err, res);
});
});
});
});
test
"test": "npm run lint && npm run build:theme && cross-env CI_ENV=/dev/ BABEL_ENV=test karma start test/unit/karma.conf.js --single-run" "test:watch": "npm run build:theme && cross-env BABEL_ENV=test karma start test/unit/karma.conf.js"
單元測試:UI 組件作為高度抽象的基礎(chǔ)公共組件,編寫單元測試是很有必要的。
小結(jié)
系列二結(jié)合系列一中 bootstrap 和 build:file 腳本命令。我們一共學(xué)習(xí)了 15 中命令,分別是:
bootstrap安裝依賴包build:file構(gòu)建官網(wǎng)所需的文件build:theme構(gòu)建主題樣式build:utils用babel編譯工具函數(shù)庫build:umd編譯生成umd模塊的語言包clean清理打包后的文件deploy:build生產(chǎn)環(huán)境下構(gòu)建官網(wǎng)deploy:extension在生產(chǎn)環(huán)境下構(gòu)建主題插件dev:extension啟動主題插件的開發(fā)環(huán)境,可以進(jìn)行開發(fā)調(diào)試dev啟動本地開發(fā)環(huán)境,進(jìn)行能夠調(diào)試開發(fā)dev:play用于查看某個組件的效果dist打包組件庫lint校驗(yàn)文件pub檢測分支是否沖突test測試項(xiàng)目
我們不僅知道了每條命令目的,還逐行解析了命令所對應(yīng)的構(gòu)建腳本,這對于我們對項(xiàng)目工程化、模塊化很有幫助。所以我們還要繼續(xù)對源碼進(jìn)行學(xué)習(xí)和推敲。
最后,希望大家一定要點(diǎn)贊三連。
可以閱讀我的其他文章,見blog地址
一個學(xué)習(xí)編程技術(shù)的公眾號。每天推送高質(zhì)量的優(yōu)秀博文、開源項(xiàng)目、實(shí)用工具、面試技巧、編程學(xué)習(xí)資源等等。目標(biāo)是做到個人技術(shù)與公眾號一起成長。歡迎大家關(guān)注,一起進(jìn)步,走向全棧大佬的修煉之路
