前段时间在组件库npm化相关开发的过程中,遇到了不少的坑,在构建的流程上参照很多Element-UI处理方式,对Element-UI以及构建流程有了一些感悟,在这里分享一下自己学习到的关于Element-UI的构建流程的内容。
本次分析内容概要

文件篇幅比较长,由于里面加入了大量的element-ui的源码导致。本文主要从element-ui的构建流程上做了一些概览性的分析,没有做太多代码实现细节上的解读,希望通过此次分享能力对工程化的构建上有一个整体性的认识。
目录结构分析
在学习一个开源库的时候,首先就是看它的目录结构,通过目录结构可以对整个库的设计有一个初步的认知。以下是element-ui的外层目录。
├── .github //md文档,issue模板文件等github相关文件。├── build // 项目构建相关的处理文件。├── example //element-ui官网说明文档,包括组件示例文档。├── packages // element-ui组件源码。├── src // 组件的index.js入口文件,以及各种辅助类文件。├── test // 测试文件。├── types // 声明文件。├── babelrc├── .eslintignore├── .eslintrc├── .gitattributes**: github上传的项目相关定义,这里是代码语言类型定义,定义为Vue。├── .gitignore├── .travis.yml // 与github上的项目绑定,提供CI/CD服务,element-ui的自动化部署相关内容就在这里。├── CHANGELOG.en-US.md├── CHANGELOG.es.md├── CHANGELOG.fr-FR.md├── CHANGELOG.zh-CN.md├── FAQ.md├── LICENSE├── Makefile // 定义了一系列的编译命令,使用make命令执行来执行一系列的操作,element-ui在这里定义了开发、构建、发布、部署相关的命令。├── README.md├── components.json // 配置文件,组件的名称与路径映射表,在工程的构建过程中使用频次很高。├── element_logo.svg├── package.json 项目的描述文件。├── yarn.lock
在上述文件中,有一个陌生的文件Makefile,在之前没有接触过,特意学习了一下,下面简单介绍一下这个文件。
Makefile文件简单介绍
Makefile是一个适用于C/C++的工具,较早作为工程化工具出现在UNIX系统中, 通过make命令来执行一系列的编译和连接操作。在拥有make环境的目录下, 如果存在一个Makefile文件。 那么输入make命令将会执行Makefile文件中的某个目标命令。 在mac 中,可以直接执行 make 命令。Windows系统则需要下载make的GUN工具。
在element-ui目录下执行make命令,会看到下图的展示结果,有一个element-ui的命令使用说明,列举了开发和构建的一些关键命令。
Makefile文件格式
Makefile 文件由一系列规则构成,每一条的规则需要明确两件事情:构建目标是什么,以及如何构建。 每一条的规则都遵循以下格式:
<target> : <prerequisites>[tab] <commands>
- target: 目标,通常是文件,也可以是标签(label),即
make install中的install。 - prerequisites:target的前置条件,可以是多个目标,先于target之前执行,可选项。
commands:要执行的语句,必须有一个tab键开始。prerequisites和commands都是可选的,但两者之间必须存在一个。
Element-UI中的Makefile
下面是Element-UI的makefile文件,截取了一部分,命令规则能明白,还有一些特殊的标识。分别介绍一下。
.PHONY:伪命令标识,正常情况下如果存在test文件,将会忽略下面的test命令,添加这个标识,下面的dist和test命令就会正常执行了。
- MAKECMDGOALS 变量,记录了命令行参数。make new test 将会打印出 new test 这两个字段。
- $(filter-out pattert, objs) 过滤函数,过滤掉 objs 中所有含有 pattert 的内容。
- $@ 指代的是当前的目标,即 new
- @符号,表示关闭输入,正常情况下会#号注释的内容也会被打印,使用@符号可关闭。
@# .PHONY 伪命令标识,正常情况下如果存在test文件,将会忽略下发的test命令,添加这个标识,下面的dist和test命令就会正常执行了 .PHONY: dist test default: help install-cn: npm install --registry=http://registry.npm.taobao.org dev: npm run dev @# MAKECMDGOALS 变量记录了命令行参数。make new test 将会打印出 new test 这两个字段 @# $(filter-out pattert, objs) 过滤函数,过滤掉 objs 中所有含有 pattert 的内容。 @# $@ 指代的是当前的目标,即 new @# make new test 测试 => test 测试 new: node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS)) dist: install npm run dist test: npm run test:watch help: @# @符号,表示关闭输入,正常情况下会#号注释的内容也会被打印,使用@符号可关闭。 @echo " \033[35mmake install\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 安装依赖" @echo " \033[35mmake new <component-name> [中文名]\033[0m\t--- 创建新组件 package. 例如 'make new button 按钮'"构建命令解析 npm-script
在makefile中我们已经看到一些重要的构建命令了,我们再来看一下package.json中都定义了哪些命令。如下,第一眼看到的时候是真糟心。这里就不追个的过了,根据上的makefile来调几个重要的看。"scripts": { "bootstrap": "yarn || npm i", "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js", "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk", "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js", "build:umd": "node build/bin/build-locale.js", "clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage", "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", "deploy:extension": "cross-env NODE_ENV=production webpack --config build/webpack.extension.js", "dev:extension": "rimraf examples/extension/dist && cross-env NODE_ENV=development webpack --watch --config build/webpack.extension.js", "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", "dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js", "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", "i18n": "node build/bin/i18n.js", "lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet", "pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh", "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", "components": "webpack --config build/webpack.component.js" }开发模式构建-npm run dev
&& 表示串行执行命令,即前面的命令执行成功之后在执行后面的。&表示并行执行命令,即后面的命令不用等待前面的命令执行成功与失败,同时执行。
npm run bootstrap // yarn || npm i
&& 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
yarn || npm i 优先使用yarn,如果yarn执行不成功则执行npm i。
npm run build:file
一些必要文件的构建,包括四个部分。
node build/bin/iconInit.js: 借助postcss.parse方法,生成css语法树,然后再遍历ast,匹配出所有定义的icon名称,最后保存到
examples/icon.json,用于官网的icon的展示。var postcss = require('postcss'); var fs = require('fs'); var path = require('path'); var fontFile = fs.readFileSync(path.resolve(__dirname, '../../packages/theme-chalk/src/icon.scss'), 'utf8'); var nodes = postcss.parse(fontFile).nodes; var classList = []; nodes.forEach((node) => { var selector = node.selector || ''; var reg = new RegExp(/\.el-icon-([^:]+):before/); var arr = selector.match(reg); if (arr && arr[1]) { classList.push(arr[1]); } }); classList.reverse(); // 希望按 css 文件顺序倒序排列 fs.writeFile(path.resolve(__dirname, '../../examples/icon.json'), JSON.stringify(classList), () => {});node build/bin/build-entry.js:使用components.json文件来生成
src/index.js文件,即element-ui的构建入口文件。var Components = require('../../components.json'); var fs = require('fs'); var render = require('json-templater/string'); var uppercamelcase = require('uppercamelcase'); var path = require('path'); var endOfLine = require('os').EOL; var OUTPUT_PATH = path.join(__dirname, '../../src/index.js'); var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';'; var INSTALL_COMPONENT_TEMPLATE = ' {{name}}'; var MAIN_TEMPLATE = `/* Automatically generated by './build/bin/build-entry.js' */ {{include}} import locale from 'element-ui/src/locale'; import CollapseTransition from 'element-ui/src/transitions/collapse-transition'; const components = [ {{install}}, CollapseTransition ]; const install = function(Vue, opts = {}) { locale.use(opts.locale); locale.i18n(opts.i18n); components.forEach(component => { Vue.component(component.name, component); }); Vue.use(InfiniteScroll); Vue.use(Loading.directive); Vue.prototype.$ELEMENT = { size: opts.size || '', zIndex: opts.zIndex || 2000 }; Vue.prototype.$loading = Loading.service; Vue.prototype.$msgbox = MessageBox; Vue.prototype.$alert = MessageBox.alert; Vue.prototype.$confirm = MessageBox.confirm; Vue.prototype.$prompt = MessageBox.prompt; Vue.prototype.$notify = Notification; Vue.prototype.$message = Message; }; /* istanbul ignore if */ if (typeof window !== 'undefined' && window.Vue) { install(window.Vue); } export default { version: '{{version}}', locale: locale.use, i18n: locale.i18n, install, CollapseTransition, Loading, {{list}} }; `; delete Components.font; var ComponentNames = Object.keys(Components); var includeComponentTemplate = []; var installTemplate = []; var listTemplate = []; ComponentNames.forEach(name => { var componentName = uppercamelcase(name); includeComponentTemplate.push(render(IMPORT_TEMPLATE, { name: componentName, package: name })); if (['Loading', 'MessageBox', 'Notification', 'Message', 'InfiniteScroll'].indexOf(componentName) === -1) { installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, { name: componentName, component: name })); } if (componentName !== 'Loading') listTemplate.push(` ${componentName}`); }); var template = render(MAIN_TEMPLATE, { include: includeComponentTemplate.join(endOfLine), install: installTemplate.join(',' + endOfLine), version: process.env.VERSION || require('../../package.json').version, list: listTemplate.join(',' + endOfLine) }); fs.writeFileSync(OUTPUT_PATH, template); console.log('[build entry] DONE:', OUTPUT_PATH);node build/bin/i18n.js:element-ui官网的导航等多语言版本的构建,通过读取模板(.tpl)文件和语言包(page.json),进行文件的字符替换来生成多语言版本的文件。这么处理的目的是,官网的多语言切换。
var fs = require('fs'); var path = require('path'); var langConfig = require('../../examples/i18n/page.json'); langConfig.forEach(lang => { try { fs.statSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`)); } catch (e) { fs.mkdirSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`)); } Object.keys(lang.pages).forEach(page => { var templatePath = path.resolve(__dirname, `../../examples/pages/template/${ page }.tpl`); var outputPath = path.resolve(__dirname, `../../examples/pages/${ lang.lang }/${ page }.vue`); var content = fs.readFileSync(templatePath, 'utf8'); var pairs = lang.pages[page]; Object.keys(pairs).forEach(key => { content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]); }); fs.writeFileSync(outputPath, content); }); });node build/bin/version.js : 根据package.json中的versions,生成examples/versions.json,然后在官网的头部的版本切换中使用。
webpack.demo.js
demo站的webpack构建配置,element-ui的官方UI示例文档使用的md文档,也内置了一个,为了解析md文档特意写了一个md-loader。这里不重点介绍了。
node build/bin/template.js
这里对
examples/pages/template下的文件做了监听处理。const path = require('path'); const templates = path.resolve(process.cwd(), './examples/pages/template'); const chokidar = require('chokidar'); let watcher = chokidar.watch([templates]); watcher.on('ready', function() { watcher .on('change', function() { exec('npm run i18n'); }); }); function exec(cmd) { return require('child_process').execSync(cmd).toString().trim(); }npm包构建-npm run dist
我们知道element-ui支持npm安装,也支持cdn安装,cdn使用的是unpkg,所以支持通过
[https://unpkg.com/element-ui/](https://unpkg.com/element-ui/)方式获取最新资源。只要将包发布到 npm 仓库,unpkg就会自动帮你发布到cdn。unpkg访问包的格式是unpkg.com/:package@:version/:file。如果不添加version的话,默认指向最新的版本。我们可以通过unpkg访问到element-ui npm包的所有资源。下图是element-ui的npm包内容。
npm publish 文件过滤
上图是element-ui的npm包内容,npm包是通过
npm publish发布到npm仓库的,那npm包中的文件内容有哪些,是如何控制的,npm提供了以下三种方式。
- .gitignore: git忽略文件设置,在npm publish时同样会忽略。
- .npmignore: 写法跟 .gitignore 的规则完全一样。若同时使用了 .npmignore和 .gitignore,只有 .npmignore 会生效。
- files字段:在package.json中的files 字段设置发布哪些文件或目录。这个优先级最高。推荐使用此方式。
另外,npm publish 也有一些默认忽略规则和默认被包含的文件。默认被包含的文件,即使设置忽略也无效,比如以下文件。
package.json
README //(包括一些变种)
CHANGELOG //(包括一些变种)
LICENSE / LICENCE
多版本构建
npm run clean // 清除文件
&& npm run build:file
&& npm run lint // 代码风格检查
&& webpack --config build/webpack.conf.js // umd 版本
&& webpack --config build/webpack.common.js // commonjs2
&& webpack --config build/webpack.component.js // 多入口构建,用于按需加载
&& npm run build:utils // src(util/mixin等)文件babel构建
&& npm run build:umd // local umd
&& npm run build:theme // css 构建
webpack.conf.js 和 webpack.common.js 的入口文件相同,loader,plugin也基本相同,主要有以下几个区别:
- 构建目标的不同,
libraryTarget分别是umd和commonjs2。 externals排除的文件不同,umd版本在浏览器端访问,所以除了vue,全量构建。commonjs版本作为node模块引入,会将依赖包和src目录下的内容都排除掉。- 出口分别是
lib/index.js和lib/element-ui.common.js。项目中全量引入的包就是lib文件下的element-ui.common.js多入口构建
webpack --config build/webpack.component.js, 使用webpack的多入口打包,将所有组件分别打包到lib目录下。主要用于element-ui的按需引入。const Components = require('../components.json'); const webpackConfig = { mode: 'production', entry: Components, output: { path: path.resolve(process.cwd(), './lib'), publicPath: '/dist/', filename: '[name].js', chunkFilename: '[id].js', libraryTarget: 'commonjs2' }, resolve: { extensions: ['.js', '.vue', '.json'], alias: config.alias, modules: ['node_modules'] }, externals: config.externals, ... }npm run build:utils
使用babel方式,将src下的文件打包到lib目录下,babel只是对文件自身进行转换,并不会将依赖的文件,引入合并等,所以必须确保,src目录下的文件内部引入的外部模板路径正确。element-ui在同时对src下的文件做了,路径转换处理。cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js{ "env": { "utils": { "presets": [ [ "env", { "loose": true, "modules": "commonjs", "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] } } ], ], "plugins": [ // 模块路径替换,即 import xxx from 'element-ui/src/util/aaa.js' => 'element-ui/lib/util/aaa.js' ["module-resolver", { "root": ["element-ui"], "alias": { "element-ui/src": "element-ui/lib" } }] ] } } }为什么要这么处理src下的文件
src是一个辅助文件,有utils、mixins等组件公用的处理函数,如果跟着组件一起打包,势必会增加大量的重复性代码,所有element-ui在打包的时候,没有将src下的文件打包到组件内部,这样能够减少冗余。我们看下面是webpack的公用配置文件config.js, wepack将src目录下的所有文件排除在外,以减少打包的体积。var path = require('path'); var fs = require('fs'); var nodeExternals = require('webpack-node-externals'); var Components = require('../components.json'); var utilsList = fs.readdirSync(path.resolve(__dirname, '../src/utils')); var mixinsList = fs.readdirSync(path.resolve(__dirname, '../src/mixins')); var transitionList = fs.readdirSync(path.resolve(__dirname, '../src/transitions')); var externals = {}; Object.keys(Components).forEach(function(key) { externals[`element-ui/packages/${key}`] = `element-ui/lib/${key}`; }); externals['element-ui/src/locale'] = 'element-ui/lib/locale'; utilsList.forEach(function(file) { file = path.basename(file, '.js'); externals[`element-ui/src/utils/${file}`] = `element-ui/lib/utils/${file}`; }); mixinsList.forEach(function(file) { file = path.basename(file, '.js'); externals[`element-ui/src/mixins/${file}`] = `element-ui/lib/mixins/${file}`; }); transitionList.forEach(function(file) { file = path.basename(file, '.js'); externals[`element-ui/src/transitions/${file}`] = `element-ui/lib/transitions/${file}`; }); externals = [Object.assign({ vue: 'vue' }, externals), nodeExternals()]; exports.externals = externals; exports.alias = { main: path.resolve(__dirname, '../src'), packages: path.resolve(__dirname, '../packages'), examples: path.resolve(__dirname, '../examples'), 'element-ui': path.resolve(__dirname, '../') }; exports.vue = { root: 'Vue', commonjs: 'vue', commonjs2: 'vue', amd: 'vue' }; exports.jsexclude = /node_modules|utils\/popper\.js|utils\/date\.js/;npm run build:umd
element-ui的国际化语言包构建,使用babel function构建。npm run build:theme
css主题构建,使用了gulp构建。
发布和部署-npm run pub
element-ui发布命令中使用的大量的shell命令。npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js # 构建目录索引,用于文件搜索 && sh build/deploy-faas.shsh build/git-release.sh
主要就是做了一件事,发版前git检查。
- 工作树是否有未提交的。
- fetch远程最新代码。
对比远程分支和本地分支是否有不同。
#!/usr/bin/env sh 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 # 本地和远程对比 @{u}是origin/dev的缩写, 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
npm包的发布流程。

#!/usr/bin/env sh set -e git checkout master git merge dev # 交互式的选择一个version 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 ..." # npm包构建 # build VERSION=$VERSION npm run dist # ssr test node test/ssr/require.test.js # them-chalk是一个单独的git仓,更新版本号,并发版 # publish theme echo "Releasing theme-chalk $VERSION ..." cd packages/theme-chalk npm version $VERSION --message "[release] $VERSION" if [[ $VERSION =~ "beta" ]] then npm publish --tag beta else npm publish fi cd ../.. # 更新主项目的版本号,打tag,推送到远程分支 # commit git add -A git commit -m "[build] $VERSION" npm version $VERSION --message "[release] $VERSION" # publish git push eleme master git push eleme refs/tags/v$VERSION git checkout dev git rebase master git push eleme dev # 主项目发版 npm publish if [[ $VERSION =~ "beta" ]] then npm publish --tag beta else npm publish fi finode build/bin/gen-indices.js
deploy-faas.sh

#! /bin/sh set -ex mkdir temp_web npm run deploy:build cd temp_web # 克隆远程仓库,并切换的到gh-pages分支 git clone --depth 1 -b gh-pages --single-branch https://github.com/ElemeFE/element.git && cd element # 创建子目录,用于静态站点切换使用。 https://github.com/ElemeFE/element/tree/gh-pages # build sub folder SUB_FOLDER='2.13' mkdir -p $SUB_FOLDER rm -rf *.js *.css *.map static rm -rf $SUB_FOLDER/** cp -rf ../../examples/element-ui/** . cp -rf ../../examples/element-ui/** $SUB_FOLDER/ cd ../.. # element的官网站的的部署,关于faas这里稍有疑问! # deploy domestic site faas deploy alpha -P element rm -rf temp_web自动化部署
Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。它绑定 Github 上面的项目,只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,完成构建,还能部署到服务器。
Element-ui使用和github结合的Travis CI,以下是travis.yml的内容。travis 也有生命周期的概念,在每次代码更新时触发。
sudo: false
language: node_js
node_js: 10
addons:
chrome: stable
#
before_install:
- export TRAVIS_COMMIT_MSG="[deploy] $(git log --format='%h - %B' --no-merges -n 1)"
- export TRAVIS_COMMIT_USER="$(git log --no-merges -n 1 --format=%an)"
- export TRAVIS_COMMIT_EMAIL="$(git log --no-merges -n 1 --format=%ae)"
#
after_success:
- sh build/deploy-ci.sh
- cat ./test/unit/coverage/lcov.info | ./node_modules/.bin/coveralls
sh build/deploy-ci.sh
#! /bin/sh
mkdir temp_web
git config --global user.name "element-bot"
git config --global user.email "wallement@gmail.com"
# 用户身份校验,不通过就 bye
if [ "$ROT_TOKEN" = "" ]; then
echo "Bye~"
exit 0
fi
# 发布流程,通过判断是否有tag更新来确定发布的时机,在 `npm run pub`过程中会对推送tag,然后提交到远程之后,会触发这个发版的操作。
# release
if [ "$TRAVIS_TAG" ]; then
# build lib
npm run dist
cd temp_web
git clone https://$ROT_TOKEN@github.com/ElementUI/lib.git && cd lib
rm -rf `find * ! -name README.md`
cp -rf ../../lib/** .
git add -A .
git commit -m "[build] $TRAVIS_TAG"
git tag $TRAVIS_TAG
git push origin master --tags
cd ../..
# build theme-chalk
cd temp_web
git clone https://$ROT_TOKEN@github.com/ElementUI/theme-chalk.git && cd theme-chalk
rm -rf *
cp -rf ../../packages/theme-chalk/** .
git add -A .
git commit -m "[build] $TRAVIS_TAG"
git tag $TRAVIS_TAG
git push origin master --tags
cd ../..
# build site
npm run deploy:build
cd temp_web
git clone --depth 1 -b gh-pages --single-branch https://$ROT_TOKEN@github.com/ElemeFE/element.git && cd element
# build sub folder
echo $TRAVIS_TAG
SUB_FOLDER='2.13'
mkdir $SUB_FOLDER
rm -rf *.js *.css *.map static
rm -rf $SUB_FOLDER/**
cp -rf ../../examples/element-ui/** .
cp -rf ../../examples/element-ui/** $SUB_FOLDER/
git add -A .
git commit -m "$TRAVIS_COMMIT_MSG"
git push origin gh-pages
cd ../..
echo "DONE, Bye~"
exit 0
fi
# 开发仓库的部署
# build dev site
npm run build:file && CI_ENV=/dev/$TRAVIS_BRANCH/ node_modules/.bin/cross-env NODE_ENV=production node_modules/.bin/webpack --config build/webpack.demo.js
cd temp_web
git clone https://$ROT_TOKEN@github.com/ElementUI/dev.git && cd dev
mkdir $TRAVIS_BRANCH
rm -rf $TRAVIS_BRANCH/**
cp -rf ../../examples/element-ui/** $TRAVIS_BRANCH/
git add -A .
git commit -m "$TRAVIS_COMMIT_MSG"
git push origin master
cd ../..
# push dev theme-chalk
cd temp_web
git clone -b $TRAVIS_BRANCH https://$ROT_TOKEN@github.com/ElementUI/theme-chalk.git && cd theme-chalk
rm -rf *
cp -rf ../../packages/theme-chalk/** .
git add -A .
git commit -m "$TRAVIS_COMMIT_MSG"
git push origin $TRAVIS_BRANCH
cd ../..
创建新组建-make new
这个命令实际运行的是 node build/bin/new.js 脚本。 以make new test 测试 为例,new.js主要做了一下几个事。
- components.json中添加文件路径映射
"test": "./packages/test/index.js"。 - 在
package/theme-chalk/src目录下添加到 index.scss。 - 在
package/theme-chalk/index.css文件追加@import "./test.scss";。 - 追加生命文件
types/test.d.ts。 - 添加单元测试文件。
- 在packages目录下追加test文件。
- 添加各个语言版本的 md文件。
- 更新路由配置文件 nav.config.json。
总结
深入了解Element-UI的构建过程之后发现,一个成熟的开源项目的构建过程远比预想的要复杂很多。
element-ui构建过程中使用了大量的文件构建,能自动构建的文件,绝不动手写。
官网多语言切换由于使用模板构建,不在wepack热更新的范畴内,还做了文件的监听处理。
文章篇幅有限,还有很多不错的实现没有一一解析,比如md-loader的实现,在线换肤的原理等等。
element-ui构建过程中使用的细碎的知识点也很多,比如postcss.parse,gulp构建,shell命令,tarvis-ci。参考连接
Element-UI 中 Make 自动化构建分析
