如何快速為團隊打造自己的組件庫(下)—— 基於 element-ui 為團隊打造自己的組件庫
文章已收錄到 github,歡迎 Watch 和 Star。
簡介
在了解 Element 源碼架構 的基礎上,接下來我們基於 element-ui 為團隊打造自己的組件庫。
主題配置
基礎組件庫在 UI 結構上差異很小,一般只是在主題色上會有較大差異,畢竟每個團隊都有了 UI 風格。比如,我們團隊的 UI 設計稿其實是基於 Ant Design 來出的,而組件庫是基於 Element-UI 來開發,即使是這種情況,對組件本身的改動也很少。所以,基於開源庫打造團隊自己的組件庫時,主題配置就很重要了。
element-ui 的一大特色就是支援自定義主題,它通過在線主題編輯器、Chrome 插件或命令行主題工具這三種方式來訂製 element-ui 所有組件的樣式。那麼 element-ui 是怎麼做到這一點的呢?
因為 element-ui 組件樣式中的顏色、字體、線條等樣式都是通過變數的方式引入的,在 packages/theme-chalk/src/common/var.scss
中可以看到這些變數的定義,這就為自定義主題提供了方便,因為我們只需要修改這些變數,就可以實現組件主題的改變。
在線主題編輯器和 Chrome 插件支援實時預覽。並且可以下載訂製的樣式包,然後使用。在線主題編輯器和 Chrome 插件的優點是可視化,簡潔明了,但是有個最大的缺點就是,最後下載出來的是一個將所有組件樣式打包到一起的樣式包,沒辦法支援按需載入,不推薦使用。這裡我們使用命令行主題工具來訂製樣式。
命令行主題工具
-
初始化項目目錄並安裝主題生成工具(element-theme)
mkdir theme && cd theme && npm init -y && npm i element-theme -D
-
安裝白堊主題
npm i element-theme-chalk -D
-
初始化變數文件
node_modules/.bin/et -i
命令執行以後可能會得到如下報錯資訊
原因是 element-theme 包中依賴了低版本的 graceful-fs,低版本 graceful-fs 在高版本的 node.js 中不兼容,最簡單的方案是升級 graceful-fs。
在項目根目錄下創建
npm-shrinkwrap.json
文件,並添加如下內容:{ "dependencies": { "graceful-fs": { "version": "4.2.2" } } }
運行
npm install
重新安裝依賴即可解決,然後重新執行node_modules/.bin/et -i
,執行完以後會在當前目錄生成element-variables.scss
文件。 -
修改變數
直接編輯
element-variables.scss
文件,例如修改主題色為紅色,將文件中的$--color-primary
的值修改為red
,$--color-primary: red !default;
。文件中寫了很好的注釋,並且樣式程式碼也是按照組件來分割組織的,所以大家可以對照設計團隊給到的設計稿來一一修改相關的變數。如果實在覺得看程式碼比較懵,可以參照在線主題編輯器,兩邊的變數名是一致的。
題外話:element-ui 還提供了兩個資源包,供設計團隊使用,所以最理想的是,讓設計團隊根據 element-ui 的資源包出設計稿,這樣兩邊就可以做到統一,研發團隊的工作量也會降低不少。比如我們團隊就不是這樣,設計團隊給到的設計稿是基於 Ant Design 出的,研發組件庫時改動的工作量和難度就會相對比較大。所以研發、設計、產品一定要進行很好的溝通。
-
編譯主題
修改完以後,保存文件,然後執行以下命令編譯主題,會產生一個 theme 目錄。生產出來都是 CSS 樣式文件,文件名和組件名一一對應,支援按需引入(指定組件的樣式文件)和全量引入(index.css)。
-
生產未壓縮的樣式文件
node_modules/.bin/et --out theme-chalk
-
生產經過壓縮的樣式文件
node_modules/.bin/et --minimize --out theme-chalk
-
幫助命令
node_modules/.bin/et --help
-
啟用
watch
模式,實時編譯主題node_modules/.bin/et --watch --out theme-chalk
-
-
使用自定義主題
-
用新生成的主題目錄(theme-chalk)替換掉框架中的
packages/theme-chalk
目錄。重命名老的 theme-chalk 為 theme-chalk.bak,不要刪掉,後面需要用建議將生成主題時用到的 element-variables.scss 文件保存在項目中,因為以後你可能會需要重新生成主題
-
修改
/examples/entry.js
、/examples/play.js
、/examples/extension/src/app.js
中引入的組件庫的樣式// 用新的樣式替換舊的默認樣式 // import 'packages/theme-chalk/src/index.scss import 'packages/theme-chalk/index.css
-
修改
/build/bin/iconInit.js
中引入的圖標樣式文件// var fontFile = fs.readFileSync(path.resolve(__dirname, '../../packages/theme-chalk/src/icon.scss'), 'utf8'); var fontFile = fs.readFileSync(path.resolve(__dirname, '../../packages/theme-chalk/icon.css'), 'utf8');
-
修改
/examples/docs/{四種語言}/custom-theme.md
// @import "~element-ui/packages/theme-chalk/src/index"; @import "~element-ui/packages/theme-chalk/index";
-
執行
make dev
啟動開發環境,查看效果
到這一步,主題配置就結束了,你會發現,element-ui 官網的組件樣式基本上和設計稿上的一致。但是仔細對比後,會發現有一些組件的樣式和設計稿有差異,這時候就需要對這些組件的樣式進行深度訂製,覆寫不一致的樣式。
其實這塊兒漏掉了 /build/bin/new.js 中涉及的樣式目錄,這塊兒的改動會放到後面
-
樣式深度訂製
上一步的主題配置,只能解決主題相關的樣式,但是有些組件的有些樣式不屬於主題樣式,如果這部分樣式剛好又和設計稿不一致的話,那就需要重寫這部分樣式去覆蓋上一步的樣式。
以下配置還支援為自定義組件添加樣式
樣式目錄
-
將
主題配置
步驟中備份的/packages/theme-chalk.bak
重命名為/packages/theme-lyn
,作為覆寫組件和自定義組件的樣式目錄 -
刪掉
/packages/theme-lyn/src
目錄的所有文件 -
你會寫 scss ?
- 忽略掉下一步,然後後續步驟你只需將對應的 less 操作換成 sass 即可
-
你不會寫 scss,擴展其它語法,假設你會寫 less
-
在項目根目錄執行以下命令,然後刪掉 gulp-sass
npm i less less-loader gulp-less -D && npm uninstall gulp-sass -D
如果一會兒啟動開發環境以後,報錯 「TypeError: this.getOptions is not a function」,則降級 less-loader 版本,比如我的版本是:[email protected]、[email protected]
-
在
/packages/theme-lyn
目錄下執行以下命令,然後刪掉 gulp-sassnpm i gulp-less -D && npm uninstall gulp-sass -D
-
將
/packages/theme-lyn/gulpfile.js
更改為以下內容'use strict'; /** * 將 ./src/*.less 文件編譯成 css 文件輸出到 ./lib 目錄 * 將 ./src/fonts/中的所有字體文件輸出到 ./lib/fonts 中,如果你沒有覆寫字體樣式的需要,則刪掉拷貝字體樣式部分 */ const { series, src, dest } = require('gulp'); const less = require('gulp-less'); const autoprefixer = require('gulp-autoprefixer'); const cssmin = require('gulp-cssmin'); const path = require('path') function compile() { return src('./src/*.less') .pipe(less({ paths: [ path.join(__dirname, './src') ] })) .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')); } // 也可以在這裡擴展其它功能,比如拷貝靜態資源 exports.build = series(compile, copyfont);
-
在
build/webpack.demo.js
中增加解析less
文件的規則{ test: /\.less$/, use: [ isProd ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader', 'less-loader' ] }
-
-
假如你要覆寫 button 組件的部分樣式
-
在
/packages/theme-lyn/src
目錄下新建button.less
文件,編寫覆寫樣式時請遵循如下規則- 組件樣式的覆寫,最好遵循 BEM 風格,目的是提供良好的命名空間隔離,避免樣式打包以後發生意料之外的覆蓋
- 只覆寫已有的樣式,可以在組件上新增類名,但不要刪除,目的是兼容線上程式碼
// 這裡我要把主要按鈕的字型大小改大有些,只是為了演示效果 .el-button--primary { font-size: 24px; }
-
-
改造
build/bin/gen-cssfile.js
腳本/** * 將各個覆寫的樣式文件在 packages/theme-lyn/src/index.less 文件中自動引入 */ var fs = require('fs'); var path = require('path'); // 生成 theme-lyn/src 中的 index.less 文件 function genIndexLessFile(dir) { // 文件列表 const files = fs.readdirSync(dir); /** * @import 'x1.less'; * @import 'x2.less; */ let importStr = "/* Automatically generated by './build/bin/gen-cssfile.js' */\n"; // 需要排除的文件 const excludeFile = ['assets', 'font', 'index.less', 'base.less', 'variable.less']; files.forEach(item => { if (excludeFile.includes(item) || !/\.less$/.test(item)) return; // 只處理非 excludeFile 中的 less 文件 importStr += `@import "./${item}";\n`; }); // 在 packages/theme-lyn/src/index.less 文件中寫入 @import "xx.less",即在 index.less 中引入所有的樣式文件 fs.writeFileSync(path.resolve(dir, 'index.less'), importStr); } genIndexLessFile(path.resolve(__dirname, '../../packages/theme-lyn/src/'));
-
在項目根目錄下執行以下命令
npm i shelljs -D
-
新建
/build/bin/compose-css-file.js
/** * 負責將打包後的兩個 css 目錄(lib/theme-chalk、lib/theme-lyn)合併 * lib/theme-chalk 目錄下的樣式文件是通過主題配置自動生成的 * lib/theme-lyn 是擴展組件的樣式(覆寫默認樣式和自定義組件的樣式) * 最後將樣式都合併到 lib/theme-chalk 目錄下 */ const fs = require('fs'); const fileSave = require('file-save'); const { resolve: pathResolve } = require('path'); const shelljs = require('shelljs'); const themeChalkPath = pathResolve(__dirname, '../../lib/theme-chalk'); const themeStsUIPath = pathResolve(__dirname, '../../lib/theme-lyn'); // 判斷樣式目錄是否存在 let themeChalk = null; let themeStsUI = null; try { themeChalk = fs.readdirSync(themeChalkPath); } catch (err) { console.error('/lib/theme-chalk 不存在'); process.exit(1); } try { themeStsUI = fs.readdirSync(themeStsUIPath); } catch (err) { console.error('/lib/theme-lyn 不存在'); process.exit(1); } /** * 遍歷兩個樣式目錄,合併相同文件,將 theme-lyn 的中樣式追加到 theme-chalk 中對應樣式文件的末尾 * 如果 theme-lyn 中的文件在 theme-chalk 中不存在(比如擴展的新組件),則直接將文件拷貝到 theme-chalk */ const excludeFiles = ['element-variables.css', 'variable.css']; for (let i = 0, themeStsUILen = themeStsUI.length; i < themeStsUILen; i++) { if (excludeFiles.includes(themeStsUI[i])) continue; if (themeStsUI[i] === 'fonts') { shelljs.cp('-R', pathResolve(themeStsUIPath, 'fonts/*'), pathResolve(themeChalkPath, 'fonts')); continue; } if (themeStsUI[i] === 'assets') { shelljs.cp('-R', pathResolve(themeStsUIPath, 'assets'), themeChalkPath); continue; } if (themeChalk.includes(themeStsUI[i])) { // 說明當前樣式文件是覆寫 element-ui 中的樣式 const oldFileContent = fs.readFileSync(pathResolve(themeChalkPath, themeStsUI[i]), { encoding: 'utf-8' }); fileSave(pathResolve(themeChalkPath, themeStsUI[i])).write(oldFileContent).write(fs.readFileSync(pathResolve(themeStsUIPath, themeStsUI[i])), 'utf-8').end(); } else { // 說明當前樣式文件是擴展的新組件的樣式文件 // fs.writeFileSync(pathResolve(themeChalkPath, themeStsUI[i]), fs.readFileSync(pathResolve(themeStsUIPath, themeStsUI[i]))); shelljs.cp(pathResolve(themeStsUIPath, themeStsUI[i]), themeChalkPath); } } // 刪除 lib/theme-lyn shelljs.rm('-rf', themeStsUIPath);
-
改造 package.json 中的 scripts
{ "gen-cssfile:comment": "在 /packages/theme-lyn/src/index.less 中自動引入各個組件的覆寫樣式文件", "gen-cssfile": "node build/bin/gen-cssfile", "build:theme:comment": "構建主題樣式:在 index.less 中自動引入各個組件的覆寫樣式文件 && 通過 gulp 將 less 文件編譯成 css 並輸出到 lib 目錄 && 拷貝基礎樣式 theme-chalk 到 lib/theme-chalk && 拷貝 編譯後的 theme-lyn/lib/* 目錄到 lib/theme-lyn && 合併 theme-chalk 和 theme-lyn", "build:theme": "npm run gen-cssfile && gulp build --gulpfile packages/theme-lyn/gulpfile.js && cp-cli packages/theme-lyn/lib lib/theme-lyn && cp-cli packages/theme-chalk lib/theme-chalk && node build/bin/compose-css-file.js", }
-
執行以下命令
npm run gen-cssfile
-
改造
/examples/entry.js
和/examples/play.js
// 用新的樣式替換舊的默認樣式 // import 'packages/theme-chalk/src/index.scss import 'packages/theme-chalk/index.css // 在這行下面引入自定義樣式 // 引入自定義樣式 import 'packages/theme-lyn/src/index.less'
-
訪問官網,查看 button 組件的覆寫樣式是否生效
自定義組件
組件庫在後續的開發和迭代中,需要兩種自定義組件的方式:
-
增加新的 element-ui 組件
element-ui 官網可能在某個時間點增加一個你需要的基礎組件,這時你需要將其集成進來
-
增加業務組件
基礎組件就緒以後,團隊就會開始推動業務組件的建設,這時候就會向組件庫中增加新的組件
新的 element-ui 組件
element-ui 提供了增加新組件的腳本,執行 make new <component-name> [中文名]
即可生成新組件所需的所有文件以及配置,比如:make new button 按鈕
,有了該腳本可以讓你專註於組件的編寫,不需要管任何配置。
/build/bin/new.js
但是由於我們調整了框架主題庫的結構,所以腳本文件也需要做相應的調整。需要將 /build/bin/new.js
文件中處理樣式的程式碼刪掉,樣式文件不再需要腳本自動生成,而是通過重新生成主題的方式實現。
// /build/bin/new.js 刪掉以下程式碼
{
filename: path.join('../../packages/theme-chalk/src', `${componentname}.scss`),
content: `@import "mixins/mixins";
@import "common/var";
@include b(${componentname}) {
}`
},
// 添加到 index.scss
const sassPath = path.join(__dirname, '../../packages/theme-chalk/src/index.scss');
const sassImportText = `${fs.readFileSync(sassPath)}@import "./${componentname}.scss";`;
fileSave(sassPath)
.write(sassImportText, 'utf8')
.end('\n');
Makefile
改造 Makefile
文件,在 new
配置後面增加 && npm run build:file
命令,重新生成組件庫入口文件,不然不會引入新增加的組件。
new:
node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS)) && npm run build:file
增加新組件
完成上述改動以後,只需兩步即可完成新 element-ui 組件的創建:
-
執行
make new <component-name> [組件中文名]
命令新建新的 element-ui 組件這一步會生成眾多文件,你只需要從新的 element-ui 源碼中將該組件對應的程式碼複製過來填充到對應的文件即可
-
重新生成主題,然後覆蓋現在的
/packages/theme-chalk
業務組件
新增的業務組件就不要以 el 開頭了,避免和 element 組件重名或造成誤會。需要模擬 /build/bin/new.js
腳本寫一個新建業務組件的腳本 /build/bin/new-lyn-ui.js
,大家可以基於該腳本去擴展。
/build/bin/new-lyn-ui.js
'use strict';
/**
* 新建組件腳本,以 lyn-city 組件為例
* 1、在 packages 目錄下新建組件目錄,並完成目錄結構的基本創建
* 2、創建組件文檔
* 3、組件單元測試文件
* 4、組件樣式文件
* 5、組件類型聲明文件
* 6、並將上述新建的相關資源自動添加的相應的文件,比如組件組件註冊到 components.json 文件、樣式文件在 index.less 中自動引入等
* 總之你只需要專註於編寫你的組件程式碼即可,其它一概不用管
*/
console.log();
process.on('exit', () => {
console.log();
});
if (!process.argv[2]) {
console.error('[組件名]必填 - Please enter new component name');
process.exit(1);
}
const path = require('path');
const fs = require('fs');
const fileSave = require('file-save');
const uppercamelcase = require('uppercamelcase');
// 組件名稱 city
const componentname = process.argv[2];
// 組件中文名 城市列表
const chineseName = process.argv[3] || componentname;
// 組件大駝峰命名 City
const ComponentName = uppercamelcase(componentname);
// 組件路徑:/packages/city
const PackagePath = path.resolve(__dirname, '../../packages', componentname);
const Files = [
// packages/city/index.js 的內容
{
filename: 'index.js',
content: `import ${ComponentName} from './src/main';
/* istanbul ignore next */
${ComponentName}.install = function(Vue) {
Vue.component(${ComponentName}.name, ${ComponentName});
};
export default ${ComponentName};`
},
// packages/city/src/main.vue 組件定義
{
filename: 'src/main.vue',
content: `<template>
<div class="lyn-${componentname}"></div>
</template>
<script>
export default {
name: 'Lyn${ComponentName}'
};
</script>`
},
// 組件中文文檔
{
filename: path.join('../../examples/docs/zh-CN', `${componentname}.md`),
content: `## ${ComponentName} ${chineseName}`
},
// 組件單元測試文件
{
filename: path.join('../../test/unit/specs', `${componentname}.spec.js`),
content: `import { createTest, destroyVM } from '../util';
import ${ComponentName} from 'packages/${componentname}';
describe('${ComponentName}', () => {
let vm;
afterEach(() => {
destroyVM(vm);
});
it('create', () => {
vm = createTest(${ComponentName}, true);
expect(vm.$el).to.exist;
});
});
`
},
// 組件樣式文件
{
filename: path.join(
'../../packages/theme-lyn/src',
`${componentname}.less`
),
content: `@import "./base.less";\n\n.lyn-${componentname} {
}`
},
// 組件類型聲明文件
{
filename: path.join('../../types', `${componentname}.d.ts`),
content: `import { LynUIComponent } from './component'
/** ${ComponentName} Component */
export declare class Lyn${ComponentName} extends LynUIComponent {
}`
}
];
// 將新組件添加到 components.json
const componentsFile = require('../../components.json');
if (componentsFile[componentname]) {
console.error(`${componentname} 已存在.`);
process.exit(1);
}
componentsFile[componentname] = `./packages/${componentname}/index.js`;
fileSave(path.join(__dirname, '../../components.json'))
.write(JSON.stringify(componentsFile, null, ' '), 'utf8')
.end('\n');
// 在 index.less 中引入新組件的樣式文件
const lessPath = path.join(
__dirname,
'../../packages/theme-lyn/src/index.less'
);
const lessImportText = `${fs.readFileSync(
lessPath
)}@import "./${componentname}.less";`;
fileSave(lessPath).write(lessImportText, 'utf8').end('\n');
// 添加到 element-ui.d.ts
const elementTsPath = path.join(__dirname, '../../types/element-ui.d.ts');
let elementTsText = `${fs.readFileSync(elementTsPath)}
/** ${ComponentName} Component */
export class ${ComponentName} extends Lyn${ComponentName} {}`;
const index = elementTsText.indexOf('export') - 1;
const importString = `import { Lyn${ComponentName} } from './${componentname}'`;
elementTsText =
elementTsText.slice(0, index) +
importString +
'\n' +
elementTsText.slice(index);
fileSave(elementTsPath).write(elementTsText, 'utf8').end('\n');
// 新建剛才聲明的所有文件
Files.forEach(file => {
fileSave(path.join(PackagePath, file.filename))
.write(file.content, 'utf8')
.end('\n');
});
// 將新組建添加到 nav.config.json
const navConfigFile = require('../../examples/nav.config.json');
Object.keys(navConfigFile).forEach(lang => {
const groups = navConfigFile[lang].find(item => Array.isArray(item.groups))
.groups;
groups[groups.length - 1].list.push({
path: `/${componentname}`,
title:
lang === 'zh-CN' && componentname !== chineseName
? `${ComponentName} ${chineseName}`
: ComponentName
});
});
fileSave(path.join(__dirname, '../../examples/nav.config.json'))
.write(JSON.stringify(navConfigFile, null, ' '), 'utf8')
.end('\n');
console.log('DONE!');
Makefile
在 Makefile
中增加如下配置:
new-lyn-ui:
node build/bin/new-lyn-ui.js $(filter-out $@,$(MAKECMDGOALS)) && npm run build:file
help:
@echo " \033[35mmake new-lyn-ui <component-name> [中文名]\033[0m\t--- 創建新的 LynUI 組件 package. 例如 'make new-lyn-ui city 城市選擇'"
icon 圖標
element-ui 雖然提供了大量的 icon,但往往不能滿足團隊的業務需求,所有就需要往組件庫中增加業務 icon,這裡以 Iconfont 為例。不建議直接使用設計給的圖片或者 svg,太佔資源了。
-
登陸 -> 資源管理 -> 我的項目 -> 新建項目
注意,這裡為 icon 設置前綴時不要使用 el-icon-,避免和 element-ui 中的 icon 重複。這個項目就作為團隊項目使用了,以後團隊所有的業務圖標都上傳到該項目,所以最好註冊一個團隊帳號。
-
新建成功後,點擊
上傳圖標至項目
,選擇上傳圖標
,上傳設計給的 svg(必須是 svg),根據需要選擇保留顏色或不保留並提交
-
上傳完畢,編輯、檢查沒問題後,點擊
下載至本地
-
複製其中的
iconfont.ttf
和iconfont.woff
到/packages/theme-lyn/src/fonts
目錄下 -
新建
/packages/theme-lyn/src/icon.less
文件,並添加如下內容@font-face { font-family: 'iconfont'; src: url('./fonts/iconfont.woff') format('woff'), url('./fonts/iconfont.ttf') format('truetype'); font-weight: normal; font-display: auto; font-style: normal; } [class^="lyn-icon-"], [class*=" lyn-icon-"] { font-family: 'iconfont' !important; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; vertical-align: baseline; display: inline-block; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale } /** * 示例: * .lyn-icon-iconName:before { * content: "\unicode 16進位碼" * } * .lyn-icon-add:before { * content: "\e606" * } */
-
執行
npm run gen-cssfile
-
更新
/build/bin/iconInit.js
文件為以下內容'use strict'; var postcss = require('postcss'); var fs = require('fs'); var path = require('path'); /** * 從指定的 icon 樣式文件(entry)中按照給定正則表達式(regExp)解析出 icon 名稱,然後輸出到指定位置(output) * @param {*} entry 被解析的文件相對於當前文件的路徑,比如:../../packages/theme-chalk/icon.css * @param {*} regExp 被解析的正則表達式,比如:/\.el-icon-([^:]+):before/ * @param {*} output 解析後的資源輸出到相對於當前文件的指定位置,比如:../../examples/icon.json */ function parseIconName(entry, regExp, output) { // 讀取樣式文件 var fontFile = fs.readFileSync(path.resolve(__dirname, entry), 'utf8'); // 將樣式內容解析為樣式節點 var nodes = postcss.parse(fontFile).nodes; var classList = []; // 遍歷樣式節點 nodes.forEach((node) => { // 從樣式選擇器中根據給定匹配規則匹配出 icon 名稱 var selector = node.selector || ''; var reg = new RegExp(regExp); var arr = selector.match(reg); // 將匹配到的 icon 名稱放入 classList if (arr && arr[1]) { classList.push(arr[1]); } }); classList.reverse(); // 希望按 css 文件順序倒序排列 // 將 icon 名稱數組輸出到指定 json 文件中 fs.writeFile(path.resolve(__dirname, output), JSON.stringify(classList), () => { }); } // 根據 icon.css 文件生成所有的 icon 圖標名 parseIconName('../../packages/theme-chalk/icon.css', /\.el-icon-([^:]+):before/, '../../examples/icon.json') // 根據 icon.less 文件生成所有的 sts icon 圖標名 parseIconName('../../packages/theme-lyn/src/icon.less', /\.lyn-icon-([^:]+):before/, '../../examples/lyn-icon.json')
-
執行
npm run build:file
,會看到在/examples
目錄下生成了一個lyn-icon.json
文件 -
在
/examples/entry.js
中增加如下內容import lynIcon from './lyn-icon.json'; Vue.prototype.$lynIcon = lynIcon; // StsIcon 列表頁用
-
在
/examples/nav.config.json
中業務配置部分增加lyn-icon
路由配置{ "groupName": "LynUI", "list": [ { "path": "/lyn-icon", "title": "icon 圖標" } ] }
-
增加文檔
/examples/docs/{語言}/lyn-icon.md
,添加如下內容 -
查看官網 看圖標是否生效
-
後續如需擴展新的 icon
-
在前面新建的 iconfont 項目中上傳新的圖標,然後點擊
下載至本地
,將其中的iconfont.ttf
和iconfont.woff
複製/packages/theme-lyn/src/fonts
目錄下即可(替換已有的文件) -
在
/packages/theme-lyn/src/icon.less
中設置新的 icon 樣式聲明 -
執行
npm run build:file
-
查看官網 看圖標添加是否成功
-
升級 Vue 版本
element-ui 本身依賴的是 vue@^2.5.x,該版本的 vue 不支援最新的 v-slot
插槽語法(v-slot 是在 2.6.0 中新增的),組件的 markdown 文檔中使用 v-slot
語法不生效且會報錯,所以需要升級 vue 版本。涉及三個包:vue@2.6.12、@vue/component-compiler-utils@3.2.0、vue-template-compiler@^2.6.12。執行以下命令即可完成更新:
-
刪除舊包
npm uninstall vue @vue/component-compiler-utils vue-template-compiler -D
-
安裝新包
npm install vue@^2.6.12 @vue/component-compiler-utils@^3.2.0 vue-template-compiler@^2.6.12 -D
-
更新 package.json 中的 peerDependencies
{ "peerDependencies": { "vue": "^2.6.12" } }
擴展
到這裡,組件庫的架構調整其實已經完成了,接下來只需組織團隊成員對照設計稿進行組件開發就可以了。但是對於一些有潔癖的開發者來說,其實還差點。
比如:
-
團隊的組件庫不想叫 element-ui,有自己的名稱,甚至整個組件庫的程式碼都不想出現 element 字樣
-
element-ui 的某些功能團隊不需要,比如:官網項目(examples)中的主題、資源模組、chrome 插件(extension)、國際化相關(只保留中文即可)
-
靜態資源,element-ui 將所有的靜態資源都上傳到自己的 CDN 上了,我們去訪問其實優點慢,可以將相關資源挪到團隊自己的 CDN 上
-
工程程式碼品質問題,element-ui 本身提供了 eslint,做了一點程式碼品質的控制,但是做的不夠,比如格式限制、自動格式化等,可以參考 搭建自己的 typescript 項目 + 開發自己的腳手架工具 ts-cli 中的 程式碼品質 部分去配置
-
替換官網 logo、渲染資訊等
-
element-ui 樣式庫的優化,其實 element-ui 的樣式存在重複載入的問題
雖然它通過 webpack 打包已經解決了一部分問題,但是某些情況還是會出現重複載入,比如 table 組件中使用 checkbox 組件,就會載入兩次 checkbox 組件的樣式程式碼。有精力的同學可以去研究研究
-
你的業務只需要 element-ui 的部分基礎組件,把不需要的刪掉,可以降低組件庫的體積,提升載入速度
-
…
這些工作有一些是對官網項目(examples)的裁剪,有一些是項目整體優化,還有一些是潔癖,不過,相信凡是進行到這一步的同學,都已經為團隊構建出了自己的組件庫,解決以上列出的那些問題完全不再話下,這裡就不一一列出方法了。
鏈接
- Element 源碼架構 思維導圖版
- 組件庫專欄
- 如何快速為團隊打造自己的組件庫(上)—— Element 源碼架構
- Element 源碼架構 影片版,關注微信公眾號,回復: “Element 源碼架構影片版” 獲取
文章已收錄到 github,歡迎 Watch 和 Star。