[譯]教程:如何使用Rollup打包樣式文件並添加LiveReload

經過這個教程學習如何使用JavaScript打包工具Rollup配合PostCSS來取代Grunt或Gulp處理樣式文件。css

上一篇文章中,咱們完成了使用Rollup打包前端JavaScript入門html

這篇文章包含Part IIPart III前端

Part II會繼續在上次的項目中進行,爲Rollup添加處理樣式的能力,使用PostCSS進行一些轉換,讓咱們能使用更簡單的變量寫法和嵌套規則等語法糖。node

而後完成Part III,圓滿結束。第三部分將爲項目添加文件監聽和LiveReload,這樣每當文件變化時就不用再手動地打包bundle文件了。react

準備工做

咱們會在上週的項目基礎上繼續進行,所以若是你尚未看上一節,推薦你先看一下webpack

NOTE: 若是你沒有項目的副本,你能夠經過這條命令克隆在Part I結束這個狀態下的項目:git clone -b part-2-starter --single-branch https://github.com/jlengstorf/learn-rollup.gitgit

Part II:如何在下一代應用中使用Rollup.js: PostCSS

你能夠輕鬆地處理CSS並注入到文檔的head中,這取決於你的項目如何配置,也是Rollup另外一個優勢。github

另外,全部的構建過程都在一個地方,下降了開發流程的複雜度 - 這對咱們頗有幫助,尤爲是在團隊協做時。web

不過糟糕的是,咱們使得樣式依賴JavaScript,而且在無樣式HTML在樣式注入前會產生一個短暫的閃爍。這對有些項目來講是沒法接受的,而且應該經過像使用PostCSS提取等方式解決。npm

既然這篇文章是關於Rollup的,那麼:來吧。讓咱們使用Rollup!

STEP 0: 在main.js中加載樣式。

若是你以前歷來沒用過構建工具,可能感受這樣有點怪,但請跟着我繼續。爲了在文檔中使用樣式,咱們不會像日常那樣使用<link>標籤,取而代之,咱們將在main.min.js中使用import語句。

如今在src/scripts/main.js開頭加載樣式:

+ // Import styles (automatically injected into <head>).
+ 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}\n\n`;
  printTarget.innerText += `addArray([1, 2, 3, 4]) => ${result2}`;

STEP 1: 安裝PostCSS Rollup插件。

首先須要Rollup PostCSS插件,使用以下命令安裝:

npm install --save-dev rollup-plugin-postcss

STEP 2: 更新rollup.config.js.

而後,添加插件到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。

如今咱們已經可以處理樣式了,能夠看一下新生成的bundle,看看它是如何工做的。

運行./node_modules/.bin/rollup -c,而後看一下生成的build/js/main.min.js,在文件開頭幾行,能夠看到一個名叫__$styleInject()的新函數:

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... */");

簡單地說,這個函數建立了一個<style>元素並設置樣式,而後添加到文檔的<head>標籤中。

就在這個函數聲明的下方,能夠看到經過傳入樣式調用該函數,經過PostCSS輸出。很酷,不是嗎?

但如今這些樣式並無真正地被處理;PostCSS只是直接地傳輸了咱們的樣式。讓咱們添加一些須要的PostCSS插件,使得樣式能在目標瀏覽器上工做。

STEP 3: 安裝必要的PostCSS插件。

我愛PostCSS。我已經放棄LESS陣營了,當全部人都拋棄LESS時,我發現本身或多或少被影響加入了Sass陣營,後來PostCSS出現了我就很是開心地去學。

我喜歡它是由於它提供了部分在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 — 容許使用嵌套規則。實際上我不用它寫嵌套規則;我用它簡化BEM友好的選擇器的寫法而且劃分個人區塊,元素和修飾到單個CSS塊。

  • postcss-cssnext — 這個插件集使得大多數現代CSS語法(經過最新的CSS標準)可用,編譯後甚至能夠在不支持新特性的舊瀏覽器中工做。

  • cssnano — 壓縮,減少輸出CSS文件大小。至關於JavaScript中對應的UglifyJS。

使用這個命令安裝插件:

npm install --save-dev postcss-simple-vars postcss-nested postcss-cssnext cssnano

STEP 4: 更新rollup.config.js

接下來,在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()),
    ],
  };

NOTE: 在cssnext()中配置了{ warnForDuplicates: false }是由於它和cssnano()都使用了Autoprefixer,會致使一個警告。不用計較配置, 咱們只須要知道它被執行了兩次(在這個例子中沒什麼壞處)而且取消了警告。

檢查<head>中的輸出內容。

插件安裝完後,從新構建bundle文件。(./node_modules/.bin/rollup -c),而後在瀏覽器中打開build/index.html。能夠看到頁面是有樣式的,若是咱們審查元素能夠發現樣式被注入到頁面頭部,壓縮簡化,添加全部瀏覽器前綴和其它咱們預期的優勢:

樣式被PostCSS處理並經過Rpllup注入

太棒了!咱們如今擁有十分可靠的構建流程:JavaScript會被打包,無用的代碼會被移除,輸出文件是通過壓縮精簡的,樣式時經過PostCSS處理後注入到文檔頭部的。

然而,最後仍然存在一個痛點,每當咱們作了一些修改後都不得不手動地從新構建。所以在下個部分,咱們讓Rollup監聽文件的變化,每當有文件改變時就讓瀏覽器自動從新載入文件。

Part III: 如何在下一代應用中使用Rollup.js:實時加載

如今,咱們的項目已經能夠打包JavaScript和樣式文件了,但仍然是一個手動的過程。並且因爲過程的每一個步驟都是手動的,相比自動化流程失敗風險更高 - 由於每次修改文件後都執行./node_modules/.bin/rollup -c太痛苦了 - 咱們但願自動從新構建bundle。

NOTE: 若是你沒有項目的副本,你能夠經過這條命令克隆在Part II結束這個狀態下的項目:: git clone -b part-3-starter --single-branch https://github.com/jlengstorf/learn-rollup.git

STEP 0: 爲Rollup添加監聽插件。

基於Node.js的監聽器是很常見的開發工具。若是你以前使用過webpack,Grunt,Gulp或者其餘構建工具會很熟悉。

監聽器是在一個項目中運行的進程,當你修改了它監聽的文件夾內的任意文件時就會觸發一個動做。對於構建工具而言,一般這個動做是觸發從新構建。

在咱們的項目中,咱們須要監聽src目錄下的任何文件,而且探測到文件變化後但願Rollup從新打包。

爲了達到目的,咱們使用rollup-watch插件,它和前面的其它Rollup插件有些不一樣 - but more on that in a bit。讓咱們先安裝插件:

npm install --save-dev rollup-watch

STEP 1: 傳入--watch標識運行Rollup。

rollup-watch與其餘插件的不一樣就是使用這個插件不須要對rollup.config.js作任何修改。

取而代之的是,在終端命令中添加一個標識:

# Run Rollup with the watch plugin enabled
./node_modules/.bin/rollup -c --watch

運行完後,能夠發現控制檯的輸出和以前有點不一樣:

$ ./node_modules/.bin/rollup -c --watch
checking rollup-watch version...
bundling...
bundled in 949ms. Watching for changes...

這個進程依然保持活動狀態,正在監聽變化。

若是咱們在src/main.js作點小變化 - 好比加一條註釋 - 在咱們保存修改的那一刻新的bundle文件就生成了。

監聽程序執行時,變化會觸發從新構建。LINTER會馬上捕獲錯誤。很優雅,不是嗎?

這爲咱們在開發過程當中節省了大量時間,不過還能夠更進一步。如今咱們仍然須要手動刷新瀏覽器來獲取更新後的bundle - 添加一個工具,在bundle更新後自動刷新瀏覽器。

TIP: 在終端窗口輸入control + C結束監聽進程。

STEP 2: 安裝Liveload自動刷新瀏覽器。

LiveReload是加速開發的經常使用工具。它是一個跑在後臺的進程,每當有它監聽的文件變化時,他就會通知瀏覽器刷新。

先下載插件:

npm install --save-dev livereload

STEP 3: 注入livereload腳本。

In src/main.js, add the following:

在LiveReload工做前,須要向頁面中注入一段腳本用於和LiveReload的服務器創建鏈接。

不過只有開發環境下有這個需求,利用環境變量的能力判斷只有在非生產環境下才注入腳本。

src/main.js中添加下面代碼:

// Import styles (automatically injected into <head>).
  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(
+     '<script src="http://' + (location.host || 'localhost').split(':')[0] +
+     ':35729/livereload.js?snipver=1"></' + 'script>'
+   );
  } 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}\n\n`;
  printTarget.innerText += `addArray([1, 2, 3, 4]) => ${result2}`;

作一些修改而後保存,如今咱們試試看。

NOTE: Livereload是如何工做的並不重要,簡單的解釋就是命令行進程監聽文件變化,而後經過websockets向客戶端腳本發送消息觸發重加載。

STEP 4: 運行Livereload。

LiveReload安裝好而且腳本注入到文檔中後,咱們能夠運行它去監聽build目錄:

./node_modules/.bin/livereload 'build/'

NOTE: 監聽build/是由於咱們只須要在新的bundle產生時才從新構建。

輸出結果像下面這樣:

$ ./node_modules/.bin/livereload 'build/'
Starting LiveReload v0.5.0 for /Users/jlengstorf/dev/code.lengstorf.com/projects/learn-rollup/build on port 35729.

若是咱們用瀏覽器打開build/index.html - 務必在開啓LiveReload後再刷新頁面,確保socket鏈接處於激活狀態 - 能夠發現build/index.html的變化會讓瀏覽器自動從新加載:

文件變化觸發瀏覽器從新加載。

很是棒,但仍然不完美:如今咱們只能運行Rollup的監聽函數或者LiveReload,除非咱們打開多個終端會話。這可不理想。接下來咱們會選擇一個解決辦法。

STEP 5: 使用package.json腳本簡化啓動過程。

教程至今,咱們都不得不輸入rollup腳本的全路徑,我猜你已經感受到這很蠢了。

所以咱們須要一個能同時運行監放任務和Livereload的工具,先將這兩個rollup命令和LiveReload命令當作腳本寫在package.json中。

打開package.json - 它位於項目根目錄。在這個文件裏能看到以下內容:

{
  "name": "learn-rollup",
  "version": "0.0.0",
  "description": "This is an example project to accompany a tutorial on using Rollup.",
  "main": "build/js/main.min.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/jlengstorf/learn-rollup.git"
  },
  "author": "Jason Lengstorf <jason@lengstorf.com> (@jlengstorf)",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/jlengstorf/learn-rollup/issues"
  },
  "homepage": "https://github.com/jlengstorf/learn-rollup#readme",
  "devDependencies": {
    "babel-preset-es2015-rollup": "^1.2.0",
    "cssnano": "^3.7.4",
    "livereload": "^0.5.0",
    "npm-run-all": "^3.0.0",
    "postcss-cssnext": "^2.7.0",
    "postcss-nested": "^1.0.0",
    "postcss-simple-vars": "^3.0.0",
    "rollup": "^0.34.9",
    "rollup-plugin-babel": "^2.6.1",
    "rollup-plugin-commonjs": "^3.3.1",
    "rollup-plugin-eslint": "^2.0.2",
    "rollup-plugin-node-resolve": "^2.0.0",
    "rollup-plugin-postcss": "^0.1.1",
    "rollup-plugin-replace": "^1.1.1",
    "rollup-plugin-uglify": "^1.0.1",
    "rollup-watch": "^2.5.0"
  },
  "dependencies": {
    "debug": "^2.2.0"
  }
}

看到scripts屬性了嗎?咱們將爲它增長兩個新屬性:

  • 一個運行Rollup打包的腳本(原先手動執行的./node_modules/.bin/rollup -c --watch)

  • 一個啓動LiveReload的腳本(原先手動執行的./node_modules/.bin/livereload 'build/')

package.json加上下面的內容:

{
"name": "learn-rollup",
"version": "0.0.0",
"description": "This is an example project to accompany a tutorial on using Rollup.",
"main": "build/js/main.min.js",
"scripts": {
+     "dev": "rollup -c --watch",
+     "reload": "livereload 'build/'",
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "repository": {
      "type": "git",
      "url": "git+ssh://git@github.com/jlengstorf/learn-rollup.git"
    },
    "author": "Jason Lengstorf <jason@lengstorf.com> (@jlengstorf)",
    "license": "ISC",
    "bugs": {
      "url": "https://github.com/jlengstorf/learn-rollup/issues"
    },
    "homepage": "https://github.com/jlengstorf/learn-rollup#readme",
    "devDependencies": {
      "babel-preset-es2015-rollup": "^1.2.0",
      "cssnano": "^3.7.4",
      "livereload": "^0.5.0",
      "npm-run-all": "^3.0.0",
      "postcss-cssnext": "^2.7.0",
      "postcss-nested": "^1.0.0",
      "postcss-simple-vars": "^3.0.0",
      "rollup": "^0.34.9",
      "rollup-plugin-babel": "^2.6.1",
      "rollup-plugin-commonjs": "^3.3.1",
      "rollup-plugin-eslint": "^2.0.2",
      "rollup-plugin-node-resolve": "^2.0.0",
      "rollup-plugin-postcss": "^0.1.1",
      "rollup-plugin-replace": "^1.1.1",
      "rollup-plugin-uglify": "^1.0.1",
      "rollup-watch": "^2.5.0"
    },
    "dependencies": {
      "debug": "^2.2.0"
    }
  }

這些腳本讓咱們可使用簡稱來執行選擇的腳本。

使用npm run dev運行Rollup。

使用npm run reload執行LiveReload。

接下來要作的就是讓他們一塊兒運行。

STEP 6: 安裝同時運行watcher和Livereload的工具。

爲了能同時執行Rollup和LiveReload,咱們要使用一個叫作npm-run-all的工具。

這是一個強大的工具,咱們先不討論他的所有功能。咱們如今只使用它並行執行腳本的能力 - 意味着同一個終端會話能夠同時執行兩個任務。

先安裝npm-run-all:

npm install --save-dev npm-run-all

而後咱們要在package.json中再加入一條調用npm-run-all的腳本。在scripts代碼塊內,添加以下內容(簡單起見我省略了文件的大部份內容):

"scripts": {
      "dev": "rollup -c --watch",
      "reload": "livereload 'build/' -d",
+     "watch": "npm-run-all --parallel reload dev",
      "test": "echo \"Error: no test specified\" && exit 1"
    }

保存修改,切換到終端。準備好最後一步!

STEP 7: 執行最後的watch腳本。

就像以前作的同樣。

在終端中運行下面的命令:

npm run watch

而後刷新瀏覽器, 改變一下JS或CSS, 瀏覽器會加載更新後的bundle並自動刷新 - 太奇妙了!

Liveload + 自動構建如同魔法通常。

如今咱們是Rollup專家了。咱們的打包代碼變得更小更快,開發流程也更輕鬆快速。

Further Reading

拓展閱讀

PostCSS

Some discussion about using JS to insert styles, and when/whether it’s appropriate

這篇文章的代碼放在GitHub上。你能夠fork 這個倉庫進行修改或測試,開issue或者報告bug,或者新建pull request進行建議或者修改。

原文連接

相關文章
相關標籤/搜索