Rollup的插件機(jī)制及構(gòu)建流程深入解析
引言
上一節(jié)我們學(xué)會(huì)了 Rollup 構(gòu)建工具的使用,相信你已經(jīng)對(duì) Rollup 的基礎(chǔ)概念和使用有了基本的掌握。同時(shí)我們也知道,僅僅使用 Rollup 內(nèi)置的打包能力很難滿足項(xiàng)目日益復(fù)雜的構(gòu)建需求。對(duì)于一個(gè)真實(shí)的項(xiàng)目構(gòu)建場景來說,我們還需要考慮到模塊打包之外的問題,比如路徑別名(alias) 、全局變量注入和代碼壓縮等等。
可要是把這些場景的處理邏輯與核心的打包邏輯都寫到一起,一來打包器本身的代碼會(huì)變得十分臃腫,二來也會(huì)對(duì)原有的核心代碼產(chǎn)生一定的侵入性,混入很多與核心流程無關(guān)的代碼,不易于后期的維護(hù)。因此 ,Rollup 設(shè)計(jì)出了一套完整的插件機(jī)制,將自身的核心邏輯與插件邏輯分離,讓你能按需引入插件功能,提高了 Rollup 自身的可擴(kuò)展性。
那接下來,我會(huì)帶你分析 Rollup 的插件機(jī)制,熟悉 Rollup 插件的完整構(gòu)建階段和工作流程,并且結(jié)合案例深入插件開發(fā)細(xì)節(jié)。Rollup 的打包過程中,會(huì)定義一套完整的構(gòu)建生命周期,從開始打包到產(chǎn)物輸出,中途會(huì)經(jīng)歷一些標(biāo)志性的階段,并且在不同階段會(huì)自動(dòng)執(zhí)行對(duì)應(yīng)的插件鉤子函數(shù)(Hook)。對(duì) Rollup 插件來講,最重要的部分是鉤子函數(shù),一方面它定義了插件的執(zhí)行邏輯,也就是"做什么";另一方面也聲明了插件的作用階段,即"什么時(shí)候做",這與 Rollup 本身的構(gòu)建生命周期息息相關(guān)。
一、Rollup構(gòu)建階段
在執(zhí)行 rollup 命令之后,在 cli 內(nèi)部的主要邏輯簡化如下:
// Build 階段 const bundle = await rollup.rollup(inputOptions); // Output 階段 await Promise.all(outputOptions.map(bundle.write)); // 構(gòu)建結(jié)束 await bundle.close();
而Rollup 內(nèi)部主要經(jīng)歷了 Build 和 Output 兩大階段,如下圖所示。
首先,Build 階段主要負(fù)責(zé)創(chuàng)建模塊依賴圖,初始化各個(gè)模塊的 AST 以及模塊之間的依賴關(guān)系。下面我們用一個(gè)簡單的例子來感受一下:
// src/index.js import { a } from './module-a'; console.log(a); // src/module-a.js export const a = 1;
然后,執(zhí)行如下的構(gòu)建腳本:
const rollup = require('rollup'); const util = require('util'); async function build() { const bundle = await rollup.rollup({ input: ['./src/index.js'], }); console.log(util.inspect(bundle)); } build();
執(zhí)行上面的代碼,可以看到如下的 bundle對(duì)象信息。
{ cache: { modules: [ { ast: 'AST 節(jié)點(diǎn)信息,具體內(nèi)容省略', code: 'export const a = 1;', dependencies: [], id: '/Users/code/rollup-demo/src/data.js', // 其它屬性省略 }, { ast: 'AST 節(jié)點(diǎn)信息,具體內(nèi)容省略', code: "import { a } from './data';\n\nconsole.log(a);", dependencies: [ '/Users/code/rollup-demo/src/data.js' ], id: '/Users/code/rollup-demo/src/index.js', // 其它屬性省略 } ], plugins: {} }, closed: false, // 掛載后續(xù)階段會(huì)執(zhí)行的方法 close: [AsyncFunction: close], generate: [AsyncFunction: generate], write: [AsyncFunction: write] }
從上面的信息中可以看出,目前經(jīng)過 Build 階段的 bundle 對(duì)象其實(shí)并沒有進(jìn)行模塊的打包,這個(gè)對(duì)象的作用在于存儲(chǔ)各個(gè)模塊的內(nèi)容及依賴關(guān)系,同時(shí)暴露generate和write方法,以進(jìn)入到后續(xù)的 Output 階段。
所以,真正進(jìn)行打包的過程會(huì)在 Output 階段進(jìn)行,即在bundle對(duì)象的 generate或者write方法中進(jìn)行。還是以上面的 demo 為例,我們稍稍改動(dòng)一下構(gòu)建邏輯:
const rollup = require('rollup'); async function build() { const bundle = await rollup.rollup({ input: ['./src/index.js'], }); const result = await bundle.generate({ format: 'es', }); console.log('result:', result); } build();
重新執(zhí)行項(xiàng)目后可以得到如下的輸出:
{ output: [ { exports: [], facadeModuleId: '/Users/code/rollup-demo/src/index.js', isEntry: true, isImplicitEntry: false, type: 'chunk', code: 'const a = 1;\n\nconsole.log(a);\n', dynamicImports: [], fileName: 'index.js', // 其余屬性省略 } ] }
這里可以看到所有的輸出信息,生成的output數(shù)組即為打包完成的結(jié)果。當(dāng)然,如果使用 bundle.write 會(huì)根據(jù)配置將最后的產(chǎn)物寫入到指定的磁盤目錄中。
因此,對(duì)于一次完整的構(gòu)建過程而言, Rollup 會(huì)先進(jìn)入到 Build 階段,解析各模塊的內(nèi)容及依賴關(guān)系,然后進(jìn)入Output階段,完成打包及輸出的過程。對(duì)于不同的階段,Rollup 插件會(huì)有不同的插件工作流程,接下來我們就來拆解一下 Rollup 插件在 Build 和 Output 兩個(gè)階段的詳細(xì)工作流程。
二、拆解插件工作流
2.1 插件 Hook 類型
在具體講述 Rollup 插件工作流之前,我想先給大家介紹一下不同插件 Hook 的類型,這些類型代表了不同插件的執(zhí)行特點(diǎn),是我們理解 Rollup 插件工作流的基礎(chǔ),因此有必要跟大家好好拆解一下。
通過上文的例子,相信你可以直觀地理解 Rollup 兩大構(gòu)建階段(Build和Output)各自的原理??赡苣銜?huì)有疑問,這兩個(gè)階段到底跟插件機(jī)制有什么關(guān)系呢?實(shí)際上,插件的各種 Hook 可以根據(jù)這兩個(gè)構(gòu)建階段分為兩類: Build Hook 與 Output Hook。
- Build Hook:在Build階段執(zhí)行的鉤子函數(shù),在這個(gè)階段主要進(jìn)行模塊代碼的轉(zhuǎn)換、AST 解析以及模塊依賴的解析,那么這個(gè)階段的 Hook 對(duì)于代碼的操作粒度一般為模塊級(jí)別,也就是單文件級(jí)別。
- Ouput Hook:(官方稱為Output Generation Hook),則主要進(jìn)行代碼的打包,對(duì)于代碼而言,操作粒度一般為 chunk級(jí)別,而一個(gè) chunk通常指很多文件打包到一起的產(chǎn)物。
除了根據(jù)構(gòu)建階段可以將 Rollup 插件進(jìn)行分類,根據(jù)不同的 Hook 執(zhí)行方式也會(huì)有不同的分類,主要包括Async、Sync、Parallel、Squential、First這五種。在實(shí)際的開發(fā)過程中,我們將接觸各種各樣的插件 Hook,但無論哪個(gè) Hook 都離不開這五種執(zhí)行方式。接下來,讓我們具體來認(rèn)識(shí)下這五個(gè)函數(shù)鉤子。
Async & Sync
首先是Async和Sync鉤子函數(shù),兩者其實(shí)是相對(duì)的,分別代表異步和同步的鉤子函數(shù),兩者最大的區(qū)別在于同步鉤子里面不能有異步邏輯,而異步鉤子可以有。
Parallel
Parallel用來代表并行鉤子函數(shù)。如果有多個(gè)插件實(shí)現(xiàn)了這個(gè)鉤子的邏輯,一旦有鉤子函數(shù)是異步邏輯,則并發(fā)執(zhí)行鉤子函數(shù),不會(huì)等待當(dāng)前鉤子完成(底層使用 Promise.all)。
比如對(duì)于Build階段的buildStart鉤子,它的執(zhí)行時(shí)機(jī)其實(shí)是在構(gòu)建剛開始的時(shí)候,各個(gè)插件可以在這個(gè)鉤子當(dāng)中做一些狀態(tài)的初始化操作,但其實(shí)插件之間的操作并不是相互依賴的,也就是可以并發(fā)執(zhí)行,從而提升構(gòu)建性能。反之,對(duì)于需要依賴其他插件處理結(jié)果的情況就不適合用 Parallel 鉤子了,比如 transform。
Sequential
Sequential 指串行的鉤子函數(shù)。這種 Hook 往往適用于插件間處理結(jié)果相互依賴的情況,前一個(gè)插件 Hook 的返回值作為后續(xù)插件的入?yún)?,這種情況就需要等待前一個(gè)插件執(zhí)行完 Hook,獲得其執(zhí)行結(jié)果,然后才能進(jìn)行下一個(gè)插件相應(yīng) Hook 的調(diào)用,如transform。
First
如果有多個(gè)插件實(shí)現(xiàn)了這個(gè) Hook,那么 Hook 將依次運(yùn)行,直到返回一個(gè)非 null 或非 undefined 的值為止。比較典型的 Hook 是 resolveId,一旦有插件的 resolveId 返回了一個(gè)路徑,將停止執(zhí)行后續(xù)插件的 resolveId 邏輯。
剛剛我們介紹了 Rollup 當(dāng)中不同插件 Hook 的類型,實(shí)際上不同的類型是可以疊加的,Async/Sync 可以搭配后面三種類型中的任意一種,比如一個(gè) Hook既可以是 Async 也可以是 First 類型,接著我們來具體分析一下 Rollup 當(dāng)中的插件工作流程。
2.2 Build 階段工作流
首先,我們來分析 Build 階段的插件工作流程。對(duì)于 Build 階段,插件 Hook 的調(diào)用流程如下圖所示。
流程圖的最上面聲明了不同 Hook 的類型,也就是我們?cè)谏厦婵偨Y(jié)的 5 種 Hook 分類,每個(gè)方塊代表了一個(gè) Hook,邊框的顏色可以表示Async和Sync 類型,方塊的填充顏色可以表示Parallel、Sequential 和First 類型。
接下來,我們一步步來分析 Build Hooks 的工作流程,你可以對(duì)照著圖一起看。
- 首先經(jīng)歷 options 鉤子進(jìn)行配置的轉(zhuǎn)換,得到處理后的配置對(duì)象。
- 隨之 Rollup 會(huì)調(diào)用buildStart鉤子,正式開始構(gòu)建流程。
- Rollup 先進(jìn)入到 resolveId 鉤子中解析文件路徑。(從 input 配置指定的入口文件開始)。
- Rollup 通過調(diào)用load鉤子加載模塊內(nèi)容。
- 緊接著 Rollup 執(zhí)行所有的 transform 鉤子來對(duì)模塊內(nèi)容進(jìn)行進(jìn)行自定義的轉(zhuǎn)換,比如 babel 轉(zhuǎn)譯。
- 現(xiàn)在 Rollup 拿到了模塊內(nèi)容,接下來就是進(jìn)行 AST 分析,得到所有的 import 內(nèi)容,調(diào)用 moduleParsed 鉤子。如果是普通的 import,則執(zhí)行 resolveId 鉤子,繼續(xù)回到步驟3;如果是動(dòng)態(tài) import,則執(zhí)行 resolveDynamicImport 鉤子解析路徑,如果解析成功,則回到步驟4加載模塊,否則回到步驟3通過 resolveId 解析路徑。
- 直到所有的 import 都解析完畢,Rollup 執(zhí)行buildEnd鉤子,Build 階段結(jié)束。
當(dāng)然,在 Rollup 解析路徑的時(shí)候,即執(zhí)行resolveId或者resolveDynamicImport的時(shí)候,有些路徑可能會(huì)被標(biāo)記為external(翻譯為排除),也就是說不參加 Rollup 打包過程,這個(gè)時(shí)候就不會(huì)進(jìn)行l(wèi)oad、transform等等后續(xù)的處理了。
在流程圖最上面,不知道大家有沒有注意到watchChange和closeWatcher這兩個(gè) Hook,這里其實(shí)是對(duì)應(yīng)了 rollup 的watch模式。當(dāng)你使用 rollup --watch 指令或者在配置文件配有watch: true的屬性時(shí),代表開啟了 Rollup 的watch打包模式,這個(gè)時(shí)候 Rollup 內(nèi)部會(huì)初始化一個(gè) watcher 對(duì)象,當(dāng)文件內(nèi)容發(fā)生變化時(shí),watcher 對(duì)象會(huì)自動(dòng)觸發(fā)watchChange鉤子執(zhí)行并對(duì)項(xiàng)目進(jìn)行重新構(gòu)建。在當(dāng)前打包過程結(jié)束時(shí),Rollup 會(huì)自動(dòng)清除 watcher 對(duì)象調(diào)用closeWacher鉤子。
2.3 Output 階段工作流
好,接著我們來看看 Output 階段的插件到底是如何來進(jìn)行工作的。這個(gè)階段的 Hook 相比于 Build 階段稍微多一些,流程上也更加復(fù)雜。需要注意的是,其中會(huì)涉及的 Hook 函數(shù)比較多,可能會(huì)給你理解整個(gè)流程帶來一些困擾,因此我會(huì)在 Hook 執(zhí)行的階段解釋其大致的作用和意義,關(guān)于具體的使用大家可以去 Rollup 的官網(wǎng)自行查閱,畢竟這里的主線還是分析插件的執(zhí)行流程,摻雜太多的使用細(xì)節(jié)反而不易于理解。下面我結(jié)合一張完整的插件流程圖和你具體分析一下。
以下是關(guān)于Output 階段工作流的說明:
- 執(zhí)行所有插件的 outputOptions 鉤子函數(shù),對(duì) output 配置進(jìn)行轉(zhuǎn)換。
- 執(zhí)行 renderStart,并發(fā)執(zhí)行 renderStart 鉤子,正式開始打包。
- 并發(fā)執(zhí)行所有插件的banner、footer、intro、outro 鉤子(底層用 Promise.all 包裹所有的這四種鉤子函數(shù)),這四個(gè)鉤子功能很簡單,就是往打包產(chǎn)物的固定位置(比如頭部和尾部)插入一些自定義的內(nèi)容,比如協(xié)議聲明內(nèi)容、項(xiàng)目介紹等等。
- 從入口模塊開始掃描,針對(duì)動(dòng)態(tài) import 語句執(zhí)行 renderDynamicImport鉤子,來自定義動(dòng)態(tài) import 的內(nèi)容。
- 對(duì)每個(gè)即將生成的 chunk,執(zhí)行 augmentChunkHash鉤子,來決定是否更改 chunk 的哈希值,在 watch 模式下即可能會(huì)多次打包的場景下,這個(gè)鉤子會(huì)比較適用。
- 如果沒有遇到 import.meta 語句,則進(jìn)入下一步,否則執(zhí)行如下的Case。對(duì)于 import.meta.url 語句調(diào)用 resolveFileUrl 來自定義 url 解析邏輯;對(duì)于其他import.meta 屬性,則調(diào)用 resolveImportMeta 來進(jìn)行自定義的解析。
- 接著 Rollup 會(huì)生成所有 chunk 的內(nèi)容,針對(duì)每個(gè) chunk 會(huì)依次調(diào)用插件的renderChunk方法進(jìn)行自定義操作,也就是說,在這里時(shí)候可以直接操作打包產(chǎn)物了。
- 隨后會(huì)調(diào)用 generateBundle 鉤子,這個(gè)鉤子的入?yún)⒗锩鏁?huì)包含所有的打包產(chǎn)物信息,包括 chunk (打包后的代碼)、asset(最終的靜態(tài)資源文件)??梢栽谶@里刪除一些 chunk 或者 asset,最終這些內(nèi)容將不會(huì)作為產(chǎn)物輸出。
- 由于rollup.rollup方法會(huì)返回一個(gè)bundle對(duì)象,這個(gè)對(duì)象是包含generate和write兩個(gè)方法,兩個(gè)方法唯一的區(qū)別在于后者會(huì)將代碼寫入到磁盤中,同時(shí)會(huì)觸發(fā)writeBundle鉤子,傳入所有的打包產(chǎn)物信息,包括 chunk 和 asset,和 generateBundle鉤子非常相似。不過值得注意的是,這個(gè)鉤子執(zhí)行的時(shí)候,產(chǎn)物已經(jīng)輸出了,而 generateBundle 執(zhí)行的時(shí)候產(chǎn)物還并沒有輸出。
- 當(dāng)上述的bundle的close方法被調(diào)用時(shí),會(huì)觸發(fā)closeBundle鉤子,到這里 Output 階段正式結(jié)束。
到這里,我們終于梳理完了 Rollup 當(dāng)中完整的插件工作流程,從一開始在構(gòu)建生命周期中對(duì)兩大構(gòu)建階段的感性認(rèn)識(shí),到現(xiàn)在插件工作流的具體分析,不禁感嘆 Rollup 看似簡單,實(shí)則內(nèi)部細(xì)節(jié)繁雜。
三、常用 Hook
實(shí)際上開發(fā) Rollup 插件就是在編寫一個(gè)個(gè) Hook 函數(shù),你可以理解為一個(gè) Rollup 插件基本就是各種 Hook 函數(shù)的組合。
1,路徑解析: resolveId
resolveId 鉤子一般用來解析模塊路徑,為Async + First類型即異步優(yōu)先的鉤子。這里我們拿官方的 alias 插件 來說明,這個(gè)插件用法演示如下:
// rollup.config.js import alias from '@rollup/plugin-alias'; module.exports = { input: 'src/index.js', output: { dir: 'output', format: 'cjs' }, plugins: [ alias({ entries: [ // 將把 import xxx from 'module-a' // 轉(zhuǎn)換為 import xxx from './module-a' { find: 'module-a', replacement: './module-a.js' }, ] }) ] };
插件的代碼簡化后如下:
export default alias(options) { // 獲取 entries 配置 const entries = getEntries(options); return { // 傳入三個(gè)參數(shù),當(dāng)前模塊路徑、引用當(dāng)前模塊的模塊路徑、其余參數(shù) resolveId(importee, importer, resolveOptions) { // 先檢查能不能匹配別名規(guī)則 const matchedEntry = entries.find((entry) => matches(entry.find, importee)); // 如果不能匹配替換規(guī)則,或者當(dāng)前模塊是入口模塊,則不會(huì)繼續(xù)后面的別名替換流程 if (!matchedEntry || !importerId) { // return null 后,當(dāng)前的模塊路徑會(huì)交給下一個(gè)插件處理 return null; } // 正式替換路徑 const updatedId = normalizeId( importee.replace(matchedEntry.find, matchedEntry.replacement) ); // 每個(gè)插件執(zhí)行時(shí)都會(huì)綁定一個(gè)上下文對(duì)象作為 this // 這里的 this.resolve 會(huì)執(zhí)行所有插件(除當(dāng)前插件外)的 resolveId 鉤子 return this.resolve( updatedId, importer, Object.assign({ skipSelf: true }, resolveOptions) ).then((resolved) => { // 替換后的路徑即 updateId 會(huì)經(jīng)過別的插件進(jìn)行處理 let finalResult: PartialResolvedId | null = resolved; if (!finalResult) { // 如果其它插件沒有處理這個(gè)路徑,則直接返回 updateId finalResult = { id: updatedId }; } return finalResult; }); } } }
從這里你可以看到 resolveId 鉤子函數(shù)的一些常用使用方式,它的入?yún)⒎謩e是當(dāng)前模塊路徑、引用當(dāng)前模塊的模塊路徑、解析參數(shù),返回值可以是 null、string 或者一個(gè)對(duì)象,下面我們分情況討論一下。
- 返回值為 null 時(shí),會(huì)默認(rèn)交給下一個(gè)插件的 resolveId 鉤子處理。
- 返回值為 string 時(shí),則停止后續(xù)插件的處理。這里為了讓替換后的路徑能被其他插件處理,特意調(diào)用了 this.resolve 來交給其它插件處理,否則將不會(huì)進(jìn)入到其它插件的處理。
- 返回值為一個(gè)對(duì)象,也會(huì)停止后續(xù)插件的處理,不過這個(gè)對(duì)象就可以包含更多的信息了,包括解析后的路徑、是否被 enternal、是否需要 tree-shaking 等等,不過大部分情況下返回一個(gè) string 就夠用了。
2,load
load 為Async + First類型,即異步優(yōu)先的鉤子,和resolveId類似。它的作用是通過 resolveId 解析后的路徑來加載模塊內(nèi)容。這里,我們以官方的 image 插件 為例來介紹一下 load 鉤子的使用。源碼簡化后如下所示:
const mimeTypes = { '.jpg': 'image/jpeg', // 后面圖片類型省略 }; export default function image(opts = {}) { const options = Object.assign({}, defaults, opts); return { name: 'image', load(id) { const mime = mimeTypes[extname(id)]; if (!mime) { // 如果不是圖片類型,返回 null,交給下一個(gè)插件處理 return null; } // 加載圖片具體內(nèi)容 const isSvg = mime === mimeTypes['.svg']; const format = isSvg ? 'utf-8' : 'base64'; const source = readFileSync(id, format).replace(/[\r\n]+/gm, ''); const dataUri = getDataUri({ format, isSvg, mime, source }); const code = options.dom ? domTemplate({ dataUri }) : constTemplate({ dataUri }); return code.trim(); } }; }
從中可以看到,load 鉤子的入?yún)⑹悄K id,返回值一般是 null、string 或者一個(gè)對(duì)象:
- 如果返回值為 null,則交給下一個(gè)插件處理;
- 如果返回值為 string 或者對(duì)象,則終止后續(xù)插件的處理,如果是對(duì)象可以包含 SourceMap、AST 等。
3,代碼轉(zhuǎn)換: transform
transform 鉤子也是非常常見的一個(gè)鉤子函數(shù),為Async + Sequential類型,也就是異步串行鉤子,作用是對(duì)加載后的模塊內(nèi)容進(jìn)行自定義的轉(zhuǎn)換。我們以官方的 replace 插件為例,這個(gè)插件的使用方式如下:
// rollup.config.js import replace from '@rollup/plugin-replace'; module.exports = { input: 'src/index.js', output: { dir: 'output', format: 'cjs' }, plugins: [ // 將會(huì)把代碼中所有的 __TEST__ 替換為 1 replace({ __TEST__: 1 }) ] };
事實(shí)上,transform的內(nèi)部實(shí)現(xiàn)也并不是很復(fù)雜,主要通過字符串替換來實(shí)現(xiàn),核心邏輯簡化如下:
import MagicString from 'magic-string'; export default function replace(options = {}) { return { name: 'replace', transform(code, id) { // 省略一些邊界情況的處理 // 執(zhí)行代碼替換的邏輯,并生成最后的代碼和 SourceMap return executeReplacement(code, id); } } } function executeReplacement(code, id) { const magicString = new MagicString(code); // 通過 magicString.overwrite 方法實(shí)現(xiàn)字符串替換 if (!codeHasReplacements(code, id, magicString)) { return null; } const result = { code: magicString.toString() }; if (isSourceMapEnabled()) { result.map = magicString.generateMap({ hires: true }); } // 返回一個(gè)帶有 code 和 map 屬性的對(duì)象 return result; }
transform 鉤子的入?yún)⒎謩e為模塊代碼、模塊 ID,返回一個(gè)包含 code(代碼內(nèi)容) 和 map(SourceMap 內(nèi)容) 屬性的對(duì)象,當(dāng)然也可以返回 null 來跳過當(dāng)前插件的 transform 處理。需要注意的是,當(dāng)前插件返回的代碼會(huì)作為下一個(gè)插件 transform 鉤子的第一個(gè)入?yún)?,?shí)現(xiàn)類似于瀑布流的處理。
4,Chunk 級(jí)代碼修改: renderChunk
這里我們繼續(xù)以 replace插件舉例,在這個(gè)插件中,也同樣實(shí)現(xiàn)了 renderChunk 鉤子函數(shù):
export default function replace(options = {}) { return { name: 'replace', transform(code, id) { // transform 代碼省略 }, renderChunk(code, chunk) { const id = chunk.fileName; // 省略一些邊界情況的處理 // 拿到 chunk 的代碼及文件名,執(zhí)行替換邏輯 return executeReplacement(code, id); }, } }
可以看到這里 replace 插件為了替換結(jié)果更加準(zhǔn)確,在 renderChunk 鉤子中又進(jìn)行了一次替換,因?yàn)楹罄m(xù)的插件仍然可能在 transform 中進(jìn)行模塊內(nèi)容轉(zhuǎn)換,進(jìn)而可能出現(xiàn)符合替換規(guī)則的字符串。
這里我們把關(guān)注點(diǎn)放到 renderChunk 函數(shù)本身,可以看到有兩個(gè)入?yún)?,分別為 chunk 代碼內(nèi)容、chunk 元信息,返回值跟 transform 鉤子類似,既可以返回包含 code 和 map 屬性的對(duì)象,也可以通過返回 null 來跳過當(dāng)前鉤子的處理。
5,generateBundle
generateBundle 也是異步串行的鉤子,你可以在這個(gè)鉤子里面自定義刪除一些無用的 chunk 或者靜態(tài)資源,或者自己添加一些文件。這里我們以 Rollup 官方的html插件來具體說明,這個(gè)插件的作用是通過拿到 Rollup 打包后的資源來生成包含這些資源的 HTML 文件,源碼簡化后如下所示:
export default function html(opts: RollupHtmlOptions = {}): Plugin { // 初始化配置 return { name: 'html', async generateBundle(output: NormalizedOutputOptions, bundle: OutputBundle) { // 省略一些邊界情況的處理 // 1. 獲取打包后的文件 const files = getFiles(bundle); // 2. 組裝 HTML,插入相應(yīng) meta、link 和 script 標(biāo)簽 const source = await template({ attributes, bundle, files, meta, publicPath, title}); // 3. 通過上下文對(duì)象的 emitFile 方法,輸出 html 文件 const htmlFile: EmittedAsset = { type: 'asset', source, name: 'Rollup HTML Asset', fileName }; this.emitFile(htmlFile); } } }
相信從插件的具體實(shí)現(xiàn)中,你也能感受到這個(gè)鉤子的強(qiáng)大作用了。入?yún)⒎謩e為output 配置、所有打包產(chǎn)物的元信息對(duì)象,通過操作元信息對(duì)象你可以刪除一些不需要的 chunk 或者靜態(tài)資源,也可以通過 插件上下文對(duì)象的emitFile方法輸出自定義文件。
好,常用的 Rollup 鉤子我們就先介紹到這里,相信這些知識(shí)點(diǎn)已經(jīng)足夠你應(yīng)付大多數(shù)的構(gòu)建場景了。順便說一句,大家在后面的章節(jié)可以了解到,Vite 的插件機(jī)制也是基于 Rollup 來實(shí)現(xiàn)的,像上面介紹的這些常用鉤子在 Vite 當(dāng)中也隨處可見,因此,掌握了這些常用鉤子,也相當(dāng)于給 Vite 插件的學(xué)習(xí)做下了很好的鋪墊。
以上就是Rollup 的插件機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于 Rollup 插件機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信公眾號(hào) 提示:Unauthorized API function 問題解決方法
這篇文章主要介紹了微信公眾號(hào) 提示:Unauthorized API function 問題解決方法的相關(guān)資料,這里提供了出現(xiàn)提示的解決方法,需要的朋友可以參考下2016-12-12Javascript繼承(上)——對(duì)象構(gòu)建介紹
Javascript中除了基本數(shù)據(jù)(Undefined、Null、Boolean、Number、String),其他都是對(duì)象(Object)2012-11-11ECharts transform數(shù)據(jù)轉(zhuǎn)換和dataZoom在項(xiàng)目中使用
這篇文章主要為大家介紹了ECharts transform數(shù)據(jù)轉(zhuǎn)換和dataZoom在項(xiàng)目中使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12JS實(shí)現(xiàn)簡單的操作桿旋轉(zhuǎn)示例詳解
這篇文章主要為大家介紹了JS實(shí)現(xiàn)簡單的操作桿旋轉(zhuǎn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01微信小程序 wxapp內(nèi)容組件 icon詳細(xì)介紹
這篇文章主要介紹了微信小程序 wxapp內(nèi)容組件 icon詳細(xì)介紹的相關(guān)資料,并附簡單實(shí)例,需要的朋友可以參考下2016-10-10js實(shí)現(xiàn)兔年轉(zhuǎn)圈圈動(dòng)畫示例
這篇文章主要為大家介紹了js實(shí)現(xiàn)兔年轉(zhuǎn)圈圈動(dòng)畫示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01