2023.12 ElementUI源码-组件打包源码分析
大家好,我是wo不是黄蓉,今年学习目标从源码共读开始,希望能跟着若川大佬学习源码的思路学到更多的东西。
最近在写组件库的时候,在使用elementui
的基础上进行了封装,但是,我想按需引入组件,然后将引入的组件放在一个数组里面,循环进行注册,结果就提示找不到样式系统。试了不使用as
然后循环注册组件就不会报错,好吧,不知道咋回事,然后就想看下element
里面怎么引入样式系统和打包.
感觉不去做永远不会踩坑,然后就永远不会学到新东西,工作中稍微加点挑战挺开心的,哈哈

编译时候就报错

使用全局引入组件
import Vue from 'vue';
import ElementUI from 'element-ui';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
使用局部引入组件
import Vue from 'vue';
import App from './App.vue';
import { Button } from 'element-ui'
Vue.use(Button)
new Vue({
el: '#app',
render: h => h(App)
});
组件打包源码分析入口
看组件如何打包的从package.json
入手
打包组件主要在这边"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",
组件打包是怎么实现的?
其实就是根据build-entry.js
生成一个入口文件,根据入口文件打包成不同的规范的包(umd
和commonjs
两种),打包好的内容又分为全部的和单个的,全部的就是按照全局引入的方式使用的(方式一),单个的就是按照按需引入的方式(方式二)
npm run build:file
其中最主要的一条命令node build/bin/build-entry.js
使用json-templater/string
生成入口文件
目标文件位置为:D:\workspace\element\src\index.js
,
这点从build\webpack.conf.js
的配置中可以看出来
module.exports = {
entry: {
app: ["./src/index.js"],
},
output: {
path: path.resolve(process.cwd(), "./lib"),
publicPath: "/dist/",
filename: "index.js",
chunkFilename: "[id].js",
libraryTarget: "umd",
libraryExport: "default",
library: "ELEMENT",
umdNamedDefine: true,
globalObject: "typeof self !== 'undefined' ? self : this",
},
}
源码实现全局引入:通过向外暴露一个install
方法
导出单个组件和install
方法,单个引入的时候用的Vue.use
,Vue.use
就是需要一个install
方法,每个组件中都有install
方法,全局也有一个install
方法,下面的install
指的是全局的
/* Automatically generated by './build/bin/build-entry.js' */
import Pagination from '../packages/pagination/index.js';
import Loading from '../packages/loading/index.js';
//省略一些组件的引入
const components = [
Pagination,
];
const install = function(Vue, opts = {}) {
locale.use(opts.locale);
locale.i18n(opts.i18n);
components.forEach(component => {
Vue.component(component.name, component);
});
Vue.use(InfiniteScroll);
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.15.12',
locale: locale.use,
i18n: locale.i18n,
install,
CollapseTransition,
Loading,
Pagination
};
组件里的install
,为了方便单个组件的引入
packages\alert\index.js
import Alert from './src/main';
/* istanbul ignore next */
Alert.install = function(Vue) {
Vue.component(Alert.name, Alert);
};
export default Alert;
打包成commonjs
通过 build/webpack.common.js
将 src/index.js
转换为 lib/element-ui.common.js
为啥要生成element-ui.common.js
呢?
可以通过require
方式引入


什么场景下会用到用require
方式引入呢?
又看了一遍搜了下其他答案,还是没太知道
打包成commonjs
的webpack
配置:
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',
libraryTarget: 'commonjs2'
},
在 package.json
配置入口 "main": "lib/element-ui.common.js"
用于项目全局引入 import ElementUI from 'element-ui'
单个组件的引入方式
webpack --config build/webpack.component.js
入口文件为每个文件,打包成单个的js

这边components
中的内容也相当于是一个入口文件,注册方式前面讲过,参考前面的内容
{
"alert": "./packages/alert/index.js",
}
样式是怎么打包的?
node build/bin/gen-cssfile
其中打包里面有一句这样的编译命令:npm run build:theme
即为样式打包的实现
node build/bin/gen-cssfile
根据packages\theme-chalk
下的文件生成css
文件
var fs = require('fs');
var path = require('path');
var Components = require('../../components.json');
var themes = [
'theme-chalk'
];
Components = Object.keys(Components);
var basepath = path.resolve(__dirname, '../../packages/');
function fileExists(filePath) {
try {
return fs.statSync(filePath).isFile();
} catch (err) {
return false;
}
}
themes.forEach((theme) => {
var isSCSS = theme !== 'theme-default';
var indexContent = isSCSS ? '@import "./base.scss";\n' : '@import "./base.css";\n';
Components.forEach(function(key) {
if (['icon', 'option', 'option-group'].indexOf(key) > -1) return;
var fileName = key + (isSCSS ? '.scss' : '.css');
indexContent += '@import "./' + fileName + '";\n';
var filePath = path.resolve(basepath, theme, 'src', fileName);
if (!fileExists(filePath)) {
fs.writeFileSync(filePath, '', 'utf8');
console.log(theme, ' 创建遗漏的 ', fileName, ' 文件');
}
});
fs.writeFileSync(path.resolve(basepath, theme, 'src', isSCSS ? 'index.scss' : 'index.css'), indexContent);
});
gulp build --gulpfile packages/theme-chalk/gulpfile.js
gulp src
创建一个流,用于从文件系统读取vinyl(虚拟文件对象)
gulp series
将任务和/或组合操作组合成更大的操作,这些操作将按顺序依次执行
const { series, src, dest } = require('gulp');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');
function compile() {
//匹配src下所有的.scss文件;
//pipe把流导入到你想导入的地方;
//sass.sync把sass转换成css;
//autoprefixer转换后的css自动补全css浏览器前缀;
//cssmin压缩css代码
//dest把转好的css文件存入css文件夹
return src('./src/*.scss')
.pipe(sass.sync())
.pipe(autoprefixer({
browsers: ['ie > 9', 'last 2 versions'],
cascade: false
}))
.pipe(cssmin())
.pipe(dest('./lib'));
}
function copyfont() {
return src('./src/fonts/**')
.pipe(cssmin())
.pipe(dest('./lib/fonts'));
}
//按顺序执行这compile和copyfont函数
exports.build = series(compile, copyfont);
cp-cli packages/theme-chalk/lib lib/theme-chalk
package.json
cp-cli packages/theme-chalk/lib lib/theme-chalk
cp-cli
文件复制工具,将packages/theme-chalk/lib 移动到 lib/theme-chalk,用于单独的样式引入
总结
element-ui
组件可以按照按需引入的方式进行引入,通过webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js
打包的是全部组件的包,分别是umd
和commonjs
格式
打包成局部组件的包:通过webpack --config build/webpack.component.js
实现的
css
的按需引入通过单独引入某个文件实现
//全局
import 'element-ui/lib/theme-chalk/index.css'
//局部
import 'element-ui/lib/theme-chalk/button.css'
遗留问题,为什么使用as
就引入不到组件的问题没解决
我的问题,既然样式系统和组件按需导入没有关系,为什么给组件起了别名之后,就引用不到对应的样式?
这个问题这边应该是找到了答案,参考链接
就是按需引入的组件最终会被转换成这种形式,前提是使用了[babel](https://so.csdn.net/so/search?q=babel&spm=1001.2101.3001.7020)-plugin-component
这个webpack
插件
配置信息
"plugins": [
"transform-vue-jsx",
[
"@babel/plugin-transform-runtime",
{
"corejs": 3
}
],
[
"component",
{
"libraryName": "@zto/zui",
"styleLibraryName": "theme-chalk"
}
]
],
按需引入最后会被转换成以下格式:

所以会自动引入button.css
文件,由于我们给组件做了别名,所以查找组件的时候会按照引入z-button.css
来加载,但是此时lib
目录下的theme-chalk
没有z-button.css
文件因此就会报错
打包好的文件的目录结构
