在前两篇文章中,我们分析了 element-ui
的目录结构,入口文件,组件页面的路由,md-loder。今天我们回归到 的源码中继续分析 package.json
中的 dist
命令,看看 element-ui
打包上线的流程是怎样实现的。
执行 npm run dis 命令会发生什么?
js
//package.json
"scripts": {
"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"
"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",
},
第 14 行的 dist
命令由 9 个串行命令组成,执行 npm run 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
npm run clean
js
"clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage"
clean
在脚本中对应着以上三个命令,rimraf
是一个用来删除文件和文件夹的包。
- rimraf lib :删除 lib 文件夹
- rimraf packages :删除 packages 文件夹下的 lib 文件夹
- rimraf test / ** / coverage :删除 test文件夹下的 coverage 文件夹
该命令用于在打包之前,删除一些文件
npm run build : file
js
"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 / iconInit.js
该命令指向 build/bin/iconInit.js
文件 ,它主要进行的操作是从 packages/theme-chalk/src/icon.scss 文件中提取字体图标的类名到 examples/icon.json 中;
node build/bin/build-entry.js
该命令指向 build/bin/build-entry.js
文件,它根据components.json中组件的列表,结合字符串模板生成src/index.js,用来建造组件库的入口文件;
node build/bin/i18n.js
该命令指向 build/bin/i18n.js
文件,它为 examples / pages 中的模板文件生成不同语言的vue文件到 examples / pages / [语言] /;
node build/bin/version.js
该命令指向 build/bin/version.js
文件,它生成版本字符串数组到examples/version.json中,用来表示组件库的版本。
npm run lint
npm run lint
是一个常用的命令,通常用于在 JavaScript 项目中运行代码检查工具。代码检查工具用于分析代码中潜在的错误、bug 和风格问题。当运行 npm run lint
命令时,它将执行 package.json
文件中指定的代码检查工具。
js
"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet"
上面的配置将使用 ESLint
检查src、test、packages、build目录中的所有 JavaScript 文件,并根据 .eslintrc.json 文件中的配置指定检查规则和选项。
webpack --config build / webpack.conf.js
使用 webpack 打包,config 指向了 build / webpack.conf.js
- 入口文件:
src / index.js
(npm run build:file 生成) - 输出:以
umd
形式输出到lib/index.js
,该格式支持客户端使用 - loader:
bable-loader
处理 jsx 等文件;vue-loader
处理 packages 下的 vue 组件
webpack --config build/webpack.common.js
使用 webpack 打包,config 指向了 build/webpack.common.js
- 入口文件:
src / index.js
(npm run build:file 生成) - 输出:以
commonjs2
形式输出到lib/element-ui.common.js
,该格式支持服务端 - loader:
babel-loader
处理 jsx、babel 和 es6 等文件;vue-loader
处理 packages 下的 vue 组件;style-loader
和css-loader
处理 css 文件;url-loader
处理图片等;
webpack --config build/webpack.component.js
config 指向了 build/webpack.component.js
,该命令目的是为了实现组件的按需引入
- 入口文件:
components.json
,包含 packages 下的组件 - 输出:把 packages 下的组件,以
connomjs2
形式分别输出到lib目录
- loader:
babel-loader
处理 jsx、babel 和 es6 等文件;vue-loader
处理 packages 下面的 vue 组件;style-loader
和css-loader
处理 css 文件;url-loader
处理图片等 - externals:
externals
配置项用来告诉Webpack
要构建的代码中使用了哪些不用被打包的模块,也就是说这些模版是外部环境提供的,Webpack
在打包时可以忽略它们。
防止将某些
import
的包(package)打包到bundle
中,而是在运行时(runtime)再去从外部获取这些扩展依赖
------【正确使用externals,vue工程构建性能提升67%】
js
const Components = require('../components.json');
const config = require('./config');
const webpackConfig = {
mode: 'production',
// 此处实现了多入口打包
entry: Components,
output: {
path: path.resolve(process.cwd(), './lib'),
publicPath: '/dist/',
filename: '[name].js',
chunkFilename: '[id].js',
libraryTarget: 'commonjs2'
},
// 防止代码冗余,此处做性能优化
externals: config.externals,
}
按需引入
webpackConfig.output 打包出来的内容如下图,可以按组件打包,方便按需引入:
根据官网的提示,按需引入需要安装插件babel-plugin-component
在页面中引入需要的组件:
js
import { Button } from 'element-ui'
借助babel插件可以把上面的代码转换为以下的形式:
js
// lib/button.js即按组件打包后的el-button组件
var button = require('element-ui/lib/button')
require('element-ui/lib/theme-chalk/button.css')
externals
解决按需加载的代码冗余问题
注意:按需引入能有效减小项目体积,遇到将要引入的组件依赖于已引入的组件时
会发生代码的冗余,此时用 externals
可以防止将这些 import 的包打包到 bundle 中,当运行时再从外部获取这些扩展依赖,但是对于 CSS 部分,element-ui
并未处理冗余情况。
以上问题该如何理解呢 ? 我们举个例子说明一下!
在 element-ui 中,Table
组件依赖了 CheckBox
组件,那么当我在项目中同时引入了 Table 组件和 CheckBox 组件的时候,会不会产生代码冗余呢?
js
import { Table, CheckBox } from 'element-ui'
答案是会,在项目中最终引入的包会有 2 份 CheckBox 的代码,此时通过在 webpack 配置文件中配置的 externals 能有效解决部分冗余,不会打包生成 2 份 CheckBox
JS 部分的代码。但是对于 CSS ,element-ui
并未处理冗余情况,可以看到 lib/theme-chalk/checkbox.css
和 lib/theme-chalk/table.css
中都会有 CheckBox
组件的 CSS 样式。
npm run build : utils
js
"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
使用 Babel 工具链中的 babel 命令,将 src 目录下的 javaScript 代码编译为 lib 下的 ES5代码,并忽略了 src / index.js 文件。
cross-env
是一个跨平台的环境变量设置工具。用于设置 BABEL_ENV
环境变量为 utils(.babelrc 文件中 env 选项的值将从 process.env.BABEL_ENV 获取,如果没有的话,则获取 process.env.NODE_ENV 的值,它也无法获取时会设置为 "development" )。
npm run build : umd
js
"build:umd": "node build/bin/build-locale.js",
src/locale/lang
里的文件被require('babel-core').transformFile
转换后的文件放在lib/umd/locale
中。- transform 函数是一个用于将指定文件转换为 UMD 模块格式的函数。具体来说,它使用了 Babel 工具链中的 transformFile 方法,并在编译过程中使用了
add-module-exports
和transform-es2015-modules-umd
两个插件。其中,add-module-exports
插件用于将 ES6 模块转换为 CommonJS 模块,并添加 module.exports 属性,以便在 Node.js 等环境中使用。 transform-es2015-modules-umd
插件用于将 ES6 模块转换为 UMD 模块,并添加适当的 UMD 包装器,以便在浏览器等环境中使用。利用file-save
进一步处理,如将define('zh-CN'
处理成define('element/locale/zh-CN'
,将global.zhCN = mod.exports
处理成global.ELEMENT.lang = global.ELEMENT.lang || {};global.ELEMENT.lang.zhCN = mod.exports;
。
编译前关键代码 build/bin/build-locale.js :
编译后关键代码 lib/umd/locale/ar.js :
npm run build : theme
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",
node build/bin/gen-cssfile
检查 packages/theme-chalk/src 里是否有 components.json 对应的 scss 文件,没有的话,生成。并生成 packages/theme-chalk/src/index.scss 里面是所有组件的导入。
gulp build --gulpfile packages/theme-chalk/gulpfile.js
将 packages/theme-chalk/src
下的 scss
转换为 css
并压缩,将 font
压缩,后放到 packages/theme-chalk/lib
下面。
cp-cli packages/theme-chalk/lib lib/theme-chalk
将 packages/theme-chalk/lib
复制一份到 lib/theme-chalk
下面。
babel-plugin-component 插件的原理
分析到这里,element ui 的样式部分编译就结束了,我们提出一个小问题,如果不使用babel-plugin-component
插件,我们如何将打包好的组件引入到项目中呢?上文提到的babel-plugin-component
的作用是什么?
先回答第一个问题,如果不是用插件,我们用 import 'xxx' from './xxx/ccc.js'的方式将组件引入到项目中,连同打包好的 css 文件一并引入,示例如下:
js
//index.vue
import button from "element-ui/lib/button.js"
import "element-ui/lib/theme-chalk/button.css"
一个组件需要引入两行代码,试问,如果该页面需要引入 5 个组件,那岂不是需要 10 行代码? 这显然很麻烦且臃肿,后期维护变更也显得很繁琐。为了解决这个问题,我们可以通过以下形式结合babel-plugin-component
来进行优化
js
import {button, alert, aside, card, checkbox-button} fron 'element-ui'
babel
能够将ES6、ES7等高级语法转换成ES5语法。而babel-plugin-component
作为babel
的一个插件,专门用于按需加载UI组件的JS和CSS,并且自动支持按需加载,使用起来非常方便,可以最大限度地减少页面加载时间并提高页面性能,babel-plugin-component
也是按需加载的重要一环。