首頁  >  文章  >  web前端  >  如何使用Rollup打包JavaScript

如何使用Rollup打包JavaScript

高洛峰
高洛峰原創
2016-11-21 14:39:221553瀏覽

教學:如何使用Rollup打包JavaScript

透過這個系列教學一步一步學習如何使用更小更快的Rollup取代webpack和Browserify打包JavaScript檔案。

這週,我們要使用Rollup來建立我們的第一個項目,Rollup是一個打包JavaScript(和樣式,不過下週才會做)的建構工具。

透過這個教學課程,我們的Rollup將能夠:

合併scripts程式碼,

刪除多餘程式碼,

編譯成對舊瀏覽器友善的程式碼,

支援在瀏覽器中使用Node模組,

使用環境變量,

盡可能的壓縮,減少檔案大小。

Rollup是什麼?

用自己的話說:

Rollup是下一代JavaScript模組打包工具。開發者可以在你的應用程式或函式庫中使用ES2015模組,然後有效率地將它們打包成單一檔案用於瀏覽器和Node.js使用。

和Browserify和webpack很像。

你也可以稱Rollup是一個建置工具,可以和像Grunt和Gulp等一起設定使用。但是,要注意的一點是當你使用Grunt和Gulp來處理像是打包JavaScript這樣的任務時,這些工具的底層還是使用了像Rollup,Browserify或webpack這些東西。

為什麼要關注Rollup?

Rollup最令人興奮的地方,就是能讓打包檔案體積很小。這麼說很難理解,更詳細的解釋:比起其他JavaScript打包工具,Rollup總是能打出更小,更快的包。

因為Rollup是基於ES2015模組,比webpack和Browserify使用的CommonJS模組機制更有效率。這也讓Rollup從模組中刪除無用的程式碼,即tree-shaking變得更容易。

當我們引入擁有大量函數和方法的三方工具或框架時tree-shaking會變得很重要。想想lodash或jQuery,如果我們只使用一個或兩個方法,就會因為載入其餘內容而產生大量無用的開銷。

Browserify和webpack就會包含大量無用的程式碼。但是Rollup不會 - 它只會包括我們真正使用的東西。

更新 (2016-08-22): 澄清一下,Rollup只能對ES模組上進行tree-shaking。 CommonJS模組 - 像lodash和jQuery那樣寫的模組不能進行tree-shaking。然而,tree-shaking並不是Rollup在速度/表現上唯一的優勢。可以看Rich Harris的解釋和Nolan Lawson的補充了解更多。

還有一個大新聞。

注意: 由於Rollup很有效率,webpack 2 也會支援tree-shaking。

Part I: 如何使用Rollup處理並打包JavaScript檔案

為了展示Rollup如何使用,讓我們透過建立一個簡單的專案來走一遍使用Rollup打包JavaScript的過程。

STEP 0: 建立一個包含將被編譯的JavaScript和CSS的專案.

為了開始工作,我們需要一些用來處理的程式碼。這個教學裡,我們將用一個小應用,可從GitHub取得。

目錄結構如下:

learn-rollup/
├── src/
│   ├── scripts/
│   │   ├── modules/
│   │   │   ├── mod1.js
│   │   │   └── mod2.js
│   │   └── main.js
│   └── styles/
│       └── main.css
└── package.json

你可以在終端執行下面的命令下載這個應用,我們將在這個教程中使用它。

# Move to the folder where you keep your dev projects.
cd /path/to/your/projects

# Clone the starter branch of the app from GitHub.
git clone -b step-0 --single-branch https://github.com/jlengstorf/learn-rollup.git

# The files are downloaded to /path/to/your/projects/learn-rollup/

STEP 1: 安裝Rollup並且建立設定檔。

第一步,執行下面的指令安裝Rollup:

npm install --save-dev rollup

然後,在learn-rollup資料夾下新建rollup.config.js。在文件中加入以下內容。

export default {
  entry: 'src/scripts/main.js',
  dest: 'build/js/main.min.js',
  format: 'iife',
  sourceMap: 'inline',
};

說說每個配置項目實際上做了什麼:

entry — 希望Rollup处理的文件路径。大多数应用中,它将是入口文件,初始化所有东西并启动应用。

dest — 编译完的文件需要被存放的路径。

format — Rollup支持多种输出格式。因为我们是要在浏览器中使用,需要使用立即执行函数表达式(IIFE)[注1]

sourceMap — 调试时sourcemap是非常有用的。这个配置项会在生成文件中添加一个sourcemap,让开发更方便。

NOTE: 对于其他的format选项以及你为什么需要他们,看Rollup’s wiki。

测试Rollup配置

当创建好配置文件后,在终端执行下面的命令测试每项配置是否工作:

./node_modules/.bin/rollup -c

在你的项目下会出现一个build目录,包含js子目录,子目录中包含生成的main.min.js文件。

在浏览器中打开build/index.html可以看到打包文件正确生成了。

完成第一步后我们的示例项目的状态。

注意:现在,只有现代浏览器下不会报错。为了能够在不支持ES2015/ES6的老浏览器中运行,我们需要添加一些插件。

看看打包出来的文件

事实上Rollup强大是因为它使用了“tree-shaking”,可以在你引入的模块中删除没有用的代码。举个例子,在src/scripts/modules/mod1.js中的sayGoodbyeTo()函数在我们的应用中并没有使用 - 而且因为它从不会被使用,Rollup不会将它打包到bundle中:

(function () {
'use strict';

/**
 * Says hello.
 * @param  {String} name a name
 * @return {String}      a greeting for `name`
 */
function sayHelloTo( name ) {
  const toSay = `Hello, ${name}!`;

  return toSay;
}

/**
 * Adds all the values in an array.
 * @param  {Array} arr an array of numbers
 * @return {Number}    the sum of all the array values
 */
const addArray = arr => {
  const result = arr.reduce((a, b) => a + b, 0);

  return result;
};

// Import a couple modules for testing.
// 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}`;

}());
//# sourceMappingURL=data:application/json;charset=utf-8;base64,...

其他的构建工具则不是这样的,所以如果我们引入了一个像lodash这样一个很大的库而只是使用其中一两个函数时,我们的包文件会变得非常大。

比如使用webpack的话,sayGoodbyeTo()也会打包进去,产生的打包文件比Rollup生成的大了两倍多。

STEP 2: 配置babel支持JavaScript新特性。

现在我们已经得到能在现代浏览器中运行的包文件了,但是在一些旧版本浏览器中就会崩溃 - 这并不理想。

幸运的是,Babel已发布了。这个项目编译JavaScript新特性(ES6/ES2015等等)到ES5, 差不多在今天的任何浏览器上都能运行。

如果你还没用过Babel,那么你的开发生涯要永远地改变了。使用JavaScript的新方法让语言更简单,更简洁而且整体上更友好。

那么让我们为Rollup加上这个过程,就不用担心上面的问题了。

INSTALL THE NECESSARY MODULES.

安装必要模块

首先,我们需要安装Babel Rollup plugin和适当的Babel preset。

# Install Rollup’s Babel plugin.
npm install --save-dev rollup-plugin-babel

# Install the Babel preset for transpiling ES2015 using Rollup.
npm install --save-dev babel-preset-es2015-rollup

提示: Babel preset是告诉Babel我们实际需要哪些babel插件的集合。

创建.babelrc

然后,在项目根目录(learn-rollup/)下创建一个.babelrc文件。在文件中添加下面的JSON:

{
  "presets": ["es2015-rollup"],
}

它会告诉Babel在转换时哪些preset将会用到。

更新rollup.config.js

为了让它能真正工作,我们需要更新rollup.config.js。

在文件中,importBabel插件,将它添加到新配置属性plugins中,这个属性接收一个插件组成的数组。

// Rollup plugins
import babel from 'rollup-plugin-babel';

export default {
  entry: 'src/scripts/main.js',
  dest: 'build/js/main.min.js',
  format: 'iife',
  sourceMap: 'inline',
  plugins: [
    babel({
      exclude: 'node_modules/**',
    }),
  ],
};

为避免编译三方脚本,通过设置exclude属性忽略node_modules目录。

检查输出文件

全部都安装并配置好后,重新打包一下:

./node_modules/.bin/rollup -c

再看一下输出结果,大部分是一样的。但是有一些地方不一样:比如,addArray()这个函数:

var addArray = function addArray(arr) {
  var result = arr.reduce(function (a, b) {
    return a + b;
  }, 0);

  return result;
};

Babel是如何将箭头函数(arr.reduce((a, b) => a + b, 0))转换成一个普通函数的呢?

这就是编译的意义:结果是相同的,但是现在的代码可以向后支持到IE9.

注意: Babel也提供了babel-polyfill,使得像Array.prototype.reduce()这些方法在IE8甚至更早的浏览器也能使用。

STEP 3: 添加ESLint检查常规JavaScript错误

在你的项目中使用linter是个好主意,因为它强制统一了代码风格并且能帮你发现很难找到的bug,比如花括号或者圆括号。

在这个项目中,我们将使用ESLint。

安装模块

为使用ESLint,我们需要安装ESLint Rollup plugin:

npm install --save-dev rollup-plugin-eslint

生成一个.eslintrc.json

为确保我们只得到我们想检测的错误,首先要配置ESLint。很幸运,我们可以通过执行下面的命令自动生成大多数配置:

$ ./node_modules/.bin/eslint --init
? How would you like to configure ESLint? Answer questions about your style
? Are you using ECMAScript 6 features? Yes
? Are you using ES6 modules? Yes
? Where will your code run? Browser
? Do you use CommonJS? No
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Single
? What line endings do you use? Unix
? Do you require semicolons? Yes
? What format do you want your config file to be in? JSON
Successfully created .eslintrc.json file in /Users/jlengstorf/dev/code.lengstorf.com/projects/learn-rollup

如果你按上面展示的那样回答问题,你将在生成的.eslintrc.json中得到下面的内容:

{
  "env": {
    "browser": true,
    "es6": true
  },
  "extends": "eslint:recommended",
  "parserOptions": {
    "sourceType": "module"
  },
  "rules": {
    "indent": [
      "error",
      4
    ],
    "linebreak-style": [
      "error",
      "unix"
    ],
    "quotes": [
      "error",
      "single"
    ],
    "semi": [
      "error",
      "always"
    ]
  }
}

修改.eslintrc.json

然而我们需要改动两个地方来避免项目报错。

使用2空格代替4空格。

后面会使用到ENV这个全局变量,因此要把它加入白名单中。

在.eslintrc.json进行如下修改 — 添加globals属性并修改indent属性:

{
  "env": {
    "browser": true,
    "es6": true
  },
  "globals": {
    "ENV": true
  },
  "extends": "eslint:recommended",
  "parserOptions": {
    "sourceType": "module"
  },
  "rules": {
    "indent": [
      "error",
      2
    ],
    "linebreak-style": [
      "error",
      "unix"
    ],
    "quotes": [
      "error",
      "single"
    ],
    "semi": [
      "error",
      "always"
    ]
  }
}

更新rollup.config.js

然后,引入ESLint插件并添加到Rollup配置中:

// Rollup plugins
import babel from 'rollup-plugin-babel';
import eslint from 'rollup-plugin-eslint';

export default {
  entry: 'src/scripts/main.js',
  dest: 'build/js/main.min.js',
  format: 'iife',
  sourceMap: 'inline',
  plugins: [
    babel({
      exclude: 'node_modules/**',
    }),
    eslint({
      exclude: [
        'src/styles/**',
      ]
    }),
  ],
};

检查控制台输出

第一次,当执行./node_modules/.bin/rollup -c时,似乎什么都没发生。因为这表示应用的代码通过了linter,没有问题。

但是如果我们制造一个错误 - 比如删除一个分号 - 我们会看到ESLint是如何提示的:

$ ./node_modules/.bin/rollup -c

/Users/jlengstorf/dev/code.lengstorf.com/projects/learn-rollup/src/scripts/main.js
  12:64  error  Missing semicolon  semi

✖ 1 problem (1 error, 0 warnings)

一些包含潜在风险和解释神秘bug的东西立刻出现了,包括出现问题的文件,行和列。

但是它不能排除我们调试时的所有问题,很多由于拼写错误和疏漏产生的bug还是要自己花时间去解决。

STEP 4: 添加插件处理非ES模块

如果你的依赖中有任何使用Node风格的模块这个插件就很重要。如果没有它,你会得到关于require的错误。

添加一个Node模块作为依赖

在这个小项目中不引用三方模块很正常,但实际项目中不会如此。所以为了让我们的Rollup配置变得真正可用,需要保证在我们的代码中也能引用是三方模块。

举个简单的例子,我们将使用debug包添加一个简单的日志打印器到项目中。先安装它:

npm install --save debug

注意:因为它是会在主程序中引用的,应该使用--save参数,可以避免在生产环境下出现错误,因为devDependencies在生产环境下不会被安装。

然后在src/scripts/main.js中添加一个简单的日志:

// 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');

// Enable the logger.
debug.enable('*');
log('Logging is enabled!');

// 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}`;

到此一切都很好,但是当运行rollup时会得到一个警告:

$ ./node_modules/.bin/rollup -c
Treating 'debug' as external dependency
No name was provided for external module 'debug' in options.globals – guessing 'debug'

而且如果在查看index.html,会发现一个ReferenceError抛出了:

默认情况下,三方的Node模块无法在Rollup中正确加载。

哦,真糟糕。完全无法运行。

因为Node模块使用CommonJS,无法与Rollup直接兼容。为解决这个问题,需要添加一组处理Node模块和CommonJS模块的插件。

安装模块

围绕这个问题,我们将在Rollup中新增两个插件:

rollup-plugin-node-resolve,运行加载node_modules中的三方模块。

rollup-plugin-commonjs,将CommonJS模块转换成ES6,防止他们在Rollup中失效。

通过下面的命令安装两个插件:

npm install --save-dev rollup-plugin-node-resolve rollup-plugin-commonjs

更新rollup.config.js.

然后,引入插件并添加进Rollup配置:

// 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';

export default {
  entry: 'src/scripts/main.js',
  dest: 'build/js/main.min.js',
  format: 'iife',
  sourceMap: 'inline',
  plugins: [
    resolve({
      jsnext: true,
      main: true,
      browser: true,
    }),
    commonjs(),
    eslint({
      exclude: [
        'src/styles/**',
      ]
    }),
    babel({
      exclude: 'node_modules/**',
    }),
  ],
};

注意: jsnext属性是为了帮助Node模块迁移到ES2015的一部分。main和browser 属性帮助插件决定哪个文件应该被bundle文件使用。

检查控制台输出

执行./node_modules/.bin/rollup -c重新打包,然后再检查浏览器输出:

成功了!日志现在打印出来了。

STEP 5: 添加插件替换环境变量

环境变量使开发流程更强大,让我们有能力做一些事情,比如打开或关闭日志,注入仅在开发环境使用的脚本等等。

那么让Rollup支持这些功能吧。

在main.js中添加ENV变量

让我们通过一个环境变量控制日志脚本,让日志脚本只能在非生产环境下使用。在src/scripts/main.js中修改log()的初始化方式。

// 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();
}

然而,重新打包(./node_modules/.bin/rollup -c)后检查浏览器,会看到一个ENV的ReferenceError。

不必惊讶,因为我们没有在任何地方定义它。如果我们尝试ENV=production ./node_modules/.bin/rollup -c,还是不会成功。因为那样设置的环境变量只是在Rollup中可用,不是在Rollup打包的bundle中可用。

我们需要使用一个插件将环境变量传入bundle。

安装模块

安装rollup-plugin-replace插件,它本质上只是做了查找-替换的工作。它能做很多事情,但现在我们只需要让它简单地找到出现的环境变量并将其替换成实际的值。(比如,所有在bundle出现的ENV变量都会被替换成"production" )。

npm install --save-dev rollup-plugin-replace

更新rollup.config.js

在rollup.config.js中引入插件并且添加到插件列表中。

配置非常简单:只需添加一个键值对的列表,key是将被替换的字符串,value是应该被替换成的值。

// 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';

export default {
  entry: 'src/scripts/main.js',
  dest: 'build/js/main.min.js',
  format: 'iife',
  sourceMap: 'inline',
  plugins: [
    resolve({
      jsnext: true,
      main: true,
      browser: true,
    }),
    commonjs(),
    eslint({
      exclude: [
        'src/styles/**',
      ]
    }),
    babel({
      exclude: 'node_modules/**',
    }),
    replace({
      exclude: 'node_modules/**',
      ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
    }),
  ],
};

在我们的配置中,将找打所有出现的ENV并且替换成process.env.NODE_ENV - 在Node应用中最普遍的设置环境变量的方法 - 或者 "development"中的一个。使用JSON.stringify()确保值被双引号包裹,如果ENV没有的话。

为了确保不会和三方代码造成问题,同样设置exclude属性来忽略node_modules目录和其中的全部包。

检查结果

首先,重新打包然后在浏览器中检查。控制台日志会显示,就像之前一样。很棒 - 这意味着我们的默认值生效了。

为了展示新引入的能力,我们在production模式下运行命令:

NODE_ENV=production ./node_modules/.bin/rollup -c

注意: 在Windows上,使用SET NODE_ENV=production ./node_modules/.bin/rollup -c防止在设置环境变量时报错。

当刷新浏览器后,控制台没有任何日志打出了:

不改变任何代码的情况下,使用一个环境变量禁用了日志插件。

STEP 6: 添加UglifyJS压缩减小生成代码体积

这个教程中最后一步是添加UglifyJS来减小和压缩bundle文件。可以通过移除注释,缩短变量名和其他压缩换行等方式大幅度减少bundle的大小 - 会让文件的可读性变差,但提高了网络间传输的效率。

安装插件

我们将使用UglifyJS压缩bundle,通过rollup-plugin-uglify插件。

通过下面命令安装:

npm install --save-dev rollup-plugin-uglify

更新rollup.config.js

然后添加Uglify到Rollup配置。为了开发环境下可读性更好,设置代码丑化仅在生产环境下使用:

// 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';

export default {
  entry: 'src/scripts/main.js',
  dest: 'build/js/main.min.js',
  format: 'iife',
  sourceMap: 'inline',
  plugins: [
    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()),
  ],
};

我们使用了短路运算,很常用(虽然也有争议)的条件性设置值的方法。[注4]

在我们的例子中,只有在NODE_ENV是"production"时才会加载uglify()。

检查压缩过的bundle

保存配置文件,让我们在生成环境下运行Rollup:

NODE_ENV=production ./node_modules/.bin/rollup -c

注意: 在Windows上,使用SET NODE_ENV=production ./node_modules/.bin/rollup -c防止在设置环境变量时报错。

输出内容并不美观,但是更小了。这有build/js/main.min.js的截屏,看起来像这样:

丑化过的代码确实能更高效地传输。

之前,我们的bundle大约42KB。使用UglifyJS后,减少到大约29KB - 在没做其他优化的情况下节省了超过30%文件大小。


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn