教程:怎样运用Rollup打包JavaScript
经由历程这个系列教程一步一步进修怎样运用更小更快的Rollup庖代webpack和Browserify打包JavaScript文件。
这周,我们要运用Rollup构建我们的第一个项目,Rollup是一个打包JavaScript(和款式,不过下周才会做)的构建东西。
经由历程这个教程,我们的Rollup将能够:
兼并scripts代码,
删除过剩代码,
编译成对旧浏览器友爱的代码,
支撑在浏览器中运用Node模块,
能运用环境变量,
尽量的紧缩,削减文件大小。
准备事变
最少懂一点JavaScript的话将会更好明白。
对ES2015 modules有基础相识,不过不相识也不妨。
在你的装备上要有npm。(还没有?在这下载Node.js)
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
。
在文件中,import
Babel插件,将它增加到新设置属性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
目次和个中的悉数包。(幸而@wesleycoder先在这上面踩坑了。)
搜检效果
起首,从新打包然后在浏览器中搜检。掌握台日记会显现,就像之前一样。很棒 – 这意味着我们的默许值见效了。
为了展现新引入的才能,我们在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%文件大小。
接下来的内容
在这个系列的下一节,我们将相识经由历程Rollup和PostCSS处置惩罚款式,而且增加live reloading来及时瞥见我们的修正。
Further Reading
扩大浏览
The cost of small modules – 这篇文章让我最先对Rollup感兴趣,因为它展现了一些Rollup比拟webpack和Browserify的上风。
注1: 这是一个异常难明白的观点,所以没全明白也不要有压力。简朴来讲,我们愿望我们的代码在他们本身的作用域中,防备和别的剧本的争执。IIFE是一个包含我们的代码在本身作用域的一个[闭包]。
注2:It’s important to keep in mind, though, that when we’re dealing with such a small example app it doesn’t take much to double a file size. The comparison at this point is ~3KB vs. ~8KB.
注3:作为曾花数小时找bug然后发明拼错一个变量名的人,不须要强调运用linter带来的效力提拔。
注4:举个例子,运用这类要领来赋默许值时异常罕见的。(比方
var foo = maybeThisExists || 'default';
)
这篇文章的代码放在GitHub上。你能够fork 这个堆栈举行修正或测试,开issue或许报告bug,或许新建pull request举行发起或许修正。