組件庫(kù)會(huì)包含幾十甚至上百個(gè)組件,但是應(yīng)用的時(shí)候往往只使用其中的一部分。這個(gè)時(shí)候如果全部引入到項(xiàng)目中,就會(huì)使輸出產(chǎn)物體積變大。按需加載的支持是組件庫(kù)中必須考慮的問(wèn)題。
目前組件的按需引入會(huì)分成兩個(gè)方法:
- 經(jīng)典方法:組件單獨(dú)分包 + 按需導(dǎo)入 +
babel-plugin-component
( 自動(dòng)化按需引入); - 次時(shí)代方法:
ESModule
+Treeshaking
+ 自動(dòng)按需import
(unplugin-vue-components
自動(dòng)化配置)。
分包與樹搖(Treeshaking)
傳統(tǒng)的解決方案就是將組件庫(kù)分包導(dǎo)出,比如將組件庫(kù)分為 List
、Button
、Card
,用到哪個(gè)加載哪個(gè),簡(jiǎn)單粗暴。這樣寫有兩個(gè)弊端:
- 需要了解軟件包內(nèi)部構(gòu)造 例:
import "ui/xxx" or import "ui/package/xxx"
; - 需要不斷手工調(diào)整組件加載預(yù)注冊(cè)。
// 全部導(dǎo)入
const SmartyUI = require("smarty-ui-vite")
// 單獨(dú)導(dǎo)入
const Button = require("smarty-ui-vite/button")
好在后面有 babel-plugin-component
,解決了需要了解軟件包構(gòu)造的問(wèn)題。當(dāng)然你需要按照約定規(guī)則導(dǎo)出軟件包。
// 轉(zhuǎn)換前
const { Button } = require("smarty-ui-vite")
// 轉(zhuǎn)換后
const Button = require("smarty-ui-vite/button")
隨著時(shí)代的發(fā)展,esmodule
已經(jīng)成為了前端開發(fā)的主流。esmodule
帶來(lái)好處是靜態(tài)編譯,也就是說(shuō),在編譯階段就可以判斷需要導(dǎo)入哪些包。
這樣就給 Treeshaking
提供了可能。Treeshaking
是一種通過(guò)語(yǔ)法分析去除無(wú)用代碼的方法。目前,Treeshaking
逐漸成為了構(gòu)建工具的標(biāo)配,Rollup
、Vite
、新版本的 Webpack
都支持了這個(gè)功能。
比如:組件庫(kù)只使用了 Button
。
import { Button } from 'smarty-ui-vite'
使用 ES 模塊并且只引用了 Button,編譯器會(huì)自動(dòng)將其他組件的代碼去掉。
實(shí)現(xiàn)分包導(dǎo)出
分包導(dǎo)出相當(dāng)于將組件庫(kù)形成無(wú)數(shù)各子軟件包,軟件包必須滿足一下要求:
- 文件名即組件名;
- 獨(dú)立的
package.json
定義,包含esm
和umd
的入口定義; - 每個(gè)組件必須以
Vue
插件形式進(jìn)行加載; - 每個(gè)軟件包還需要有單獨(dú)的
css
導(dǎo)出。
- 重構(gòu)代碼結(jié)構(gòu)
首先需要在原有代碼上進(jìn)行重構(gòu):
- 首先將組件目錄由 【button】 改為 【Button】
特別提醒:git 提交的時(shí)候注意,默認(rèn) git 修改的時(shí)候是忽略大小寫的。需要修改一下 git 配置才可以提交。
# 禁止忽略大小寫
git config core.ignorecase false
- Button 組件入口
index.ts
默認(rèn)作為插件導(dǎo)出。
重構(gòu)前:
import Button from "./Button";
// 導(dǎo)出 Button 組件
export default Button
重構(gòu)后:
import Button from "./Button";
import { App } from "vue";
// 導(dǎo)出Button組件
export { Button };
// 導(dǎo)出Vue插件
export default {
install(app: App) {
app.component(Button.name, Button);
},
};
- 編寫分包導(dǎo)出腳本
默認(rèn)導(dǎo)出方式是通過(guò)配置 vite.config.ts
的 build
屬性完成。但是在分包導(dǎo)出的時(shí)候需要每個(gè)組件都分別配置自己的配置文件,而且需要由程序自動(dòng)讀取組件文件夾,根據(jù)文件夾的名字遍歷打包,還需要為每個(gè)子組件包配上一個(gè) package.json
文件。
新建一個(gè) scripts/build.ts
文件。
首先需要學(xué)會(huì)的是如何使用代碼讓 vite
打包。
導(dǎo)入 vite.config.ts
中的配置,然后調(diào)用 vite
的 build api
打包。
- 修改
vite.config.ts
/// <reference types="vitest" />
import { defineConfig,UserConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// 引入重構(gòu)后的unocss配置
import UnoCSS from './config/unocss'
const rollupOptions = {
external: ['vue', 'vue-router'],
output: {
globals: {
vue: 'Vue',
},
},
}
export const config = {
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
},
},
plugins: [
vue(), // VUE插件
vueJsx({}), // JSX 插件
UnoCSS(), // 這是重構(gòu)后的unocss配置
// 添加UnoCSS插件
// Unocss({
// presets: [presetUno(), presetAttributify(), presetIcons()],
// }),
],
// 添加庫(kù)模式配置
build: {
rollupOptions,
cssCodeSplit: true, // 追加 css代碼分割
minify: "terser",
sourcemap: true, // 輸出單獨(dú)的source文件
reportCompressedSize: true, // 生成壓縮大小報(bào)告
lib: {
entry: './src/entry.ts',
name: 'SmartyUI',
fileName: 'smarty-ui',
// 導(dǎo)出模塊格式
formats: ['es', 'umd', 'iife'],
},
outDir: "./dist"
},
test: {
// enable jest-like global test APIs
globals: true,
// simulate DOM with happy-dom
// (requires installing happy-dom as a peer dependency)
environment: 'happy-dom',
// 支持tsx組件,很關(guān)鍵
transformMode: {
web: [/.[tj]sx$/]
}
}
}
export default defineConfig(config as UserConfig)
- 讀取
vite
配置
文件名:scripts/build.ts
// 讀取 vite 配置
import { config } from "../vite.config";
import { build, InlineConfig, defineConfig, UserConfig } from "vite";
// 全量打包
build(defineConfig(config as UserConfig) as InlineConfig);
- 讀取組件文件夾遍歷組件庫(kù)文件夾
文件名:scripts/build.ts
const srcDir = path.resolve(__dirname, "../src/");
fs.readdirSync(srcDir)
.filter((name) => {
// 過(guò)濾文件只保留包含index.ts
const componentDir = path.resolve(srcDir, name);
const isDir = fs.lstatSync(componentDir).isDirectory();
return isDir && fs.readdirSync(componentDir).includes("index.ts");
})
.forEach(async (name) => {
// 文件夾遍歷
});
-
為每個(gè)模塊定制不同的編譯規(guī)則
- 導(dǎo)出文件夾為
dist/ <組件名>/ 例: dist/Button
; - 導(dǎo)出模塊名為:
index.es.js
、index.umd.js
; - 導(dǎo)出模塊名為:
<組件名> iffe 中綁定到全局的名字
。
- 導(dǎo)出文件夾為
const outDir = path.resolve(config.build.outDir, name);
const custom = {
lib: {
entry: path.resolve(srcDir, name),
name, // 導(dǎo)出模塊名
fileName: `index`,
formats: [`esm`, `umd`],
},
outDir,
};
Object.assign(config.build, custom);
await build(defineConfig(config as UserConfig) as InlineConfig);
- 為每個(gè)子組件包定制一個(gè)自己的
package.json
最后還需要為每個(gè)子組件包定制一個(gè)自己的 package.json
。因?yàn)楦鶕?jù) npm
軟件包規(guī)則,當(dāng)你 import
子組件包的時(shí)候,會(huì)根據(jù)子包中的 package.json
文件找到對(duì)應(yīng)的模塊。
// 讀取
import Button from "smarty-ui-vite/Button"
子包的 package.json
。
{
"name": "smarty-ui-vite/Button",
"main": "index.umd.js",
"module": "index.umd.js"
}
生成 package.json
使用模版字符串實(shí)現(xiàn)。
fs.outputFile(
path.resolve(outDir, `package.json`),
`{
"name": "smarty-ui-vite/${name}",
"main": "index.umd.js",
"module": "index.umd.js",
}`,
`utf-8`
);
- 最終代碼
import fs from "fs-extra";
import path from "path";
// 讀取 vite 配置
import {config} from "../vite.config";
import { build, InlineConfig, defineConfig, UserConfig } from "vite";
const buildAll = async () => {
// 全量打包
await build(defineConfig(config as UserConfig) as InlineConfig);
// await build(defineConfig({}))
const srcDir = path.resolve(__dirname, "../src/");
fs.readdirSync(srcDir)
.filter((name) => {
// 只要目錄不要文件,且里面包含index.ts
const componentDir = path.resolve(srcDir, name);
const isDir = fs.lstatSync(componentDir).isDirectory();
return isDir && fs.readdirSync(componentDir).includes("index.ts");
})
.forEach(async (name) => {
const outDir = path.resolve(config.build.outDir, name);
const custom = {
lib: {
entry: path.resolve(srcDir, name),
name, // 導(dǎo)出模塊名
fileName: `index`,
formats: [`es`, `umd`],
},
outDir,
};
Object.assign(config.build, custom);
await build(defineConfig(config as UserConfig) as InlineConfig);
fs.outputFile(
path.resolve(outDir, `package.json`),
`{
"name": "smarty-ui-vite/${name}",
"main": "index.umd.js",
"module": "index.umd.js"
}`,
`utf-8`
);
});
};
buildAll();
- 安裝需要的依賴
由于腳本是使用typescript編寫的所以需要使用 esno
運(yùn)行。
pnpm i esno -D
代碼中還使用的fs
的功能所以需要安裝fs-extra
pnpm i fs-extra -D
- 在
package.json
中添加腳本
"scripts": {
"build": "pnpm build:components",
"build:all": "vite build",
"build:components": "esno ./scripts/build.ts",
}
運(yùn)行代碼
pnpm build
測(cè)試按需加載
假設(shè)頁(yè)面中只使用 Button 按鈕,那么只調(diào)用Button子包中的 js、css 就可以了。
文件名:demo/iffe/test_button.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test button</title>
<link rel="stylesheet" href="../../dist/Button/assets/index.cb9ba4f4.css">
<script src="../../node_modules/vue/dist/vue.global.js"></script>
<script src="../../dist/Button/index.iife.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { createApp } = Vue
console.log('vue', Vue)
console.log('SmartyUI', Button)
createApp({
template: `
<div style="margin-bottom:20px;">
<SButton color="blue">主要按鈕</SButton>
<SButton color="green">綠色按鈕</SButton>
<SButton color="gray">灰色按鈕</SButton>
<SButton color="yellow">黃色按鈕</SButton>
<SButton color="red">紅色按鈕</SButton>
</div>
<div style="margin-bottom:20px;">
<SButton color="blue" icon="search">搜索按鈕</SButton>
<SButton color="green" icon="edit">編輯按鈕</SButton>
<SButton color="gray" icon="check">成功按鈕</SButton>
<SButton color="yellow" icon="message">提示按鈕</SButton>
<SButton color="red" icon="delete">刪除按鈕</SButton>
</div>
<div style="margin-bottom:20px;">
<SButton color="blue" icon="search"></SButton>
<SButton color="green" icon="edit"></SButton>
<SButton color="gray" icon="check"></SButton>
<SButton color="yellow" icon="message"></SButton>
<SButton color="red" icon="delete"></SButton>
</div>
`}).use(Button.default).mount('#app')
</script>
</body>
</html>
- 啟動(dòng)項(xiàng)目
pnpm dev
瀏覽器查看結(jié)果文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-675220.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-675220.html
到了這里,關(guān)于第十三章 實(shí)現(xiàn)組件庫(kù)按需引入功能的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!