欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Rollup的插件機(jī)制及構(gòu)建流程深入解析

 更新時(shí)間:2023年06月20日 12:03:23   作者:xiangzhihong  
這篇文章主要為大家介紹了Rollup的插件機(jī)制及構(gòu)建流程深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

上一節(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 問題解決方法

    這篇文章主要介紹了微信公眾號(hào) 提示:Unauthorized API function 問題解決方法的相關(guān)資料,這里提供了出現(xiàn)提示的解決方法,需要的朋友可以參考下
    2016-12-12
  • Javascript繼承(上)——對(duì)象構(gòu)建介紹

    Javascript繼承(上)——對(duì)象構(gòu)建介紹

    Javascript中除了基本數(shù)據(jù)(Undefined、Null、Boolean、Number、String),其他都是對(duì)象(Object)
    2012-11-11
  • ECharts transform數(shù)據(jù)轉(zhuǎn)換和dataZoom在項(xiàng)目中使用

    ECharts transform數(shù)據(jù)轉(zhuǎn)換和dataZoom在項(xiàng)目中使用

    這篇文章主要為大家介紹了ECharts transform數(shù)據(jù)轉(zhuǎn)換和dataZoom在項(xiàng)目中使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • JavaScript遞歸詳述

    JavaScript遞歸詳述

    這篇文章主要介紹了JavaScript遞歸,遞歸就是當(dāng)一個(gè)函數(shù)可以調(diào)用自己,那么這個(gè)函數(shù)就是遞歸,接下倆我們就來看看下面文章的詳細(xì)介紹內(nèi)容,需要的小伙伴可以參考一下,希望對(duì)你有所幫助
    2021-12-12
  • 不可變數(shù)據(jù)方案之immer.js原理解析

    不可變數(shù)據(jù)方案之immer.js原理解析

    這篇文章主要為大家介紹了不可變數(shù)據(jù)方案之immer.js原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • 15個(gè)值得收藏的JavaScript函數(shù)

    15個(gè)值得收藏的JavaScript函數(shù)

    在開發(fā)一個(gè)JavaScript項(xiàng)目時(shí),經(jīng)常會(huì)用到以前開發(fā)過的一些工具函數(shù),收集這些函數(shù),當(dāng)你需要它們的時(shí)候,將節(jié)省你大量的開發(fā)時(shí)間,本文將給大家?guī)?5個(gè)常用的工具函數(shù),需要的小伙伴可以參考下
    2021-09-09
  • JS實(shí)現(xiàn)簡單的操作桿旋轉(zhuǎn)示例詳解

    JS實(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ì)介紹

    這篇文章主要介紹了微信小程序 wxapp內(nèi)容組件 icon詳細(xì)介紹的相關(guān)資料,并附簡單實(shí)例,需要的朋友可以參考下
    2016-10-10
  • js實(shí)現(xiàn)兔年轉(zhuǎn)圈圈動(dòng)畫示例

    js實(shí)現(xiàn)兔年轉(zhuǎn)圈圈動(dòng)畫示例

    這篇文章主要為大家介紹了js實(shí)現(xiàn)兔年轉(zhuǎn)圈圈動(dòng)畫示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Selection與Range 對(duì)象操作示例指南

    Selection與Range 對(duì)象操作示例指南

    這篇文章主要為大家介紹了Selection與Range 對(duì)象操作示例指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09

最新評(píng)論