學(xué)習(xí)筆記-Rollup/Parcel

Rollup

Rollup 同樣也是一款 ES Module 的打包器,它也可以將我們項(xiàng)目中散落的細(xì)小模塊打包為整塊的代碼,從而可以使這些劃分的模塊可以更好的運(yùn)行在瀏覽器或者 Nodejs 環(huán)境中。

從作用上來(lái)看,Rollup 與 webpack 非常類似,不過(guò)相比于 webpack,Rollup 要小巧的多。webpack 在配合一些插件的試用下,幾乎可以完成我們開(kāi)發(fā)過(guò)程總前端工程化的絕大多數(shù)工作,而 Rollup 僅僅可以說(shuō)是一款 ESM 打包器,并沒(méi)有其他額外的功能。
例如 webpack 中有對(duì)我們十分友好的 HMR,在 Rollup 中并不支持類似 HMR 這種高級(jí)特性。

Rollup 并不是要與 webpack 全面競(jìng)爭(zhēng),它的初衷只是希望提供一個(gè)充分利用 ESM各項(xiàng)特性的高效打包器,充分利用 ESM 的各項(xiàng)特性,構(gòu)建出結(jié)構(gòu)比較扁平,性能比較出眾的類庫(kù)。

快速上手

首先有一個(gè)小的demo,項(xiàng)目結(jié)構(gòu)如下:

image.png

src/logger.js:

export const log = msg => {
  console.log('---------- INFO ----------')
  console.log(msg)
  console.log('--------------------------')
}

export const error = msg => {
  console.error('---------- ERROR ----------')
  console.error(msg)
  console.error('---------------------------')
}

src/messages.js:

export default {
  hi: 'Hey Guys, I am zce~'
}

src/index.js:

// 導(dǎo)入模塊成員
import { log } from './logger'
import messages from './messages'

// 使用模塊成員
const msg = messages.hi

log(msg)

開(kāi)始使用 rollup,首先安裝依賴 yarn add rollup --dev。

然后運(yùn)行以下命令,rollup 后邊是入口文件,傳入?yún)?shù) --format 為 iife 表示編譯瀏覽器端的結(jié)果

yarn rollup ./src/index.js --format iife

可以在命令行看到編譯結(jié)果,想要輸出到 dist 中,可以執(zhí)行

yarn rollup ./src/index.js --format iife --file dist/bundle.js

運(yùn)行后看到 dist/bundle.js 中打包結(jié)果:

(function () {
  'use strict';

  const log = msg => {
    console.log('---------- INFO ----------');
    console.log(msg);
    console.log('--------------------------');
  };

  var messages = {
    hi: 'Hey Guys, I am zce~'
  };

  // 導(dǎo)入模塊成員

  // 使用模塊成員
  const msg = messages.hi;

  log(msg);

}());

可以發(fā)現(xiàn) rollup 的打包結(jié)果驚人的簡(jiǎn)潔,就跟我們以前手寫的代碼是一樣的,相比于 webpack 中大量的引導(dǎo)代碼和一堆的模塊函數(shù),這里的輸出結(jié)果幾乎沒(méi)有任何多余的代碼,它就是把我們的模塊按照引入的順序先后拼接在一起。而且仔細(xì)觀察打包結(jié)果會(huì)發(fā)現(xiàn),打包結(jié)果中只會(huì)保留用到的部分,對(duì)于未引用的部分都沒(méi)有輸出,這是因?yàn)?rollup 默認(rèn)會(huì)開(kāi)啟 Tree Shaking,去優(yōu)化我們輸出的結(jié)果。Tree Shaking 這樣的一個(gè)概念,最早也是在 Rollup 中提出的。

配置文件

Rollup 同樣支持以配置文件的方式打包。在項(xiàng)目根目錄下新建 rollup.config.js 文件。這個(gè)文件同樣也可以運(yùn)行在 node 環(huán)境中,不過(guò) rollup 自身會(huì)額外處理這個(gè)配置文件,所以可以直接使用 ESM。

rollup.config.js:

export default {
  // input: 指定打包入口文件路徑
  input: 'src/index.js',
  // output: 指定輸出的相關(guān)配置
  output: {
    // file: 指定輸出的文件名
    file: 'dist/bundle.js',
    // format: 指定輸出格式
    format: 'iife'
  }

}

運(yùn)行時(shí)需要用 --config 參數(shù)表明要使用項(xiàng)目中的配置文件,默認(rèn)是不會(huì)讀取配置文件的。 yarn rollup --config

也可以通過(guò)這個(gè)參數(shù)指定不同配置文件的名稱,例如 .dev, .prod,等針對(duì)不同環(huán)境的配置文件
yarn rollup --config rollup.config.js
或者 yarn rollup --config rollup.dev..js

使用插件

Rollup 自身的功能就只是 ESM 模塊的合并打包,如果項(xiàng)目有更高級(jí)的需求,例如想加載其他類型的資源模塊,或者要在代碼中導(dǎo)入 CommonJS 模塊,又或者想要它區(qū)編譯 ECMAScript 新特性,這些額外的需求,rollup 同樣支持使用插件的方式實(shí)現(xiàn),而插件是 Rollup 唯一的擴(kuò)展途徑。

插件是 Rollup 唯一的擴(kuò)展途徑,它不像 webpack 有 loader, plugins, minimizer 三種擴(kuò)展方式.

我們先嘗試使用可以讓我們?cè)诖a中導(dǎo)入 JSON 文件的插件,通過(guò)這個(gè)過(guò)程了解如何在 rollup 中使用插件。這個(gè)插件叫 rollup-plugin-json.

// 這個(gè)插件默認(rèn)導(dǎo)出的是插件函數(shù)
import json from 'rollup-plugin-json'


export default {
  // input: 指定打包入口文件路徑
  input: 'src/index.js',
  // output: 指定輸出的相關(guān)配置
  output: {
    // file: 指定輸出的文件名
    file: 'dist/bundle.js',
    // format: 指定輸出格式
    format: 'iife'
  },
  plugins:[
    // 將插件的函數(shù)調(diào)用結(jié)果放入 plugins 的數(shù)組中。
    json()
  ]

}

這樣我們就可以在代碼中導(dǎo)入 json 文件了。我們嘗試導(dǎo)入 package.json 文件,這個(gè)文件中的屬性就會(huì)作為單獨(dú)的導(dǎo)出成員,然后我們打印它們。

src/index.js:

// 導(dǎo)入模塊成員
import { log } from './logger'
import messages from './messages'
import { name, version } from '../package.json'
// 使用模塊成員
const msg = messages.hi

log(msg)
log(name)
log(version)

運(yùn)行 yarn rollup --config

在 bundle.js 中可以看到 name 和 version 被打包進(jìn)來(lái)了。package.json 中沒(méi)有被引用到的屬性會(huì)被 Tree Shaking 移除掉。

(function () {
  'use strict';

  const log = msg => {
    console.log('---------- INFO ----------');
    console.log(msg);
    console.log('--------------------------');
  };

  var messages = {
    hi: 'Hey Guys, I am zce~'
  };

  var name = "01-getting-started";
  var version = "0.1.0";

  // 導(dǎo)入模塊成員
  // 使用模塊成員
  const msg = messages.hi;

  log(msg);
  log(name);
  log(version);

}());

加載 npm 模塊

Rollup 默認(rèn)只能按照文件路徑加載模塊,對(duì)于 node_modules 中的第三方模塊,不能像 webpack 一樣直接通過(guò)模塊的名稱導(dǎo)入對(duì)應(yīng)的模塊。為了抹平這樣一個(gè)差異,rollup 官方給出了一個(gè)插件 - rollup-plugin-node-resolve,通過(guò)使用這個(gè)插件就可以在代碼中直接使用模塊名稱導(dǎo)入對(duì)應(yīng)的模塊了。

在 rollup.config.js 中引入插件 import resolve from 'rollup-plugin-node-resolve',然后在 plugins 中添加 resolve(),就可以在項(xiàng)目中使用名稱引入 node_modules 中的模塊了。

image.png

我們來(lái)使用一下,裝一個(gè)第三方依賴‘lodash-es’,這是 lodash 模塊的 es 版本,在 index.js 中引入。

src/index.js:

// 導(dǎo)入模塊成員
import _ from 'lodash-es'
import { log } from './logger'
import messages from './messages'
import { name, version } from '../package.json'
// 使用模塊成員
const msg = messages.hi

log(msg)
log(name)
log(version)

log(_.camelCase('hello world'))

執(zhí)行 yarn rollup --config 進(jìn)行打包,bundle.js 中就有了 loadsh 的方法

image.png

這里使用 lodash-es,而不是 lodash 是因?yàn)閞ollup 默認(rèn)只能處理 ESM 模塊,如果需要使用普通版本,需要做額外的處理。

加載 CommonJS 模塊

目前還是會(huì)有大量 npm 模塊使用 CommonJS 導(dǎo)出成員,所以為了兼容這些模塊,官方給出了一個(gè)插件 - rollup-plugin-commonjs。

image.png

新建文件 src/cjs.module.js:

module.exports = {
  foo: 'bar'
}

src/index.js:

// 導(dǎo)入模塊成員
import _ from 'lodash-es'
import { log } from './logger'
import messages from './messages'
import { name, version } from '../package.json'
// 引入 CommonJS 文件
import cjs from './cjs.module'

// 使用模塊成員
const msg = messages.hi

log(msg)
log(name)
log(version)

log(_.camelCase('hello world'))

log(cjs)

打包后插件 bundle.js 會(huì)發(fā)現(xiàn),CommonJS 中的內(nèi)容以一個(gè)對(duì)象的形式出現(xiàn)在我們的文件中了

image.png

Code Splitting - 代碼拆分

可以使用 ESM 的動(dòng)態(tài)導(dǎo)入的方式實(shí)現(xiàn)模塊的按需加載,rollup 內(nèi)部也會(huì)自動(dòng)處理代碼的拆分,也就是分包。

src/index.js 中代碼都注釋掉,然后使用動(dòng)態(tài)加載的方式來(lái)寫。

import('./logger').then(({ log }) => {
  log('code splitting~')
})

import('./logger') 動(dòng)態(tài)引入模塊,并返回一個(gè) Promise 對(duì)象,在它的 then 方法中可以拿到文件導(dǎo)出的內(nèi)容,由于導(dǎo)出的是多個(gè)函數(shù),可以通過(guò)解構(gòu)的方式拿到想要的方法。

執(zhí)行打包命令,會(huì)報(bào)錯(cuò) IIFE 格式輸出不支持代碼拆分。

image.png

原因很簡(jiǎn)單,因?yàn)?IIFE - 自執(zhí)行函數(shù)會(huì)把所有的模塊都放入同一個(gè)函數(shù)當(dāng)中,并沒(méi)有像 webpack 一樣有一些引導(dǎo)代碼,所以說(shuō)它沒(méi)有辦法實(shí)現(xiàn)代碼拆分。

想要使用代碼拆分就要使用 AMD 或是 CommonJS 這樣的一些其他標(biāo)準(zhǔn),在瀏覽器中只能使用 AMD 的標(biāo)準(zhǔn)。

執(zhí)行yarn rollup --config --format amd,傳入?yún)?shù) --format 為 amd 覆蓋默認(rèn)的格式,執(zhí)行后還是報(bào)錯(cuò)。

image.png

報(bào)錯(cuò)內(nèi)容是說(shuō)當(dāng)使用 Code Splitting 時(shí)需要輸出多個(gè)文件,不能再使用 output.file 的配置方式,因?yàn)?file 是指定單個(gè)文件,需要輸出多個(gè)文件可以使用 dir 的參數(shù)。修改后:

image.png

再次運(yùn)行 yarn rollup --config打包,就會(huì)輸出 index 和動(dòng)態(tài)導(dǎo)入的 log 的 bundle,他們都是通過(guò) amd 輸出的。

image.png

多入口打包

Rollup 同樣支持多入口打包,而且對(duì)于不同入口那些公共的部分也會(huì)自動(dòng)提取到獨(dú)立的文件中作為獨(dú)立的 bundle。

有這樣一個(gè) demo,src 中的 index.js 和 album.js 共用了src/fetch.js 和 src/logger.js。

image.png

rollup 的多入口打包非常簡(jiǎn)單,只需要把 input 設(shè)為數(shù)組就可以了。

export default {
  // 多入口打包
  input: ['src/index.js', 'src/album.js'],
  output: {
    dir: 'dist',
    format: 'amd'
  }
}

也可以像 webpack 一樣設(shè)置一個(gè)對(duì)象,屬性名和屬性值分別為導(dǎo)入文件的文件名和路徑

export default {
  // 多入口打包
  // input: ['src/index.js', 'src/album.js'],
  input: {
    foo: 'src/index.js',
    bar: 'src/album.js'
  },
  output: {
    dir: 'dist',
    format: 'amd'
  }
}

打包后會(huì)輸出三個(gè)文件,分別是 foo,bar 和公共模塊的文件。

image.png

需要注意的是,對(duì)于 amd 這種格式輸出的 js 文件,我們不能直接引用到頁(yè)面上,必須要用實(shí)現(xiàn) amd 標(biāo)準(zhǔn)的庫(kù)區(qū)加載。

我們?cè)?dist 目錄下手動(dòng)創(chuàng)建一個(gè)文件 index.html,嘗試在這個(gè)文件中使用打包生成的 bundle。采用 Require.js 這個(gè)庫(kù)來(lái)加載 amd 標(biāo)準(zhǔn)輸出的 bundle。

<script src="https://unpkg.com/requirejs@2.3.6/require.js" data-main="foo.js"></script>

Rollup / Webpack 選用原則

Rollup 優(yōu)點(diǎn):

  • 輸出結(jié)果更加扁平,執(zhí)行效率自然就會(huì)更高
  • 自動(dòng)移除未引用代碼
  • 打包結(jié)果依然完全可讀

Rollup 缺點(diǎn):

  • 加載非 ESM 的第三方模塊比較復(fù)雜
  • 模塊最終都被打包到一個(gè)函數(shù)中,無(wú)法實(shí)現(xiàn) HMR
  • 瀏覽器環(huán)境中,代碼拆分功能依賴 AMD 庫(kù)

如果我們正在開(kāi)發(fā)應(yīng)用程序,肯定需要大量引入第三方模塊,也需要 HMR 提升我們的開(kāi)發(fā)體驗(yàn),而且我們的應(yīng)用一旦大了,必須要去分包,這些需求在滿足上都有所欠缺。

而如果我們開(kāi)發(fā)的是一個(gè)框架或者類庫(kù),這些優(yōu)點(diǎn)就特別有必要,而這些缺點(diǎn)都可以忽略。

所以很多知名框架、庫(kù)都在使用 Rollup 打包器,而并非 Webpack。

但是目前為止,社區(qū)中還是希望這兩者能夠共同存在,共同發(fā)展,并且能夠相互支持和借鑒,就是希望能夠讓更專業(yè)的工具做更專業(yè)的事情。

webpack 大而全,rollup 小而美。

  • 應(yīng)用程序 - webpack
  • 庫(kù)/框架 - rollup

不過(guò)隨著近幾年的發(fā)展,rollup 中的這些優(yōu)勢(shì)已經(jīng)被抹平了,例如它的扁平化輸出,webpack 中可以使用插件完成。

Parcel

Parcel - 零配置的前端應(yīng)用打包器。提供了近乎傻瓜式的體驗(yàn)。我們只需要了解它提供的幾個(gè)簡(jiǎn)單的命令,就可以直接使用它構(gòu)建我們的前端應(yīng)用程序。

快速上手

創(chuàng)建一個(gè) parcel-demo 的項(xiàng)目,通過(guò) yarn init 初始化一個(gè) package.json 文件。安裝 parcel-bundler。

新建 src/index.html 作為打包入口(parcel 支持任意類型的文件作為打包入口,但是官方還是建議使用 html 類型的文件作為打包入口,給出的理由是 html 是應(yīng)用在瀏覽器端的入口)。

在入口文件中可以正常編寫,也可以正常引入資源文件,被引入的資源最后都會(huì)被 parcel 打包到一起,最終輸出到輸出目錄。

在 html 中引入 main.js

src/main.js:

import foo from './foo'

foo.bar()

src/foo.js:

export default {
  bar:() => {
    console.log('hello bar~')
  }
}

Parcel 同樣支持對(duì) ESM 的打包。
執(zhí)行yarn parcel src/index.html后,parcel 不僅進(jìn)行了打包,還啟動(dòng)了一個(gè)開(kāi)發(fā)服務(wù)器。

image.png

打開(kāi)這個(gè)地址,打開(kāi)控制臺(tái),嘗試修改foo.js 中打印的值,會(huì)發(fā)現(xiàn)瀏覽器會(huì)自動(dòng)刷新執(zhí)行最新的打包結(jié)果。

HMR

如果需要的是模塊熱替換的體驗(yàn),parcel 同樣也支持。

src/index.js:

import foo from './foo'

foo.bar()


// 先判斷是否存在
if (module.hot) {
  // 處理模塊熱替換的邏輯
  // 這里的 accept 和 webpack 提供的 api 不一樣
  // webpack 中支持接受兩個(gè)參數(shù),用來(lái)處理指定模塊更新過(guò)后的邏輯
  // 而 parcel 提供的 accept 只可以接受一個(gè)參數(shù)
  // 作用就是當(dāng)當(dāng)前這個(gè)模塊更新,或當(dāng)前模塊所依賴的模塊更新后,自動(dòng)執(zhí)行
  module.hot.accept(() => {
    console.log('hmr')
  })
}

自動(dòng)安裝依賴

除了熱替換的功能,parcel 還可以自動(dòng)安裝依賴。試想一下,你正在開(kāi)發(fā)一個(gè)應(yīng)用,突然想要使用某個(gè)第三方模塊,此時(shí)就需要先停止正在運(yùn)行的dev serve,然后去安裝依賴,再重新啟動(dòng) Dev serve,有了自動(dòng)安裝依賴的功能,我們就不用這么麻煩了。

我們只需要在開(kāi)發(fā)的時(shí)候直接引入使用,保存以后 parcel 會(huì)自動(dòng)安裝依賴并重新編譯。

其他類型資源模塊

parcel 同樣支持加載其他類型的資源模塊,而且相比于其他的模塊打包器,在 parcel 中加載任意類型的資源模塊,同樣還是零配置。

例如添加一個(gè) css 樣式文件,或者圖片,在 index 中導(dǎo)入,它可以立即生效,不需要添加額外的插件或loader。

Parcel 希望給開(kāi)發(fā)者的體驗(yàn)就是,你想要做什么你就只管去做,額外的事就由工具負(fù)責(zé)處理。另外 Parcel 同樣支持動(dòng)態(tài)導(dǎo)入,如果使用了動(dòng)態(tài)導(dǎo)入,也會(huì)自動(dòng)拆分代碼。

生產(chǎn)模式打包

yarn parcel build src/index.html

image.png

打包后的文件都會(huì)被壓縮,而且樣式代碼也都單獨(dú)提取到單個(gè)文件中了。

對(duì)于相同體量的項(xiàng)目打包,parcel 會(huì)比 webpack 快很多,因?yàn)樵?parcel 的內(nèi)部使用的是多進(jìn)程同時(shí)工作,充分發(fā)揮了多核CPU的性能,webpack 中也可以使用一個(gè)叫做 happypack 的插件實(shí)現(xiàn)這一點(diǎn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容