前沿技术

  • 背景

    当前项目为vue 2.6 + element-ui 2.14.1, 我们需要开发一个类似表格的表单组件, 可以看到除了表格样式以外,我们还需要嵌套各种表单组件,而组件功能基本与框架功能一致,如果对每个组件都做独立开发,显然是不现实的。所以我们的目标一定是尽量使用原组件

    • 方案一: 使用容器组件或自定义类名, 覆盖原组件样式。

    • 方案二:将element拉到本地,做二次开发

    这里我们选择了第二种。

    概况

    因为项目依赖,所以我们这里拉取的是 element-ui 2.14.1, 如需要其他版本 切换不同的 tag 标签分支即可。

    命令

    二次开发前我们需要先将项目跑起来,所以需要了解基础的命令

    "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",
         // 组件打包, 将在本地生成 lib/ 目录
        "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"
      },

    这里我们一般只用到了,dev dist 以及后面我们一个自定命令。

    项目目录

    • root/

      • program/

      • element-ui/

    element目录概览

    • build/ 构建配置 | 构建脚本

    • examples/ 文档, 组件用例

    • src/ 项目源码| 项目入口

    • packages/ 组件源码

    • types/ ts类型定义

    这里主要关注 examples/, packages/ , 这与我们开发直接相关。

    examples/

    这里其实是个vue项目, dev 将运行该工程。

    • examples/

      • zh-CN/ 中文包

      • en-US/ 英文包

      • .... 其他语言包

      • docs/ 组件文档 | 组件用例

      • pages/ 页面模板

      • i18n/ 国际化

      • components/ 页面公共组件

      • extension/ 扩展

      • entry.js 项目入口

      • nav.config.json 导航配置

      • router.config.js vue 路由加载器

    packages/

    这里是我们编写组件的地方,所以组件都以独立目录包的形式存在,方便按需加载。

    • 约定

    每个包遵守基础的包结构

    \- package
      - index.js 导出入口  - src/ 源
    • 样式文件

    看过组件包后, 会发现包内是不包含样式文件的,样式文件放在了 /packages/theme-chalk/ 目录下。 所以其实 element-ui 的样式作为独立的主题包存在。


    开发自定组件

    这里我们以 row 为例子,通过在源组件基础上修改一个自己的新组件 z-row

    新建组件目录

    // 拷贝row 目录 并重命名为 z-rowmv row - 副本  z-row// 修改组件导出// index.jsimport ZRow from './src/z-row';/\* istanbul ignore next \*/ZRow.install = function(Vue) {
      Vue.component(ZRow.name, ZRow);};export default ZRow;// 修改源文件名// z-row/src/z-row

    修改组件

    export default {
      name: 'ZRow',
    
      componentName: 'ZRow', // 修改组件名
    
      props: {
        tag: {
          type: String,
          default: 'div'
        },
        gutter: Number,
        type: String,
        justify: {
          type: String,
          default: 'start'
        },
        align: {
          type: String,
          default: 'top'
        }
      },
    
      computed: {
        style() {
          const ret = {};
    
          if (this.gutter) {
            ret.marginLeft = \`-${this.gutter / 2}px\`;
            ret.marginRight = ret.marginLeft;
          }
    
          return ret;
        }
      },
    
      render(h) {
        return h(this.tag, {
          class: \[
            'z-row',  // 将源 el-row 类名 替换为 z-row, 并替换其他 el- 前缀
            this.justify !== 'start' ? \`is-justify-${this.justify}\` : '',
            this.align !== 'top' ? \`is-align-${this.align}\` : '',
            { 'z-row--flex': this.type === 'flex' }
          \],
          style: this.style    }, this.$slots.default);

    修改样式

    //  进入/packages/theme-chalk/src// copy row.sccs 并更名为 z-row.sccs// 修改样式@import "common/var";@import "mixins/mixins";@import "mixins/utils";// zb 为自定义组件前缀添加器, 具体的实现可以参考 mixins/mixings 的 @mixin b@include zb(row) { // 生成类名 .z-row
      position: relative;
      box-sizing: border-box;
      border: 1px solid #E5E5E5;
      align-items: stretch;
      
      & + &{
        border-top: 0;
      }
      
      @include utils-clearfix;
    
      @include m(flex) {
        display: flex;
        &:before,
        &:after {
          display: none;
        }
    
        @include when(justify-center) {
          justify-content: center;
        }
        @include when(justify-end) {
          justify-content: flex-end;
        }
        @include when(justify-space-between) {
          justify-content: space-between;
        }
        @include when(justify-space-around) {
          justify-content: space-around;
        }
    
        @include when(align-middle) {
          align-items: stretch;
        }
        @include when(align-bottom) {
          align-items: flex-end;
        }
      }

    注册组件

    打开根目录下的 components.json

    {
      // 添加新组件
      "z-row": "./packages/z-row/index.js",
      ...}

    编写文档/用例

    • 新增文档

    // /examples/docs/zh-CN/ 新增  z-layout.md## z-layout 布局
    ## 基础使用
    \`\`\`html// 编写组件用例<z-row>
      <z-col :span="24"><div class="grid-content bg-purple-dark"> content </div></z-col></z-row>\`\`\`
    • 注册路由

    // examples/nav.config.json// 在组件列表下新增目录配置
          {
              "groupName": "z-ui",
              "list": \[
                {
                  "path": "/z-layout",
                  "title": "z-layout| z-row | z-col"
                }
              \]
           },
    • 运行用例

    yarn dev```这样我们就可以看到element-ui 组件目录下新增了我们的自定义组件, 这里是支持热更新的,修改文档的同时,内容也将即时显示。
    
    ## 打包
    
    这里我们希望依然以elemnt官方的方式使用本地的魔改包, 只是添加注册新增的自定义组件。开始使用了`lerna` 但是存在命名冲突的问题, lerna无法通过包名判断安装的是本地包还是线上包,如果只修改package.json 的 elemnt包名,将导致无法正常导入组件的问题, 因为还需要修改打包的配置,这样会比较的麻烦。 所以使用了hack方法, 直接替换项目下 node\_modules 内的 element-ui 文件, 其他也可以直接就在项目下引入已经构建好的 elemnt-ui 打包文件。
    
    ### 生成lib
    
    ```javascript
    yarn dist // 生成

    迁移脚本

    每次打包后,导出新的包文件会很麻烦,所以可以使用gulp将打包后的文件导入到项目中.

    const { series, src, dest } = require('gulp');const packageJson = require('../package.json')const path = require('path')const resolve = \_path => path.resolve(\_\_dirname, \_path)const move = (from, to) => cb => {
      src(from)
      .pipe(dest(to))
      cb()}const buildTask = (files, target) => files.map(source => {
      const from = resolve(\`../${source}/\*\*/\*\`)
      const to =  resolve(\`../${target}/${source}/\`)
      return move(from, to)})function trucks(){
      console.log(packageJson.files)
    
      const task = buildTask(packageJson.files, packageJson.moveTarger)
      console.log(task)
      return series(...task)}module.exports.default = tr