赞
踩
我的本地开发环境:M1 芯片Mac,node v12.22.10。
1. 从 Element 官方 clone 一份 dev 源码到本地
2. 安装依赖:yarn 或者 npm i
我用的是 npm i(用 yarn 安装依赖会失败报红色错误)。
npm i 的时候可能会出现 node-pre-gyp 的报错(但不是红色的),直接忽略,最终本地开发环境还是能成功启动。
已经安装过依赖、或者已经启动过的项目,第二次启动的时候遇到报错的解决办法:
删除 node_modules, package-lock.json,关闭整个终端重新启动,然后再 npm i,npm run dev。
3. 启动开发环境 npm run dev
每次 npm run dev 的时候都会去重新安装依赖,非常耗时,可以把 package.json dev script 里的 npm run bootstrap 删掉。
4. 修改组件源码
组件内容在 “packages/组件目录” 里修改。
组件样式在 “packages/theme-chalk/src/组件名.scss” 里修改。
查看效果可以在 “examples/docs/zh-CN/组件名.md” 里增加自己需要调试的代码,就可以在页面的相应的组件的例子中看到自己修改后的效果。
5. 新增组件可以用 make 命令
在项目根目录下命令行输入 make 可以查看全部可用的 make 命令。新增组件的命令是:
make new <component-name> [中文名]
这条命令会创建新组件 package. 例如 'make new button 按钮',新增组件时要添加新文件或修改的地方都会自动处理。
6. 如果要上传到 npm,需要更改包名(npm 上包名不能重复)
项目根目录名和 package.json 里的 “name” 值都要改。
7. 打包 npm run dist
打包完成后会重新在项目根目录生成 lib 目录。
提交二次开发后的代码到 git(注意:lib文件也一定要上传)。
8. 在其他项目中使用更新后的 Element UI
偷懒的一种方法是:复制打包生成的 lib 目录替换掉开发项目中 node_modules 下 element-ui 下的 lib 目录。(但不建议,不规范)
规范的方法是:在开发项目的 package.json 文件的 dependencies 中新增 element-ui 这一项,指向二次开发组件库的 github 地址(这里一定注意需指定分支或者tag号,默认是master)。
- "dependencies": {
- "element-ui": "git+https://github.com/xxx/element.git#dev"
- },
执行 npm i,将二次开发的 Element 组件库安装到开发项目里。
9. 重启项目
<!-- 引入样式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 引入组件库 --> <script src="https://unpkg.com/element-ui/lib/index.js"></script>从官网直接引用样式文件和js文件的示例可以知道,完整的样式文件在 lib/theme-chalk/index.css,完整的 js 文件在 lib/index.js。
- |- build webpack相关的打包配置文件
- |- examples Element 官网
- |- assets 官网需要的一些静态资源,例如: 字体、样式、图片
- |- components 官网的公共组件, 例如: headr、foot
- |- docs/zh-CN 各个组件的文档
- |- pages 官网的页面
- |- packages 组件源码
- |- src
- |- index.js 所有组件注册的入口文件, 该文件由脚本`build/bin/build-entry.js`生成
- |- test 测试文件, 使用 karma 框架
- |- types 类型声明文件
- |- components.json 表明了组件的文件路径,方便webpack打包时获取组件的文件路径
- |- .gitattributes 允许我们指定由git使用的文件和路径的属性
- |- .npmignore 上传npm的配置文件
- |- FAQ.md 常见的组件库问题
- |- Makefile 是一个适用于c/c++的工具,make环境下,输入make命令将会执行makefile文件中的某个目标命令
packages 目录分为组件目录和 theme-chalk
主题样式目录。
theme-chalk
主题样式目录包含了所有组件的样式,用 sass
编写。其中 src/common/var.scss
文件包含了所有颜色、边框以及单个组件抽取出来的的样式变量。如果要修改涉及到全局变量,那么就在 var.scss
中定义。
bootstrap
安装依赖包build:file
构建官网所需的文件build:theme
构建主题样式build:utils
用 babel
编译工具函数库build:umd
编译生成 umd
模块的语言包clean
清理打包后的文件deploy:build
生产环境下构建官网deploy:extension
在生产环境下构建主题插件dev:extension
启动主题插件的开发环境,可以进行开发调试dev
启动本地开发环境,进行能够调试开发dev:play
用于查看某个组件的效果dist
打包组件库lint
校验文件pub
检测分支是否冲突test
测试项目"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"
这个命令自动构建一些文件,让我们拆开来一一剖析:
node build/bin/iconInit.js
将 packages/theme-chalk/src/icon.scss
文件中的 icon
抽离出来放入 examples/icon.json
文件。
如将 platform-eleme
放入 icon.json
的数组中:
packages/theme-chalk/src/icon.scss
- .el-icon-platform-eleme:before {
- content: "\e7ca";
- }
icon.json
"platform-eleme"
生成 icon.json
文件,在 entry.js
中引入,并且绑定在 vue
原型上。
- import icon from './icon.json';
- Vue.prototype.$icon = icon; // Icon 列表页用
应用在文档中 examples/docs/zh-CN/icon.md,
通过之后说到的 md-loader
解析成 vue
组件,渲染在官网上。
node build/bin/build-entry.js
执行后自动生成项目的入口文件 src/index.js
,用 json-templater
结合变量生成模板,避免每次新增组件时手动在 src/index.js
中引入并导出组件。新增组件时,只要在 components.json 里维护好组件路径就可以了。
node build/bin/i18n.js
根据模板创建各个语言下的组件,只需要维护 examples/i18n/page.json
文件,执行命令自动生成官网上的语言组件。
node build/bin/version.js
添加 /package.json
里的 version 作为新版本号,自动生成 examples/version.json
,用于记录组件库的版本信息,这些版本会渲染在官网组件页面的头部导航栏。
examples/components/header.vue
- created() {
- const xhr = new XMLHttpRequest();
- xhr.onreadystatechange = _ => {
- if (xhr.readyState === 4 && xhr.status === 200) {
- const versions = JSON.parse(xhr.responseText);
- this.versions = Object.keys(versions).reduce((prev, next) => {
- prev[next] = versions[next];
- return prev;
- }, {});
- }
- };
- xhr.open('GET', '/versions.json');
- xhr.send();
- let primaryLast = '#409EFF';
- bus.$on(ACTION_USER_CONFIG_UPDATE, (val) => {
- let primaryColor = val.global['$--color-primary'];
- if (!primaryColor) primaryColor = '#409EFF';
- const base64svg = 'data:image/svg+xml;base64,';
- const imgSet = document.querySelectorAll('h1 img');
- imgSet.forEach((img) => {
- img.src = `${base64svg}${window.btoa(window.atob(img.src.replace(base64svg, '')).replace(primaryLast, primaryColor))}`;
- });
- primaryLast = primaryColor;
- });
- }
构建新版本的时候要更改2个地方:
if (!content[version]) content[version] = '2.15';
"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
执行该文件通过组件列表(components.json)生成组件样式入口文件 packages/theme-chalk/src/index.scss
,并将所有组件的样式都导入。
gulp build --gulpfile packages/theme-chalk/gulpfile.js
把所有组件的 scss 文件(packages/theme-chalk/src/*.scss
)通过 gulp
编译成 css,并放置到 packages/theme-chalk/lib 目录下
。
打包和压缩的工作平时一般交给 webpack
来做,但是基于工作流用 gulp
更加快捷和方便。
为什么需要编译呢?
因为 element
在使用时有两种引入方式:
- import Vue from 'vue';
- import ElementUI from 'element-ui';
- import 'element-ui/lib/theme-chalk/index.css';
- import App from './App.vue';
-
- Vue.use(ElementUI);
-
- new Vue({
- el: '#app',
- render: h => h(App)
- });
引入了 lib\theme-chalk\index.css
文件
- import Vue from 'vue';
- import { Button, Select } from 'element-ui';
- import App from './App.vue';
-
- Vue.component(Button.name, Button);
- Vue.component(Select.name, Select);
- /* 或写为
- * Vue.use(Button)
- * Vue.use(Select)
- */
-
- new Vue({
- el: '#app',
- render: h => h(App)
- });
不需要引入 css
文件,只需引入对应的 scss
文件。 这就是为什么需要编译 scss
的原因。
cp-cli packages/theme-chalk/lib lib/theme-chalk
cp-cli
是一个跨平台的 copy
工具,将gulp build --gulpfile .\packages\theme-chalk\gulpfile.js
编译生成的 css
目录(packages/theme-chalk/lib
)复制到 lib/theme-chalk
下,方便全局引入 css:
import 'element-ui/lib/theme-chalk/index.css';
"build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js"
将 src
目录下的内容忽略 index.js
通过 babel
转译后移动到 lib
下。
"build:umd": "node build/bin/build-locale.js"
执行后生成 umd
模块的语言包。
将 src/locale/lang
下的语言包都编译到 lib/umd/locale
下。
"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"
npm run build:file
前文分析过了,主要构建官网文件。接下来分析新的构建脚本。
生产环境下构建官网:
cross-env NODE_ENV=production webpack --config build/webpack.demo.js
"deploy:extension": "cross-env NODE_ENV=production webpack --config build/webpack.extension.js"
在生产环境下构建主题插件,主题编辑器的 chorme
插件项目的 webpack
配置,项目在 extension
目录下。执行命令后会在 extension
目录下生成 dist
目录,其中包含了 chorme
插件,在浏览器加载已解压的扩展程序就可以使用主题生成插件。
"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"
首先用 npm run bootstrap
安装依赖。npm run build:file
在前面也有提到,主要用来自动化生成一些文件。主要是 node build/bin/build-entry.js
,用于生成 Element
的入口 js
:先是读取根目录的 components.json
,这个 json
文件维护着 Element
所有的组件路径映射关系,键为组件名,值为组件源码的入口文件;然后遍历键值,将所有组件进行 import
,对外暴露 install
方法,把所有 import
的组件通过 Vue.component(name, component)
方式注册为全局组件,并且把一些弹窗类的组件挂载到 Vue
的原型链上。
在生成了入口文件的 src/index.js
之后就会运行 webpack-dev-server
。
启动组件库本地开发环境。在更改后可以热更新官网。
"dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js"
组件测试项目,在 examples/play/index.vue
中可以引入组件库任意组件,也可以直接使用 dev
启动的项目,在文档中使用组件。
用于查看某个组件的效果。适用于组件按需加载的显示效果。在 webpack.demo.js
通过环境变量配置输入。
build/webpack.demo.js
- const isPlay = !!process.env.PLAY_ENV;
-
- // ……省略webpack具体配置
-
- entry: isProd ? {
- docs: './examples/entry.js'
- } : (isPlay ? './examples/play.js' : './examples/entry.js'),
examples/play.js
- import Vue from 'vue';
- import Element from 'main/index.js';
- import App from './play/index.vue';
- import 'packages/theme-chalk/src/index.scss';
-
- Vue.use(Element);
-
- new Vue({ // eslint-disable-line
- render: h => h(App)
- }).$mount('#app');
"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 && npm run build:file && npm run lint
都已经解释过了,分别是清除上一次打包产物、生成入口文件以及 i18n
文件和 eslint
检测。
webpack --config build/webpack.conf.js
生成 umd
格式的 js
文件(index.js
)
- const path = require('path');
- const ProgressBarPlugin = require('progress-bar-webpack-plugin');
- const VueLoaderPlugin = require('vue-loader/lib/plugin');
- const TerserPlugin = require('terser-webpack-plugin');
-
- const config = require('./config');
- console.log(config)
- module.exports = {
- // 模式
- mode: 'production',
- // 入口
- entry: {
- app: ['./src/index.js']
- },
- // 输出
- output: {
- path: path.resolve(process.cwd(), './lib'),
- publicPath: '/dist/',
- // 输出的文件名
- filename: 'index.js',
- // 初始的chunk文件名称
- chunkFilename: '[id].js',
- // library 暴露为 AMD 模块。 在 AMD 或 CommonJS 的 require 之后可访问(libraryTarget:'umd')
- libraryTarget: 'umd',
- // 入口的默认导出将分配给 library target:
- // if your entry has a default export of `MyDefaultModule`
- // var MyDefaultModule = _entry_return_.default;
- libraryExport: 'default',
- // 输出一个库,为你的入口做导出。
- library: 'ELEMENT',
- // 会把 AMD 模块命名为 UMD 构建
- umdNamedDefine: true,
- // 为了使 UMD 构建在浏览器和 Node.js 上均可用,应将 output.globalObject 选项设置为 'this'。对于类似 web 的目标,默认为 self。
- globalObject: 'typeof self !== \'undefined\' ? self : this'
- },
- // 解析
- resolve: {
- // 能够使用户在引入模块时不带扩展.尝试按顺序解析这些后缀名。如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀。
- extensions: ['.js', '.vue', '.json'],
- // 创建 import 或 require 的别名,来确保模块引入变得更简单。
- alias: config.alias
- },
- // 外部扩展
- externals: {
- vue: config.vue
- },
- // 优化
- optimization: {
- // 允许你通过提供一个或多个定制过的 TerserPlugin 实例, 覆盖默认压缩工具(minimizer)
- minimizer: [
- new TerserPlugin({
- terserOptions: {
- output: {
- comments: false
- }
- }
- })
- ]
- },
- // 性能
- performance: {
- // 不展示警告或错误提示。
- // 官网推荐使用error,有助于防止把体积大的bundle部署到生产环境,从而影响网页的性能
- // 很奇怪这里要把它关闭
- hints: false
- },
- // stats对象
- stats: {
- // 告知 stats 是否添加关于子模块的信息。
- children: false
- },
- // 模块
- module: {
- // 使用babel-loader和vue-loader
- rules: [
- {
- test: /\.(jsx?|babel|es6)$/,
- include: process.cwd(),
- exclude: config.jsexclude,
- loader: 'babel-loader'
- },
- {
- test: /\.vue$/,
- loader: 'vue-loader',
- options: {
- compilerOptions: {
- preserveWhitespace: false
- }
- }
- }
- ]
- },
- // 插件
- plugins: [
- new ProgressBarPlugin(),
- new VueLoaderPlugin()
- ]
- };
webpack --config build/webpack.common.js
生成 commonjs
格式的 js
文件(element-ui.common.js
),require
时默认加载的是这个文件。
libraryTarget: 'commonjs2'
与 webpack.conf.js
不同在于输出 output
的 libraryExport
。
前者暴露的是 commonjs2
,后者暴露的是 umd
。
webpack --config build/webpack.component.js
与前两者的 index.js
入口不同,以 components.json
为入口,将每一个组件打包生成一个文件,用于按需加载。
npm run build:utils && npm run build:umd && npm run build:theme
也已经讲过,分别是转译工具方法、转译语言包、生成样式文件。
"lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet"
eslint
校验 src、packages
和 build
目录下的文件。
14、pub
"pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh"
npm run bootstrap
下载依赖。
sh build/git-release.sh
:主要是检测 dev
分支是否冲突。
- #!/usr/bin/env sh
- # 切换到dev分支
- git checkout dev
- # 检测本地是否有未提交文件
- if test -n "$(git status --porcelain)"; then
- # 输出日志
- echo 'Unclean working tree. Commit or stash changes first.' >&2;
- exit 128;
- fi
- # 检测本地分支是否有误
- if ! git fetch --quiet 2>/dev/null; then
- # 输出日志
- echo 'There was a problem fetching your branch. Run `git fetch` to see more...' >&2;
- exit 128;
- fi
- # 检测是否有最新提交
- if test "0" != "$(git rev-list --count --left-only @'{u}'...HEAD)"; then
- # 输出日志
- echo 'Remote history differ. Please pull changes.' >&2;
- exit 128;
- fi
- # 输出日志
- echo 'No conflicts.' >&2;
sh build/release.sh
脚本完成了以下工作:
该脚本在发布组件库时可以使用,特别是其中自动更改版本号的功能(每次 publish
时都忘改版本号)。这里提交代码到远程仓库的日志很简单。
- #!/usr/bin/env sh
- set -e
- # 切换到master
- git checkout master
- # 合并dev分支
- git merge dev
- # npx: 使用本地已安装的可执行工具,而不需要配置 scripts
- VERSION=`npx select-version-cli`
- # 更新版本号
- read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r
- echo # (optional) move to a new line
- if [[ $REPLY =~ ^[Yy]$ ]]
- then
- # 输出:压缩版本
- echo "Releasing $VERSION ..."
-
- # build
- # 编译打包
- VERSION=$VERSION npm run dist
-
- # ssr test
- node test/ssr/require.test.js
-
- # 发布到npm
- # publish theme
- # 输出:压缩theme-chalk版本
- echo "Releasing theme-chalk $VERSION ..."
- cd packages/theme-chalk
- # 更改主题包的版本信息
- npm version $VERSION --message "[release] $VERSION"
- # 如果是beta版本则打个beta标签
- if [[ $VERSION =~ "beta" ]]
- then
- npm publish --tag beta
- else
- npm publish
- fi
- cd ../..
-
- # commit
- git add -A
- git commit -m "[build] $VERSION"
- # 更改组件库的版本信息
- npm version $VERSION --message "[release] $VERSION"
- # publish
- # 发布到远程仓库
- git push eleme master
- git push eleme refs/tags/v$VERSION
- git checkout dev
- git rebase master
- git push eleme dev
- # 发布组件库
- if [[ $VERSION =~ "beta" ]]
- then
- npm publish --tag beta
- else
- npm publish
- fi
- fi
node build/bin/gen-indices.js
生成目录,支持搜索:
- 'use strict';
- // 生成目录
- const fs = require('fs');
- const path = require('path');
- // 是一个托管的全文、数字和分面搜索引擎,能够从第一次击键交付实时结果。
- const algoliasearch = require('algoliasearch');
- // 将Unicode str转换为段字符串,确保在URL或文件名中使用它是安全的。
- // https://www.npmjs.com/package/transliteration?activeTab=readme
- // demo:
- // slugify('你好,世界');
- // // ni-hao-shi-jie
- const slugify = require('transliteration').slugify;
- // 密钥
- const key = require('./algolia-key');
-
- const client = algoliasearch('4C63BTGP6S', key);
- const langs = {
- 'zh-CN': 'element-zh',
- 'en-US': 'element-en',
- 'es': 'element-es',
- 'fr-FR': 'element-fr'
- };
- // 四种语言
- ['zh-CN', 'en-US', 'es', 'fr-FR'].forEach(lang => {
- const indexName = langs[lang];
- const index = client.initIndex(indexName);
- index.clearIndex(err => {
- if (err) return;
- // 读取/examples/docs/中的文件
- fs.readdir(path.resolve(__dirname, `../../examples/docs/${ lang }`), (err, files) => {
- if (err) return;
- let indices = [];
- files.forEach(file => {
- console.log(file)
- const component = file.replace('.md', '');
- const content = fs.readFileSync(path.resolve(__dirname, `../../examples/docs/${ lang }/${ file }`), 'utf8');
- const matches = content
- .replace(/:::[\s\S]*?:::/g, '')
- .replace(/```[\s\S]*?```/g, '')
- .match(/#{2,4}[^#]*/g)
- .map(match => match.replace(/\n+/g, '\n').split('\n').filter(part => !!part))
- .map(match => {
- const length = match.length;
- if (length > 2) {
- const desc = match.slice(1, length).join('');
- return [match[0], desc];
- }
- return match;
- });
-
- indices = indices.concat(matches.map(match => {
- const isComponent = match[0].indexOf('###') < 0;
- const title = match[0].replace(/#{2,4}/, '').trim();
- const index = { component, title };
- index.ranking = isComponent ? 2 : 1;
- index.anchor = slugify(title);
- index.content = (match[1] || title).replace(/<[^>]+>/g, '');
- return index;
- }));
- });
-
- index.addObjects(indices, (err, res) => {
- console.log(err, res);
- });
- });
- });
- });
"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"
单元测试:UI
组件作为高度抽象的基础公共组件,编写单元测试是很有必要的。
1. 新增组件的 scss 样式文件放在 packages/theme-chalk/src 目录下,css 样式文件要放在 packages/theme-default/src 目录下。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。