基於Element構建自己的ui組件庫的流程


摘要

最近萌發要做一個自己的基於Vue的組件庫的想法,嘗試下自己去造一些輪子;於是翻了下業內的標桿element-ui組件庫的設計,在此基礎上去構建自己的ui庫(站在巨人的肩膀上成功)。注:項目是基於vue-cli@3.x的;

組件庫的基礎工程要完成的幾個目標

  • 項目的整體結構設計
  • 實現組件按需加載
  • 項目文檔的管理(隨后將單獨寫一篇來介紹)
  • 發布npm

基於element的目錄設計

├─build // 構建相關的腳本和配置
├─docs // 打包后的靜態文件
├─examples // 用於展示Element組件的demo
├─lib // 構建后生成的文件,發布到npm包
├─packages // 組件代碼
|   ├── button
|   |     ├── button.vue //組件文件
|   |     └── index.js // 導出Button組件以及處理供按需加載的方法
|   ├── .. 各個組件
|   └── theme-chalk // 這里存放所有的組件樣式.scss
├─public // index.html
└─package.json

注:這里的設計將組件跟樣式進行了分離,這樣做的目的是為了更好的去做按需加載

Vue組件構建

    我們都知道在vue中組件的安裝要使用Vue.use(install),install是向外暴漏的一個方法,里面調用Vue.component(component.name, component)完成組件的注冊;具體的源碼如下:

/* eslint-disable */
// This file is auto gererated by build/build-entry.js
import FeButton from './button'
import FeInput from './input'
const version = '0.0.46'
const components = [
  FeButton,
  FeInput
]
const install = Vue => {
  components.forEach(Component => {
    Vue.use(Component);
    // Vue.component(component.name, component) 也可使用這個進行注冊
  })

  Vue.prototype.$message = Message
};
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}
export {
  install,
  version,
  FeButton,
  FeInput
}
export default {
  install,
  version
}

    install內部之所以能夠使用Vue實例是Vue.use中進行了Vue實例的參數的合並,有興趣的可以去看看源碼這里就提上一嘴;

    以上基本的組件構造就完成了,我們也就很好的完成了第一步可在main.js全局導入one-piece-ui的組件進行測試;這時候我們應該思考下如何去實現按需加載的問題啦!

import FeUI from '../packages';
import '../packages/theme-chalk/index.css';  
Vue.use(FeUI);

組件的按需加載

  • 如何實現按需加載
        根據element-ui的介紹,借助 babel-plugin-component,我們可以只引入需要的組件,以達到減小項目體積的目的。

    然后,將 .babelrc 修改為:

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

    這個插件的作用是什么呢?就是將引用路徑進行了變換,如下:

import { Button } from 'one-piece-ui' 

    轉換為:

var button = require('one-piece-ui/lib/button')
require('one-piece-ui/lib/theme-chalk/button.css')

    這樣我們就精准地引入了對應 lib 下的 Button 組件的 JS 和 CSS 代碼了,也就實現了按需引入 Button 組件。

  1. 實現組件跟樣式的分離

    根據以上的分析,我們知道我們接下來的任務就是要把組件跟樣式進行分離;
  • 首先我們看下element-uipackage.jsonscripts構建命令
"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 && 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"

    是不是有點懵逼,這都是啥咋這么多??? 我的內心其實也是很這啥都啥...

    我對其中的一些命令進行了刪除保留了一些 暫時我們能用到的,如下:

"init": "npm install commitizen -g && commitizen init cz-conventional-changelog --save-dev --save-exact && npm run bootstrap",
"bootstrap": "npm install && cd ./packages/theme-chalk && npm install",
"build:style": "gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
"build:docs": "vue-cli-service build",
"build:lib": "node build/build-lib.js",
"build:entry": "node build/build-entry.js ",
"serve": "vue-cli-service serve",
"clean": "rimraf lib && rimraf packages/*/lib",
"deploy": "sh build/deploy.sh",
"lint": "vue-cli-service lint",
"lib": "vue-cli-service build --target lib --name feui --dest lib packages/index.js && webpack --config ./build/webpack.component.js"
  • 組件樣式的分離
  1. 首先解釋一下npm run init做的那幾件事:

    1. 安裝了兩個有關git commit的相關規范插件,主要是在功能提交上做一些規定
    2. 啟動了第二個命令npm run bootstrap
  2. npm run bootstrap做的幾件事:

    1. 安裝所有的依賴
    2. cd ./packages/theme-chalk目錄下,安裝gulp相關的依賴;

注:element-ui采用了gulp對scss文件進行打包;個人覺得還是挺好的,gulp比較簡單不像webpack的配置那么復雜,能夠很簡單的處理scss

  1. build:style做的幾件事:
    1. 對組件樣式進行打包到當前lib目錄下
    2. 利用cp-cli插件將打包好的lib文件夾內容復制到lib/theme-chalk目錄下

到此我們就完成了樣式的打包及輸出到指定的目錄啦,此處有掌聲...

  • 接下來我們介紹一下將組件js單獨抽離:
  1. 如果你運行npm run lib命令時候,你會發現組件的js都被單獨打包出來了,那這是如何實現的呢?我們看下這條命令webpack --config ./build/webpack.component.js,對,就是他了;源碼如下:
const path = require('path');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

const Components = require('./get-components')();
const entry = {};
Components.forEach(c => {
  entry[c] = `./packages/${c}/index.js`;
});
const webpackConfig = {
  mode: 'production',
  entry: entry,
  output: {
    path: path.resolve(process.cwd(), './lib'),
    filename: '[name].js',
    chunkFilename: '[id].js',
    libraryTarget: 'umd'
  },
  resolve: {
    extensions: ['.js', '.vue', '.json']
  },
  performance: {
    hints: false
  },
  stats: 'none',  
  module: {
    rules: [{
      test: /\.js$/,
      loader: 'babel-loader',
      exclude: /node_modules/
    }, {
      test: /\.vue$/,
      loader: 'vue-loader'
    }]
  },
  plugins: [
    new ProgressBarPlugin(),
    new VueLoaderPlugin()
  ]
};

module.exports = webpackConfig;

    很熟悉是吧,對!就是你想的那樣---多文件入口打包;這里里面有個get-components工具方法就是返回所有的packages下的組件文件名稱;這樣我們就可以通過命令自動注入組件的js文件啦~

const fs = require('fs');
const path = require('path');

const excludes = [
  'index.js',
  'theme-chalk',
  'mixins',
  'utils',
  'fonts',
  '.DS_Store'
];

module.exports = function () {
  const dirs = fs.readdirSync(path.resolve(__dirname, '../packages'));
  return dirs.filter(dirName => excludes.indexOf(dirName) === -1);
};
  1. 再來說一下npm run build:entry 這里會執行build/build-entry.js,源碼如下:
const fs = require('fs-extra');
const path = require('path');
const uppercamelize = require('uppercamelcase');

const Components = require('./get-components')();
const packageJson = require('../package.json');

const version = process.env.VERSION || packageJson.version;
const tips = `/* eslint-disable */
// This file is auto gererated by build/build-entry.js`;

function buildPackagesEntry() {
  const uninstallComponents = ['Message'];
  const importList = Components.map(
    name => `import ${uppercamelize(name)} from './${name}'`
  );

  const exportList = Components.map(name => `${uppercamelize(name)}`);

  const installList = exportList.filter(
    name => !~uninstallComponents.indexOf(`${uppercamelize(name)}`)
  );

  const content = `${tips}
    ${importList.join('\n')}
    const version = '${version}'
    const components = [
      ${installList.join(',\n  ')}
    ]
    const install = Vue => {
      components.forEach(Component => {
        Vue.use(Component)
      })

      Vue.prototype.$message = Message
    };
    /* istanbul ignore if */
    if (typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }
    export {
      install,
      version,
      ${exportList.join(',\n  ')}
    }
    export default {
      install,
      version
    }
  `;
  fs.writeFileSync(path.join(__dirname, '../packages/index.js'), content);
}

buildPackagesEntry();

    不難發現,這里給我們自動化生成了一個packages/index.js文件,是不是感覺技術減少人力? 心里:"卧槽..."

3. npm run build:lib,通過上面的學習,到此這個命令其實也就不神秘啦,里面也就是做了一件匯總的事情:

/**
 * Build npm lib
 */
const shell = require('shelljs');
const signale = require('signale');

const { Signale } = signale;
const tasks = [
  'bootstrap',
  'lint',
  'clean',
  'build:entry',
  'lib',
  'build:style'
];

tasks.forEach(task => {
  signale.start(task);
  const interactive = new Signale({ interactive: true });
  interactive.pending(task);
  shell.exec(`npm run ${task} --silent`);
  interactive.success(task);
});

    好了,基本的組件js分離功能也就完成了;

發布npm以及跟新到GitHub Pages

    這里就不介紹了,具體請戳項目如何發布到NPM;
靜態頁面如何發布到GithubPages

  • 最后附上本項目的地址,如果喜歡就給個star✨吧
    項目地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM