摘要:使用進行模塊化打包在之前打包的過程中,命令行中輸出了一行,這表示并沒有收到任何模塊化的格式指令,因此會用默認的模塊標準來對文件進行打包。
前言
最近在做一個提供給瀏覽器和node同時使用的js的url模板工具類,在用什么打包工具上糾結了一段時間,正好有一天在知乎上看到了關于rollup的介紹,在自己試了試之后,就決定用rollup.js來打包自己的工具類了。
這篇文章主要是為了讓對rollup.js也有興趣的同學能夠快速入門rollup的使用方式而寫的,文章除了開始對rollup.js的基本介紹之外,主要用多個demo來介紹rollup.js的不同使用方法,以及介紹一些比較常用的rollup插件。讀者可以選擇自己有興趣的部分查看。
文章博客鏈接
本教程相關的所有demo都已上傳到github,rollup-demos,歡迎star。
rollup.js簡介首先簡單介紹一下rollup.JS。根據官方的介紹,rollup.js是一個模塊打包工具,可以幫助你從一個入口文件開始,將所有使用到的模塊文件都打包到一個最終的發布文件中(極其適合構建一個工具庫,這也是我選擇用rollup來打包的原因)。
rollup.js有兩個重要的特性,其中一個就是它使用ES6的模塊標準,這意味著你可以直接使用import和export而不需要引入babel(當然,在現在的項目中,babel可以說是必用的工具了)。
rollup.js的另一個重要特性叫做"tree-shaking",這個特性可以幫助你將無用代碼(即沒有使用到的代碼)從最終的生成文件中刪去。舉個例子,我在A.js文件中定義了A1和A2兩個方法,同時在B文件中引入了這兩個方法,但是在B文件中只引入了A文件中的A1方法,那么在最后打包B文件時,rollup就不會將A2方法引入到最終文件中。(這個特性是基于ES6模塊的靜態分析的,也就是說,只有export而沒有import的變量是不會被打包到最終代碼中的)
rollup.js實例 demo0 開始使用rollup初始化一個工程,創建一個依賴模塊文件lib.js和入口文件index.js。
export function logA() { console.log("function logA called") } export function logB() { console.log("function logB called") }
import { logA } from "./lib" logA()
現在我們要把lib.js和index.js打包成dist.js,首先要做的就是安裝rollup.js。
在這里我們有兩種安裝方法:
全局安裝:
打開你的命令行,輸入npm install rollup -g,等待rollup安裝完畢。安裝完成之后,試著輸入rollup -v來查看一下rollup是否安裝成功了
成功安裝完rollup之后,進入到工程目錄下,輸入打包命令rollup index.js -o dist.js,index.js 是我們的入口文件, -o 表示輸出文件的路徑,在 -o 后面跟著的 dist.js 就是我們要生成的最終打包文件了。(其實這里本來應該加上一個參數-i,用來表示入口文件的路徑,但rollup是會把沒有加參數的文件默認為是入口文件,因此我們在這里省略了這個參數)
顯示出這條信息之后,我們發現目錄下已經多出了一個 dist.js 文件,打開文件,我們發現里面的代碼是這樣的
function logA() { console.log("function logA called"); } logA();
此時我們就已經完成了打包作業,可以將dist.js引入到HTML文件或是node模塊中了
項目本地安裝:
進入到項目目錄,打開命令行輸入npm install rollup --save-dev,把rollup加入到開發依賴中,然后在命令行中輸入./node_modules/.bin/rollup index.js -o dist.js
或者在package.json文件中添加npm scripts命令"build": "rollup index.js -o dist.js",在命令行中輸入npm run build來進行打包
在打包完成之后,我們查看一下效果,新建一個index.html文件,在這個文件中引入我們打包出來的dist.js文件
rollup 打包測試
用瀏覽器打開index.html文件,打開控制臺,我們可以看到控制臺上輸出了一行文字
使用命令行運行dist.js文件,我們也可以看到命令行中輸出了一行文字
這說明我們的打包文件dist.js是可以運行的,打包成功。
PS:
接下來的demo中,默認在項目內安裝了rollup
接下來的demo中,非必要情況下不會對打包結果進行運行結果測試,讀者若需要驗證打包效果,請自己編寫其他測試代碼。
demo1 使用rollup進行模塊化打包在之前打包的過程中,命令行中輸出了一行No format option was supplied – defaulting to "es",這表示rollup并沒有收到任何模塊化的格式指令,因此會用默認的es模塊標準來對文件進行打包。
如果在demo0中的index.js文件中把logA()改成export default logA(),那么rollup最后的打包結果就會是
function logA() { console.log("function logA called"); } var index = logA(); export default index;
顯然這樣的代碼是不能直接在瀏覽器端和node端運行的,我們需要把原先的ES6模塊轉化成瀏覽器和node支持的形式。
那么要去哪里找rollup把ES6代碼轉化成其他形式的方法呢?這里有兩個方案,一是去rollup的官網找相關的資料,二是使用rollup命令行的幫助命令,看看能不能找到相關的參數
我們使用rollup命令行的幫助命令,在命令行中輸入rollup -h
在這里我們可以看到類似版本號,幫助,使用配置文件等一系列參數。在這里我們可以找到-f這個參數,他的說明是輸出的類型(amd,cjs,es,iife,umd),從括號里的內容我們可以看出,使用這個參數可以確定打包完后的文件的模塊處理方式。(如果你還不知道這幾種模塊之間的區別,建議先去找一下相關的資料學習一下)
接下來我們用rollup來打包一下,在demo0中的index.js文件里將logA()改成export default logA(),在package.json文件中寫好不同模塊的打包命令
"build:amd": "rollup index.js -f amd -o ./dist/dist.amd.js", "build:cjs": "rollup index.js -f cjs -o ./dist/dist.cjs.js", "build:es": "rollup index.js -f es -o ./dist/dist.es.js", "build:iife": "rollup index.js -f iife -n result -o ./dist/dist.iife.js", "build:umd": "rollup index.js -f umd -n result -o ./dist/dist.umd.js", "build:all": "npm run build:amd && npm run build:cjs && npm run build:es && npm run build:iife && npm run build:umd"
在這里我們發現在設置模塊為iife(立即執行函數)和umd時,還加上了一個參數-n,這是因為我們將logA()的結果設置為模塊的輸出結果,那么在使用iife和umd時,需要事先設定模塊的名稱,才能讓其他人通過這個模塊名稱引用到你的模塊的輸出結果。
在命令行中輸入npm run build:all,運行所有打包命令,查看效果
可以看到已經輸出了5種不同模塊標準的打包文件,由于字數原因,在這里我們只查看一個打包文件(dist.iife.js)的內容
var result = (function () { "use strict"; function logA() { console.log("function logA called"); } var index = logA(); return index; }());
可以看到所有代碼都被打包到了一個立即執行函數中,并且將函數的返回值(模塊的輸出內容)賦值給了一個全局變量,而這個全局變量的名稱就是我們之前設置的模塊名稱。
PS: 使用amd模塊打包方式時,若不指定模塊名稱,則會打包成匿名函數,若想打包成一個具名函數,則需要使用-u或--id來指定具名函數名稱。
除了-f之外,還有許多其他的參數可以使用,看到這里可能有些同學會覺得麻煩了,這么多參數用起來好麻煩,每次都要輸一長串的命令,那么有沒有更好的方法來控制rollup的參數配置呢?
當然有,接下來我們就嘗試使用配置文件來控制rollup打包。
demo2 使用配置文件來進行rollup打包創建一個demo2,沿用之前demo1的內容,我們在demo2的項目下創建一個文件,取名為rollup.config.js,這個文件就是rollup的配置文件了,rollup根據配置文件的輸出配置來進行打包,接下來我們在配置文件中輸入配置代碼:
export default { entry: "index.js", format: "cjs", dest: "./dist/dist.js" }
entry表示打包的入口文件,format表示要打包成的模塊類型,dest表示輸出文件的名稱路徑
PS: 若使用iife或umd模塊打包,需要添加屬性moduleName,用來表示模塊的名稱;若用amd模塊打包,可以配置amd相關的參數(使用umd模塊模式時,也會使用到amd相關配置參數):
amd: { id: "amd-name", // amd具名函數名稱 define: "def" // 用來代替define函數的函數名稱 }
在這里我們發現配置文件也是使用了ES6語法,這是因為rollup可以自己處理配置文件,所以能夠直接用ES6的模塊輸出(當然,你也可以選擇使用node的module.exports方式來輸出配置。
在package.json文件中編寫npm scripts命令
"build": "rollup -c"
-c這個參數表示使用配置文件來進行打包,若后面沒有指定使用的配置文件路徑,則使用默認的配置文件名稱rollup.config.js。
在命令行中輸入npm run build,執行打包,可以看到生成了打包文件dist.js
"use strict"; function logA() { console.log("function logA called"); } var index = logA(); module.exports = index;
進階: 當rollup配置文件最終輸出的不是一個對象而是一個數組時,rollup會把每一個數組元素當成一個配置輸出結果,因此可以在一個配置文件內設置多種輸出配置
例如,我們添加一個indexB.js文件,在這個文件中我們將logA替換為logB,并將rollup配置文件改為:
export default [{ entry: "index.js", format: "cjs", dest: "./dist/distA.js" },{ entry: "indexB.js", format: "iife", moduleName: "indexB", dest: "./dist/distB.js" }]
運行打包命令,發現在dist目錄下生成了distA.js和distB.js兩個文件,說明多項配置打包成功。
除了上面這種輸出一個配置數組之外,你還可以通過配置target屬性來輸出多個打包文件:
export default { entry: "index.js", targets: [{ dest: "dist/bundle.cjs.js", format: "cjs" }, { dest: "dist/bundle.umd.js", moduleName: "res", format: "umd" }, { dest: "dist/bundle.es.js", format: "es" }, ] }
這樣配置會在dist目錄下面輸出bundle.cjs.js,bundle.umd.js和bundle.es.js三個打包文件,同時umd模塊的名稱會被定義成res。
demo3 監聽文件變化,隨時打包我們在開發過程中,需要頻繁對源文件進行修改,如果每次都自己手動輸一遍打包命令,那真的是要煩死。因此,我們選擇使用rollup提供的監聽功能,安裝rollup-wacth模塊,再在rollup命令后面加上-w參數,就能讓rollup監聽文件變化,即時打包。
安裝watch包:
npm i rollup-watch --save-dev // or yarn add rollup-watch --dev
編寫npm scripts:
"dev": "rollup -c -w"
執行npm run dev,看到下面的提示:
好了,這個時候你就可以隨便修改你的源文件了,rollup會自動為你打包的。
PS: 若是你不想監聽某些文件,只要在配置文件中加上
watch: { exclude: ["path/to/file/which/you/want/to/ignore"] }
就行了,其中的exclude表示你想要忽略的文件的路徑(支持glob模式匹配)
demo4 是時候寫ES6了ES6可以說是現代JS開發100%會用到的技術了,rollup雖然支持了解析import和export兩種語法,但是卻不會將其他的ES6代碼轉化成ES5代碼,因此我們在編寫ES6代碼的時候,需要引入插件來支持ES6代碼的解析。
安裝插件和你需要的babel preset:
npm i rollup-plugin-babel babel-preset-es2015 --save-dev // or yarn add rollup-plugin-babel babel-preset-es2015 --dev
創建.babalrc文件:
{ "presets": [ ["es2015", { "modules": false }] ] }
之所以使用modules:false這個參數,是因為rollup默認是通過ES6模塊語法來解析文件間的依賴,rollup默認是不支持解析common.js的模塊規范的(怎么讓rollup支持我會在接下來的demo中講解),因此需要讓babel不轉化模塊相關的語法,不然rollup在使用過程中會報錯。
編寫rollup配置文件:
import babel from "rollup-plugin-babel"; export default [{ entry: "index.js", format: "iife", dest: "./dist/dist.js", plugins: [ babel({ exclude: "node_modules/**" }) ] }]
rollup的配置文件的plugins屬性可以讓你添加在rollup打包過程中所要用到的插件,但是要注意的是,插件的添加順序決定了它們在打包過程中的使用順序,因此要注意配置文件的插件使用順序。
編寫ES6代碼
在這里我們新建三個文件,兩個類Person和Man和一個入口文件index.js
export default class Person { constructor (name, gender = "男") { this.name = name this.gender = gender } say () { console.log(`我的名字是${this.name},是一個${this.gender}生`) } }
import Person from "./Person" export default class Man extends Person { constructor (name) { super(name, "男") } }
import Man from "./src/Man" new Man("KainStar").say()
運行打包命令npm run build
可以看到rollup輸出了一段提示文字,我們先不去管它,先看看打包出來的文件能不能運行,執行node dist/dist.js
可以看到代碼運行成功了,那么我們回來繼續看之前的提示文字,它的意思是"classCallCheck"這個babel helper函數使用了多次,rollup推薦我們使用external-helpers這個插件或es2015-rollup這個babel-preset來簡化打包出來的代碼。
我們查看一下打包出來的dist.js文件,發現_classCallCheck這個函數被定義了兩次,分別被取名為_classCallCheck和_classCallCheck$1,這樣的代碼肯定是可以簡化的,因此我們引入external-helpers這個插件:
npm i babel-plugin-external-helpers --save-dev // or yarn add babel-plugin-external-helpers --dev
修改.babelrc文件為
{ "presets": [ ["es2015", { "modules": false }] ], "plugins": [ "external-helpers" ] }
或者在配置文件中使用babel配置
plugins: [ babel({ plugins: ["external-helpers"] }) ]
注意! 在rollup-plugin-babel的官方github倉庫中有一段配置是這樣的:
plugins: [ babel({ plugins: ["external-helpers"], externalHelpers: true }) ]
這段配置的使用要求是你需要設置全局的babelHelpers對象,以此來將打包文件中的babel相關代碼刪除,所以一般情況下不需要使用externalHelpers這個屬性。
PS: 你也可以使用babel-preset-es2015-rollup這個包(搭配babel-core),它集成了babel-preset-es2015,babel-plugin-transform-es2015-modules-commonjs和babel-plugin-external-helpers三個模塊,使用起來更加方便,只要將.babelrc文件修改成{ "presets": ["es2015-rollup"] }就可以使用了。
demo5 解析cjs,打包第三方模塊有時候我們會引入一些其他模塊的文件(第三方的或是自己編寫的),但是這些第三方的模塊為了能夠直接使用,往往不是ES6模塊而是用commonjs的模塊方式編寫的,這個時候我們需要將commonjs的模塊轉化為ES6模塊,這樣才能讓rollup進行正確的解析。
解析commonjs
解析commonjs需要引入一個rollup插件——rollup-plugin-commonjs
安裝插件
npm i rollup-plugin-commonjs --save-dev // or yarn add rollup-plugin-commonjs --dev
在配置文件中配置插件
import commonjs from "rollup-plugin-commonjs" export default { entry: "index_cjs.js", format: "iife", dest: "./js/dist_cjs.js", plugins: [ commonjs() ] }
編寫cjs模塊的文件
exports.logA = function logA() { console.log("function logA called") } exports.logB = function logB() { console.log("function logB called") }
執行打包,可以看到打包成功,也沒有輸出任何提示信息
打包第三方模塊
在打包第三方模塊的過程中,rollup無法直接解析npm模塊,因此需要引入插件rollup-plugin-node-resolve并配合之前的commonjs插件來解析這些第三方模塊
安裝插件和第三方模塊
npm i rollup-plugin-node-resolve lodash --save-dev // or yarn add rollup-plugin-node-resolve lodash --dev
在配置文件中配置插件
import commonjs from "rollup-plugin-commonjs" import resolve from "rollup-plugin-node-resolve" export default { entry: "index_module.js", format: "iife", dest: "./js/dist_module.js", plugins: [ resolve({ jsnext: true, main: true, browser: true }), commonjs() ] }
jsnext表示將原來的node模塊轉化成ES6模塊,main和browser則決定了要將第三方模塊內的哪些代碼打包到最終文件中。
由于commonjs和node-resolve中的配置屬性很多,因此不一一解釋,希望了解更多的同學可以去官方倉庫查看說明。
編寫入口文件
import compact from "lodash/compact" const array = [0, 1, false, 2, "", 3] const compctedArray = compact(array) console.log(compctedArray)
在這里我們只引用了lodash中的compact方法,那么在最終代碼里,應該也只會添加compact方法的代碼。
執行打包命令,查看打包出來的文件:
(function () { "use strict"; /** * Creates an array with all falsey values removed. The values `false`, `null`, * `0`, `""`, `undefined`, and `NaN` are falsey. * * @static * @memberOf _ * @since 0.1.0 * @category Array * @param {Array} array The array to compact. * @returns {Array} Returns the new array of filtered values. * @example * * _.compact([0, 1, false, 2, "", 3]); * // => [1, 2, 3] */ function compact(array) { var index = -1, length = array == null ? 0 : array.length, resIndex = 0, result = []; while (++index < length) { var value = array[index]; if (value) { result[resIndex++] = value; } } return result; } var compact_1$1 = compact; const array = [0, 1, false, 2, "", 3]; const compctedArray = compact_1$1(array); console.log(compctedArray); }());
確實只添加了compact方法的代碼,而沒有將lodash全部引入。
demo6 不要打包到一個文件,為rollup設置外部模塊和全局變量在平時的開發中,我們經常會引入其他的模塊,但是在使用的時候,我們又不想把它們打包到一個文件里,想讓他們作為多帶帶的模塊(或文件)來使用,方便瀏覽器端進行緩存,這個時候就需要使用配置文件中的external屬性了
我們在demo5的基礎上,把jquery安裝到第三方模塊中
npm i jquery --save-dev // or yarn add jquery --dev
將配置文件改成
import commonjs from "rollup-plugin-commonjs" import resolve from "rollup-plugin-node-resolve" export default { entry: "index.js", format: "iife", dest: "./js/dist.js", external: ["jquery"], plugins: [ resolve({ jsnext: true, main: true, browser: true }), commonjs() ] }
external用來表示一個模塊是否要被當成外部模塊使用,屬性的值可以是一個字符串數組或一個方法,當傳入的是一個字符串數組時,所有數組內的模塊名稱都會被當成是外部模塊,不會被打包到最終文件中
當傳入的是一個方法時,方法有一個參數id,表示解析的模塊的名稱,我們可以自定義解析方式,若是要當做外部模塊不打包到最終文件中,則返回true,若要一起打包到最終文件中,則返回false
在這里我們把jquery當成一個外部模塊,執行打包命令:
檢查打包出來的文件,我們發現lodash的compact方法依舊被打包進了最終文件中,但是jquery卻沒有被打包進去,而是以$的全局變量形式被傳入到了立即執行函數中。
在這里rollup又給我們輸出了一條提示信息,意思是我們沒有在配置文件中給外部模塊jquery設置全局變量名稱,因此rollup自己猜測了一個名稱$,當成是依賴的全局變量名。
如果直接使用全局的$的話,可能會因為變量$被其他引入的代碼覆蓋而報錯,因此我們要將$替換為不容易沖突的jQuery變量,在配置文件中添加globals屬性:
globals: { jquery: "jQuery" }
globals的值是一個對象,key表示使用的模塊名稱(npm模塊名),value表示在打包文件中引用的全局變量名,在這里我們就是把jquery模塊的全局變量名設置為jQuery,重新打包
在重新打包出來的文件中,我們發現最后傳入的參數已經由$變為了jQuery,而且rollup也沒有輸出提示信息。
demo7 打包node內置模塊有時候我們想要在瀏覽器端使用node自帶的一些內置模塊,一般情況下會使用browserify這個工具來打包,但是browserify打包出來的文件實在太大,因此我們用rollup選擇性地導入我們需要的node內置模塊
安裝插件
npm i rollup-plugin-node-builtins --save-dev // or yarn add rollup-plugin-node-builtins --dev
PS: node-builtins對不同的node內置模塊支持不同,有些模塊可能需要使用其他的插件(例如rollup-plugin-node-globals)才能正常打包,具體的支持情況可以查看node-builtins的官方倉庫。
編寫配置文件
import builtins from "rollup-plugin-node-builtins" export default { entry: "index.js", format: "iife", dest: "./dist/dist.js", plugins: [ builtins() ] }
編寫入口文件
import { join } from "path" const path_base = "E://node" const path_joined = join(path_basem, "bin") console.log(path_joined)
在這里我們使用node內置的path模塊,運行打包命令,發現dist.js文件中引入了額外的100多行代碼,這100多行代碼就實現了path模塊的join方法供我們使用。
PS: 我建議,如果不是必要的情況,最好能夠使用其他人編寫的第三方實現庫或自己造輪子實現,而不是使用node內置的模塊,因為在引用某些模塊時,node-builtins可能會引入過多的代碼,這樣會大大增加最后打包的文件的大小,使用他人的第三方庫或自己的實現可控性更高
demo8 配合CDN來使用rollup有時候我們可能會使用CDN服務器上的js文件,但是又不想在本地安裝一個相同的模塊(也有可能沒有對應的模塊),可能在版本升級的時候會產生一些問題,這個時候我們就需要使用rollup的paths屬性了,這個屬性可以幫助你把依賴的代碼文件地址注入到打包之后的文件里。
編寫配置文件
export default { entry: "index.js", format: "amd", dest: "./dist/dist.js", external: ["jquery"], paths: { jquery: "https://cdn.bootcss.com/jquery/3.2.1/jquery.js" } }
在這里我們要使用cdn上的jquery文件,paths屬性的值可以是一個對象或用法與external屬性方法相似的方法(只是返回的不是boolean值而是文件的地址)。若使用對象來表示,則key值為需要引入的模塊名稱,value值為對應的文件地址
編寫源文件
import $ from "jquery" $("#p").html("rollup 使用paths屬性配合CDN")
執行打包命令,最后打包出來的文件內容是:
define(["https://cdn.bootcss.com/jquery/3.2.1/jquery.js"], function ($) { "use strict"; $ = $ && $.hasOwnProperty("default") ? $["default"] : $; $("#p").html("rollup 使用paths屬性配合CDN"); });
可以看到rollup已經把我們需要的CDN地址作為依賴加入到了打包文件中。
demo9 最小化你的代碼代碼發布時,我們經常會把自己的代碼壓縮到最小,以減少網絡請求中的傳輸文件大小。
rollup的插件rollup-plugin-uglify就是來幫助你壓縮代碼的,我們接下來就用這個插件來壓縮一下我們的代碼
npm i rollup-plugin-uglify --save-dev // or yarn add rollup-plugin-uglify --dev
編寫配置文件
import uglify from "rollup-plugin-uglify" export default { entry: "index.js", format: "iife", dest: "./dist/dist.js", plugins: [ uglify() ] }
運行打包命令,查看dist.js文件,發現代碼已經被壓縮了
但是,壓縮過的代碼在debug時會帶來很大的不便,因此我們需要在壓縮代碼的同時生成一個sourceMap文件
幸運的是,rollup自己就支持sourceMap文件的生成,不需要我們去引入其他插件,只需要在配置文件中加上:
sourceMap: true
就可以了。
重新打包,我們發現不僅生成了dist.js.map文件,而且dist文件最后加上了一行//# sourceMappingURL=dist.js.map,并且在瀏覽器中可以正確加載源文件
PS: 若是將sourceMap屬性的值設置為inline,則會將sourceMap的內容添加到打包文件的最后。
demo10 為你的代碼添eslint檢查在大型工程的團隊開發中,我們需要保證團隊代碼風格的一致性,因此需要引入eslint,而且在打包時需要檢測源文件是否符合eslint設置的規范,若是不符合則拋出異常并停止打包。在這里我們使用rollup的eslint插件rollup-plugin-eslint:
安裝插件
npm i eslint rollup-plugin-eslint --save-dev // or yarn add eslint rollup-plugin-eslint --dev
編寫eslint配置文件.eslintrc
{ "env": { "browser": true, "commonjs": true, "es6": true, "node": true }, "parserOptions": { "ecmaFeatures": { "jsx": false }, "sourceType": "module" }, "rules": { "semi": ["error","never"] } }
在這里我們強制要求不使用分號,然后在源文件中加上一個分號
foo(element);
編寫rollup配置文件
import eslint from "rollup-plugin-eslint"; export default { entry: "./src/index.js", format: "iife", dest: "./dist/dist.js", plugins: [ eslint({ throwOnError: true, throwOnWarning: true, include: ["src/**"], exclude: ["node_modules/**"] }) ] }
eslint插件有兩個屬性需要說明:throwOnError和throwOnWarning設置為true時,如果在eslint的檢查過程中發現了error或warning,就會拋出異常,阻止打包繼續執行(如果設置為false,就只會輸出eslint檢測結果,而不會停止打包)
執行打包命令,發現eslint在輸出了檢查結果之后拋出了異常,而且dist.js文件也沒有生成
刪除index.js文件中的分號,重新打包,發現打包成功
進階: 在平時的開發過程中,我們經常會使用IDE或編輯器的eslint插件,以便提早發現問題,但是有時候這些插件會去檢查打包完的文件,導致你的提示框里一直會有eslint檢測到錯誤的消息
我們現在有兩種解決方案,一是創建一個.eslintignore文件,將打包文件加進去,讓eslint忽略這個文件
還有一種就是讓rollup在打包文件的開始和最后自動生成注釋來阻止eslint檢測代碼,使用這種方法時,需要使用rollup配置文件的兩個屬性:banner和footer,這兩個屬性會在生成文件的開頭和結尾插入一段你自定義的字符串。我們利用這個屬性,在打包文件的開頭添加/*eslint-disable */注釋,讓eslint不檢測這個文件。
添加banner和footer屬性
banner: "/*eslint-disable */"
重新打包,我們發現打包文件的開頭被插入了這段注釋字符串,而且eslint插件也不報dist.js文件的錯了
/*eslint-disable */ (function () { "use strict"; // 具體代碼 }());demo11 控制開發環境和生產環境下的配置
配置文件的開發/生產環境配置
有時候我們會需要區分開發環境和生產環境,針對不同的打包要求輸出不同的打包配置,但是我們又不想寫rollup.config.dev.js和rollup.config.prod.js兩個文件,因為可能兩者之間的區別只是一個uglify插件。
因此,我們就需要用變量來控制配置文件的輸出內容,rollup命令行給我們提供了一個設置環境變量的參數--environment,在這個參數后面加上你需要設置的環境變量,不同變量間用逗號分隔,用冒號后面的字符串表示對應變量的值(若不加冒號,則默認將值設為字符串true):
在package.json文件中編寫對應的npm scripts命令:
"dev": "rollup -c --environment NODE_ENV:development", "build": "rollup -c --environment NODE_ENV:production"
最后修改我們的rollup配置文件
import uglify from "rollup-plugin-uglify" let isProd = process.env.NODE_ENV === "production" // 通用的插件 const basePlugins = [] // 開發環境需要使用的插件 const devPlugins = [] // 生產環境需要使用的插件 const prodPlugins = [uglify()] let plugins = [...basePlugins].concat(isProd ? prodPlugins:devPlugins) let destFilePath = isProd ? "./dist/dist.min.js": "./dist/dist.js" export default { entry: "index.js", format: "iife", dest: destFilePath, sourceMap: isProd, plugins: plugins }
我們分別運行兩個npm scripts命令,查看打包的結果:
源文件開發/生產環境信息注入
上面是在配置文件里通過變量來改變輸出的配置類型,但是我們有時候需要將生產環境信息添加到源文件里,這個時候就需要使用rollup的配置屬性intro和outro了
如果說banner和footer是在文件開始和結尾添加字符串,那么intro和outro就是在被打包的代碼開頭和結尾添加字符串了,以iife模式來舉例,如果我們配置了這四個屬性,那么輸出結果就會是:
// banner字符串 (function () { "use strict"; // intro字符串 // 被打包的代碼 // outro字符串 }()); // footer字符串
這樣的形式
下面我們實際使用一下,在index.js文件里加上一段需要依賴的代碼
if (DEVELOPMENT) { console.log("處于開發環境") } else { console.log("處于生產環境") }
然后在我們的rollup配置文件里添加:
intro: "var DEVELOPMENT = " + !isProd,
這樣,當我們最后生成的代碼時,就會輸出開發環境或生產環境的提示:
源文件開發/生產環境信息替換
有時候我們會把開發/生產環境的信息直接寫在源文件里面,這個時候用intro來注入代碼的方式就不適合了。這個時候我們就需要使用rollup-plugin-replace插件來對源代碼的變量值進行替換:
安裝插件
npm i rollup-plugin-replace --save-dev // or yarn add rollup-plugin-replace --dev
編寫配置文件
const basePlugins = [replace({ DEVELOPMENT: !isProd })] // 將intro屬性注釋掉 // intro: "var DEVELOPMENT = " + !isProd,
這里我們使用replace插件,以key-value對象的形式,將DEVELOPMENT的值替換為!isProd的值
執行打包命令,并檢查打包結果:
進階: replace除了直接使用key-value的形式替換對應key同名變量的方法之外,還可以通過配置delimiters參數來實現模板功能:
配置replace插件參數
VERSION: "1.0.0", delimiters: ["{{", "}}"]
通過這個配置,在打包過程中,{{VERSION}}會被替換成1.0.0
在index.js文件內添加相關代碼
var version = "{{VERSION}}" console.log("版本 v" + version)
打包的結果
var version = "1.0.0"; console.log("版本 v" + version);demo12 使用rollup的API
有時候我們會需要在打包的前后執行一些其他的代碼,但是又不想引入其他構建工具(例如gulp),那么就可以使用rollup提供的node API來編寫你自己的打包流程。
rollup模塊只提供了一個rollup函數,這個函數的參數和我們編寫配置文件時導出的參數不同,減少了很多配置屬性,留下來的主要是一些輸入相關的配置。(具體的配置屬性可以查看rollup wiki的javascript API一節)
執行這個函數返回的是一個Promise,并且在then方法中提供一個bundle對象作為參數,這個對象保存了rollup對源文件編譯一次之后的結果,而且提供了generate和write兩個方法
write方法提供了編譯并將打包結果輸出到文件里的功能,返回的是一個沒有參數的Promise,可以讓你自定義接下來執行的代碼
generate方法是只提供了編譯的功能,返回一個Promise,這個Promise有一個對象參數,包含了code(編譯完之后的代碼)和map(分析出來的sourceMap對象)兩個屬性,一般用在插件開發中
write和gengerate方法都接受有編譯相關屬性的對象作為傳入的編譯參數,而write方法還額外接受dset屬性作為導出文件的名稱。
在這里我們只使用write方法來編寫一個為所有模塊類型打包,并輸出打包完畢提示的文件,至于generate的使用方法我們會放在編寫插件一節中介紹。
const rollup = require("rollup").rollup rollup({ entry: "index.js" }).then(bundle => { // 保存所有Promise的列表 let writePromiseList = [] // 聲明所有需要打包的模塊類型 let moduleTypesList = ["es","cjs","amd","umd","iife"] moduleTypesList.forEach(function(moduleType) { writePromiseList.push(bundle.write({ dest: "./dist/dist." + moduleType + ".js", format: moduleType, sourceMap: true })) }) return Promise.all(writePromiseList) }).then(() => { console.log("全部模塊格式打包完畢") // 其他代碼 })
將package.json文件內的npm scripts命令修改為
"build": "node rollup.js"
執行打包命令,查看打包結果
在這里我們可以看到,一個bundle可以被重復使用多次,因此我們可以用Promise.all方法來等待所有模塊打包完成后再輸出打包完畢的提示。
demo13 除了打包JS,我們還能……一個web項目內肯定不會只有js文件,還有css、html(也可能是模板文件)和其他類型的文件,那么我們在打包的時候能不能把這些文件一起打包呢?
我們需要區分一下,在這里的打包有兩種意思,一種是讓這些文件可以像JS文件一樣,在源代碼中被import并使用;還有一種是通過在源文件中import這些文件,最后將它們合并到一起并導出到一個最終文件內。
不同的rollup插件有不同的效果,在使用的時候一定要查看插件的相關說明
安裝插件
npm i rollup-plugin-scss --save-dev // or yarn add rollup-plugin-scss --dev
編寫配置文件
import scss from "rollup-plugin-scss" export default { entry: "./src/js/index.js", format: "iife", dest: "./dist/js/dist.js", sourceMap: true, plugins: [ scss({ output: "./dist/css/style.css" }) ] }
在這里我們嘗試編譯和打包scss文件,將其合并成一個style.css文件,并輸出到dist/css目錄下
編寫scss文件
$blue: #69c4eb; .bg-blue { background-color: $blue }
$white: #fff; .text-white { color: $white; }
然后在源文件中引用這兩個scss文件
import "../scss/text.scss" import "../scss/bg.scss" var html = `` document.body.innerHTML = html測試文字
執行打包命令,查看效果
extra 編寫你自己的rollup插件有時候我們可能需要自己編寫rollup插件來實現需求,rollup官方在wiki上提供了關于編寫插件的一些介紹,下面我們就根據這些介紹來寫一個自己的rollup插件。
我們在這里仿照scss插件編寫一個stylus的rollup插件,讓使用者可以import stylus文件,并編譯打包導出到指定的目錄下(為了節省代碼量,只寫了輸出到指定路徑的功能代碼,其他的功能可以參考scss插件的具體代碼)。
首先創建項目,在package.json文件中,除了一般信息之外,還要加上
"main": "index.cjs.js", "module": "index.es.js", "jsnext:main": "index.es.js"
這些信息用來區分使用不同模塊規范時使用的文件
安裝我們需要用到的模塊
npm i rollup rollup-plugin-babel babel-preset-es2015-rollup babel-core --save-dev npm i rollup-pluginutils stylus --save // or yarn add rollup rollup-plugin-babel babel-preset-es2015-rollup babel-core --dev yarn add rollup-pluginutils stylus
rollup-pluginutils和stylus是我們運行時需要的兩個模塊,stylus用來解析stylus文件,pluginutils則提供給了我們一些編寫插件常用的函數
編寫rollup配置文件
import babel from "rollup-plugin-babel" export default { entry: "./index.es.js", dest: "./index.cjs.js", format: "cjs", plugins: [ babel() ] }
rollup插件需要一個含有指定屬性的對象作為插件內容,rollup官方建議我們在編寫插件的時候,export一個返回值為插件對象的函數,這樣可以方便使用者指定插件的參數。
rollup會將解析的部分結果作為參數調用插件返回的對象中的一些函數屬性,這些函數會在合適的時候被rollup調用(相當于rollup在執行各個操作時的鉤子函數),下面我們介紹一些常用的屬性:
name:插件的名稱,提供給rollup進行相關信息的輸出
load:不指定這個屬性時,解析模塊會默認去讀取對應路徑文件的內容;而當該值為函數(id => code)時,可以將函數最后的返回值作為文件的內容提供給rollup(可以用來生成自定義格式的代碼)
resolveId:一個( (importee, importer) => id)形式的函數,用來解析ES6的import語句,最后需要返回一個模塊的id
transform:最常使用的屬性,是一個函數,當rollup解析一個import時,會獲取到對應路徑文件的內容,并將內容和模塊的名稱作為參數提供給我們;這個函數執行完畢之后,需要返回一個作為代碼的字符串或是類似{ code, map }結構的對象,用來表示解析完之后該模塊的實際內容,map指的是sourceMap,而如果我們沒有要導出的sourceMap,就可以將返回的map值設為{mappings: ""}
ongenerate:當我們或rollup調用generate方法時,會被調用的一個鉤子函數,接受generate的option作為參數
onwrite:和ongenerate一樣,調用write方法時,會被調用的一個鉤子函數,接受write的option作為參數
一般情況下,我們通過transform函數來獲取文件的id和內容,并對內容做一些處理,若需要輸出文件則使用ongenerate或onwrite在rollup打包的最后階段來做相應的輸出。
load和resolveId在一般情況下不會使用,除非你有特殊的需求(例如對路徑、模塊id進行修改等)
根據上面這些內容,我們編寫具體的插件內容
import { createFilter } from "rollup-pluginutils" import fs from "fs" import path from "path" import stylus from "stylus" // 遞歸創建文件夾 function mkdirs(dir) { return new Promise((resolve, reject) => { fs.exists(dir, (exist) => { if (exist) { resolve() } else { mkdirs(path.dirname(dir)).then(() => { fs.mkdir(dir, (err) => { if (err) { reject() } else { resolve() } }) }) } }) }) } // 導出一個function export default function stylusPlugin(options = {}) { // 創建一個文件過濾器,過濾以css,styl結尾的文件 const stylusFilter = createFilter(options.include || ["**/*.css", "**/*.styl"], options.exclude) // dest用來保存指定的輸出路徑 let dest = options.output, // styleNodes用來暫存不同文件的css代碼 styleNodes = {} // 編譯stylus文件 function complier(str, stylusOpt) { return new Promise((resolve, reject) => { stylus.render(str, stylusOpt, (err, css) => { if (err) { reject(err) } else { resolve(css) } }) }) } return { // 插件名稱 name: "rollup-plugin-stylus", // 解析import時調用,獲取文件名稱和具體代碼,將它們保存起來 transform (code, id) { if (!stylusFilter(id)) { return } styleNodes[id] = code return "" }, // generate時調用,用stylus解析代碼,并輸出到指定目錄中 async ongenerate (genOpt) { let css = "" for (let id in styleNodes) { // 合并所有css代碼 css += styleNodes[id] || "" } // 編譯stylus代碼 if (css.length) { try { css = await complier(css, Object.assign({}, options.stylusOpt)) } catch (error) { console.log(error) } } // 沒有指定輸出文件路徑時,設置一個默認文件 if (typeof dest !== "string") { if (!css.length) { return } dest = genOpt.dest || "bundle.js" if (dest.endsWith(".js")) { dest = dest.slice(0, -3) } dest = dest + ".css" } // 創建目錄,并將css寫入到結果文件內 await mkdirs(path.dirname(dest)) return new Promise((resolve, reject) => { fs.writeFile(dest, css, (err) => { if (err) { reject(err) } else { resolve() } }) }) } } }
這樣,一個解析并打包stylus文件的rollup插件就寫好了,你可以在你的工程中引用這個文件,也可以將其作為一個模塊發布,以便于分享給其他人使用。
總結 and 一個完整的rollup項目的模板rollup在打包JS上是一個十分快捷方便的工具,但和webpack相比,他的生態圈還是不夠強大,對于大型web工程的適應度相對不足
rollup的優點在于方便的配置,天然的ES6模塊支持讓我們可以直接使用import和export語法,在打包JS上,不實現自己的模塊機制,而是使用目前常見的模塊規范有助于其他工具(例如requirejs)來引用打包文件;tree-shaking的特性也有助于減少代碼量,因此我認為rollup比起構建應用工程項目,更適合用來構建一個JS庫或node模塊
我將上面介紹的插件集合到一起,添加了測試的支持,制作了一個較為完整的rollup工程模板。放在rollup-project-template目錄下,需要的同學可以自?。阋部梢栽黾踊騽h除任意你需要的模塊,來組建屬于你自己的rollup項目模板)
參考資料rollup官方wiki
rollup插件合集
如何通過 Rollup.js 打包 JavaScript —— 知乎專欄
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87245.html
摘要:我寫過一些開源項目,在開源方面有一些經驗,最近開到了阮老師的微博,深有感觸,現在一個開源項目涉及的東西確實挺多的,特別是對于新手來說非常不友好最近我寫了一個,旨在從多方面快速幫大家搭建一個標準的庫,本文將已為例,介紹寫一個開源庫的知識 我寫過一些開源項目,在開源方面有一些經驗,最近開到了阮老師的微博,深有感觸,現在一個開源項目涉及的東西確實挺多的,特別是對于新手來說非常不友好 show...
摘要:通過這個教程學習如何使用打包工具配合來取代或處理樣式文件。使用這個命令安裝插件更新。如果你沒有項目的副本,你可以通過這條命令克隆在結束這個狀態下的項目為添加監聽插件。在代碼塊內,添加如下內容簡單起見我省略了文件的大部分內容 通過這個教程學習如何使用JavaScript打包工具Rollup配合PostCSS來取代Grunt或Gulp處理樣式文件。 上一篇文章中,我們完成了使用Rollup...
摘要:所以,打包工具就出現了,它可以幫助做這些繁瑣的工作。打包工具介紹僅介紹款主流的打包工具,,,,以發布時間為順序。它定位是模塊打包器,而屬于構建工具。而且在其他的打包工具在處理非網頁文件比如等基本還是需要借助它來實現。 本文當時寫在本地,發現換電腦很不是方便,在這里記錄下。 前端的打包工具 打包工具可以更好的管理html,css,javascript,使用可以錦上添花,不使用也沒關系...
摘要:平時有使用過和開發的同學,應該能體會到模塊化開發的好處。原因很簡單打包出來的使用了關鍵字,而小程序內部并支持。是一個模塊打包器,可以將小塊代碼編譯成大塊復雜的代碼,例如或應用程序。官網的這段介紹,正說明了就是用來打包的。 博客地址 最近有個需求,需要為小程序寫一個SDK,監控小程序的后臺接口調用和頁面報錯(類似fundebug) 聽起來高大上的SDK,其實就是一個JS文件,類似平時開發...
摘要:既可以通過一個配置文件使用命令行接口來調用,也可以他自己的使用。使用最簡單的方法就是通過命令行接口。命令縮寫會以監視模式運行。這時運行下將不會有錯誤拋出,包含導入的組件。 介紹 概覽 rollup是一個js打包器,用來將很細碎的js編譯打包成大的復雜的東西,像是一個庫或者一個應用。其使用了ES6自帶的新標準來格式化和打包js代碼,而不是原先的Commonjs或者AMD這類解決方案。ES...
閱讀 3384·2023-04-25 20:37
閱讀 3142·2021-09-07 09:59
閱讀 1665·2019-08-29 12:43
閱讀 1185·2019-08-28 18:27
閱讀 479·2019-08-26 13:50
閱讀 2025·2019-08-26 10:33
閱讀 3591·2019-08-23 18:39
閱讀 2390·2019-08-23 18:09