Rollup是什么?
Rollup是下一代的ES6 JS文件打包工具。和Webpack相似,Rollup支持扩展插件开发,能把模块化的多个JS文件打包成一个文件,还能打包CSS文件(这个功能我还一直没有尝试过)。但是,经常被Rich Harris拿出来炫耀的是Rollup的tree-shaking的能力。即,在打包过程中,Rollup能够自动过滤与剔除没有用到的JS代码和没有调用过的JS函数。 Rollup打包的底层逻辑是“内联”处理被import的ES6模块代码。我理解Rollup是把ES6模块当作是JAVA里的Inline Function来处理的。对于模块动态加载,或许Rollup打包器不能直接满足这个需求。而需要另一个ES6 API:System.import(…)。
Rollup-WebWorker打包插件 的 需求由来
Rollup自身强悍,也有丰富的第三方插件。请见。但是,这个活跃的生态系统似乎遗漏了我正在遇到的需求类型。简单地概括: 打包运行在Web Worker里的,依赖传统JS库的,遵循ES6 Moulde规范的 JavaScript程序文件 成为一个IIFE文件。 我的需求包括以下几个关键点:
- “运行在Web Worker里”意为着 Rollup基于external和globals配置参数的映射机制 对 我的需求 无效。
- 简单地说,Rollup的external和globals配置参数 被设计用来 把‘ES6 Import指令’先映射到 HTML <script>标签,再映射到 被引用JS文件构建的全局变量 上。
- 比如,把 import io from ‘socket.io-client’; 指令 先映射到 <script src="socket.io-client.js">标签,再映射到 全局变量io。
- 但是,这一切对Web Worker是无效的。因为在Web Worker里不能定义<script>标签,所以仅能使用importScripts(…)函数在程序内手动导入依赖库。
- 于是,挑战1出现了。
- 简单地说,Rollup的external和globals配置参数 被设计用来 把‘ES6 Import指令’先映射到 HTML <script>标签,再映射到 被引用JS文件构建的全局变量 上。
- “依赖传统JS库”意为着 被依赖的JS文件 很有可能不是 CMD,AMD,UMD或ES 6模块文件。相反,所谓的传统JS库文件通常是被包装成为一个IIFE表达式(即,一个立即执行的大闭包),并且输出一个全局变量 作为 暴露API集的顶层命名空间。
- 但是,挑战2并没有出现,因为‘Rollup的external和globals配置参数’就是被用来解决这个需求的。只不过,正如#1里已经提到的,‘Rollup的external和globals配置参数’要求你的JS文件运行在网页里,而不是运行在Web Worker里。
- “遵循ES6 Moulde规范”这需求最容易解决。只要在Rollup的插件链条的最后加上一个Rollup Babel Plugin就行了。
- “输出一个IIFE文件”这需求也简单。只需设置Rollup的打包输出格式参数“format”为“iief”即可。
Rollup-WebWorker打包插件 的 设计目标
至此,通过总结我的需求,新的Rollup插件需要完成如下几个工作步骤:
- 映射 一条ES 6 Module Import 指令 到 一个或多个 JS文件。
- 比如,映射 import Zlib from ‘zlib’; 到 两个JS文件 ‘js/lib/gzip.min.js’和’js/lib/gunzip.min.js’。
- 生成一条importScripts(…)函数调用语句。
- 比如,importScripts(‘js/lib/gzip.min.js’, ‘js/lib/gunzip.min.js’);
- 插入 第2步生成的importScripts(…)语句 到 打包结果文件的顶部第一句的位置。特别提示:importScripts(…)语句一定要被插入在IIFE闭包表达之前,而绝对不能在IIFE的闭包函数体内。
Rollup-WebWorker打包插件 的 设计详细
- 实现Rollup Plugin的resolveId(importeeimportee, importer){…}成员函数。
- 捕获哪些 传统JS库 正在 ES6代码中 被依赖。即,知道import指令中from关键字后面的依赖模块名。 比如,从ES6指令import Zlib from ‘zlib’;抽取模块名’zlib’。
- 翻译 被捕获的依赖模块名 为 具体的一个或多个JS文件路径。即,通过 预传入的“模块名-文件名 映射表”,把’zlib’映射成 'js/lib/gzip.min.js’和’js/lib/gunzip.min.js’两个文件路径。
- 生成importScripts(…)函数调用语句。比如,importScripts(‘js/lib/gzip.min.js’, ‘js/lib/gunzip.min.js’);
- 实现Rollup Plugin的banner(){…}成员函数。
- 把 被生成的importScripts(…)语句 直接作为banner(){…}函数的返回字符串return出去即可。
- 最终,importScripts(…)语句就会出现在 打包结果文件的顶行第一句的位置。
Rollup-WebWorker打包插件 带来的改善
给Rollup写插件是不是so easy。更重要的是,仅只需要投入大约40到60分钟就能够立杆见影地解决工程构建过程中出现的棘手问题。仅这一点点的改善就确保了“网页JS编程”与“Web Worker开发”的一致编码风格与开发体验。即, 在ES6代码里,透明化对传统JS依赖的导入操作。 1. 无论依赖模块是AMD,CMD,UMD,ES6 Module,还是传统的IIFE闭包, 2. 无论JS程序是运行在网页渲染主线程,还是运行在Web Worker线程里, ES6 Module的Import指令都能够将它们导入你当前的运行环境上下文。
Rollup-WebWorker打包插件 源码
var _ = require('underscore'), path = require('path');
module.exports = function(options){
var pluginName = 'Web Worker', optExternal, optPaths, importScripts = [], history = [], dirTopFilepath, relTopFilepath, topFilepath;
return {
'name': pluginName,
'options': function(options){ // 从Rollup的配置中抽取external与paths配置参数。external与paths都是Rollup的标准配置项。
optExternal = options.external || []; // 用法与语义都等同于Rollup对external参数的默认配置定义。
optPaths = options.paths || {}; // 用法与语义都等同于Rollup对paths参数的默认配置定义。
delete options.external;
delete options.paths;
},
'resolveId': function(importee, importer){
if (!importer) {
topFilepath = importee; // 获得ES 6代码编译的入口文件的文件名
relTopFilepath = path.relative(options.cwd, topFilepath);
dirTopFilepath = path.dirname(relTopFilepath);
}
if (optExternal.indexOf(importee) < 0) {
return null; // 将控件权移交给Rollup插件链条中的下一个Rollup插件实例。
}
if (history.indexOf(importee) > -1) {
return false; // 此依赖已经处理过了,不再重复处理,在此处跳过。
}
var pathImportees = optPaths[importee];
if (pathImportees) {
if (!_.isArray(pathImportees)) {
pathImportees = [pathImportees];
}
pathImportees.forEach(function(pathImportee){
var refImportee = path.relative(dirTopFilepath, pathImportee);
importScripts.push(refImportee); // 在此处,收集传统JS依赖库的文件名。
});
}
history.push(importee);
return false;
},
'banner': function(){
var polyfills = options.polyfills;
if (!_.isArray(polyfills)) {
polyfills = [polyfills];
}
polyfills.reverse().forEach(function(polyfill){
var refImportee = path.relative(dirTopFilepath, polyfill);
importScripts.unshift(refImportee);
});
return "importScripts('" + importScripts.join("', '").replace(/\\/g, '/') + "');"; // 生成importScripts(...)调用语句,并输出到 打包结果文件的 顶行第一句的位置。
}
};
};
Rollup-WebWorker打包插件 的使用
在这里,以Grunt为例。
var Babel = require('rollup-plugin-babel'), WebWorker = require('./src/js/lib/rollup-plugin-webworker');
grunt.config.merge({
'rollup': {
'worker': {
'options': {
'external': ['underscore', 'socket.io', 'zlib'], // 注册外部依赖的模块名称
'globals': { // 注册 外部依赖的 模块名称 至 全局变量名 的映射关系表
'underscore': '_',
'zlib': 'Zlib',
'socket.io': 'io'
},
'paths': { // 注册 外部依赖的 模块名称 至 文件路径 的映射关系表
'underscore': 'js/lib/underscore.js',
'zlib': ['js/lib/gzip.min.js', 'js/lib/gunzip.min.js'],
'socket.io': 'js/lib/socket.io-1.5.1.js'
},
'plugins': [WebWorker({ // 启用Rollup WebWorker打包插件
'cwd': 'src',
'polyfills': ['js/lib/web-worker-runtime.js', 'js/lib/polyfill.js']
}), Babel()]
},
'files': [{
'cwd': 'src',
'expand': true,
'dest': '.build',
'src': ['js/workers/*.js']
}]
}
}
});
后面的计划
我正在学习如何在Github上创建工程,我想把这个rollup-webworker-plugin贡献到Rollup的第三方插件集中。