Rollup 是一个 JavaScript 模块打包器,能够将小块代码编译成大块复杂的代码,例如 library 或应用程序,对代码模块采用es6格式javascript
Rollup静态分析代码中的import,并将排除任何未实际使用的代码,使得不会增长额外的依赖或使项目的大小膨胀。css
而且,这种基于显式的 import 和 export 语句的方式,它远比「在编译后的输出代码中,简单地运行自动 minifier 检测未使用的变量」更有效html
经过插件导入已存在的commonJS模块java
为了确保你的 ES6 模块能够直接与「运行在 CommonJS(例如 Node.js 和 webpack)中的工具(tool)」使用,你可使用 Rollup 编译为 UMD 或 CommonJS 格式,而后在 package.json 文件的 main 属性中指向当前编译的版本。若是你的 package.json 也具备 module 字段,像 Rollup 和 webpack 2 这样的 ES6 感知工具(ES6-aware tools)将会直接导入 ES6 模块版本。node
安装完毕后,****rollup -v***查看下是否安装成功
jquery
接下来,咱们用一系列demo',逐级去接触rollupwebpack
// lib.js export function logA() { console.log('function logA called') } export function logB() { console.log('function logB called') }
// index.js import { logA } from './lib' logA()
index.js是咱们的入口文件
dist.js是咱们的输出文件
-f es 表示--format es,打包输出格式为es格式,"output.format", which can be one of "amd", "cjs", "system", "esm", "iife" or "umd"
-o 表示输出文件的路径,在 -o 后面跟着的 dist.js 就是咱们要生成的最终打包文件了。
其实这里原本应该加上一个参数-i,用来表示入口文件的路径,但rollup是会把没有加参数的文件默认为是入口文件,所以咱们在这里省略了这个参数es6
显示出这条信息以后,咱们发现目录下已经多出了一个 dist.js 文件,打开文件,咱们发现里面的代码是这样的web
function logA() { console.log('function logA called'); } logA();
3.1 为了测试下,咱们新建一个index.htmlnpm
<!DOCTYPE html> <html lang="en"> <head> <title>rollup 打包测试</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <script src="./dist.js"></script> </body> </html>
而后用chorme打开
3.2 用node运行 node dist.js
若是在demo0中的index.js文件中把logA()改为export default logA(),那么rollup最后的打包结果就会是
function logA() { console.log('function logA called'); } var index = logA(); export default index
显然这样的代码是不能直接在浏览器端和node端运行的,咱们须要把原先的ES6模块转化成浏览器和node支持的形式
那么咱们先来看下模块输出的几种格式,咱们使用rollup -h查看下
接下来咱们用rollup来打包一下,在demo0中的index.js文件里将logA()改为export default logA(),而后npm init初始化一个package.json,并在package.json文件中写好不一样模块的打包命令
"build:amd": "rollup index.js -f amd -o ./dist/dist.amd.js", "build:cjs": "rollup index.js -f cjs -o ./dist/dist.cjs.js", "build:es": "rollup index.js -f es -o ./dist/dist.es.js", "build:iife": "rollup index.js -f iife -n result -o ./dist/dist.iife.js", "build:umd": "rollup index.js -f umd -n result -o ./dist/dist.umd.js", "build:all": "npm run build:amd && npm run build:cjs && npm run build:es && npm run build:iife && npm run build:umd"
在这里咱们发如今设置模块为iife(当即执行函数)和umd时,还加上了一个参数-n,这是由于咱们将logA()的结果设置为模块的输出结果,那么在使用iife和umd时,须要事先设定模块的名称,才能让其余人经过这个模块名称引用到你的模块的输出结果。
在命令行中输入npm run build:all,运行全部打包命令,查看效果
PS: 使用amd模块打包方式时,若不指定模块名称,则会打包成匿名函数,若想打包成一个具名函数,则须要使用-u或--id来指定具名函数名称。
除了-f以外,还有许多其余的参数可使用,看到这里可能有些同窗会以为麻烦了,这么多参数用起来好麻烦,每次都要输一长串的命令,那么有没有更好的方法来控制rollup的参数配置呢?
固然有,接下来咱们就尝试使用配置文件来控制rollup打包。
沿用以前demo1的内容,咱们在demo2的项目下建立一个文件,取名为rollup.config.js,这个文件就是rollup的配置文件了,rollup根据配置文件的输出配置来进行打包,接下来咱们在配置文件中输入配置代码:
export default { input: 'index.js', output: { file: 'dist.js', format: 'cjs' } };
PS: 若使用iife或umd模块打包,须要添加属性moduleName,用来表示模块的名称;若用amd模块打包,能够配置amd相关的参数(使用umd模块模式时,也会使用到amd相关配置参数):
amd: { id: 'amd-name', // amd具名函数名称 define: 'def' // 用来代替define函数的函数名称 }
在这里咱们发现配置文件也是使用了ES6语法,这是由于rollup能够本身处理配置文件,因此可以直接用ES6的模块输出(固然,你也能够选择使用node的module.exports方式来输出配置。
"build": "rollup -c"
-c这个参数表示使用配置文件来进行打包,若后面没有指定使用的配置文件路径,则使用默认的配置文件名称rollup.config.js。
'use strict'; function logA() { console.log('function logA called'); } var index = logA(); module.exports = index;
例如,咱们添加一个indexB.js文件,在这个文件中咱们将logA替换为logB,并将rollup配置文件改成:
export default [ { input: 'index.js', output: { file: 'dist/dist.js', format: 'cjs' } }, { input: 'indexB.js', output: { file: 'dist/distB.js', format: 'cjs' } } ];
运行打包命令,发如今dist目录下生成了distA.js和distB.js两个文件,说明多项配置打包成功。
除了上面这种输出一个配置数组以外,你还能够经过配置target属性来输出多个打包文件:
export default { input: 'index.js', output: [ { file: 'dist/dist.es.js', format: 'es' }, { file: 'dist/dist.cjs.js', format: 'cjs' }, { file: 'dist/dist.umd.js', name: 'dist_umd', format: 'umd' } ] }
这样配置会在dist目录下面输出bundle.cjs.js,bundle.umd.js和bundle.es.js三个打包文件,同时umd模块的名称会被定义成dist_umd。
咱们在开发过程当中,须要频繁对源文件进行修改,若是每次都本身手动输一遍打包命令,那真的是要烦死。所以,咱们选择使用rollup提供的监听功能,安装rollup-wacth模块,再在rollup命令后面加上-w参数,就能让rollup监听文件变化,即时打包。
"watch": "rollup -c -w"
执行npm run watch,看到下面的提示:
好了,这个时候你就能够随便修改你的源文件了,rollup会自动为你打包的。
PS: 如果你不想监听某些文件,只要在配置文件中加上
watch: { exclude: ['path/to/file/which/you/want/to/ignore'] }
就好了,其中的exclude表示你想要忽略的文件的路径(支持glob模式匹配)
ES6能够说是现代JS开发100%会用到的技术了,rollup虽然支持了解析import和export两种语法,可是却不会将其余的ES6代码转化成ES5代码,所以咱们在编写ES6代码的时候,须要引入插件来支持ES6代码的解析。
npm i rollup-plugin-babel @babel/preset-env @babel/core --save-dev
{ "presets": [ [ "env", { "modules": false } ] ] }
之因此使用modules:false这个参数,是由于rollup默认是经过ES6模块语法来解析文件间的依赖,rollup默认是不支持解析common.js的模块规范的(怎么让rollup支持我会在接下来的demo中讲解),所以须要让babel不转化模块相关的语法,否则rollup在使用过程当中会报错。
import babel from 'rollup-plugin-babel'; export default { input: 'index.js', output: [ { file: 'dist/dist.es.js', format: 'es' }, { file: 'dist/dist.cjs.js', format: 'cjs' }, { file: 'dist/dist.umd.js', name: 'dist_umd', format: 'umd' } ], plugins: [ babel({ exclude: 'node_modules/**' }) ] }
这里咱们新建三个文件,两个类Person和Man和一个入口文件index.js
// Person.js export default class Person { constructor (name, gender = '男') { this.name = name this.gender = gender } say () { console.log(`个人名字是${this.name},是一个${this.gender}生`) } }
// Man.js import Person from './Person' export default class Man extends Person { constructor (name) { super(name, '男') } }
// index.js import Man from './Man' new Man('KainStar').say()
打开打包后的代码看一眼,这里咱们看下dist.es.js
看看打包出来的文件能不能运行,执行node dist/dist.js
有时候咱们会引入一些其余模块的文件(第三方的或是本身编写的),可是这些第三方的模块为了可以直接使用,每每不是ES6模块而是用commonjs的模块方式编写的,这个时候咱们须要将commonjs的模块转化为ES6模块,这样才能让rollup进行正确的解析。
解析commonjs须要引入一个rollup插件——rollup-plugin-commonjs
安装插件
npm i rollup-plugin-commonjs --save-dev // or yarn add rollup-plugin-commonjs --dev
import commonjs from 'rollup-plugin-commonjs' export default { input: 'index.js', output: [{ file: 'dist/dist.cjs.js', name: 'cjs_test', format: 'iife' } ], plugins: [ commonjs() ] }
// index.js exports.logA = function logA() { console.log('function logA called') } exports.logB = function logB() { console.log('function logB called') }
能够看到打包成功
因为rollup没法直接解析npm模块,所以须要引入插件rollup-plugin-node-resolve并配合以前的commonjs插件来解析这些第三方模块
npm i rollup-plugin-node-resolve lodash --save-dev // or yarn add rollup-plugin-node-resolve lodash --dev
// rollup.config.sj import commonjs from 'rollup-plugin-commonjs' import resolve from 'rollup-plugin-node-resolve' export default { input: 'index.js', output: [{ file: 'dist/dist.cjs.js', name: 'cjs_test', format: 'iife' } ], plugins: [ resolve({ jsnext: true, main: true, browser: true }), commonjs() ] }
jsnext表示将原来的node模块转化成ES6模块,
main和browser则决定了要将第三方模块内的哪些代码打包到最终文件中。
ps:因为commonjs和node-resolve中的配置属性不少,所以不一一解释,但愿了解更多的同窗能够去官方仓库查看说明。
import compact from 'lodash/compact' const array = [0, 1, false, 2, '', 3] const compctedArray = compact(array) console.log(compctedArray) // 在这里咱们只引用了lodash中的compact方法,那么在最终代码里,应该也只会添加compact方法的代码。
确实只添加了compact方法的代码,而没有将lodash所有引入。
在平时的开发中,咱们常常会引入其余的模块,可是在使用的时候,咱们又不想把它们打包到一个文件里,想让他们做为单独的模块(或文件)来使用,方便浏览器端进行缓存,这个时候就须要使用配置文件中的external属性了
npm i jquery --save-dev // or yarn add jquery --dev
import commonjs from 'rollup-plugin-commonjs' import resolve from 'rollup-plugin-node-resolve' export default { input: 'index.js', output: [{ file: 'dist/dist.cjs.js', name: 'cjs_test', format: 'iife' }], external: ['jquery'], plugins: [ resolve({ jsnext: true, main: true, browser: true }), commonjs() ] }
external用来表示一个模块是否要被当成外部模块使用,属性的值能够是一个字符串数组或一个方法,当传入的是一个字符串数组时,全部数组内的模块名称都会被当成是外部模块,不会被打包到最终文件中
import compact from 'lodash/compact' import JQuery from 'jquery' const array = [0, 1, false, 2, '', 3] const compctedArray = compact(array) console.log(compctedArray)
检查打包出来的文件,咱们发现lodash的compact方法依旧被打包进了最终文件中,可是jquery却没有被打包进去,而是以$的全局变量形式被传入到了当即执行函数中。
在这里rollup又给咱们输出了一条提示信息,意思是咱们没有在配置文件中给外部模块jquery设置全局变量名称,所以rollup本身猜想了一个名称$,当成是依赖的全局变量名。
若是直接使用全局的$的话,可能会由于变量$被其余引入的代码覆盖而报错,所以咱们要将$替换为不容易冲突的jQuery变量,在配置文件中添加globals属性:
import commonjs from 'rollup-plugin-commonjs' import resolve from 'rollup-plugin-node-resolve' export default { input: 'index.js', output: [{ file: 'dist/dist.cjs.js', name: 'cjs_test', format: 'iife', globals: { jquery: 'jQuery' } }], external: ['jquery'], plugins: [ resolve({ jsnext: true, main: true, browser: true }), commonjs() ] }
再次打包结果:
只是提示第三方模块未使用,实际上咱们确实没有使用
globals的值是一个对象,key表示使用的模块名称(npm模块名),value表示在打包文件中引用的全局变量名,在这里咱们就是把jquery模块的全局变量名设置为jQuery,从新打包
在从新打包出来的文件中,咱们发现最后传入的参数已经由$变为了jQuery,并且rollup也没有输出提示信息
有时候咱们想要在浏览器端使用node自带的一些内置模块,通常状况下会使用browserify这个工具来打包,可是browserify打包出来的文件实在太大,所以咱们用rollup选择性地导入咱们须要的node内置模块
npm i rollup-plugin-node-builtins --save-dev // or yarn add rollup-plugin-node-builtins --dev
node-builtins对不一样的node内置模块支持不一样,有些模块可能须要使用其余的插件(例如rollup-plugin-node-globals)才能正常打包,具体的支持状况能够查看node-builtins的官方仓库。
import builtins from 'rollup-plugin-node-builtins' export default { input: 'index.js', output: [{ file: 'dist/dist.cjs.js', name: 'cjs_test', format: 'iife' }], plugins: [ builtins() ] }
import { join } from 'path' const path_base = 'E://node' const path_joined = join(path_basem, 'bin') console.log(path_joined)
在这里咱们使用node内置的path模块,运行打包命令,发现dist.js文件中引入了额外的100多行代码,这100多行代码就实现了path模块的join方法供咱们使用。
PS: 我建议,若是不是必要的状况,最好可以使用其余人编写的第三方实现库或本身造轮子实现,而不是使用node内置的模块,由于在引用某些模块时,node-builtins可能会引入过多的代码,这样会大大增长最后打包的文件的大小,使用他人的第三方库或本身的实现可控性更高
有时候咱们可能会使用CDN服务器上的js文件,可是又不想在本地安装一个相同的模块(也有可能没有对应的模块),可能在版本升级的时候会产生一些问题,这个时候咱们就须要使用rollup的paths属性了,这个属性能够帮助你把依赖的代码文件地址注入到打包以后的文件里。
export default { input: 'index.js', output: [{ file: 'dist/dist.amd.js', format: 'amd' }], external: ['jquery'], paths: { jquery: 'https://cdn.bootcss.com/jquery/3.2.1/jquery.js' } }
在这里咱们要使用cdn上的jquery文件,paths属性的值能够是一个对象或用法与external属性方法类似的方法(只是返回的不是boolean值而是文件的地址)。若使用对象来表示,则key值为须要引入的模块名称,value值为对应的文件地址
import $ from 'jquery' $('#p').html('rollup 使用paths属性配合CDN')
代码发布时,咱们常常会把本身的代码压缩到最小,以减小网络请求中的传输文件大小
rollup的插件rollup-plugin-uglify就是来帮助你压缩代码的,咱们接下来就用这个插件来压缩一下咱们的代码
npm i rollup-plugin-uglify --save-dev // or yarn add rollup-plugin-uglify --dev
import { uglify } from 'rollup-plugin-uglify' export default { input: 'index.js', output: [{ file: 'dist/dist.js', format: 'iife', name: 'test' }], plugins: [ uglify() ] }
运行打包命令,查看dist.js文件,发现代码已经被压缩了
可是,压缩过的代码在debug时会带来很大的不便,所以咱们须要在压缩代码的同时生成一个sourceMap文件
幸运的是,rollup本身就支持sourceMap文件的生成,不须要咱们去引入其余插件,只须要在配置文件的对应output中加上:
import { uglify } from 'rollup-plugin-uglify' export default { input: 'index.js', output: [{ file: 'dist/dist.js', format: 'iife', name: 'test', sourcemap: true }], plugins: [ uglify() ] }
从新打包,咱们发现不只生成了dist.js.map文件,并且dist文件最后加上了一行//# sourceMappingURL=dist.js.map,而且在浏览器中能够正确加载源文件
PS: 如果将sourcemap属性的值设置为inline,则会将sourceMap的内容添加到打包文件的最后
在大型工程的团队开发中,咱们须要保证团队代码风格的一致性,所以须要引入eslint,并且在打包时须要检测源文件是否符合eslint设置的规范,如果不符合则抛出异常并中止打包。在这里咱们使用rollup的eslint插件rollup-plugin-eslint:
npm i eslint rollup-plugin-eslint --save-dev // or yarn add eslint rollup-plugin-eslint --dev
{ "env": { "browser": true, "commonjs": true, "es6": true, "node": true }, "parserOptions": { "ecmaFeatures": { "jsx": false }, "sourceType": "module" }, "rules": { "semi": [ "error", "never" ] } }
// src/index.js console.log(1);
import eslint from 'rollup-plugin-eslint'; export default { input: 'src/index.js', output: [{ file: 'dist/dist.js', format: 'iife', name: 'test', sourcemap: true }], plugins: [ eslint.eslint({ throwOnError: true, throwOnWarning: true, include: ['src/**'], exclude: ['node_modules/**'] }) ] }
throwOnError和throwOnWarning设置为true时,若是在eslint的检查过程当中发现了error或warning,就会抛出异常,阻止打包继续执行(若是设置为false,就只会输出eslint检测结果,而不会中止打包)
删除index.js文件中的分号,从新打包,发现打包成功
在平时的开发过程当中,咱们常常会使用IDE或编辑器的eslint插件,以便提前发现问题,可是有时候这些插件会去检查打包完的文件,致使你的提示框里一直会有eslint检测到错误的消息
咱们如今有两种解决方案:
banner: '/*eslint-disable */'
从新打包,咱们发现打包文件的开头被插入了这段注释字符串,并且eslint插件也不报dist.js文件的错了
有时候咱们会须要区分开发环境和生产环境,针对不一样的打包要求输出不一样的打包配置,可是咱们又不想写rollup.config.dev.js和rollup.config.prod.js两个文件,由于可能二者之间的区别只是一个uglify插件。
所以,咱们就须要用变量来控制配置文件的输出内容,rollup命令行给咱们提供了一个设置环境变量的参数--environment,在这个参数后面加上你须要设置的环境变量,不一样变量间用逗号分隔,用冒号后面的字符串表示对应变量的值(若不加冒号,则默认将值设为字符串true):
在package.json文件中编写对应的npm scripts命令:
"dev": "rollup -c --environment NODE_ENV:development", "build": "rollup -c --environment NODE_ENV:production"
最后修改咱们的rollup配置文件
import { uglify } from 'rollup-plugin-uglify' let isProd = process.env.NODE_ENV === 'production' // 通用的插件 const basePlugins = [] // 开发环境须要使用的插件 const devPlugins = [] // 生产环境须要使用的插件 const prodPlugins = [uglify()] let plugins = [...basePlugins].concat(isProd ? prodPlugins : devPlugins) let outputFilePath = isProd ? './dist/dist.min.js' : './dist/dist.js' export default { input: 'src/index.js', output: [{ file: outputFilePath, format: 'iife', name: 'test', sourcemap: isProd }], plugins: plugins }
咱们分别运行两个npm scripts命令,查看打包的结果:
有时候须要将生产环境信息添加到源文件里,这个时候就须要使用rollup的配置属性intro和outro了
若是说banner和footer是在文件开始和结尾添加字符串,那么intro和outro就是在被打包的代码开头和结尾添加字符串了,以iife模式来举例,若是咱们配置了这四个属性,那么输出结果就会是:
// banner字符串 (function () { 'use strict'; // intro字符串 // 被打包的代码 // outro字符串 }()); // footer字符串
下面咱们实际使用一下,在index.js文件里加上一段须要依赖的代码
if (DEVELOPMENT) { console.log('development') } else { console.log('production') }
而后修改配置文件
import { uglify } from 'rollup-plugin-uglify' let isProd = process.env.NODE_ENV === 'production' // 通用的插件 const basePlugins = [] // 开发环境须要使用的插件 const devPlugins = [] // 生产环境须要使用的插件 const prodPlugins = [uglify()] let plugins = [...basePlugins].concat(isProd ? prodPlugins : devPlugins) let outputFilePath = isProd ? './dist/dist.min.js' : './dist/dist.js' export default { input: 'src/index.js', output: [{ file: outputFilePath, format: 'iife', name: 'test', sourcemap: isProd, intro: 'const DEVELOPMENT = ' + !isProd }], plugins: plugins }
接着运行npm run dev,查看打包后的文件以下】
有时候咱们会把开发/生产环境的信息直接写在源文件里面,这个时候用intro来注入代码的方式就不适合了。这个时候咱们就须要使用rollup-plugin-replace插件来对源代码的变量值进行替换:
在上面这个文件的基础上,先把intro给注释了,而后
npm i rollup-plugin-replace --save-dev // or yarn add rollup-plugin-replace --dev
接着,更改下配置,
import { uglify } from 'rollup-plugin-uglify' import replace from 'rollup-plugin-replace' let isProd = process.env.NODE_ENV === 'production' // 通用的插件 const basePlugins = [replace({ DEVELOPMENT: !isProd })] // 开发环境须要使用的插件 const devPlugins = [] // 生产环境须要使用的插件 const prodPlugins = [uglify()] let plugins = [...basePlugins].concat(isProd ? prodPlugins : devPlugins) let outputFilePath = isProd ? './dist/dist.min.js' : './dist/dist.js' export default { input: 'src/index.js', output: [{ file: outputFilePath, format: 'iife', name: 'test', sourcemap: isProd, // intro: 'const DEVELOPMENT = ' + !isProd }], plugins: plugins }
接着,打包npm run dev,查看打包结果
有时候咱们会须要在打包的先后执行一些其余的代码,可是又不想引入其余构建工具(例如gulp),那么就可使用rollup提供的node API来编写你本身的打包流程。
rollup模块只提供了一个rollup函数,这个函数的参数和咱们编写配置文件时导出的参数不一样,减小了不少配置属性,留下来的主要是一些输入相关的配置。(具体的配置属性能够查看rollup wiki的javascript API一节)
执行这个函数返回的是一个Promise,而且在then方法中提供一个bundle对象做为参数,这个对象保存了rollup对源文件编译一次以后的结果,并且提供了generate和write两个方法
在这里咱们只使用write方法来编写一个为全部模块类型打包,并输出打包完毕提示的文件,至于generate的使用方法咱们会放在编写插件一节中介绍。
const rollup = require('rollup').rollup rollup({ entry: 'index.js' }).then(bundle => { // 保存全部Promise的列表 let writePromiseList = [] // 声明全部须要打包的模块类型 let moduleTypesList = ['es','cjs','amd','umd','iife'] moduleTypesList.forEach(function(moduleType) { writePromiseList.push(bundle.write({ dest: './dist/dist.' + moduleType + '.js', format: moduleType, sourceMap: true })) }) return Promise.all(writePromiseList) }).then(() => { console.log('所有模块格式打包完毕') // 其余代码 })
将package.json文件内的npm scripts命令添加
"rollup": "node rollup.js"
执行npm run rollup
一个web项目内确定不会只有js文件,还有css、html(也多是模板文件)和其余类型的文件,那么咱们在打包的时候能不能把这些文件一块儿打包呢?
咱们须要区分一下,在这里的打包有两种意思,
不一样的rollup插件有不一样的效果,在使用的时候必定要查看插件的相关说明
安装插件
npm i rollup-plugin-scss --save-dev // or yarn add rollup-plugin-scss --dev
编写配置文件
import { uglify } from 'rollup-plugin-uglify' import scss from 'rollup-plugin-scss' let isProd = process.env.NODE_ENV === 'production' // 通用的插件 const basePlugins = [scss({ output: { file: './dist/css/style.css' } })] // 开发环境须要使用的插件 const devPlugins = [] // 生产环境须要使用的插件 const prodPlugins = [uglify()] let plugins = [...basePlugins].concat(isProd ? prodPlugins : devPlugins) let outputFilePath = isProd ? './dist/dist.min.js' : './dist/dist.js' export default { input: 'src/index.js', output: [{ file: outputFilePath, format: 'iife', name: 'test', sourcemap: isProd }], plugins: plugins }
编写src/index.js
import './scss/text.scss' import './scss/bg.scss' var html = ` <div class="bg-blue"> <p class="text-white">测试文字</p> </div> ` document.body.innerHTML = html
编写src/scss/text.scss
$white: #fff; .text-white { color: $white; }
编写src/scss/bg.scss
$blue: #69c4eb; .bg-blue { background-color: $blue }
执行打包命令,查看效果