摘要:通過這個教程學習如何使用打包工具配合來取代或處理樣式文件。使用這個命令安裝插件更新。如果你沒有項目的副本,你可以通過這條命令克隆在結束這個狀態(tài)下的項目為添加監(jiān)聽插件。在代碼塊內(nèi),添加如下內(nèi)容簡單起見我省略了文件的大部分內(nèi)容
通過這個教程學習如何使用JavaScript打包工具Rollup配合PostCSS來取代Grunt或Gulp處理樣式文件。
上一篇文章中,我們完成了使用Rollup打包前端JavaScript入門。
這篇文章包含Part II和Part III。
Part II會繼續(xù)在上次的項目中進行,為Rollup添加處理樣式的能力,使用PostCSS進行一些轉換,讓我們能使用更簡單的變量寫法和嵌套規(guī)則等語法糖。
然后完成Part III,圓滿結束。第三部分將為項目添加文件監(jiān)聽和LiveReload,這樣每當文件變化時就不用再手動地打包bundle文件了。
準備工作我們會在上周的項目基礎上繼續(xù)進行,因此如果你還沒有看上一節(jié),推薦你先看一下。
Part II:如何在下一代應用中使用Rollup.js: PostCSSNOTE: 如果你沒有項目的副本,你可以通過這條命令克隆在Part I結束這個狀態(tài)下的項目:git clone -b part-2-starter --single-branch https://github.com/jlengstorf/learn-rollup.git
你可以輕松地處理CSS并注入到文檔的head中,這取決于你的項目如何配置,也是Rollup另一個優(yōu)點。
另外,所有的構建過程都在一個地方,降低了開發(fā)流程的復雜度 - 這對我們很有幫助,尤其是在團隊協(xié)作時。
不過糟糕的是,我們使得樣式依賴JavaScript,并且在無樣式HTML在樣式注入前會產(chǎn)生一個短暫的閃爍。這對有些項目來說是無法接受的,并且應該通過像使用PostCSS提取等方式解決。
既然這篇文章是關于Rollup的,那么:來吧。讓我們使用Rollup!
STEP 0: 在main.js中加載樣式。如果你之前從來沒用過構建工具,可能感覺這樣有點怪,但請跟著我繼續(xù)。為了在文檔中使用樣式,我們不會像平常那樣使用標簽,取而代之,我們將在main.min.js中使用import語句。
現(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,看看它是如何工作的。
運行./node_modules/.bin/rollup -c,然后看一下生成的build/js/main.min.js,在文件開頭幾行,可以看到一個名叫__$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... */");
簡單地說,這個函數(shù)創(chuàng)建了一個元素并設置樣式,然后添加到文檔的標簽中。
就在這個函數(shù)聲明的下方,可以看到通過傳入樣式調用該函數(shù),通過PostCSS輸出。很酷,不是嗎?
但現(xiàn)在這些樣式并沒有真正地被處理;PostCSS只是直接地傳輸了我們的樣式。讓我們添加一些需要的PostCSS插件,使得樣式能在目標瀏覽器上工作。
STEP 3: 安裝必要的PostCSS插件。我愛PostCSS。我已經(jīng)放棄LESS陣營了,當所有人都拋棄LESS時,我發(fā)現(xiàn)自己或多或少被影響加入了Sass陣營,后來PostCSS出現(xiàn)了我就非常開心地去學。
我喜歡它是因為它提供了部分在LESS和Sass中我喜歡的功能 - 嵌套,簡單的變量 - 而且沒有完全放棄LESS和Sass中我認為誘人也危險的功能,比如邏輯運算。
我喜歡它的插件模式,而不是一個叫做“PostCSS”的語言。我們可以只選擇真正需要的特性 - 更重要的是,我們可以移除不想要的特性。
因此在我們的項目里,只會使用到四個插件 - 兩個是語法糖,一個用來在兼容舊瀏覽器的新CSS特性,一個用來壓縮,減少生成的樣式文件大小。
postcss-simple-vars — 可以使用Sass風格的變量(e.g. $myColor: #fff;,color: $myColor;)而不是冗長的CSS語法(e.g. :root {--myColor: #fff},color: var(--myColor))。這樣更簡潔;我更喜歡較短的語法。
postcss-nested — 允許使用嵌套規(guī)則。實際上我不用它寫嵌套規(guī)則;我用它簡化BEM友好的選擇器的寫法并且劃分我的區(qū)塊,元素和修飾到單個CSS塊。
postcss-cssnext — 這個插件集使得大多數(shù)現(xiàn)代CSS語法(通過最新的CSS標準)可用,編譯后甚至可以在不支持新特性的舊瀏覽器中工作。
cssnano — 壓縮,減小輸出CSS文件大小。相當于JavaScript中對應的UglifyJS。
使用這個命令安裝插件:
STEP 4: 更新rollup.config.js。npm install --save-dev postcss-simple-vars postcss-nested postcss-cssnext cssnano
接下來,在rollup.config.js引入PostCSS插件,在配置對象的plugins屬性上添加一個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 }是因為它和cssnano()都使用了Autoprefixer,會導致一個警告。不用計較配置, 我們只需要知道它被執(zhí)行了兩次(在這個例子中沒什么壞處)并且取消了警告。
插件安裝完后,重新構建bundle文件。(./node_modules/.bin/rollup -c),然后在瀏覽器中打開build/index.html。可以看到頁面是有樣式的,如果我們審查元素可以發(fā)現(xiàn)樣式被注入到頁面頭部,壓縮簡化,添加所有瀏覽器前綴和其它我們預期的優(yōu)點:
樣式被PostCSS處理并通過Rpllup注入
太棒了!我們現(xiàn)在擁有十分可靠的構建流程:JavaScript會被打包,無用的代碼會被移除,輸出文件是經(jīng)過壓縮精簡的,樣式時通過PostCSS處理后注入到文檔頭部的。
然而,最后仍然存在一個痛點,每當我們做了一些修改后都不得不手動地重新構建。因此在下個部分,我們讓Rollup監(jiān)聽文件的變化,每當有文件改變時就讓瀏覽器自動重新載入文件。
Part III: 如何在下一代應用中使用Rollup.js:實時加載現(xiàn)在,我們的項目已經(jīng)可以打包JavaScript和樣式文件了,但仍然是一個手動的過程。而且由于過程的每個步驟都是手動的,相比自動化流程失敗風險更高 - 因為每次修改文件后都執(zhí)行./node_modules/.bin/rollup -c太痛苦了 - 我們希望自動重新構建bundle。
STEP 0: 為Rollup添加監(jiān)聽插件。NOTE: 如果你沒有項目的副本,你可以通過這條命令克隆在Part II結束這個狀態(tài)下的項目:: git clone -b part-3-starter --single-branch https://github.com/jlengstorf/learn-rollup.git
基于Node.js的監(jiān)聽器是很常見的開發(fā)工具。如果你之前使用過webpack,Grunt,Gulp或者其他構建工具會很熟悉。
監(jiān)聽器是在一個項目中運行的進程,當你修改了它監(jiān)聽的文件夾內(nèi)的任意文件時就會觸發(fā)一個動作。對于構建工具而言,通常這個動作是觸發(fā)重新構建。
在我們的項目中,我們需要監(jiān)聽src目錄下的任何文件,并且探測到文件變化后希望Rollup重新打包。
為了達到目的,我們使用rollup-watch插件,它和前面的其它Rollup插件有些不同 - but more on that in a bit。讓我們先安裝插件:
npm install --save-dev rollup-watchSTEP 1: 傳入--watch標識運行Rollup。
rollup-watch與其他插件的不同就是使用這個插件不需要對rollup.config.js做任何修改。
取而代之的是,在終端命令中添加一個標識:
# Run Rollup with the watch plugin enabled ./node_modules/.bin/rollup -c --watch
運行完后,可以發(fā)現(xiàn)控制臺的輸出和之前有點不同:
$ ./node_modules/.bin/rollup -c --watch checking rollup-watch version... bundling... bundled in 949ms. Watching for changes...
這個進程依然保持活動狀態(tài),正在監(jiān)聽變化。
如果我們在src/main.js做點小變化 - 比如加一條注釋 - 在我們保存修改的那一刻新的bundle文件就生成了。
監(jiān)聽程序執(zhí)行時,變化會觸發(fā)重新構建。LINTER會立刻捕獲錯誤。很優(yōu)雅,不是嗎?
這為我們在開發(fā)過程中節(jié)省了大量時間,不過還可以更進一步。現(xiàn)在我們?nèi)匀恍枰謩铀⑿聻g覽器來獲取更新后的bundle - 添加一個工具,在bundle更新后自動刷新瀏覽器。
STEP 2: 安裝Liveload自動刷新瀏覽器。TIP: 在終端窗口輸入control + C結束監(jiān)聽進程。
LiveReload是加速開發(fā)的常用工具。它是一個跑在后臺的進程,每當有它監(jiān)聽的文件變化時,他就會通知瀏覽器刷新。
先下載插件:
npm install --save-dev livereloadSTEP 3: 注入livereload腳本。
In src/main.js, add the following:
在LiveReload工作前,需要向頁面中注入一段腳本用于和LiveReload的服務器建立連接。
不過只有開發(fā)環(huán)境下有這個需求,利用環(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( + "