摘要:通過這個(gè)教程學(xué)習(xí)如何使用打包工具配合來取代或處理樣式文件。使用這個(gè)命令安裝插件更新。如果你沒有項(xiàng)目的副本,你可以通過這條命令克隆在結(jié)束這個(gè)狀態(tài)下的項(xiàng)目為添加監(jiān)聽插件。在代碼塊內(nèi),添加如下內(nèi)容簡(jiǎn)單起見我省略了文件的大部分內(nèi)容
通過這個(gè)教程學(xué)習(xí)如何使用JavaScript打包工具Rollup配合PostCSS來取代Grunt或Gulp處理樣式文件。
上一篇文章中,我們完成了使用Rollup打包前端JavaScript入門。
這篇文章包含Part II和Part III。
Part II會(huì)繼續(xù)在上次的項(xiàng)目中進(jìn)行,為Rollup添加處理樣式的能力,使用PostCSS進(jìn)行一些轉(zhuǎn)換,讓我們能使用更簡(jiǎn)單的變量寫法和嵌套規(guī)則等語(yǔ)法糖。
然后完成Part III,圓滿結(jié)束。第三部分將為項(xiàng)目添加文件監(jiān)聽和LiveReload,這樣每當(dāng)文件變化時(shí)就不用再手動(dòng)地打包bundle文件了。
準(zhǔn)備工作我們會(huì)在上周的項(xiàng)目基礎(chǔ)上繼續(xù)進(jìn)行,因此如果你還沒有看上一節(jié),推薦你先看一下。
Part II:如何在下一代應(yīng)用中使用Rollup.js: PostCSSNOTE: 如果你沒有項(xiàng)目的副本,你可以通過這條命令克隆在Part I結(jié)束這個(gè)狀態(tài)下的項(xiàng)目:git clone -b part-2-starter --single-branch https://github.com/jlengstorf/learn-rollup.git
你可以輕松地處理CSS并注入到文檔的head中,這取決于你的項(xiàng)目如何配置,也是Rollup另一個(gè)優(yōu)點(diǎn)。
另外,所有的構(gòu)建過程都在一個(gè)地方,降低了開發(fā)流程的復(fù)雜度 - 這對(duì)我們很有幫助,尤其是在團(tuán)隊(duì)協(xié)作時(shí)。
不過糟糕的是,我們使得樣式依賴JavaScript,并且在無樣式HTML在樣式注入前會(huì)產(chǎn)生一個(gè)短暫的閃爍。這對(duì)有些項(xiàng)目來說是無法接受的,并且應(yīng)該通過像使用PostCSS提取等方式解決。
既然這篇文章是關(guān)于Rollup的,那么:來吧。讓我們使用Rollup!
STEP 0: 在main.js中加載樣式。如果你之前從來沒用過構(gòu)建工具,可能感覺這樣有點(diǎn)怪,但請(qǐng)跟著我繼續(xù)。為了在文檔中使用樣式,我們不會(huì)像平常那樣使用標(biāo)簽,取而代之,我們將在main.min.js中使用import語(yǔ)句。
現(xiàn)在在src/scripts/main.js開頭加載樣式:
+ // Import styles (automatically injected into ). + import "../styles/main.css"; // Import a couple modules for testing. import { sayHelloTo } from "./modules/mod1"; import addArray from "./modules/mod2"; // Import a logger for easier debugging. import debug from "debug"; const log = debug("app:log"); // The logger should only be disabled if we’re not in production. if (ENV !== "production") { // Enable the logger. debug.enable("*"); log("Logging is enabled!"); } else { debug.disable(); } // Run some functions from our imported modules. const result1 = sayHelloTo("Jason"); const result2 = addArray([1, 2, 3, 4]); // Print the results on the page. const printTarget = document.getElementsByClassName("debug__output")[0]; printTarget.innerText = `sayHelloTo("Jason") => ${result1} `; printTarget.innerText += `addArray([1, 2, 3, 4]) => ${result2}`;STEP 1: 安裝PostCSS Rollup插件。
首先需要Rollup PostCSS插件,使用如下命令安裝:
STEP 2: 更新rollup.config.js.npm install --save-dev rollup-plugin-postcss
然后,添加插件到rollup.config.js:
// Rollup plugins import babel from "rollup-plugin-babel"; import eslint from "rollup-plugin-eslint"; import resolve from "rollup-plugin-node-resolve"; import commonjs from "rollup-plugin-commonjs"; import replace from "rollup-plugin-replace"; import uglify from "rollup-plugin-uglify"; + import postcss from "rollup-plugin-postcss"; export default { entry: "src/scripts/main.js", dest: "build/js/main.min.js", format: "iife", sourceMap: "inline", plugins: [ + postcss({ + extensions: [ ".css" ], + }), resolve({ jsnext: true, main: true, browser: true, }), commonjs(), eslint({ exclude: [ "src/styles/**", ] }), babel({ exclude: "node_modules/**", }), replace({ ENV: JSON.stringify(process.env.NODE_ENV || "development"), }), (process.env.NODE_ENV === "production" && uglify()), ], };看一下生成的bundle。
現(xiàn)在我們已經(jīng)能夠處理樣式了,可以看一下新生成的bundle,看看它是如何工作的。
運(yùn)行./node_modules/.bin/rollup -c,然后看一下生成的build/js/main.min.js,在文件開頭幾行,可以看到一個(gè)名叫__$styleInject()的新函數(shù):
function __$styleInject(css) { css = css || ""; var head = document.head || document.getElementsByTagName("head")[0]; var style = document.createElement("style"); style.type = "text/css"; if (style.styleSheet){ style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } head.appendChild(style); } __$styleInject("/* Styles omitted for brevity... */");
簡(jiǎn)單地說,這個(gè)函數(shù)創(chuàng)建了一個(gè)元素并設(shè)置樣式,然后添加到文檔的標(biāo)簽中。
就在這個(gè)函數(shù)聲明的下方,可以看到通過傳入樣式調(diào)用該函數(shù),通過PostCSS輸出。很酷,不是嗎?
但現(xiàn)在這些樣式并沒有真正地被處理;PostCSS只是直接地傳輸了我們的樣式。讓我們添加一些需要的PostCSS插件,使得樣式能在目標(biāo)瀏覽器上工作。
STEP 3: 安裝必要的PostCSS插件。我愛PostCSS。我已經(jīng)放棄LESS陣營(yíng)了,當(dāng)所有人都拋棄LESS時(shí),我發(fā)現(xiàn)自己或多或少被影響加入了Sass陣營(yíng),后來PostCSS出現(xiàn)了我就非常開心地去學(xué)。
我喜歡它是因?yàn)樗峁┝瞬糠衷贚ESS和Sass中我喜歡的功能 - 嵌套,簡(jiǎn)單的變量 - 而且沒有完全放棄LESS和Sass中我認(rèn)為誘人也危險(xiǎn)的功能,比如邏輯運(yùn)算。
我喜歡它的插件模式,而不是一個(gè)叫做“PostCSS”的語(yǔ)言。我們可以只選擇真正需要的特性 - 更重要的是,我們可以移除不想要的特性。
因此在我們的項(xiàng)目里,只會(huì)使用到四個(gè)插件 - 兩個(gè)是語(yǔ)法糖,一個(gè)用來在兼容舊瀏覽器的新CSS特性,一個(gè)用來壓縮,減少生成的樣式文件大小。
postcss-simple-vars — 可以使用Sass風(fēng)格的變量(e.g. $myColor: #fff;,color: $myColor;)而不是冗長(zhǎng)的CSS語(yǔ)法(e.g. :root {--myColor: #fff},color: var(--myColor))。這樣更簡(jiǎn)潔;我更喜歡較短的語(yǔ)法。
postcss-nested — 允許使用嵌套規(guī)則。實(shí)際上我不用它寫嵌套規(guī)則;我用它簡(jiǎn)化BEM友好的選擇器的寫法并且劃分我的區(qū)塊,元素和修飾到單個(gè)CSS塊。
postcss-cssnext — 這個(gè)插件集使得大多數(shù)現(xiàn)代CSS語(yǔ)法(通過最新的CSS標(biāo)準(zhǔn))可用,編譯后甚至可以在不支持新特性的舊瀏覽器中工作。
cssnano — 壓縮,減小輸出CSS文件大小。相當(dāng)于JavaScript中對(duì)應(yīng)的UglifyJS。
使用這個(gè)命令安裝插件:
STEP 4: 更新rollup.config.js。npm install --save-dev postcss-simple-vars postcss-nested postcss-cssnext cssnano
接下來,在rollup.config.js引入PostCSS插件,在配置對(duì)象的plugins屬性上添加一個(gè)postcss。
// Rollup plugins import babel from "rollup-plugin-babel"; import eslint from "rollup-plugin-eslint"; import resolve from "rollup-plugin-node-resolve"; import commonjs from "rollup-plugin-commonjs"; import replace from "rollup-plugin-replace"; import uglify from "rollup-plugin-uglify"; import postcss from "rollup-plugin-postcss"; + // PostCSS plugins + import simplevars from "postcss-simple-vars"; + import nested from "postcss-nested"; + import cssnext from "postcss-cssnext"; + import cssnano from "cssnano"; export default { entry: "src/scripts/main.js", dest: "build/js/main.min.js", format: "iife", sourceMap: "inline", plugins: [ postcss({ + plugins: [ + simplevars(), + nested(), + cssnext({ warnForDuplicates: false, }), + cssnano(), + ], extensions: [ ".css" ], }), resolve({ jsnext: true, main: true, browser: true, }), commonjs(), eslint({ exclude: [ "src/styles/**", ] }), babel({ exclude: "node_modules/**", }), replace({ ENV: JSON.stringify(process.env.NODE_ENV || "development"), }), (process.env.NODE_ENV === "production" && uglify()), ], };
檢查中的輸出內(nèi)容。NOTE: 在cssnext()中配置了{ warnForDuplicates: false }是因?yàn)樗?b>cssnano()都使用了Autoprefixer,會(huì)導(dǎo)致一個(gè)警告。不用計(jì)較配置, 我們只需要知道它被執(zhí)行了兩次(在這個(gè)例子中沒什么壞處)并且取消了警告。
插件安裝完后,重新構(gòu)建bundle文件。(./node_modules/.bin/rollup -c),然后在瀏覽器中打開build/index.html。可以看到頁(yè)面是有樣式的,如果我們審查元素可以發(fā)現(xiàn)樣式被注入到頁(yè)面頭部,壓縮簡(jiǎn)化,添加所有瀏覽器前綴和其它我們預(yù)期的優(yōu)點(diǎn):
樣式被PostCSS處理并通過Rpllup注入
太棒了!我們現(xiàn)在擁有十分可靠的構(gòu)建流程:JavaScript會(huì)被打包,無用的代碼會(huì)被移除,輸出文件是經(jīng)過壓縮精簡(jiǎn)的,樣式時(shí)通過PostCSS處理后注入到文檔頭部的。
然而,最后仍然存在一個(gè)痛點(diǎn),每當(dāng)我們做了一些修改后都不得不手動(dòng)地重新構(gòu)建。因此在下個(gè)部分,我們讓Rollup監(jiān)聽文件的變化,每當(dāng)有文件改變時(shí)就讓瀏覽器自動(dòng)重新載入文件。
Part III: 如何在下一代應(yīng)用中使用Rollup.js:實(shí)時(shí)加載現(xiàn)在,我們的項(xiàng)目已經(jīng)可以打包JavaScript和樣式文件了,但仍然是一個(gè)手動(dòng)的過程。而且由于過程的每個(gè)步驟都是手動(dòng)的,相比自動(dòng)化流程失敗風(fēng)險(xiǎn)更高 - 因?yàn)槊看涡薷奈募蠖紙?zhí)行./node_modules/.bin/rollup -c太痛苦了 - 我們希望自動(dòng)重新構(gòu)建bundle。
STEP 0: 為Rollup添加監(jiān)聽插件。NOTE: 如果你沒有項(xiàng)目的副本,你可以通過這條命令克隆在Part II結(jié)束這個(gè)狀態(tài)下的項(xiàng)目:: git clone -b part-3-starter --single-branch https://github.com/jlengstorf/learn-rollup.git
基于Node.js的監(jiān)聽器是很常見的開發(fā)工具。如果你之前使用過webpack,Grunt,Gulp或者其他構(gòu)建工具會(huì)很熟悉。
監(jiān)聽器是在一個(gè)項(xiàng)目中運(yùn)行的進(jìn)程,當(dāng)你修改了它監(jiān)聽的文件夾內(nèi)的任意文件時(shí)就會(huì)觸發(fā)一個(gè)動(dòng)作。對(duì)于構(gòu)建工具而言,通常這個(gè)動(dòng)作是觸發(fā)重新構(gòu)建。
在我們的項(xiàng)目中,我們需要監(jiān)聽src目錄下的任何文件,并且探測(cè)到文件變化后希望Rollup重新打包。
為了達(dá)到目的,我們使用rollup-watch插件,它和前面的其它Rollup插件有些不同 - but more on that in a bit。讓我們先安裝插件:
npm install --save-dev rollup-watchSTEP 1: 傳入--watch標(biāo)識(shí)運(yùn)行Rollup。
rollup-watch與其他插件的不同就是使用這個(gè)插件不需要對(duì)rollup.config.js做任何修改。
取而代之的是,在終端命令中添加一個(gè)標(biāo)識(shí):
# Run Rollup with the watch plugin enabled ./node_modules/.bin/rollup -c --watch
運(yùn)行完后,可以發(fā)現(xiàn)控制臺(tái)的輸出和之前有點(diǎn)不同:
$ ./node_modules/.bin/rollup -c --watch checking rollup-watch version... bundling... bundled in 949ms. Watching for changes...
這個(gè)進(jìn)程依然保持活動(dòng)狀態(tài),正在監(jiān)聽變化。
如果我們?cè)?b>src/main.js做點(diǎn)小變化 - 比如加一條注釋 - 在我們保存修改的那一刻新的bundle文件就生成了。
監(jiān)聽程序執(zhí)行時(shí),變化會(huì)觸發(fā)重新構(gòu)建。LINTER會(huì)立刻捕獲錯(cuò)誤。很優(yōu)雅,不是嗎?
這為我們?cè)陂_發(fā)過程中節(jié)省了大量時(shí)間,不過還可以更進(jìn)一步?,F(xiàn)在我們?nèi)匀恍枰謩?dòng)刷新瀏覽器來獲取更新后的bundle - 添加一個(gè)工具,在bundle更新后自動(dòng)刷新瀏覽器。
STEP 2: 安裝Liveload自動(dòng)刷新瀏覽器。TIP: 在終端窗口輸入control + C結(jié)束監(jiān)聽進(jìn)程。
LiveReload是加速開發(fā)的常用工具。它是一個(gè)跑在后臺(tái)的進(jìn)程,每當(dāng)有它監(jiān)聽的文件變化時(shí),他就會(huì)通知瀏覽器刷新。
先下載插件:
npm install --save-dev livereloadSTEP 3: 注入livereload腳本。
In src/main.js, add the following:
在LiveReload工作前,需要向頁(yè)面中注入一段腳本用于和LiveReload的服務(wù)器建立連接。
不過只有開發(fā)環(huán)境下有這個(gè)需求,利用環(huán)境變量的能力判斷只有在非生產(chǎn)環(huán)境下才注入腳本。
在src/main.js中添加下面代碼:
// Import styles (automatically injected into ). import "../styles/main.css"; // Import a couple modules for testing. import { sayHelloTo } from "./modules/mod1"; import addArray from "./modules/mod2"; // Import a logger for easier debugging. import debug from "debug"; const log = debug("app:log"); // The logger should only be disabled if we’re not in production. if (ENV !== "production") { // Enable the logger. debug.enable("*"); log("Logging is enabled!"); + // Enable LiveReload + document.write( + "