JS打包工具rollup——彻底入门指南

前言

最近在作一个提供给浏览器和node同时使用的js的url模板工具类,在用什么打包工具上纠结了一段时间,正好有一天在知乎上看到了关于rollup的介绍,在本身试了试以后,就决定用rollup.js来打包本身的工具类了。javascript

这篇文章主要是为了让对rollup.js也有兴趣的同窗可以快速入门rollup的使用方式而写的,文章除了开始对rollup.js的基本介绍以外,主要用多个demo来介绍rollup.js的不一样使用方法,以及介绍一些比较经常使用的rollup插件。读者能够选择本身有兴趣的部分查看。css

文章博客连接html

本教程相关的全部demo都已上传到github,rollup-demos,欢迎star。java

rollup.js简介

rollup.js

首先简单介绍一下rollup.JS。根据官方的介绍,rollup.js是一个模块打包工具,能够帮助你从一个入口文件开始,将全部使用到的模块文件都打包到一个最终的发布文件中(极其适合构建一个工具库,这也是我选择用rollup来打包的缘由)。node

rollup.js有两个重要的特性,其中一个就是它使用ES6的模块标准,这意味着你能够直接使用importexport而不须要引入babel(固然,在如今的项目中,babel能够说是必用的工具了)。jquery

rollup.js的另外一个重要特性叫作'tree-shaking',这个特性能够帮助你将无用代码(即没有使用到的代码)从最终的生成文件中删去。举个例子,我在A.js文件中定义了A1和A2两个方法,同时在B文件中引入了这两个方法,可是在B文件中只引入了A文件中的A1方法,那么在最后打包B文件时,rollup就不会将A2方法引入到最终文件中。(这个特性是基于ES6模块的静态分析的,也就是说,只有export而没有import的变量是不会被打包到最终代码中的)webpack

rollup.js实例

demo0 开始使用rollup

初始化一个工程,建立一个依赖模块文件lib.js和入口文件index.js。git

export function logA() {
    console.log('function logA called')
}

export function logB() {
    console.log('function logB called')
}
import { logA } from './lib'

logA()

如今咱们要把lib.js和index.js打包成dist.js,首先要作的就是安装rollup.js。es6

在这里咱们有两种安装方法:github

  1. 全局安装:

打开你的命令行,输入npm install rollup -g,等待rollup安装完毕。安装完成以后,试着输入rollup -v来查看一下rollup是否安装成功了

查看rollup版本

成功安装完rollup以后,进入到工程目录下,输入打包命令rollup index.js -o dist.js,index.js 是咱们的入口文件, -o 表示输出文件的路径,在 -o 后面跟着的 dist.js 就是咱们要生成的最终打包文件了。(其实这里原本应该加上一个参数-i,用来表示入口文件的路径,但rollup是会把没有加参数的文件默认为是入口文件,所以咱们在这里省略了这个参数)

使用全局rollup进行打包

显示出这条信息以后,咱们发现目录下已经多出了一个 dist.js 文件,打开文件,咱们发现里面的代码是这样的

function logA() {
    console.log('function logA called');
}

logA();

此时咱们就已经完成了打包做业,能够将dist.js引入到HTML文件或是node模块中了

  1. 项目本地安装:

进入到项目目录,打开命令行输入npm install rollup --save-dev,把rollup加入到开发依赖中,而后在命令行中输入./node_modules/.bin/rollup index.js -o dist.js

使用项目本地rollup进行打包

或者在package.json文件中添加npm scripts命令"build": "rollup index.js -o dist.js",在命令行中输入npm run build来进行打包

使用项目本地rollup进行打包

在打包完成以后,咱们查看一下效果,新建一个index.html文件,在这个文件中引入咱们打包出来的dist.js文件

<!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>

用浏览器打开index.html文件,打开控制台,咱们能够看到控制台上输出了一行文字

rollup打包文件测试

使用命令行运行dist.js文件,咱们也能够看到命令行中输出了一行文字

rollup打包文件测试

这说明咱们的打包文件dist.js是能够运行的,打包成功。

PS:

  1. 接下来的demo中,默认在项目内安装了rollup
  2. 接下来的demo中,非必要状况下不会对打包结果进行运行结果测试,读者若须要验证打包效果,请本身编写其余测试代码。

demo1 使用rollup进行模块化打包

在以前打包的过程当中,命令行中输出了一行No format option was supplied – defaulting to 'es',这表示rollup并无收到任何模块化的格式指令,所以会用默认的es模块标准来对文件进行打包。

若是在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把ES6代码转化成其余形式的方法呢?这里有两个方案,一是去rollup的官网找相关的资料,二是使用rollup命令行的帮助命令,看看能不能找到相关的参数

咱们使用rollup命令行的帮助命令,在命令行中输入rollup -h

rollup命令行帮助

在这里咱们能够看到相似版本号,帮助,使用配置文件等一系列参数。在这里咱们能够找到-f这个参数,他的说明是输出的类型(amd,cjs,es,iife,umd),从括号里的内容咱们能够看出,使用这个参数能够肯定打包完后的文件的模块处理方式。(若是你还不知道这几种模块之间的区别,建议先去找一下相关的资料学习一下)

接下来咱们用rollup来打包一下,在demo0中的index.js文件里将logA()改为export default logA(),在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,运行全部打包命令,查看效果

demo1打包结果

能够看到已经输出了5种不一样模块标准的打包文件,因为字数缘由,在这里咱们只查看一个打包文件(dist.iife.js)的内容

var result = (function () {
'use strict';

function logA() {
    console.log('function logA called');
}

var index = logA();

return index;

}());

能够看到全部代码都被打包到了一个当即执行函数中,而且将函数的返回值(模块的输出内容)赋值给了一个全局变量,而这个全局变量的名称就是咱们以前设置的模块名称。

PS: 使用amd模块打包方式时,若不指定模块名称,则会打包成匿名函数,若想打包成一个具名函数,则须要使用-u--id来指定具名函数名称。

除了-f以外,还有许多其余的参数可使用,看到这里可能有些同窗会以为麻烦了,这么多参数用起来好麻烦,每次都要输一长串的命令,那么有没有更好的方法来控制rollup的参数配置呢?

固然有,接下来咱们就尝试使用配置文件来控制rollup打包。

demo2 使用配置文件来进行rollup打包

建立一个demo2,沿用以前demo1的内容,咱们在demo2的项目下建立一个文件,取名为rollup.config.js,这个文件就是rollup的配置文件了,rollup根据配置文件的输出配置来进行打包,接下来咱们在配置文件中输入配置代码:

export default {
  entry: 'index.js',
  format: 'cjs',
  dest: './dist/dist.js'
}

entry表示打包的入口文件,format表示要打包成的模块类型,dest表示输出文件的名称路径

PS: 若使用iife或umd模块打包,须要添加属性moduleName,用来表示模块的名称;若用amd模块打包,能够配置amd相关的参数(使用umd模块模式时,也会使用到amd相关配置参数):

amd: {
    id: 'amd-name',   // amd具名函数名称
    define: 'def'     // 用来代替define函数的函数名称
}

在这里咱们发现配置文件也是使用了ES6语法,这是由于rollup能够本身处理配置文件,因此可以直接用ES6的模块输出(固然,你也能够选择使用node的module.exports方式来输出配置。

在package.json文件中编写npm scripts命令

"build": "rollup -c"

-c这个参数表示使用配置文件来进行打包,若后面没有指定使用的配置文件路径,则使用默认的配置文件名称rollup.config.js

在命令行中输入npm run build,执行打包,能够看到生成了打包文件dist.js

'use strict';

function logA() {
    console.log('function logA called');
}

var index = logA();

module.exports = index;

进阶: 当rollup配置文件最终输出的不是一个对象而是一个数组时,rollup会把每个数组元素当成一个配置输出结果,所以能够在一个配置文件内设置多种输出配置

例如,咱们添加一个indexB.js文件,在这个文件中咱们将logA替换为logB,并将rollup配置文件改成:

export default [{
  entry: 'index.js',
  format: 'cjs',
  dest: './dist/distA.js'
},{
  entry: 'indexB.js',
  format: 'iife',
  moduleName: 'indexB',
  dest: './dist/distB.js'
}]

运行打包命令,发如今dist目录下生成了distA.js和distB.js两个文件,说明多项配置打包成功。

除了上面这种输出一个配置数组以外,你还能够经过配置target属性来输出多个打包文件:

export default {
  entry: 'index.js',
  targets: [{
      dest: 'dist/bundle.cjs.js',
      format: 'cjs'
    },
    {
      dest: 'dist/bundle.umd.js',
      moduleName: 'res',
      format: 'umd'
    },
    {
      dest: 'dist/bundle.es.js',
      format: 'es'
    },
  ]
}

这样配置会在dist目录下面输出bundle.cjs.jsbundle.umd.jsbundle.es.js三个打包文件,同时umd模块的名称会被定义成res。

demo3 监听文件变化,随时打包

咱们在开发过程当中,须要频繁对源文件进行修改,若是每次都本身手动输一遍打包命令,那真的是要烦死。所以,咱们选择使用rollup提供的监听功能,安装rollup-wacth模块,再在rollup命令后面加上-w参数,就能让rollup监听文件变化,即时打包。

安装watch包:

npm i rollup-watch --save-dev
// or
yarn add rollup-watch --dev

编写npm scripts:

"dev": "rollup -c -w"

执行npm run dev,看到下面的提示:

rollup 监听文件变化

好了,这个时候你就能够随便修改你的源文件了,rollup会自动为你打包的。

PS: 如果你不想监听某些文件,只要在配置文件中加上

watch: {
    exclude: ['path/to/file/which/you/want/to/ignore']
}

就好了,其中的exclude表示你想要忽略的文件的路径(支持glob模式匹配)

demo4 是时候写ES6了

ES6能够说是现代JS开发100%会用到的技术了,rollup虽然支持了解析importexport两种语法,可是却不会将其余的ES6代码转化成ES5代码,所以咱们在编写ES6代码的时候,须要引入插件来支持ES6代码的解析。

  1. 安装插件和你须要的babel preset:
npm i rollup-plugin-babel babel-preset-es2015 --save-dev
// or
yarn add rollup-plugin-babel babel-preset-es2015 --dev
  1. 建立.babalrc文件:
{
  "presets": [
    ["es2015", {
        "modules": false
    }]
  ]
}

之因此使用modules:false这个参数,是由于rollup默认是经过ES6模块语法来解析文件间的依赖,rollup默认是不支持解析common.js的模块规范的(怎么让rollup支持我会在接下来的demo中讲解),所以须要让babel不转化模块相关的语法,否则rollup在使用过程当中会报错。

  1. 编写rollup配置文件:
import babel from 'rollup-plugin-babel';

export default [{
  entry: 'index.js',
  format: 'iife',
  dest: './dist/dist.js',
  plugins: [
    babel({
      exclude: 'node_modules/**'
    })
  ]
}]

rollup的配置文件的plugins属性可让你添加在rollup打包过程当中所要用到的插件,可是要注意的是,插件的添加顺序决定了它们在打包过程当中的使用顺序,所以要注意配置文件的插件使用顺序。

  1. 编写ES6代码

在这里咱们新建三个文件,两个类Person和Man和一个入口文件index.js

export default class Person {
    constructor (name, gender = '男') {
        this.name = name
        this.gender = gender
    }

    say () {
        console.log(`个人名字是${this.name},是一个${this.gender}生`)
    }
}
import Person from './Person'

export default class Man extends Person {
    constructor (name) {
        super(name, '男')
    }
}
import Man from './src/Man'

new Man('KainStar').say()
  1. 运行打包命令npm run build

rollup babel打包1

能够看到rollup输出了一段提示文字,咱们先不去管它,先看看打包出来的文件能不能运行,执行node dist/dist.js

rollup babel打包2

能够看到代码运行成功了,那么咱们回来继续看以前的提示文字,它的意思是'classCallCheck'这个babel helper函数使用了屡次,rollup推荐咱们使用external-helpers这个插件或es2015-rollup这个babel-preset来简化打包出来的代码。

咱们查看一下打包出来的dist.js文件,发现_classCallCheck这个函数被定义了两次,分别被取名为_classCallCheck和_classCallCheck$1,这样的代码确定是能够简化的,所以咱们引入external-helpers这个插件:

npm i babel-plugin-external-helpers --save-dev
// or
yarn add babel-plugin-external-helpers --dev

修改.babelrc文件为

{
    "presets": [
        ["es2015", {
            "modules": false
        }]
    ],
    "plugins": [
        "external-helpers"
    ]
}

或者在配置文件中使用babel配置

plugins: [
    babel({
        plugins: ['external-helpers']
    })
]

注意! 在rollup-plugin-babel的官方github仓库中有一段配置是这样的:

plugins: [
    babel({
      plugins: ['external-helpers'],
      externalHelpers: true
    })
]

这段配置的使用要求是你须要设置全局的babelHelpers对象,以此来将打包文件中的babel相关代码删除,因此通常状况下不须要使用externalHelpers这个属性。

PS: 你也可使用babel-preset-es2015-rollup这个包(搭配babel-core),它集成了babel-preset-es2015,babel-plugin-transform-es2015-modules-commonjs和babel-plugin-external-helpers三个模块,使用起来更加方便,只要将.babelrc文件修改为{ "presets": ["es2015-rollup"] }就可使用了。

demo5 解析cjs,打包第三方模块

有时候咱们会引入一些其余模块的文件(第三方的或是本身编写的),可是这些第三方的模块为了可以直接使用,每每不是ES6模块而是用commonjs的模块方式编写的,这个时候咱们须要将commonjs的模块转化为ES6模块,这样才能让rollup进行正确的解析。

  1. 解析commonjs

解析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 {
  entry: 'index_cjs.js',
  format: 'iife',
  dest: './js/dist_cjs.js',
  plugins: [
    commonjs()
  ]
}

编写cjs模块的文件

exports.logA = function logA() {
    console.log('function logA called')
}

exports.logB = function logB() {
    console.log('function logB called')
}

执行打包,能够看到打包成功,也没有输出任何提示信息

rollup cjs打包

  1. 打包第三方模块

在打包第三方模块的过程当中,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

在配置文件中配置插件

import commonjs from 'rollup-plugin-commonjs'
import resolve from 'rollup-plugin-node-resolve'

export default {
  entry: 'index_module.js',
  format: 'iife',
  dest: './js/dist_module.js',
  plugins: [
    resolve({
      jsnext: true,
      main: true,
      browser: true
    }),
    commonjs()
  ]
}

jsnext表示将原来的node模块转化成ES6模块,main和browser则决定了要将第三方模块内的哪些代码打包到最终文件中。

因为commonjsnode-resolve中的配置属性不少,所以不一一解释,但愿了解更多的同窗能够去官方仓库查看说明。

编写入口文件

import compact from 'lodash/compact'

const array = [0, 1, false, 2, '', 3]
const compctedArray = compact(array)
console.log(compctedArray)

在这里咱们只引用了lodash中的compact方法,那么在最终代码里,应该也只会添加compact方法的代码。

执行打包命令,查看打包出来的文件:

(function () {
'use strict';

/**
 * Creates an array with all falsey values removed. The values `false`, `null`,
 * `0`, `""`, `undefined`, and `NaN` are falsey.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Array
 * @param {Array} array The array to compact.
 * @returns {Array} Returns the new array of filtered values.
 * @example
 *
 * _.compact([0, 1, false, 2, '', 3]);
 * // => [1, 2, 3]
 */
function compact(array) {
  var index = -1,
      length = array == null ? 0 : array.length,
      resIndex = 0,
      result = [];

  while (++index < length) {
    var value = array[index];
    if (value) {
      result[resIndex++] = value;
    }
  }
  return result;
}

var compact_1$1 = compact;

const array = [0, 1, false, 2, '', 3];
const compctedArray = compact_1$1(array);
console.log(compctedArray);

}());

确实只添加了compact方法的代码,而没有将lodash所有引入。

demo6 不要打包到一个文件,为rollup设置外部模块和全局变量

在平时的开发中,咱们常常会引入其余的模块,可是在使用的时候,咱们又不想把它们打包到一个文件里,想让他们做为单独的模块(或文件)来使用,方便浏览器端进行缓存,这个时候就须要使用配置文件中的external属性了

咱们在demo5的基础上,把jquery安装到第三方模块中

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 {
  entry: 'index.js',
  format: 'iife',
  dest: './js/dist.js',
  external: ['jquery'],
  plugins: [
    resolve({
      jsnext: true,
      main: true,
      browser: true
    }),
    commonjs()
  ]
}

external用来表示一个模块是否要被当成外部模块使用,属性的值能够是一个字符串数组或一个方法,当传入的是一个字符串数组时,全部数组内的模块名称都会被当成是外部模块,不会被打包到最终文件中

当传入的是一个方法时,方法有一个参数id,表示解析的模块的名称,咱们能够自定义解析方式,如果要当作外部模块不打包到最终文件中,则返回true,若要一块儿打包到最终文件中,则返回false

在这里咱们把jquery当成一个外部模块,执行打包命令:

rollup 添加外部模块

检查打包出来的文件,咱们发现lodash的compact方法依旧被打包进了最终文件中,可是jquery却没有被打包进去,而是以$的全局变量形式被传入到了当即执行函数中。

在这里rollup又给咱们输出了一条提示信息,意思是咱们没有在配置文件中给外部模块jquery设置全局变量名称,所以rollup本身猜想了一个名称$,当成是依赖的全局变量名。

若是直接使用全局的$的话,可能会由于变量$被其余引入的代码覆盖而报错,所以咱们要将$替换为不容易冲突的jQuery变量,在配置文件中添加globals属性:

globals: {
    jquery: 'jQuery'
}

globals的值是一个对象,key表示使用的模块名称(npm模块名),value表示在打包文件中引用的全局变量名,在这里咱们就是把jquery模块的全局变量名设置为jQuery,从新打包

在从新打包出来的文件中,咱们发现最后传入的参数已经由$变为了jQuery,并且rollup也没有输出提示信息。

demo7 打包node内置模块

有时候咱们想要在浏览器端使用node自带的一些内置模块,通常状况下会使用browserify这个工具来打包,可是browserify打包出来的文件实在太大,所以咱们用rollup选择性地导入咱们须要的node内置模块

安装插件

npm i rollup-plugin-node-builtins --save-dev
// or
yarn add rollup-plugin-node-builtins --dev

PS: node-builtins对不一样的node内置模块支持不一样,有些模块可能须要使用其余的插件(例如rollup-plugin-node-globals)才能正常打包,具体的支持状况能够查看node-builtins的官方仓库

编写配置文件

import builtins from 'rollup-plugin-node-builtins'

export default {
  entry: 'index.js',
  format: 'iife',
  dest: './dist/dist.js',
  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可能会引入过多的代码,这样会大大增长最后打包的文件的大小,使用他人的第三方库或本身的实现可控性更高

demo8 配合CDN来使用rollup

有时候咱们可能会使用CDN服务器上的js文件,可是又不想在本地安装一个相同的模块(也有可能没有对应的模块),可能在版本升级的时候会产生一些问题,这个时候咱们就须要使用rollup的paths属性了,这个属性能够帮助你把依赖的代码文件地址注入到打包以后的文件里。

编写配置文件

export default {
  entry: 'index.js',
  format: 'amd',
  dest: './dist/dist.js',
  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')

执行打包命令,最后打包出来的文件内容是:

define(['https://cdn.bootcss.com/jquery/3.2.1/jquery.js'], function ($) { 'use strict';

$ = $ && $.hasOwnProperty('default') ? $['default'] : $;

$('#p').html('rollup 使用paths属性配合CDN');

});

能够看到rollup已经把咱们须要的CDN地址做为依赖加入到了打包文件中。

demo9 最小化你的代码

代码发布时,咱们常常会把本身的代码压缩到最小,以减小网络请求中的传输文件大小。

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 {
  entry: 'index.js',
  format: 'iife',
  dest: './dist/dist.js',
  plugins: [
    uglify()
  ]
}

运行打包命令,查看dist.js文件,发现代码已经被压缩了

可是,压缩过的代码在debug时会带来很大的不便,所以咱们须要在压缩代码的同时生成一个sourceMap文件

幸运的是,rollup本身就支持sourceMap文件的生成,不须要咱们去引入其余插件,只须要在配置文件中加上:

sourceMap: true

就能够了。

从新打包,咱们发现不只生成了dist.js.map文件,并且dist文件最后加上了一行//# sourceMappingURL=dist.js.map,而且在浏览器中能够正确加载源文件

rollup sourceMap

PS: 如果将sourceMap属性的值设置为inline,则会将sourceMap的内容添加到打包文件的最后。

demo10 为你的代码添eslint检查

在大型工程的团队开发中,咱们须要保证团队代码风格的一致性,所以须要引入eslint,并且在打包时须要检测源文件是否符合eslint设置的规范,如果不符合则抛出异常并中止打包。在这里咱们使用rollup的eslint插件rollup-plugin-eslint:

安装插件

npm i eslint rollup-plugin-eslint --save-dev
// or
yarn add eslint rollup-plugin-eslint --dev

编写eslint配置文件.eslintrc

{
    "env": {
        "browser": true,
        "commonjs": true,
        "es6": true,
        "node": true
    },
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": false
        },
        "sourceType": "module"
    },
    "rules": {
        "semi": ["error","never"]
    }
}

在这里咱们强制要求不使用分号,而后在源文件中加上一个分号

foo(element);

编写rollup配置文件

import eslint from 'rollup-plugin-eslint';

export default {
  entry: './src/index.js',
  format: 'iife',
  dest: './dist/dist.js',
  plugins: [
    eslint({
      throwOnError: true,
      throwOnWarning: true,
      include: ['src/**'],
      exclude: ['node_modules/**']
    })
  ]
}

eslint插件有两个属性须要说明:throwOnError和throwOnWarning设置为true时,若是在eslint的检查过程当中发现了error或warning,就会抛出异常,阻止打包继续执行(若是设置为false,就只会输出eslint检测结果,而不会中止打包)

执行打包命令,发现eslint在输出了检查结果以后抛出了异常,并且dist.js文件也没有生成

rollup eslint抛出异常

删除index.js文件中的分号,从新打包,发现打包成功

进阶: 在平时的开发过程当中,咱们常常会使用IDE或编辑器的eslint插件,以便提前发现问题,可是有时候这些插件会去检查打包完的文件,致使你的提示框里一直会有eslint检测到错误的消息

咱们如今有两种解决方案,一是建立一个.eslintignore文件,将打包文件加进去,让eslint忽略这个文件

还有一种就是让rollup在打包文件的开始和最后自动生成注释来阻止eslint检测代码,使用这种方法时,须要使用rollup配置文件的两个属性:banner和footer,这两个属性会在生成文件的开头和结尾插入一段你自定义的字符串。咱们利用这个属性,在打包文件的开头添加/*eslint-disable */注释,让eslint不检测这个文件。

添加banner和footer属性

banner: '/*eslint-disable */'

从新打包,咱们发现打包文件的开头被插入了这段注释字符串,并且eslint插件也不报dist.js文件的错了

/*eslint-disable */
(function () {
'use strict';

// 具体代码

}());

demo11 控制开发环境和生产环境下的配置

  1. 配置文件的开发/生产环境配置

有时候咱们会须要区分开发环境和生产环境,针对不一样的打包要求输出不一样的打包配置,可是咱们又不想写rollup.config.dev.jsrollup.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 destFilePath = isProd ? './dist/dist.min.js': './dist/dist.js'

export default {
  entry: 'index.js',
  format: 'iife',
  dest: destFilePath,
  sourceMap: isProd,
  plugins: plugins
}

咱们分别运行两个npm scripts命令,查看打包的结果:

rollup 开发环境和生产环境打包结果

  1. 源文件开发/生产环境信息注入

上面是在配置文件里经过变量来改变输出的配置类型,可是咱们有时候须要将生产环境信息添加到源文件里,这个时候就须要使用rollup的配置属性intro和outro了

若是说banner和footer是在文件开始和结尾添加字符串,那么intro和outro就是在被打包的代码开头和结尾添加字符串了,以iife模式来举例,若是咱们配置了这四个属性,那么输出结果就会是:

// banner字符串
(function () {
'use strict';
// intro字符串

// 被打包的代码

// outro字符串
}());
// footer字符串

这样的形式

下面咱们实际使用一下,在index.js文件里加上一段须要依赖的代码

if (DEVELOPMENT) {
    console.log('处于开发环境')
} else {
    console.log('处于生产环境')
}

而后在咱们的rollup配置文件里添加:

intro: 'var DEVELOPMENT = ' + !isProd,

这样,当咱们最后生成的代码时,就会输出开发环境或生产环境的提示:

rollup 开发环境和生产环境信息打包结果

  1. 源文件开发/生产环境信息替换

有时候咱们会把开发/生产环境的信息直接写在源文件里面,这个时候用intro来注入代码的方式就不适合了。这个时候咱们就须要使用rollup-plugin-replace插件来对源代码的变量值进行替换:

安装插件

npm i rollup-plugin-replace --save-dev
// or
yarn add rollup-plugin-replace --dev

编写配置文件

const basePlugins = [replace({
  DEVELOPMENT: !isProd
})]

// 将intro属性注释掉
// intro: 'var DEVELOPMENT = ' + !isProd,

这里咱们使用replace插件,以key-value对象的形式,将DEVELOPMENT的值替换为!isProd的值

执行打包命令,并检查打包结果:

rollup 开发环境和生产环境信息打包结果

进阶: replace除了直接使用key-value的形式替换对应key同名变量的方法以外,还能够经过配置delimiters参数来实现模板功能:

配置replace插件参数

VERSION: '1.0.0',
delimiters: ['{{', '}}']

经过这个配置,在打包过程当中,{{VERSION}}会被替换成1.0.0

在index.js文件内添加相关代码

var version = '{{VERSION}}'
console.log('版本 v' + version)

打包的结果

var version = '1.0.0';
console.log('版本 v' + version);

demo12 使用rollup的API

有时候咱们会须要在打包的先后执行一些其余的代码,可是又不想引入其余构建工具(例如gulp),那么就可使用rollup提供的node API来编写你本身的打包流程。

rollup模块只提供了一个rollup函数,这个函数的参数和咱们编写配置文件时导出的参数不一样,减小了不少配置属性,留下来的主要是一些输入相关的配置。(具体的配置属性能够查看rollup wiki的javascript API一节)

执行这个函数返回的是一个Promise,而且在then方法中提供一个bundle对象做为参数,这个对象保存了rollup对源文件编译一次以后的结果,并且提供了generatewrite两个方法

write方法提供了编译并将打包结果输出到文件里的功能,返回的是一个没有参数的Promise,可让你自定义接下来执行的代码

generate方法是只提供了编译的功能,返回一个Promise,这个Promise有一个对象参数,包含了code(编译完以后的代码)和map(分析出来的sourceMap对象)两个属性,通常用在插件开发中

write和gengerate方法都接受有编译相关属性的对象做为传入的编译参数,而write方法还额外接受dset属性做为导出文件的名称。

在这里咱们只使用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命令修改成

"build": "node rollup.js"

执行打包命令,查看打包结果

rollup 自定义打包结果1

rollup 自定义打包结果2

在这里咱们能够看到,一个bundle能够被重复使用屡次,所以咱们能够用Promise.all方法来等待全部模块打包完成后再输出打包完毕的提示。

demo13 除了打包JS,咱们还能……

一个web项目内确定不会只有js文件,还有css、html(也多是模板文件)和其余类型的文件,那么咱们在打包的时候能不能把这些文件一块儿打包呢?

咱们须要区分一下,在这里的打包有两种意思,一种是让这些文件能够像JS文件同样,在源代码中被import并使用;还有一种是经过在源文件中import这些文件,最后将它们合并到一块儿并导出到一个最终文件内。

不一样的rollup插件有不一样的效果,在使用的时候必定要查看插件的相关说明

安装插件

npm i rollup-plugin-scss --save-dev
// or
yarn add rollup-plugin-scss --dev

编写配置文件

import scss from 'rollup-plugin-scss'

export default {
  entry: './src/js/index.js',
  format: 'iife',
  dest: './dist/js/dist.js',
  sourceMap: true,
  plugins: [
    scss({
      output: './dist/css/style.css'
    })
  ]
}

在这里咱们尝试编译和打包scss文件,将其合并成一个style.css文件,并输出到dist/css目录下

编写scss文件

$blue: #69c4eb;

.bg-blue {
    background-color: $blue
}
$white: #fff;

.text-white {
    color: $white;
}

而后在源文件中引用这两个scss文件

import '../scss/text.scss'
import '../scss/bg.scss'

var html = `
    <div class="bg-blue">
        <p class="text-white">测试文字</p>
    </div>
`

document.body.innerHTML = html

执行打包命令,查看效果

rollup 打包scss效果

extra 编写你本身的rollup插件

有时候咱们可能须要本身编写rollup插件来实现需求,rollup官方在wiki上提供了关于编写插件的一些介绍,下面咱们就根据这些介绍来写一个本身的rollup插件。

咱们在这里仿照scss插件编写一个stylus的rollup插件,让使用者能够import stylus文件,并编译打包导出到指定的目录下(为了节省代码量,只写了输出到指定路径的功能代码,其余的功能能够参考scss插件的具体代码)。

首先建立项目,在package.json文件中,除了通常信息以外,还要加上

"main": "index.cjs.js",
"module": "index.es.js",
"jsnext:main": "index.es.js"

这些信息用来区分使用不一样模块规范时使用的文件

安装咱们须要用到的模块

npm i rollup rollup-plugin-babel babel-preset-es2015-rollup babel-core --save-dev
npm i rollup-pluginutils stylus --save
// or
yarn add rollup rollup-plugin-babel babel-preset-es2015-rollup babel-core --dev
yarn add rollup-pluginutils stylus

rollup-pluginutils和stylus是咱们运行时须要的两个模块,stylus用来解析stylus文件,pluginutils则提供给了咱们一些编写插件经常使用的函数

编写rollup配置文件

import babel from 'rollup-plugin-babel'

export default {
    entry: './index.es.js',
    dest: './index.cjs.js',
    format: 'cjs',
    plugins: [
        babel()
    ]
}

rollup插件须要一个含有指定属性的对象做为插件内容,rollup官方建议咱们在编写插件的时候,export一个返回值为插件对象的函数,这样能够方便使用者指定插件的参数。

rollup会将解析的部分结果做为参数调用插件返回的对象中的一些函数属性,这些函数会在合适的时候被rollup调用(至关于rollup在执行各个操做时的钩子函数),下面咱们介绍一些经常使用的属性:

  • name:插件的名称,提供给rollup进行相关信息的输出
  • load:不指定这个属性时,解析模块会默认去读取对应路径文件的内容;而当该值为函数(id => code)时,能够将函数最后的返回值做为文件的内容提供给rollup(能够用来生成自定义格式的代码)
  • resolveId:一个( (importee, importer) => id)形式的函数,用来解析ES6的import语句,最后须要返回一个模块的id
  • transform:最常使用的属性,是一个函数,当rollup解析一个import时,会获取到对应路径文件的内容,并将内容和模块的名称做为参数提供给咱们;这个函数执行完毕以后,须要返回一个做为代码的字符串或是相似{ code, map }结构的对象,用来表示解析完以后该模块的实际内容,map指的是sourceMap,而若是咱们没有要导出的sourceMap,就能够将返回的map值设为{mappings: ''}
  • ongenerate:当咱们或rollup调用generate方法时,会被调用的一个钩子函数,接受generate的option做为参数
  • onwrite:和ongenerate同样,调用write方法时,会被调用的一个钩子函数,接受write的option做为参数

通常状况下,咱们经过transform函数来获取文件的id和内容,并对内容作一些处理,若须要输出文件则使用ongenerate或onwrite在rollup打包的最后阶段来作相应的输出。

load和resolveId在通常状况下不会使用,除非你有特殊的需求(例如对路径、模块id进行修改等)

根据上面这些内容,咱们编写具体的插件内容

import { createFilter } from 'rollup-pluginutils'
import fs from 'fs'
import path from 'path'
import stylus from 'stylus'

// 递归建立文件夹
function mkdirs(dir) {
    return new Promise((resolve, reject) => {
        fs.exists(dir, (exist) => {
            if (exist) {
                resolve()
            } else {
                mkdirs(path.dirname(dir)).then(() => {
                    fs.mkdir(dir, (err) => {
                        if (err) {
                            reject()
                        } else {
                            resolve()
                        }
                    })
                })
            }
        })
    })
}

// 导出一个function
export default function stylusPlugin(options = {}) {
    // 建立一个文件过滤器,过滤以css,styl结尾的文件
    const stylusFilter = createFilter(options.include || ['**/*.css', '**/*.styl'], options.exclude)

    // dest用来保存指定的输出路径
    let dest = options.output,
    // styleNodes用来暂存不一样文件的css代码
        styleNodes = {}

    // 编译stylus文件
    function complier(str, stylusOpt) {
        return new Promise((resolve, reject) => {
            stylus.render(str, stylusOpt, (err, css) => {
                if (err) {
                    reject(err)
                } else {
                    resolve(css)
                }
            })
        })
    }

    return {
        // 插件名称
        name: 'rollup-plugin-stylus',

        // 解析import时调用,获取文件名称和具体代码,将它们保存起来
        transform (code, id) {
            if (!stylusFilter(id)) {
                return
            }

            styleNodes[id] = code
            return ''
        },
        // generate时调用,用stylus解析代码,并输出到指定目录中
        async ongenerate (genOpt) {
            let css = ''
            for (let id in styleNodes) {
                // 合并全部css代码
                css += styleNodes[id] || ''
            }

            // 编译stylus代码
            if (css.length) {
                try {
                    css = await complier(css, Object.assign({}, options.stylusOpt))
                } catch (error) {
                    console.log(error)
                }
            }

            // 没有指定输出文件路径时,设置一个默认文件
            if (typeof dest !== 'string') {
                if (!css.length) {
                    return
                }

                dest = genOpt.dest || 'bundle.js'
                if (dest.endsWith('.js')) {
                    dest = dest.slice(0, -3)
                }
                dest = dest + '.css'
            }

            // 建立目录,并将css写入到结果文件内
            await mkdirs(path.dirname(dest))
            return new Promise((resolve, reject) => {
                fs.writeFile(dest, css, (err) => {
                    if (err) {
                        reject(err)
                    } else {
                        resolve()
                    }
                })
            })
        }
    }
}

这样,一个解析并打包stylus文件的rollup插件就写好了,你能够在你的工程中引用这个文件,也能够将其做为一个模块发布,以便于分享给其余人使用。

总结 and 一个完整的rollup项目的模板

rollup在打包JS上是一个十分快捷方便的工具,但和webpack相比,他的生态圈仍是不够强大,对于大型web工程的适应度相对不足

rollup的优势在于方便的配置,自然的ES6模块支持让咱们能够直接使用import和export语法,在打包JS上,不实现本身的模块机制,而是使用目前常见的模块规范有助于其余工具(例如requirejs)来引用打包文件;tree-shaking的特性也有助于减小代码量,所以我认为rollup比起构建应用工程项目,更适合用来构建一个JS库或node模块

我将上面介绍的插件集合到一块儿,添加了测试的支持,制做了一个较为完整的rollup工程模板。放在rollup-project-template目录下,须要的同窗能够自取(你也能够增长或删除任意你须要的模块,来组建属于你本身的rollup项目模板)

参考资料

相关文章
相关标签/搜索