当前位置:   article > 正文

基于Element构建自己的ui组件库的流程

基于element

摘要

最近萌发要做一个自己的基于Vue的组件库的想法,尝试下自己去造一些轮子;于是翻了下业内的标杆element-ui组件库的设计,在此基础上去构建自己的ui库(站在巨人的肩膀上成功)。注:项目是基于vue-cli@3.x的;

组件库的基础工程要完成的几个目标

  • 项目的整体结构设计
  • 实现组件按需加载
  • 项目文档的管理(随后将单独写一篇来介绍)
  • 发布npm

基于element的目录设计

  1. ├─build // 构建相关的脚本和配置
  2. ├─docs // 打包后的静态文件
  3. ├─examples // 用于展示Element组件的demo
  4. ├─lib // 构建后生成的文件,发布到npm包
  5. ├─packages // 组件代码
  6. | ├── button
  7. | | ├── button.vue //组件文件
  8. | | └── index.js // 导出Button组件以及处理供按需加载的方法
  9. | ├── .. 各个组件
  10. | └── theme-chalk // 这里存放所有的组件样式.scss
  11. ├─public // index.html
  12. └─package.json

注:这里的设计将组件跟样式进行了分离,这样做的目的是为了更好的去做按需加载

Vue组件构建

    我们都知道在vue中组件的安装要使用Vue.use(install),install是向外暴漏的一个方法,里面调用Vue.component(component.name, component)完成组件的注册;具体的源码如下:

  1. /* eslint-disable */
  2. // This file is auto gererated by build/build-entry.js
  3. import FeButton from './button'
  4. import FeInput from './input'
  5. const version = '0.0.46'
  6. const components = [
  7. FeButton,
  8. FeInput
  9. ]
  10. const install = Vue => {
  11. components.forEach(Component => {
  12. Vue.use(Component);
  13. // Vue.component(component.name, component) 也可使用这个进行注册
  14. })
  15. Vue.prototype.$message = Message
  16. };
  17. /* istanbul ignore if */
  18. if (typeof window !== 'undefined' && window.Vue) {
  19. install(window.Vue)
  20. }
  21. export {
  22. install,
  23. version,
  24. FeButton,
  25. FeInput
  26. }
  27. export default {
  28. install,
  29. version
  30. }

    install内部之所以能够使用Vue实例是Vue.use中进行了Vue实例的参数的合并,有兴趣的可以去看看源码这里就提上一嘴;

    以上基本的组件构造就完成了,我们也就很好的完成了第一步可在main.js全局导入one-piece-ui的组件进行测试;这时候我们应该思考下如何去实现按需加载的问题啦!

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

组件的按需加载

  • 如何实现按需加载
        根据element-ui的介绍,借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。

    然后,将 .babelrc 修改为:

  1. {
  2. "presets": [["es2015", { "modules": false }]],
  3. "plugins": [
  4. [
  5. "component",
  6. {
  7. "libraryName": "element-ui",
  8. "styleLibraryName": "theme-chalk"
  9. }
  10. ]
  11. ]
  12. }

    这个插件的作用是什么呢?就是将引用路径进行了变换,如下:

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

    转换为:

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

    这样我们就精准地引入了对应 lib 下的 Button 组件的 JS 和 CSS 代码了,也就实现了按需引入 Button 组件。

  1. 实现组件跟样式的分离

    根据以上的分析,我们知道我们接下来的任务就是要把组件跟样式进行分离;
  • 首先我们看下element-uipackage.jsonscripts构建命令
  1. "bootstrap": "yarn || npm i",
  2. "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
  3. "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
  4. "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
  5. "build:umd": "node build/bin/build-locale.js",
  6. "clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
  7. "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",
  8. "deploy:extension": "cross-env NODE_ENV=production webpack --config build/webpack.extension.js",
  9. "dev:extension": "rimraf examples/extension/dist && cross-env NODE_ENV=development webpack --watch --config build/webpack.extension.js",
  10. "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",
  11. "dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js",
  12. "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",
  13. "i18n": "node build/bin/i18n.js",
  14. "lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",
  15. "pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh",
  16. "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",
  17. "test:watch": "npm run build:theme && cross-env BABEL_ENV=test karma start test/unit/karma.conf.js"

    是不是有点懵逼,这都是啥咋这么多??? 我的内心其实也是很这啥都啥...

    我对其中的一些命令进行了删除保留了一些 暂时我们能用到的,如下:

  1. "init": "npm install commitizen -g && commitizen init cz-conventional-changelog --save-dev --save-exact && npm run bootstrap",
  2. "bootstrap": "npm install && cd ./packages/theme-chalk && npm install",
  3. "build:style": "gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
  4. "build:docs": "vue-cli-service build",
  5. "build:lib": "node build/build-lib.js",
  6. "build:entry": "node build/build-entry.js ",
  7. "serve": "vue-cli-service serve",
  8. "clean": "rimraf lib && rimraf packages/*/lib",
  9. "deploy": "sh build/deploy.sh",
  10. "lint": "vue-cli-service lint",
  11. "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,对,就是他了;源码如下:
  1. const path = require('path');
  2. const ProgressBarPlugin = require('progress-bar-webpack-plugin');
  3. const VueLoaderPlugin = require('vue-loader/lib/plugin');
  4. const Components = require('./get-components')();
  5. const entry = {};
  6. Components.forEach(c => {
  7. entry[c] = `./packages/${c}/index.js`;
  8. });
  9. const webpackConfig = {
  10. mode: 'production',
  11. entry: entry,
  12. output: {
  13. path: path.resolve(process.cwd(), './lib'),
  14. filename: '[name].js',
  15. chunkFilename: '[id].js',
  16. libraryTarget: 'umd'
  17. },
  18. resolve: {
  19. extensions: ['.js', '.vue', '.json']
  20. },
  21. performance: {
  22. hints: false
  23. },
  24. stats: 'none',
  25. module: {
  26. rules: [{
  27. test: /\.js$/,
  28. loader: 'babel-loader',
  29. exclude: /node_modules/
  30. }, {
  31. test: /\.vue$/,
  32. loader: 'vue-loader'
  33. }]
  34. },
  35. plugins: [
  36. new ProgressBarPlugin(),
  37. new VueLoaderPlugin()
  38. ]
  39. };
  40. module.exports = webpackConfig;

    很熟悉是吧,对!就是你想的那样---多文件入口打包;这里里面有个get-components工具方法就是返回所有的packages下的组件文件名称;这样我们就可以通过命令自动注入组件的js文件啦~

  1. const fs = require('fs');
  2. const path = require('path');
  3. const excludes = [
  4. 'index.js',
  5. 'theme-chalk',
  6. 'mixins',
  7. 'utils',
  8. 'fonts',
  9. '.DS_Store'
  10. ];
  11. module.exports = function () {
  12. const dirs = fs.readdirSync(path.resolve(__dirname, '../packages'));
  13. return dirs.filter(dirName => excludes.indexOf(dirName) === -1);
  14. };
  1. 再来说一下npm run build:entry 这里会执行build/build-entry.js,源码如下:
  1. const fs = require('fs-extra');
  2. const path = require('path');
  3. const uppercamelize = require('uppercamelcase');
  4. const Components = require('./get-components')();
  5. const packageJson = require('../package.json');
  6. const version = process.env.VERSION || packageJson.version;
  7. const tips = `/* eslint-disable */
  8. // This file is auto gererated by build/build-entry.js`;
  9. function buildPackagesEntry() {
  10. const uninstallComponents = ['Message'];
  11. const importList = Components.map(
  12. name => `import ${uppercamelize(name)} from './${name}'`
  13. );
  14. const exportList = Components.map(name => `${uppercamelize(name)}`);
  15. const installList = exportList.filter(
  16. name => !~uninstallComponents.indexOf(`${uppercamelize(name)}`)
  17. );
  18. const content = `${tips}
  19. ${importList.join('\n')}
  20. const version = '${version}'
  21. const components = [
  22. ${installList.join(',\n ')}
  23. ]
  24. const install = Vue => {
  25. components.forEach(Component => {
  26. Vue.use(Component)
  27. })
  28. Vue.prototype.$message = Message
  29. };
  30. /* istanbul ignore if */
  31. if (typeof window !== 'undefined' && window.Vue) {
  32. install(window.Vue)
  33. }
  34. export {
  35. install,
  36. version,
  37. ${exportList.join(',\n ')}
  38. }
  39. export default {
  40. install,
  41. version
  42. }
  43. `;
  44. fs.writeFileSync(path.join(__dirname, '../packages/index.js'), content);
  45. }
  46. buildPackagesEntry();

    不难发现,这里给我们自动化生成了一个packages/index.js文件,是不是感觉技术减少人力? 心里:"卧槽..."

3. npm run build:lib,通过上面的学习,到此这个命令其实也就不神秘啦,里面也就是做了一件汇总的事情:

  1. /**
  2. * Build npm lib
  3. */
  4. const shell = require('shelljs');
  5. const signale = require('signale');
  6. const { Signale } = signale;
  7. const tasks = [
  8. 'bootstrap',
  9. 'lint',
  10. 'clean',
  11. 'build:entry',
  12. 'lib',
  13. 'build:style'
  14. ];
  15. tasks.forEach(task => {
  16. signale.start(task);
  17. const interactive = new Signale({ interactive: true });
  18. interactive.pending(task);
  19. shell.exec(`npm run ${task} --silent`);
  20. interactive.success(task);
  21. });

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

发布npm以及跟新到GitHub Pages

    这里就不介绍了,具体请戳项目如何发布到NPM;
静态页面如何发布到GithubPages

  • 最后附上本项目的地址,如果喜欢就给个star✨吧
    项目地址
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/661940
推荐阅读
相关标签
  

闽ICP备14008679号