成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Vite 插件開發(fā)實踐:微前端的資源處理

開發(fā) 前端
最近實現(xiàn)的簡單、透明、組件化微前端方案總體感覺不錯,也收到了很多人的反饋,很具有學(xué)習(xí)參考價值。

最近實現(xiàn)的簡單、透明、組件化微前端方案總體感覺不錯,也收到了很多人的反饋,很具有學(xué)習(xí)參考價值。

但有不少朋友使用該方案打包配置出現(xiàn)了一些問題,做事應(yīng)有始有終,挖的坑總得完善一下。今天分享一下 Vite 針對微應(yīng)用方案插件開發(fā)歷程。

通過文章你可以學(xué)到:

問題點

總結(jié)下來,在 Vite 中使用該微前端方案會遇到如下問題:

Vite 打包后的資源默認(rèn)是以 HTML 為入口,我們的微前端方案需要以 JS 為入口

JS 為入口方案打包導(dǎo)出代碼被移除掉了

import.meta 語句打包被轉(zhuǎn)譯成 {} 空對象了

chunk 分離后的 CSS 文件,Vite 默認(rèn)以document.head.appendChild 處理

打包后的 CSS 文件默認(rèn)在 main.js 中沒有引用

資源路徑手動寫 new URL(image, import.meta.url) 太繁瑣

通過配置解決問題

首先前三個問題可以通過 Vite 解決。Vite 兼容了 rollup 的配置

問題一,修改 JS 入口則需要修改 Vite 配置,設(shè)置 build.rollupOptions.input 為 src/main.tsx,這樣 Vite 會默認(rèn)以自定義配置的main.tsx 為入口文件做打包處理,不再生成 index.html。

問題二,rollup 的一個特性默認(rèn)會清理掉入口文件的導(dǎo)出模塊,可以配置 preserveEntrySignatures: 'allow-extension' 來保證打包之后 export 的模塊不被移除掉。

問題三,看了 Vite 的 Issue,很多人遇到了這個問題,最初以為是 Vite 默認(rèn)對它做了處理,后面看了 Vite 源碼也沒有發(fā)現(xiàn)處理的邏輯所在,應(yīng)該是被 esbuild 做了轉(zhuǎn)譯。因此將 build.target 設(shè)置為 esnext 即可解決問題,即import.meta 屬于 es2020,設(shè)置為具體的 es2020 也行。

配置:

export default defineConfig({
build: {
// es2020 支持 import.meta 語法
target: 'es2020',
rollupOptions: {
// 用于控制 Rollup 嘗試確保入口塊與基礎(chǔ)入口模塊具有相同的導(dǎo)出
preserveEntrySignatures: 'allow-extension',
// 入口文件
input: 'src/main.tsx',
},
},
});


寫 Vite 插件

我們可以寫一個插件將上面的配置封裝。

一個普通的 Vite 插件很簡單

defineConfig({
plugins: [
{
// 可以使用 Vite 和 rollup 提供的鉤子
},
],
});


插件可以做很多事情,通過 Vite 和 rollup提供的鉤子對代碼解析、編譯、打包、輸出的整體流程進(jìn)行自定義處理。

插件一般不直接寫在 vite.config.ts 中,可以定義一個方法導(dǎo)出這個插件,這里可以用config 這個鉤子來提供默認(rèn)的 Vite 配置,將自定義的配置進(jìn)行封裝:

export function microWebPlugin(): Plugin {
// 插件鉤子
return {
name: 'vite-plugin-micro-web',
config() {
return {
build: {
target: 'es2020',
rollupOptions: {
preserveEntrySignatures: 'allow-extension',
input: 'src/main.tsx',
},
},
};
},
};
}


這樣一個簡單的插件就完成了。

Vite 獨(dú)有鉤子

config - 在解析 Vite 配置前調(diào)用,它可以返回一個將被深度合并到現(xiàn)有配置中的部分配置對象,或者直接改變配置

configResolved - 在解析 Vite 配置后調(diào)用,使用這個鉤子讀取和存儲最終解析的配置

configureServer - 是用于配置開發(fā)服務(wù)器的鉤子

transformIndexHtml - 轉(zhuǎn)換 index.html 的專用鉤子。鉤子接收當(dāng)前的 HTML 字符串和轉(zhuǎn)換上下文

handleHotUpdate - 執(zhí)行自定義 HMR 更新處理。

rollup 鉤子

rollup 鉤子非常多,一共分兩個階段

編譯階段:

輸出階段:

這里我們會用到的鉤子有:

  • transform - 用于轉(zhuǎn)換已加載的模塊內(nèi)容
  • generateBundle - 已經(jīng)編譯過的代碼塊生成階段

樣式插入節(jié)點處理

問題四,document.head.appendChild 處理

使用 transform 鉤子,替換 Vite 默認(rèn)的document.head.appendChild 為自定義節(jié)點

cssCodeSplit 打包為一個 CSS 文件

我們默認(rèn)采用 cssCodeSplit 打包為一個 CSS 文件,免去了用插件 transform 修改 Vite 的邏輯。

問題五,即打包后的 CSS 沒有引用的問題,獲取這個帶 hash 的 CSS 我們可以有多種解決方案

使用 HTML 打包模式,抽取 index.html 中的 JS 、CSS 文件再單獨(dú)處理

不添加樣式文件名 hash ,通過約定固定該樣式名稱

通過鉤子提取文件名處理

權(quán)衡之下,最終采用 generateBundle 階段提取 Vite 編譯生成的 CSS 文件名,通過修改入口代碼將其插入。但 generateBundle 已經(jīng)在輸出階段,不會再走 transform 鉤子。

發(fā)現(xiàn)一個兩全其美的辦法:創(chuàng)建極小的入口文件main.js,還可以配合 hash 和主應(yīng)用時間戳緩存處理。

async generateBundle(options, bundle) {
// 主入口文件
let entry: string | undefined;
// 所有的 CSS 模塊
const cssChunks: string[] = [];
// 找出入口文件和 CSS 文件
for (const chunkName of Object.keys(bundle)) {
if (chunkName.includes('main') && chunkName.endsWith('.js')) {
entry = chunkName;
}
if (chunkName.endsWith('.css')) {
// 使用相對路徑,避免后續(xù) ESM 無法解析模塊
cssChunks.push(`./${chunkName}`);
}
}
// 接下面代碼
},


生成新的入口文件

通過 bundle 提取可以獲取到帶 hash 的JS、CSS 入口文件了?,F(xiàn)在需要寫入一個新的文件 main.js。rollup 中有個 API emitFile可以觸發(fā)創(chuàng)建一個資源文件。

接下來對它進(jìn)行處理:

// 接上面代碼
if (entry) {
const cssChunksStr = JSON.stringify(cssChunks);

// 創(chuàng)建極小的入口文件,配合 hash 和主應(yīng)用時間戳緩存處理
this.emitFile({
fileName: 'main.js',
type: 'asset',
source: `
// 帶上 microAppEnv 參數(shù),使用相對路徑避免報錯
import defineApp from './${entry}?microAppEnv';

// 創(chuàng)建 link 標(biāo)簽
function createLink(href) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
return link;
}

// 入口文件導(dǎo)出一個方法,將打包的 CSS 文件通過 link 的方式插入到對應(yīng)的節(jié)點中
defineApp.styleInject = (parentNode) => {
${cssChunksStr}.forEach((css) => {
// import.meta.url 讓路徑保持正確,中括號取值避免被 rollup 轉(zhuǎn)換掉
const link = createLink(new URL(css, import.meta['url']));
parentNode.prepend(link);
});
};

export default defineApp;
`,
});
}


插件需要應(yīng)用入口配合導(dǎo)出一個 styleInject方法提供樣式插入,我們通過封裝入口方法得以解決。

封裝一個方法給應(yīng)用入口調(diào)用:

export function defineMicroApp(callback) {
const defineApp = (container) => {
const appConfig = callback(container);
// 處理樣式局部插入
const mountFn = appConfig.mount;
// 獲取到插件中的方法
const inject = defineApp.styleInject;
if (mountFn && inject) {
appConfig.mount = (props) => {
mountFn(props);
// 裝載完畢后,插入樣式
inject(container);
};
}
return appConfig;
};

return defineApp;
}


現(xiàn)在 build 之后會生成一個不帶 hash 的 main.js 文件,主應(yīng)用可以正常加載打包后的資源了。

進(jìn)一步優(yōu)化,main.js 的壓縮混淆,可以用 Vite 導(dǎo)出 transformWithEsbuild 進(jìn)行編譯:

const result = await transformWithEsbuild(customCode, 'main.js', {
minify: true,
});

this.emitFile({
fileName: 'main.js',
type: 'asset',
source: result.code,
});


子應(yīng)用路徑問題

之前我們需要手動添加 new URL(image, import.meta.url) 來修復(fù)子應(yīng)用路徑問題。通過 transform 鉤子自動處理該邏輯。

在這個插件之前,Vite 會將所有的資源文件轉(zhuǎn)換為路徑

import logo from './logo.svg';

// 轉(zhuǎn)換為:

export default '/src/logo.svg';


因此,我們只需要將 export default "資源路徑" 替換為 export default new URL("資源路徑", import.meta['url']).href 就可以了。

const imagesRE = new RegExp(`\\.(png|webp|jpg|gif|jpeg|tiff|svg|bmp)($|\\?)`);

transform(code, id) {
// 修正圖片資源使用絕對地址
if (imagesRE.test(id)) {
return {
code: code.replace(
/(export\s+default)\s+(".+")/,
`$1 new URL($2, import.meta['url']).href`
),
map: null,
};
}
return undefined;
},


完成,一個比較完善的 Vite 微應(yīng)用方案由此而生。

看看效果:

更多

有了插件,可以發(fā)揮出意想不到的事情。本微前端方案沒有實現(xiàn)以下的隔離方式,不保證后續(xù)會實現(xiàn),大家可以發(fā)揮更多的想象力。

CSS 樣式隔離

通過插件將主應(yīng)用節(jié)點中的 id 添加并修改CSS

.name {
color: red;
}

/* 轉(zhuǎn)換為 */

#id .name {
color: red;
}


但前提是需要為每個 設(shè)置一個唯一的 id。并且樣式性能會受到影響,CSSModules 方案會更好。

JS 沙箱

雖然在 ESM 中做運(yùn)行時沙箱目前沒有現(xiàn)成的方案,但運(yùn)行時沙箱性能非常差。換個思路,可以從編譯時沙箱入手。用 transform 鉤子將應(yīng)用所有的 window 轉(zhuǎn)譯為沙箱fakeWindow,從而達(dá)到隔離效果。

代碼示例

大家可以 clone 下來學(xué)習(xí)

插件倉庫:https://github.com/MinJieLiu/micro-app/tree/main/packages/micro-vite-plugin

微前端示例:https://github.com/MinJieLiu/micro-app-demo


責(zé)任編輯:武曉燕 來源: 前端星辰
相關(guān)推薦

2022-01-17 11:41:50

前端Vite組件

2022-05-09 09:28:04

Vite前端開發(fā)

2024-03-06 11:14:13

ViteReact微前端

2020-05-06 09:25:10

微前端qiankun架構(gòu)

2024-09-23 00:00:10

2020-09-22 12:20:23

前端架構(gòu)插件

2018-07-10 15:30:42

Go前端更新

2022-02-14 14:34:10

Next.js路由系統(tǒng)

2022-05-12 11:41:16

開發(fā)框架程序

2024-11-29 08:35:38

2021-06-22 06:52:46

Vite 插件機(jī)制Rollup

2022-08-10 10:32:47

編程實踐

2022-07-13 09:01:17

前端開發(fā)文件處理庫

2017-06-02 10:25:26

Java異常處理

2017-09-12 10:50:55

前端SDK開發(fā)

2022-07-27 22:56:45

前端應(yīng)用緩存qiankun

2019-03-25 13:12:59

前端開發(fā)編程

2023-12-29 09:04:01

前端文件處理庫PDF.js

2022-10-17 15:21:18

2020-10-18 12:00:27

前端開發(fā)架構(gòu)
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 久久性av| 成人黄在线观看 | 韩国久久精品 | jizz视频| 精品亚洲一区二区三区四区五区 | 国产精品精品久久久 | 国产免费福利小视频 | 国产精品视频网 | 黑人巨大精品欧美一区二区免费 | 91精品久久久久久久久久 | 欧洲尺码日本国产精品 | 91在线导航 | 欧美日日日日bbbbb视频 | 午夜在线影院 | 欧美日韩一 | www午夜视频 | 国内毛片毛片毛片毛片 | 99成人| 久久精品视频在线播放 | 成人国产精品免费观看视频 | 亚洲精品久久久久久久久久久久久 | 在线视频 中文字幕 | 国产一区不卡在线观看 | 精品二三区| 国产精品一区二区无线 | 亚洲日韩欧美一区二区在线 | 久久久精品一区二区 | 365夜爽爽欧美性午夜免费视频 | 午夜精品久久久久久久99黑人 | 欧美一页| www国产亚洲精品久久网站 | 午夜免费av| 国产精品一区二区久久久久 | 久久男人 | 激情综合五月 | 韩日精品一区 | 欧美精品综合 | 精品国产91久久久久久 | 日韩精品一区二区三区 | 国产清纯白嫩初高生视频在线观看 | 欧美综合一区 |