关于Rollup的学习

433 阅读7分钟

Rollup 概述

rollup 仅仅是一款 JavaScript 模块打包器,也称为 ESM 打包器,并没有像 webpack 那样有很多其他额外的功能,它可以将项目中散落的细小模块打包成整块的代码,可以让他们更好的运行在浏览器环境或者 Node.js 环境

官方文档:Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library、或应用程序。Rollup 对代码模块使用新的标准化格式,这些标准都包含在 JavaScriptES6 版本中,而不是以前的特殊解决方案,如 CommonJSAMD

webpack 偏向于应用打包的定位不同,rollup 更专注于 JavaScript 类库打包。目前主流的前端框架vuereact都采用rollup来打包。

rollup 最大的亮点就是 Tree-shaking,即可静态分析代码中的 import,并排除任何未使用的代码。这允许我们架构于现有工具和模块之上,而不会增加额外的依赖或使项目的大小膨胀。如果用 webpack 做,虽然可以实现 tree-shaking,但是需要自己配置并且打包出来的代码非常臃肿,所以对于库文件和 UI 组件,rollup 更加适合。

Rollup 和 Webpack 的对比

  1. webpack对于代码分割和静态资源导入有着“先天优势”,并且支持热模块替换HMR,而Rollup并不支持类似HMR这种高级特性;
  2. Rollup的初衷只是提供一个高效的ES Modules的打包器,充分利用ESM的各项特性构建出结构比较扁平,性能比较出众的类库;
  3. 我们开发应用时可以优先选择webpack打包,但是如果你的项目是基于ES6开发的,并且只需要打包出一个简单的bundle,可以考虑使用rollup来打包;

rollup 简单入门

  1. 安装 rollup
npm i rollup -g
  1. 创建一个新项目
mkdir -p my-rollup
  1. 创建一个入口并写入简单的代码
// src/hello.js
import testFun from './test.js'
export { testFun }

// src/test.js
export default function (name) {
  console.log(name)
}
  1. 基本代码准备好了之后,在根目录下创建一个rollup 配置文件
// rollup.config.js

export default {
  input: 'src/hello.js',//入口文件路径
  //output表示输出文件的内容,允许传入一个对象或者一个数组,当为数组时,依次输出多个文件
  output: {
    file: 'bundle.js', //输出文件路径
    //amd表示采用AMD标准,cjs为CommonJS标准,esm(或es)为ES模块标准
    format: 'cjs',//打包后生成代码的格式,可选项['amd','cjs','system','esm(或es)','iife','umd']
    banner:'',//文件头部添加的内容一般可以是license之类的
    footer:'',//文件末尾添加的内容
  },
}
  1. 随后我们在终端执行
// --config 或 -c 来使用配置文件
rollup -c

这样在根目录下就生成了我们想要的打包后的文件 bundle.js,里面便是我们打包后的代码:

'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function test (name) {
 console.log(name);
}

exports.testFun = test;

这时候我们就可以在文件中引入bundle.js中的testFun,并且使用他:

import { testFun } from '../bundle.js';
testFun('你,过来啊!');

就可以在控制台中看到打印的东西啦

image.png

以上便是rollup最简单的使用方法,到这里我们算是已经上手rollup啦,当然,在实际项目中,我们还可以在package.json中设置打包配置信息,利用npm run XXX来打包和测试代码。

rollup 插件使用

如果要加载其他类型的资源文件,或者是导入CommonJS模块,或者编译ES6新特性,我们可以配置 rollup 插件,插件是rollup唯一的扩展途径,相比之下,webpack则有pluginsmoduleoptimization三种途径; 常见的比较实用的rollup插件有:

@rollup/plugin-node-resolve //帮助 Rollup 查找外部模块,然后导入

@rollup/plugin-commonjs //将CommonJS模块转换为 ES2015 供 Rollup 处理

@rollup/plugin-babel //让我们可以使用es6新特性来编写代码

rollup-plugin-terser //压缩js代码,包括es6代码压缩

rollup-plugin-eslint //js代码检测
  1. resolve插件:在上面的小例子里我们打包的是本地js代码,但是在实际的开发中,我们肯定需要通过npm下载远程的库,rollup默认只能按照路径的方式加载本地模块,所以对于第三方模块需要插件导入(webpack可以通过名称导入第三方模块);

  2. commonjs插件:由于rollup编译源码中的模块引用默认只支持ES6+的模块方式import/export,然而大量的npm模块是基于CommonJS模块方式,这就导致很多npm模块不能直接编译使用,只得借助插件的力量来支持;

  3. babel插件:自然是完成代码转换啦,当我们在代码中写入一下ES6的新语法时,比如箭头函数,如果不借助babel插件转码而直接打包,箭头函数会被保留在打包后的代码里,这样的代码在不支持ES6的环境下是不能运行的,而babel插件可以帮助rollup在打包的过程中完成代码的转换(在使用babel插件时需注意,需要同时安装@babel/core

我们可以这样使用(先安装后使用),类似于 webpackplugin 配置:

import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import babel from '@rollup/plugin-babel'
import { terser } from 'rollup-plugin-terser'
import { eslint } from 'rollup-plugin-eslint'

export default [
  {
    input: 'src/hello.js',
    output: {
      name:'hello',//生成包名称
      file: './lib/hhh.js',//文件夹需要提前创建
      format: 'umd',
    },
    plugins: [
      resolve(), 
      commonjs(), 
      eslint(),
      babel(),
      terser(),
    ],
  },
]

这样一个简单的配置就可以实现基本的 js 文件打包,但是我在打包的过程中遇到了一个问题:

image.png

大致是因为:

Bable 插件有一个babelHelpers 的配置参数,取值为bundled | runtime | inline | external,默认是bundled

当你在开发一个应用的时候,建议使用bundled,会把bable的一些helpers函数直接打包进去; 如果你是开发lib,那么更加推荐runtime,这种方式不会打包这些helpers,而是在开发者最终打包的时候一起打包。

export default [
    //...
    plugins: [
     //...
      babel({ babelHelpers: 'runtime' }),//修改默认配置
      //...
    ],
  },
]

后面就可以成功打包啦!

一些其他的详细配置

利用 babel 来编译 es6 代码

首先我们先安装 babel 相关模块:

npm i core-js @babel/core @babel/preset-env @babel/plugin-transform-runtime

然后设置.babelrc 文件

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false,
      }
    ]
  ],
  "plugins": [
    // 解决多个地方使用相同代码导致打包重复的问题
    ["@babel/plugin-transform-runtime"]
  ],
  "ignore": ["node_modules/**"]
}

@babel/preset-env 可以根据配置的目标浏览器或者运行环境来自动将 ES2015+的代码转换为 es5。需要注意的是,我们需要设置 "modules": false,否则 Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS,导致 Rollup 的一些处理失败。

为了解决多个地方使用相同代码导致打包重复的问题,我们需要在 .babelrcplugins 里配置 @babel/plugin-transform-runtime,同时我们需要修改 rollup 的配置文件:

export default {
  plugins: [
    //...
    babel({
    exclude: 'node_modules/**', // 防止打包node_modules下的文件
    }),
    //...
  ],
};

区分测试环境和开发环境

我们可以在 package.json 中配置不同的执行脚本和环境变量来对开发和生产做不同的配置:

// package.json
"scripts": {
    "build": "NODE_ENV=production rollup -c",
    "dev": "rollup -c -w",//这里的-w表示监听源文件是否有改动,如果有则自动重新打包
  },

我们可以手动导出 NODE_ENVproductiondevelopment 来区分生产和开发环境,然后在代码中通过 process.env.NODE_ENV 来获取参数。这里我们主要用来设置在开发环境下不压缩代码:

const isDev = process.env.NODE_ENV !== 'production'
// ...
plugins: [!isDev && terser()]

我们可以使用上面的提到的 @rollup/plugin-eslint 来配置使用 eslint 来做代码检测,可以建立.eslintrc.js来根据自己风格配置具体检:

export default {
  plugins: [
    //...
    eslint({
      throwOnError: true,
      throwOnWarning: true,
      include: ["src/**"],
      exclude: ["node_modules/**"],
    }),
    //...
  ],
};

external 属性

使用 rollup 打包,我们在自己的库中需要使用第三方库,例如 lodash 等,又不想在最终生成的打包文件中出现 jquery。这个时候我们就需要使用 external 属性。比如我们使用了 lodash


// rollup.config.js
{
    input: 'src/main.js',
    // 指出应将哪些模块视为外部模块,不会与你的库打包在一起
    external: ['lodash'],
    output: [
        { file: pkg.main, format: 'cjs' },
	{ file: pkg.module, format: 'es' }
    ]
}

在上面的配置文件中,出现了pkg.main等类似字段,这个是因为我们可以引入package.json文件,在里面可以定义相关字段

{
  "name": "hhhh",
  "version": "0.1.0",
  "main": "dist/hhh.cjs.js",
  "module": "dist/hhh.esm.js",
  "browser": "dist/hhh.umd.js",
  "private": true,
  "scripts": {
    //...
  },
  "dependencies": {
    //...
  },
  "devDependencies": {
    //...
  }
}

导出模式

我们可以将自己的代码导出成 commonjs 模块,es 模块,以及浏览器能识别的模块,通过如下方式设置:

{
  input: 'src/main.js',
  external: ['ms'],
  //会依次输出多个文件
  output: [
	{ file: pkg.main, format: 'cjs' },
	{ file: pkg.module, format: 'es' },
	{ file: pkg.browser, format: 'umd' }
  ]
}