Vue CLI 3結合 Lerna 進行UI框架設計

受權轉載,做者:子奕。css

當前大部分UI框架設計的Webpack配置都相對複雜,例如Element、Ant Design Vue和Muse-UI等Vue組件庫例如Element,爲了實現業務層面的兩種引入形式(「完整引入」「按需引入」),以及拋出一些可供業務層面通用的utilsi18n等,Webpack配置變得很是複雜。爲了簡化UI框架的設計難度,這裏介紹一種簡單的UI框架設計,在此以前先簡單介紹一下「Element」的構建流程,以便對比新的UI框架設計。

通常組件庫的設計者將引入形式設計成「完整引入」「按需引入」兩種形式:「完整引入」的開發相對便利,針對一些大型業務或者對於打包體積不是特別注重的業務,「按需引入」開發的顆粒度相對精細,能夠減小業務的打包體積。前端

設計的UI框架實踐項目的github地址是ziyi2/vue-cli3-lerna-ui,包括了preset.json、本身設計的Vue CLI插件以及本身設計的一系列UI組件(和生成的UI框架示例稍有不一樣),若是以爲總體結構有不合理的或者考慮不夠全面的地方,歡迎你們提issue,這樣我也能夠對它進行完善。若是你們感興趣,但願你們可以Star一下,這裏拜謝你們了!vue

Element

首先了解Element的構建流程,查看Element2.7.0版本package.json的npm 腳本:node

// 其中的`node build/bin/build-entry.js` 生成Webpack構建入口
"build:file""node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
// 構建css樣式
"build:theme""node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
// 構建commonjs規範的`utils`
"build:utils""cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
// 構建umd模塊的語言包
"build:umd""node build/bin/build-locale.js",
// 清除構建文件夾`lib`
"clean""rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
// 整體構建
"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",
// 執行eslint校驗
"lint""eslint src/**/* test/**/* packages/**/* build/**/* --quiet"

這裏重點關注Element的構建腳本,忽略「測試、發佈、啓動開發態調試頁面、構建演示頁面」等腳本。webpack

npm run dist

Element構建相關的npm腳本繁多,可是「整體構建腳本」distgit

"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"

&&是繼發執行,只有當前任務成功,才能執行下一個任務。es6

「整體構建腳本」包含了如下按順序執行的腳本命令github

  • npm run clean - 清除構建文件夾 lib
  • npm run build:file - 其中的 node build/bin/build-entry.js 生成Webpack構建入口
  • npm run lint - 執行eslint校驗
  • webpack --config build/webpack.conf.js - 構建umd總文件
  • webpack --config build/webpack.common.js - 構建commonjs2總文件
  • webpack --config build/webpack.component.js - 構建commonjs2組件(提供按需引入)
  • npm run build:utils - 構建commonjs的 utils(供commonjs2總文件、commonjs2組件以及業務使用)
  • npm run build:umd - 構建umd語言包
  • npm run build:theme  - 構建css樣式

若是對於對於umdcommonjs2amd等模塊定義不是特別清晰,可參考Webpack文檔模塊定義系統。web

執行npm run dist後會在當前根目錄生成新的lib文件夾,包含如下構建內容:vuex

lib
├── directives                  # commonjs指令(這裏歸爲utils)
├── locale                      # commonjs國際化(commonjs語言包和API)
├── mixins          # commonjs mixins(這裏歸爲utils)
├── theme-chalk     # css 樣式文件
├── transitions   # commonjs transitions(這裏歸爲utils)
├── umd             # umd語言包   
├── utils
├── alert.js                    # commonjs組件
├── aside.js
├── ...
├── element-ui.common.js  # commonjs2總文件 
├── ...
├── index.js                    # umd總文件
├── ...

從Element官方文檔的使用指南結合lib能夠看出,Element爲咱們提供瞭如下能力:

  • 一、CDN引入(umd 總文件)
  • 二、npm包完整引入(拋出commonjs2總文件)
  • 三、按需引入(拋出commonjs2的全部UI組件)
  • 四、支持國際化
  • 五、提供 utils方法(官方文檔沒有說明,但事實上業務可使用)

CDN引入的umd總文件通常是全量構建的,不會有依賴問題,可是commonjs2模塊的文件須要在業務層面再次使用Webpack構建。例如須要在業務層面支持「國際化」「提供utils」的功能,那麼就不能將「國際化」「提供utils」的代碼「bundle」「commonjs2總文件」「commonjs2的全部UI組件」中(每個組件都「bundle」utils的方法或者國際化API顯然是不合理的),若是須要在業務層面支持「按需引入」的功能,那麼不建議將「全部UI組件」的源碼「bundle」「commonjs2總文件」中,這樣即可以實現層層引用,對外拋出功能的同時在業務層面能夠防止Webpack二次打包,從而致使引入兩遍甚至多遍相同的代碼的問題。

在組件庫中開發時,爲了構建commonjs2模塊的文件,須要對各個utils、組件等引入的路徑作出強約定,這樣不只產生的Webpack配置會變得很難維護,對於開發者的開發也須要作出必定的規範限制。

接下來分析一下各個腳本的構建功能。

npm run build:file

build:file腳本是自動生成一些源碼文件的腳本:

"build:file""node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",

其中與構建相關的腳本是node build/bin/build-entry.js,主要用於生成Webpack構建的入口源文件src/index.js

// 註釋說明該文件由build-entry.js腳本自動生成
/* Automatically generated by './build/bin/build-entry.js' */

import Pagination from '../packages/pagination/index.js';
// ... 這裏省略大部分組件引入
import TimelineItem from '../packages/timeline-item/index.js';
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';

const components = [
  Pagination,
  // ... 這裏省略大部分組件
  TimelineItem,
  CollapseTransition
];

const install = function(Vue, opts = {}{
  locale.use(opts.locale);
  locale.i18n(opts.i18n);

  components.forEach(component => {
    Vue.component(component.name, component);
  });

  Vue.use(Loading.directive);

  Vue.prototype.$ELEMENT = {
    size: opts.size || '',
    zIndex: opts.zIndex || 2000
  };

  Vue.prototype.$loading = Loading.service;
  // ...
};

/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}

export default {
  version'2.7.0',
  locale: locale.use,
  i18n: locale.i18n,
  install,
  CollapseTransition,
  Loading,
  Pagination,
  // ... 這裏省略大部分組件
  TimelineItem
};

在組件的開發過程當中若是組件較多,建議使用腳本自動生成構建入口文件。

npm run lint

構建以前使用lint腳本對構建的源碼文件進行eslint校驗:

"lint""eslint src/**/* test/**/* packages/**/* build/**/* --quiet",

Elementeslint作了嚴格控制,一旦eslint報錯那麼dist「整體構建腳本」執行中止,總體構建失敗。這裏的eslint校驗可使用eslint-loader進行處理(若是你但願eslint校驗失敗也能夠進行構建能夠查看Errors and Warning)。

webpack --config build/webpack.conf.js

webpack --config build/webpack.conf.js腳本用於構建umd總文件,執行該腳本最終會在lib下生成index.js文件:

lib
├── index.js   # umd 總文件

webpack.conf.js配置以下:

// build/webpack.conf.js

// ...忽略

module.exports = {
  mode'production',
  // 指定入口文件src/index.js,該入口文件由`build:file`腳本自動生成
  entry: {
    app: ['./src/index.js']
  },
  output: {
    // 在lib文件中生成
    path: path.resolve(process.cwd(), './lib'),
    // 生成lib/index.js
    filename: 'index.js',
    // 生成umd模塊
    libraryTarget: 'umd',
    // src/index.js文件採用export default語法拋出,所以須要設置libraryExport
    // 不然引入的UI組件庫須要使用.default才能引用到拋出的對象
    // if your entry has a default export of `MyDefaultModule`
    // var MyDefaultModule = _entry_return_.default;
    // 這裏踩過坑,因此說明一下,不配置的話遇到的問題是引入的UI組件庫無法解構
    libraryExport: 'default',
  },
  resolve: {
    extensions: ['.js''.vue''.json'],
 // 'element-ui': path.resolve(__dirname, '../')
 // alias中的'element-ui'做爲npm包拋出後指向了業務項目node_modules所在的npm包路徑
    alias: config.alias
  },
  externals: {
    // 構建只排除vue
    // umd模塊經過CDN形式引入,所以將全部的組件、utils、i18n等構建在內
    // umd模塊沒有按需引入功能
    vue: config.vue
  },
  // ...忽略
};

構建文件lib/index.js主要的功能是用於CDN形式引入項目,而且沒法作到按需加載,產生的體積很是大,對於簡單的應用可能不適用。

webpack --config build/webpack.common.js

webpack --config build/webpack.common.js腳本用於構建commonjs2總文件,執行該腳本最終會在lib下生成element-ui.common.js文件:

lib
├── element-ui.common.js   #  commonjs2 總文件 

因爲該文件須要在業務層面再次使用Webpack構建,所以考量的方面較多。在分析Webpack配置以前,再次回顧一下Element能爲咱們作什麼:

  • 一、完整引入(拋出commonjs2總文件)
  • 二、按需引入(拋出commonjs2的全部UI組件)
  • 三、支持國際化(commonjs2)
  • 四、提供 utils方法(commonjs2,固然官方沒有對外說明)

webpack --config build/webpack.common.js腳本主要用於構建完整引入功能,同時爲了能夠在業務層面拋出「按需引入、支持國際化」等功能,構建element-ui.common.js時須要將「UI組件、支持國際化、utils方法」的源代碼排除。

webpack.common.js配置以下:

// build/webpack.common.js

// ...忽略

module.exports = {
  mode'production',
  entry: {
    app: ['./src/index.js']
  },
  output: {
    path: path.resolve(process.cwd(), './lib'),
    publicPath'/dist/',
    filename'element-ui.common.js',
    chunkFilename'[id].js',
    libraryExport'default',
    library'ELEMENT',
    // 生成commonjs2模塊
    libraryTarget: 'commonjs2'
  },
  resolve: {
    extensions: ['.js''.vue''.json'],
 // 'element-ui': path.resolve(__dirname, '../')
    alias: config.alias,
    modules: ['node_modules']
  },
  // 這裏用於排除UI組件、支持國際化、utils方法的源代碼,這些源代碼須要額外的腳本進行構建
  externals: config.externals,
  optimization: {
    // commonjs2無須壓縮處理
    minimize: false
  },
  // ...忽略
};

重點須要關注一下config.externals屬性,打印輸出該變量的值:

[{ 
  vue'vue',
  // 排除全部UI組件的源代碼
  'element-ui/packages/option''element-ui/lib/option',
  // ...
  // 排除國際化的源代碼
  'element-ui/src/locale''element-ui/lib/locale',
  // 排除utils方法的源代碼
  'element-ui/src/utils/vue-popper''element-ui/lib/utils/vue-popper',
  'element-ui/src/mixins/emitter''element-ui/lib/mixins/emitter',
  'element-ui/src/transitions/collapse-transition''element-ui/lib/transitions/collapse-transition' 
  // ...
 },
 // var nodeExternals = require('webpack-node-externals');
 // nodeExternals()
 [Function
];

externals屬性能夠將一些特定的依賴從輸出的bundle中排除,例如在開發態中組件之間有依賴關係,element-ui/packages/pagination中引入element-ui/packages/option組件:

pagecages/pagination/src/pagination.js

// pagination組件中須要用到option組件
import ElOption from 'element-ui/packages/option';
// ...

Webpack構建後,能夠發如今element-ui.common.js中並無將element-ui/packages/option組件打包在內,而只是更改了它的引入路徑element-ui/lib/option(在實現「按需引入」功能時會用webpack --config build/webpack.component.js 腳本構建出該文件)。

// lib/element-ui.common.js
module.exports = require("element-ui/lib/option");

所以以上列出的config.externals屬性的keyvalue能夠排除「UI組件、支持國際化、utils方法」功能的代碼。

config.externals屬性的最後一個值是[Function],是由webpack-node-externals生成的。這裏解釋一下webpack-node-externals 的做用:

Webpack allows you to define externals - modules that should not be bundled. When bundling with Webpack for the backend - you usually don't want to bundle its node_modules dependencies. This library creates an externals function that ignores node_modules when bundling in Webpack.

例如在Elment組件庫開發中須要依賴deepmerge,那麼Webpack構建的時候不須要將該依賴bundle到element-ui.common.js中,而是將其添加到Element組件庫(做爲npm包發佈)的dependencies,這樣經過npm安裝Element的同時也會安裝它的依賴deepmerge,從而使得element-ui.common.js經過require("deepmerge")的形式引入該依賴不會報錯。

這裏列出element-ui.common.js排除的一些代碼:

// 排除utils源碼(utils源碼會經過`npm run build:utils`腳本構建)
module.exports = require("element-ui/lib/utils/dom");
// 排除vue
module.exports = require("vue");
// 排除國際化源碼(國際化源碼會經過`npm run build:utils`腳本構建)
module.exports = require("element-ui/lib/locale");
// 須要注意和Vue相關的JSX依賴(Vue CLI3系統構建的包也會有一個該功能的依賴)
module.exports = require("babel-helper-vue-jsx-merge-props");
// 排除一些Elment組件使用的其餘依賴
module.exports = require("throttle-debounce/throttle"); 
// 排除UI組件源碼(UI組件源碼會經過`webpack --config build/webpack.component.js`腳本構建)
module.exports = require("element-ui/lib/option");

須要注意Element發佈的npm包入口文件就是element-ui.common.js,能夠經過package.json中的main字段信息查看。

webpack --config build/webpack.component.js

webpack --config build/webpack.component.js 腳本用於構建commonjs2的UI組件(提供按需引入功能),執行該腳本最終會在lib下生成全部Element支持的UI組件(同時這些文件也會被element-ui.common.js總入口文件引用):

lib
├── alert.js    # commonjs 組件
├── aside.js
├── button.js
├── ...

查看build/webpack.component.js配置:

// ...忽略
const Components = require('../components.json');

// Components是全部組件的構建入口列表
// {
//   "pagination": "./packages/pagination/index.js",
//   ...
//   "timeline-item": "./packages/timeline-item/index.js"
// }


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']
  },
  // 排除其餘UI組件、支持國際化、utils的源碼,這些源碼會額外構建
  externals: config.externals,
  },
  // ...忽略
};

構建單個組件和構建整體入口文件element-ui.common.js的Webpack配置相似,須要將utilslocale以及其餘一些依賴排除。

npm run build:utils

build:utils腳本主要用於構建commonjs的utils(提供國際化以及utils功能):

"build:utils""cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",

能夠發現該命令並非經過Webpack進行多文件構建,而是經過Babel直接進行轉義處理(Webpack構建會產生額外的Webpack代碼,而且配置繁瑣,Babel轉義處理構建的代碼很是乾淨),將src目錄下除了Webpack構建入口文件src/index.js之外的全部其餘文件進行轉義處理。執行該腳本最終會在lib下生成全部的utils文件:

lib
├── directives        # commonjs 指令
├── locale            # commonjs 國際化API和語言包
├── mixins            # commonjs 混入
├── transitions       # commonjs 過分動畫
├── utils             # commonjs 工具方法

生成的這些工具方法會被lib下的element-ui.common.js和各個組件引用,同時在業務層面也能夠引用這些工具方法。查看.babelrc文件的配置信息:

{
  "presets": [
    [
      "env",
      {
        "loose"true,
        "modules"false,
        "targets": {
          "browsers": ["> 1%""last 2 versions""not ie <= 8"]
        }
      }
    ],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx"],
  "env": {
    // cross-env BABEL_ENV=utils
    "utils": {
      "presets": [
        [
          "env",
          {
            // 鬆散模式,更像人手寫的ES5代碼
            "loose"true,
            // es6轉成commonjs
            "modules""commonjs",
            "targets": {
              "browsers": ["> 1%""last 2 versions""not ie <= 8"]
            }
          }
        ],
      ],
      "plugins": [
        ["module-resolver", {
          "root": ["element-ui"],
          "alias": {
           // 相似於Webpack的externals功能
           // 將源代碼的引入路徑更改爲目標代碼的引入路徑
            "element-ui/src""element-ui/lib"
          }
        }]
      ]
    },
    "test": {
      "plugins": ["istanbul"]
    }
  }
}

utils文件源代碼之間互相引用的路徑是element-ui/src,轉義成目標代碼後互相之間的引用路徑是element-ui/lib,所以須要有相似於Webpack的externals的功能去更改目標代碼的引用路徑,進行Babel轉義時插件babel-plugin-module-resolver能夠實現該功能。

npm run build:theme

build:theme腳本主要用於構建UI組件的css樣式:

"build:theme""node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",

這裏主要關注 gulp build --gulpfile packages/theme-chalk/gulpfile.js腳本,該腳本使用Gulp構建工具構建css樣式文件,Glup構建多文件樣式會很是簡單。最終將當前構建的packages/theme-chalk/lib目錄下的內容拷貝到lib/theme-chalk目錄下供外部業務使用:

lib
├── theme-chalk        # css 樣式文件
│   ├── fonts          # icons
│   ├── alert.css      # 按需引入的組件樣式
│   ├── ...            # 按需引入的組件樣式
│   └── index.css      # 完整引入樣式

查看gulpfile.js文件:

'use strict';

const { series, src, dest } = require('gulp');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');

function compile({
  return src('./src/*.scss')
    // sass轉化成css
    .pipe(sass.sync())
    // Parse CSS and add vendor prefixes to rules by Can I Use 
    // css瀏覽器兼容處理
    .pipe(autoprefixer({
      browsers: ['ie > 9''last 2 versions'],
      cascadefalse
    }))
    // 壓縮css
    .pipe(cssmin())
    .pipe(dest('./lib'));
}

function copyfont({
  return src('./src/fonts/**')
    .pipe(cssmin())
    .pipe(dest('./lib/fonts'));
}

exports.build = series(compile, copyfont);

Vue CLI 3 & Lerna

構建整個Element組件庫的腳本繁多,構建的代碼之間互相還有引用關係,對於開發的引用路徑也會產生必定的約束。所以設計相似於Element的UI框架相對開發者而言須要必定的開發門檻。

這裏基於Vue CLI 3的「開發/構建目標/庫」能力以及Lerna工具設計了一個UI框架,這個UI框架集成了如下特色:

  • 一、 「結構特色」:每一個UI組件都是一個npm包, 「多語言、工具和樣式」都是自成體系的npm包,可被業務或UI組件靈活引用,同時自然按需加載。
  • 二、 「配置特色」:若是須要進行構建處理,那麼每一個npm包可單獨進行構建配置,配置變得更加簡單。結合Vue CLI3的 「構件庫」能力,對於簡單UI組件的構建幾乎能夠作到webpack零配置,固然須要特殊的webpack loader除外。
  • 三、 「發佈特色」:組件庫的版本迭代能夠更快,不須要進行總體構建,每一個組件可單獨快速發佈 PATCHMINOR版本。

這裏設定業務層面須要進行webpack構建處理,所以能夠對UI框架的組件不進行構建處理,固然若是UI組件的設計須要特殊的webpack loader處理除外,不然業務層面須要作額外的webpack配置。固然不構建處理是相對於必定的使用場景的,不構建處理可能也會產生額外的一些問題。

這個UI框架的設計也會有一些缺陷:

  • 一、沒有完整引入功能(也能夠進行總體構建,可是這裏不推薦)
  • 二、不提供UMD模塊
  • 三、業務層面引入繁瑣(能夠出額外的引入工具,簡化業務中的UI組件引入)

Vue CLI 3

構建庫

爲了簡化UI框架的webpack配置,這裏將Vue CLI 3做爲開發的容器引入,借用Vue CLI 3的構建庫功能(「構建web-components-組件」功能應該更合適,這裏沒有進行驗證),幾乎能夠作到UI組件構建的零配置。經過審查項目的-webpack-配置能力,能夠查看Vue CLI 3爲咱們預先設置的通用webpack配置(幾乎能夠知足大部分的UI組件構建)。

插件體系

這裏使用Vue CLI 3的插件和Preset功能開發了幾個插件,以便於快速構建起步的UI設計框架,具體的preset.json配置以下:

{
  "useConfigFiles"true,
  "router"true,
  "routerHistoryMode"true,
  "vuex"false,
  "cssPreprocessor""less",
  "plugins": {
    "@vue/cli-plugin-babel": {},
    "@vue/cli-plugin-eslint": {
      "lintOn": ["save""commit"]
    },
    "@ziyi2/vue-cli-plugin-ui-base": {},
    "@ziyi2/vue-cli-plugin-ui-cz": {},
    "@ziyi2/vue-cli-plugin-ui-lint": {}
  }
}

這裏採用了官方設計的@vue/cli-plugin-babel和@vue/cli-plugin-eslint插件,同時本身設計了額外的三個插件來支持整個新的UI框架的起步:

  • @ziyi2/vue-cli-plugin-ui-base:UI框架基礎插件,生成Monorepo結構的源碼目錄(加入Lerna管理工具),生成基礎通用的webpack配置(在VUE CLI 3的webpack配置上進行再配置,VUE CLI3提供了 vue.config.js文件供開發者進行webpack再配置),提供了幾個基礎UI組件的示例(僅參考價值)。
  • @ziyi2/vue-cli-plugin-ui-cz: UI框架的cz適配器插件,加入了cz-customizable、commitlint、conventional-changelog,用於生成Angular規範的Git提交說明、檢測提交說明是否符合規範以及自動生成UI框架的升級日誌等。
  • @ziyi2/vue-cli-plugin-ui-lint:UI框架的lint-staged插件,代碼提交前會執行Eslint校驗,校驗不經過則不容許提交辣雞代碼。

這三個插件已經發布在npm的倉庫裏,若是是已有的Vue CLI 3項目,可直接經過vue add @ziyi2/ui-cz等命令進行安裝使用,插件源碼地址ziyi2/vue-cli3-lerna-ui/plugins,若是想學習設計Vue CLI 3插件,可參考插件開發指南,不過官方文檔可能不夠詳細,建議參考官方設計的插件或者別人設計的優秀插件。

Lerna

Lerna是一個Monorepo管理工具,使全部的組件(npm包)設計都集成在一個git倉庫裏,同時能夠利用yarn的workspace特性,模擬發佈的組件環境,從而使組件的開發和測試變得簡單,不須要屢次進行組件的發佈測試(這裏用Lerna進行Vue CLI插件開發也很是方便)。

同時Lerna還集成了版本發佈工具,能夠快速的對UI框架進行版本發佈。

UI組件各自修復問題或者新增功能能夠各自快速發佈PATCHMINOR版本,若是UI組件總體有非兼容性更新,能夠利用Lerna進行MAJOR版本發佈,更多關於版本發佈規範可查看語義化版本。

UI框架實踐

利用Vue CLI 3的遠程Preset,這裏將本身設計的UI框架分享給你們進行實踐使用:

// 可能獲取會有點慢,你們耐心等待
vue create --preset ziyi2/vue-cli3-lerna-ui my-project --packageManager yarn

若是報錯unable to get local issuer certificate,能夠設置git config --global http.sslVerify false

若是遠程確實獲取preset.json失敗,能夠採用本地的方式,將preset.json配置複製下來,放入新建的preset.json文件,執行如下命令生成UI框架:

vue create --preset ziyi2/vue-cli3-lerna-ui my-project --packageManager yarn

執行後的生成過程以下:

腳本命令

// 啓動開發服務
"serve""vue-cli-service serve",
// 生成靜態資源
"build""vue-cli-service build",
// Eslint校驗
"lint""vue-cli-service lint",
// 安裝和連接Lerna repo的依賴
"bootstrap""lerna bootstrap",
// 更新升級日誌
"cz:changelog""conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
// 構建
"lib""lerna run lib"

若是須要利用GitHub Pages發佈靜態資源,能夠新增命令"deploy": "npm run build && gh-pages -d dist",須要安裝gh-page依賴。

啓動

進入項目目錄,使用yarn serve命令啓動開發態視圖

這裏給出了國際化、選擇器、警告以及按鈕等UI設計示例。

構建

執行lerna run lib後(構建能夠配合npm run lint校驗,校驗不經過則構建失敗),Lerna工具會對每個npm包執行lib腳本:

這裏分別對utilsbtntheme包進行了構建處理,其中btn採用了Vue CLI 3默認的構建庫配置。

Monorepo結構

UI框架生成並構建後的Monorepo結構以下:

.
├── packages                   # workspaces
│   ├── alert                  # 警告(不構建)
│   │     ├── alert.vue        # 組件源碼
│   │     ├── index.js         # npm包入口文件
│   │     └── package.json     # npm包描述文件
│   ├── btn                    # 按鈕
│   │     ├── lib              # 目標文件
│   │     │    └── lib.common.js # npm包入口文件
│   │     ├── btn.vue          # 組件源碼
│   │     ├── index.js         # 構建入口文件
│   │     ├── package.json     # npm包描述文件(須要vue cli的開發態依賴)
│   │     └── vue.config.js    # 構建配置文件
│   ├── locale                 # 國際化
│   │     ├── lang             # 語言包
│   │     │    ├── enjs     # 英文
│   │     │    └── zh_CN.js  # 中文
│   │     ├── mixins         # 各個組件調用的國際化API
│   │     ├── src         # 源碼
│   │     ├── index.js         # npm包入口文件
│   │     ├── alert.vue        # 組件源碼
│   │     ├── index.js         # npm包入口文件
│   │     └── package.json     # npm包描述文件
│   ├── select                 # 選擇器(相似於alert)
│   ├── theme                  # 樣式
│   │     ├── lib              # 目標文件
│   │     │    ├── alert.css     # 警告樣式
│   │     │    ├── btn.css     # 按鈕樣式
│   │     │    ├── index.css     # 整體樣式
│   │     │    └── select.css  # 選擇器樣式
│   │     ├── src              # 源文件
│   │     │    ├── utils     # 通用方法和變量
│   │     │    ├── alert.less  # 警告樣式
│   │     │    ├── btn.less     # 按鈕樣式
│   │     │    ├── index.less    # 整體樣式
│   │     │    └── select.less  # 選擇器樣式
│   │     ├── gulpfile.js            # 構建配置文件
│   │     └── package.json     # npm包描述文件
│   └── utils                  # 工具方法
│         ├── lib         # 目標文件(這裏也能夠採用lodash的方式,去掉lib文件夾這一層)
│         ├── src         # 源文件
│         ├── babel.config.js  # 構建配置文件
│         └── package.json     # npm包描述文件
├── public                     # 公共資源目錄
├── src                        # 開發態目錄
├── .browserslistrc                    # UI框架目標瀏覽器配置
├── .cz-config.js                       # cz定製化提交說明配置
├── .gitignore                       # git忽略配置
├── .lintstagedrc   # lint-staged配置
├── babel.config.js   # vue cli的babel配置
├── lerna.json    # lerna配置
├── package.json   # vue cli容器描述文件(容器不是npm包)
├── postcss.config.js   # postcss配置
├── README.md    # 說明
└── vue.common.js   # 通用的組件構建配置文件

這裏重點說明src文件,src文件能夠根據開發須要自行選定方案:

  • 一、使用默認的CLI服務進行開發和UI框架Demo演示,這裏UI框架採用原生的 .vue文件形式進行Demo演示,若是想使用 .md文件進行演示,能夠採用vue-markdown-loader。
  • 二、使用Vue 驅動的靜態網站生成器VuePress,這個目前不是很穩定。

發佈

發佈徹底能夠按照語義化版本進行:

  • 一、各自npm包可使用 npm publish快速發佈 MINORPATCH版本。
  • 二、若是某個npm包有非兼容性更新,那麼可使用 lerna publish發佈 MAJOR版本。

使用Lerna工具發佈的npm包建議採用scope的形式發佈,UI框架示例沒有給出Demo,若是想採用scope形式發佈能夠查看ziyi2/vue-cli3-lerna-ui,須要在每一個npm包的package.json中作額外的配置,具體可查看vue-cli3-lerna-ui/plugins/vue-cli-plugin-ui-base/package.json的publishConfig字段信息。

若是lerna publish發佈失敗,例如報403錯誤等,可使用lerna publish from-package命令發佈,具體查看官方說明Lerna/publish/usage。

總結

對比Element的UI框架設計,採用Vue CLI 3 & Lerna的形式能夠簡化UI框架的配置,使各個UI組件的構建配置互相獨立,對於簡單的UI組件能夠利用Vue CLI 3的默認webpack配置。同時採用Monorepo的設計結構(Why is Babel a monorepo?),配合Lerna工具,可使得UI框架修復問題和發佈新功能的響應能力變得更快。

歡迎關注公衆號「前端試煉」平時會分享一些實用或者有意思的東西,發現代碼之美。專一深度和最佳實踐,但願打造一個高質量的公衆號。偶爾還會分享一些攝影 ~

也能夠掃碼加我微信,拉你進交流划水聊天羣,有看到好文章/代碼都會發在羣裏。


本文分享自微信公衆號 - 前端試煉(code-photo)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索