JavaScript?API調用Rollup打包流程快速上手
正文
Rollup是一款基于ESModule模塊規(guī)范實現(xiàn)的JavaScript打包工具,在前端社區(qū)中赫赫有名,同時也在Vite的架構體系中發(fā)揮著重要作用。不僅是Vite生產(chǎn)環(huán)境下的打包工具,其插件機制也被Vite所兼容,可以說是Vite的構建基石。
接下來,我們將圍繞Rollup的基本概念和核心特性展開,學習完本小節(jié)內容,你不僅能知道Rollup是如何打包項目的,還能學會Rollup更高階的使用方式,甚至能夠通過JavaScriptAPI二次開發(fā)Rollup。
一、快速上手
首先,創(chuàng)建一個空的文件夾,然后使用npm init -y新建一個項目,此時,打開項目會發(fā)現(xiàn)多了一個package.json文件。
{ "name": "demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
接著,繼續(xù)安裝 rollup 依賴,命令如下。
npm i rollup
新增 src/index.js 和 src/util.js 和rollup.config.js 三個文件,目錄結構如下所示。
. ├── package.json ├── pnpm-lock.yaml ├── rollup.config.js └── src ├── index.js └── util.js
其中,index.js 和 util.js 和rollup.config.js 文件的內容分別如下。
// src/index.js import { add } from "./util"; console.log(add(1, 2)); // src/util.js export const add = (a, b) => a + b; export const multi = (a, b) => a * b; // rollup.config.js // 以下注釋是為了能使用 VSCode 的類型提示 /** * @type { import('rollup').RollupOptions } */ const buildOptions = { input: ["src/index.js"], output: { // 產(chǎn)物輸出目錄 dir: "dist/es", // 產(chǎn)物格式 format: "esm", }, }; export default buildOptions;
然后,在package.json中加入如下的構建腳本。
{ // rollup 打包命令,`-c` 表示使用配置文件中的配置 "build": "rollup -c" }
接著,在終端執(zhí)行一下npm run build,可以看到如下的命令行信息。
接下來,我們打開dist/es 目錄查看一下產(chǎn)物的內容。
const add = (a, b) => a + b; console.log(add(1, 2));
可以發(fā)現(xiàn),util.js中的multi方法并沒有被打包到產(chǎn)物中,這是因為 Rollup 具有天然的 Tree Shaking 功能,可以分析出未使用到的模塊并自動擦除。
所謂 Tree Shaking(搖樹),也是計算機編譯原理中DCE(Dead Code Elimination,即消除無用代碼) 技術的一種實現(xiàn)。由于 ES 模塊依賴關系是確定的,和運行時狀態(tài)無關。因此 Rollup 可以在編譯階段分析出依賴關系,對 AST 語法樹中沒有使用到的節(jié)點進行刪除,從而實現(xiàn) Tree Shaking。
二、常用配置解讀
2.1 多產(chǎn)物配置
在打包 JavaScript 類庫的場景中,我們通常需要對外暴露出不同格式的產(chǎn)物供他人使用,不僅包括 ESM,也需要包括諸如CommonJS、UMD等格式,保證良好的兼容性。那么,同一份入口文件,如何讓 Rollup 給我們打包出不一樣格式的產(chǎn)物呢?為了實現(xiàn)這一需求,我們基于上述的配置文件來進行如下修改。
// rollup.config.js /** * @type { import('rollup').RollupOptions } */ const buildOptions = { input: ["src/index.js"], // 將 output 改造成一個數(shù)組 output: [ { dir: "dist/es", format: "esm", }, { dir: "dist/cjs", format: "cjs", }, ], }; export default buildOptions;
從代碼中可以看到,我們將output屬性配置成一個數(shù)組,數(shù)組中每個元素都是一個描述對象,決定了不同產(chǎn)物的輸出行為。
2.2 多入口配置
除了多產(chǎn)物配置,Rollup 中也支持多入口配置,而且通常情況下兩者會被結合起來使用。接下來,就讓我們繼續(xù)改造之前的配置文件,將 input 設置為一個數(shù)組或者一個對象,如下所示。
{ input: ["src/index.js", "src/util.js"] } // 或者 { input: { index: "src/index.js", util: "src/util.js", }, }
然后,再次執(zhí)行npm run build??梢园l(fā)現(xiàn),所有入口的不同格式產(chǎn)物已經(jīng)成功輸出。
如果不同入口對應的打包配置不一樣,我們也可以使用默認的配置來導出一個配置數(shù)組,如下所示。
// rollup.config.js /** * @type { import('rollup').RollupOptions } */ const buildIndexOptions = { input: ["src/index.js"], output: [ // 省略 output 配置 ], }; /** * @type { import('rollup').RollupOptions } */ const buildUtilOptions = { input: ["src/util.js"], output: [ // 省略 output 配置 ], }; export default [buildIndexOptions, buildUtilOptions];
如果是比較復雜的打包場景(如 Vite 源碼本身的打包),我們需要將項目的代碼分成幾個部分,用不同的 Rollup 配置分別打包。
2.3 自定義output配置
前面我們提到了input的使用,主要用來聲明入口,可以配置成字符串、數(shù)組或者對象,使用比較簡單。而output與之相對,用來配置輸出的相關信息,常用的配置項如下。
output: { // 產(chǎn)物輸出目錄 dir: path.resolve(__dirname, 'dist'), // 以下三個配置項都可以使用這些占位符: // 1. [name]: 去除文件后綴后的文件名 // 2. [hash]: 根據(jù)文件名和文件內容生成的 hash 值 // 3. [format]: 產(chǎn)物模塊格式,如 es、cjs // 4. [extname]: 產(chǎn)物后綴名(帶`.`) // 入口模塊的輸出文件名 entryFileNames: `[name].js`, // 非入口模塊(如動態(tài) import)的輸出文件名 chunkFileNames: 'chunk-[hash].js', // 靜態(tài)資源文件輸出文件名 assetFileNames: 'assets/[name]-[hash][extname]', // 產(chǎn)物輸出格式,包括`amd`、`cjs`、`es`、`iife`、`umd`、`system` format: 'cjs', // 是否生成 sourcemap 文件 sourcemap: true, // 如果是打包出 iife/umd 格式,需要對外暴露出一個全局變量,通過 name 配置變量名 name: 'MyBundle', // 全局變量聲明 globals: { // 項目中可以直接用`$`代替`jquery` jquery: '$' } }
2.4 依賴 external
對于某些第三方包,有時候我們不想讓 Rollup 進行打包,也可以通過 external 進行外部化,配置如下。
{ external: ['react', 'react-dom'] }
在 SSR 構建或者使用 ESM CDN 的場景中,這個配置將非常有用
2.5 接入插件
在Rollup的日常使用中,我們難免會遇到一些Rollup本身不支持的場景,比如兼容CommonJS打包、注入環(huán)境變量、配置路徑別名、壓縮產(chǎn)物代碼等等。這個時候就需要我們引入相應的Rollup插件了。接下來以一個具體的場景為例帶大家熟悉一下Rollup插件的使用。
雖然Rollup能夠打包輸出出CommonJS格式的產(chǎn)物,但對于輸入給Rollup的代碼并不支持CommonJS,僅僅支持ESM。你可能會說,那我們直接在項目中統(tǒng)一使用ESM規(guī)范就可以了啊,這有什么問題呢?需要注意的是,我們不光要考慮項目本身的代碼,還要考慮第三方依賴。
目前為止,還是有不少第三方依賴只有CommonJS格式產(chǎn)物而并未提供ESM產(chǎn)物,比如項目中用到lodash時,打包項目會出現(xiàn)這樣的報錯:
所以,我們需要引入額外的插件去解決這個問題,我們需要安裝兩個核心的插件包。
pnpm i @rollup/plugin-node-resolve @rollup/plugin-commonjs
關于這兩個插件包的說明如下:
- @rollup/plugin-node-resolve是為了允許我們加載第三方依賴,否則像import React from 'react' 的依賴導入語句將不會被 Rollup 識別。
- @rollup/plugin-commonjs 的作用是將 CommonJS 格式的代碼轉換為 ESM 格式
然后,我們在配置文件中導入這些插件,相關的配置如下:
// rollup.config.js import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; /** * @type { import('rollup').RollupOptions } */ export default { input: ["src/index.js"], output: [ { dir: "dist/es", format: "esm", }, { dir: "dist/cjs", format: "cjs", }, ], // 通過 plugins 參數(shù)添加插件 plugins: [resolve(), commonjs()], };
現(xiàn)在,我們以lodash這個只有 CommonJS 產(chǎn)物的第三方包為例測試一下。
npm i lodash
然后,在src/index.js 加入如下的代碼。
import { merge } from "lodash"; console.log(merge);
然后,執(zhí)行 npm run build命令,就可以發(fā)現(xiàn)產(chǎn)物已經(jīng)正常生成了,如下圖所示。
在Rollup配置文件中,plugins除了可以與output配置在同一級,也可以配置在output參數(shù)里面。
// rollup.config.js import { terser } from 'rollup-plugin-terser' import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; export default { output: { // 加入 terser 插件,用來壓縮代碼 plugins: [terser()] }, plugins: [resolve(), commonjs()] }
需要注意的是,output.plugins中配置的插件是有一定限制的,只有使用Output 階段相關鉤子的插件才能夠放到這個配置中,大家可以去這個站點查看 Rollup 的 Output 插件列表。這里也給大家分享其它一些比較常用的 Rollup 插件庫:
- @rollup/plugin-json: 支持.json的加載,并配合rollup的Tree Shaking機制去掉未使用的部分,進行按需打包。
- @rollup/plugin-babel:在 Rollup 中使用 Babel 進行 JS 代碼的語法轉譯。
- @rollup/plugin-typescript: 支持使用 TypeScript 開發(fā)。
- @rollup/plugin-alias:支持別名配置。
- @rollup/plugin-replace:在 Rollup 進行變量字符串的替換。
- rollup-plugin-visualizer: 對 Rollup 打包產(chǎn)物進行分析,自動生成產(chǎn)物體積可視化分析圖。
三、JavaScript API
我們通過Rollup的配置文件結合rollup -c完成了 Rollup 的打包過程,但有些場景下我們需要基于 Rollup 定制一些打包過程,配置文件就不夠靈活了,這時候我們需要用到對應 JavaScript API 來調用 Rollup,主要分為rollup.rollup和rollup.watch兩個 API,接下來我們以具體的例子來學習一下。
首先,是 rollup.rollup,用來一次性地進行 Rollup 打包,可以新建一個build.js文件,內容如下。
// build.js const rollup = require("rollup"); // 常用 inputOptions 配置 const inputOptions = { input: "./src/index.js", external: [], plugins:[] }; const outputOptionsList = [ // 常用 outputOptions 配置 { dir: 'dist/es', entryFileNames: `[name].[hash].js`, chunkFileNames: 'chunk-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]', format: 'es', sourcemap: true, globals: { lodash: '_' } } // 省略其它的輸出配置 ]; async function build() { let bundle; let buildFailed = false; try { // 1. 調用 rollup.rollup 生成 bundle 對象 bundle = await rollup.rollup(inputOptions); for (const outputOptions of outputOptionsList) { // 2. 拿到 bundle 對象,根據(jù)每一份輸出配置,調用 generate 和 write 方法分別生成和寫入產(chǎn)物 const { output } = await bundle.generate(outputOptions); await bundle.write(outputOptions); } } catch (error) { buildFailed = true; console.error(error); } if (bundle) { // 最后調用 bundle.close 方法結束打包 await bundle.close(); } process.exit(buildFailed ? 1 : 0); } build();
讓我們來解釋一下上面的代碼:
- 通過 rollup.rollup方法,傳入 inputOptions,生成 bundle 對象;
- 調用 bundle 對象的 generate 和 write 方法,傳入outputOptions,分別完成產(chǎn)物和生成和磁盤寫入。
- 調用 bundle 對象的 close 方法來結束打包。
接著,執(zhí)行node build.js命令。這樣,我們就可以完成了以編程的方式來調用 Rollup 打包的過程。除了通過rollup.rollup完成一次性打包,我們也可以通過rollup.watch來完成watch模式下的打包,即每次源文件變動后自動進行重新打包。你可以新建watch.js文件,配置如下。
// watch.js const rollup = require("rollup"); const watcher = rollup.watch({ // 和 rollup 配置文件中的屬性基本一致,只不過多了`watch`配置 input: "./src/index.js", output: [ { dir: "dist/es", format: "esm", }, { dir: "dist/cjs", format: "cjs", }, ], watch: { exclude: ["node_modules/**"], include: ["src/**"], }, }); // 監(jiān)聽 watch 各種事件 watcher.on("restart", () => { console.log("重新構建..."); }); watcher.on("change", (id) => { console.log("發(fā)生變動的模塊id: ", id); }); watcher.on("event", (e) => { if (e.code === "BUNDLE_END") { console.log("打包信息:", e); } });
現(xiàn)在,我們可以通過執(zhí)行node watch.js開啟 Rollup 的 watch 打包模式,當你改動一個文件后可以看到如下的日志,說明 Rollup 自動進行了重新打包,并觸發(fā)相應的事件回調函數(shù)。
發(fā)生生變動的模塊id: /xxx/src/index.js 重新構建... 打包信息: { code: 'BUNDLE_END', duration: 10, input: './src/index.js', output: [ // 輸出產(chǎn)物路徑 ], result: { cache: { /* 產(chǎn)物具體信息 */ }, close: [AsyncFunction: close], closed: false, generate: [AsyncFunction: generate], watchFiles: [ // 監(jiān)聽文件列表 ], write: [AsyncFunction: write] } }
基于如上的兩個 JavaScript API 我們可以很方便地在代碼中調用 Rollup 的打包流程,相比于配置文件有了更多的操作空間,你可以在代碼中通過這些 API 對 Rollup 打包過程進行定制,甚至是二次開發(fā)。
以上就是JavaScript API調用Rollup打包流程快速上手的詳細內容,更多關于JavaScript API調用Rollup打包的資料請關注腳本之家其它相關文章!
相關文章
基于JavaScript ES新特性let與const關鍵字
這篇文章主要介紹了基于JavaScript ES新特性let與const關鍵字,let是ECMAScript 2015新增的一個關鍵字,用于聲明變量,const關鍵字用于聲明一個常量,更多詳細內容,請需要的小伙伴參考下面文章的介紹,希望對你有所幫助2021-12-12