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

本次分析内容概要

Element-UI 构建流程解析 - 图1
文件篇幅比较长,由于里面加入了大量的element-ui的源码导致。本文主要从element-ui的构建流程上做了一些概览性的分析,没有做太多代码实现细节上的解读,希望通过此次分享能力对工程化的构建上有一个整体性的认识。

目录结构分析

在学习一个开源库的时候,首先就是看它的目录结构,通过目录结构可以对整个库的设计有一个初步的认知。以下是element-ui的外层目录。

  1. ├── .github //md文档,issue模板文件等github相关文件。
  2. ├── build // 项目构建相关的处理文件。
  3. ├── example //element-ui官网说明文档,包括组件示例文档。
  4. ├── packages // element-ui组件源码。
  5. ├── src // 组件的index.js入口文件,以及各种辅助类文件。
  6. ├── test // 测试文件。
  7. ├── types // 声明文件。
  8. ├── babelrc
  9. ├── .eslintignore
  10. ├── .eslintrc
  11. ├── .gitattributes**: github上传的项目相关定义,这里是代码语言类型定义,定义为Vue
  12. ├── .gitignore
  13. ├── .travis.yml // 与github上的项目绑定,提供CI/CD服务,element-ui的自动化部署相关内容就在这里。
  14. ├── CHANGELOG.en-US.md
  15. ├── CHANGELOG.es.md
  16. ├── CHANGELOG.fr-FR.md
  17. ├── CHANGELOG.zh-CN.md
  18. ├── FAQ.md
  19. ├── LICENSE
  20. ├── Makefile // 定义了一系列的编译命令,使用make命令执行来执行一系列的操作,element-ui在这里定义了开发、构建、发布、部署相关的命令。
  21. ├── README.md
  22. ├── components.json // 配置文件,组件的名称与路径映射表,在工程的构建过程中使用频次很高。
  23. ├── element_logo.svg
  24. ├── package.json 项目的描述文件。
  25. ├── yarn.lock

在上述文件中,有一个陌生的文件Makefile,在之前没有接触过,特意学习了一下,下面简单介绍一下这个文件。

Makefile文件简单介绍

Makefile 是一个适用于 C/C++ 的工具,较早作为工程化工具出现在 UNIX 系统中, 通过 make 命令来执行一系列的编译和连接操作。在拥有 make 环境的目录下, 如果存在一个 Makefile 文件。 那么输入 make 命令将会执行 Makefile 文件中的某个目标命令。 在mac 中,可以直接执行 make 命令。Windows系统则需要下载make的GUN工具。

在element-ui目录下执行make命令,会看到下图的展示结果,有一个element-ui的命令使用说明,列举了开发和构建的一些关键命令。
Element-UI 构建流程解析 - 图2

Makefile文件格式

Makefile 文件由一系列规则构成,每一条的规则需要明确两件事情:构建目标是什么,以及如何构建。 每一条的规则都遵循以下格式:

  1. <target> : <prerequisites>
  2. [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

一些必要文件的构建,包括四个部分。
Element-UI 构建流程解析 - 图3

  1. 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), () => {});
    
  2. 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);
    
  3. 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);
    });
    });
    
  4. node build/bin/version.js : 根据package.json中的versions,生成examples/versions.json,然后在官网的头部的版本切换中使用。

    webpack.demo.js

    demo站的webpack构建配置,element-ui的官方UI示例文档使用的md文档,也内置了一个,为了解析md文档特意写了一个md-loader。这里不重点介绍了。
    Element-UI 构建流程解析 - 图4

    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包内容。
    Element-UI 构建流程解析 - 图5

    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也基本相同,主要有以下几个区别:

  1. 构建目标的不同,libraryTarget分别是umdcommonjs2
  2. externals 排除的文件不同,umd版本在浏览器端访问,所以除了vue,全量构建。commonjs版本作为node模块引入,会将依赖包和src目录下的内容都排除掉。
  3. 出口分别是lib/index.jslib/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构建。
    Element-UI 构建流程解析 - 图6

    发布和部署-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.sh
    

    sh build/git-release.sh

    主要就是做了一件事,发版前git检查。
  1. 工作树是否有未提交的。
  2. fetch远程最新代码。
  3. 对比远程分支和本地分支是否有不同。

    #!/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包的发布流程。
    Element-UI 构建流程解析 - 图7

    #!/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
    fi
    

    node build/bin/gen-indices.js

    demo站的索引的构建,主要用于组件的搜索。

    deploy-faas.sh

    Element-UI 构建流程解析 - 图8

    #! /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主要做了一下几个事。

  1. components.json中添加文件路径映射"test": "./packages/test/index.js"
  2. package/theme-chalk/src目录下添加到 index.scss。
  3. package/theme-chalk/index.css文件追加 @import "./test.scss";
  4. 追加生命文件 types/test.d.ts
  5. 添加单元测试文件。
  6. 在packages目录下追加test文件。
  7. 添加各个语言版本的 md文件。
  8. 更新路由配置文件 nav.config.json。

    总结

    深入了解Element-UI的构建过程之后发现,一个成熟的开源项目的构建过程远比预想的要复杂很多。
    element-ui构建过程中使用了大量的文件构建,能自动构建的文件,绝不动手写。
    官网多语言切换由于使用模板构建,不在wepack热更新的范畴内,还做了文件的监听处理。
    文章篇幅有限,还有很多不错的实现没有一一解析,比如md-loader的实现,在线换肤的原理等等。
    element-ui构建过程中使用的细碎的知识点也很多,比如postcss.parse,gulp构建,shell命令,tarvis-ci。

    参考连接

    Element-UI 中 Make 自动化构建分析