Rollup 入门与实战

1,854 阅读11分钟

一、概念

Rollup 是一个 JavaScript 模块打包器,可应用于打包 applicationlibrary。但 Rollup 更专注于打包 JavaScript 类库(库、组件)。

特点:

  • Tree-shaking:静态分析 ESModule 代码,并排除未使用到的代码;
  • 相较 Webpack,它轻量、可快速构建 library;
  • Vue、React、Angular、React-Redux 等这些框架/库都在用 Rollup 作为打包工具。

命令行参数:

-c, --config <filename> 使用配置文件(如果使用参数但是值没有  
指定, 默认就是 rollup.config.js)  
-d, --dir <dirname> 构建块的目录(如果不存在,就打印到标准输出)  
-e, --external <ids> 逗号分隔列出排除的模块 ID  
-f, --format <format> 输出类型 (amd, cjs, es, iife, umd, system)  
-g, --globals <pairs> 逗号分隔列出 `moduleID:Global` 对  
-h, --help 显示帮助信息  
-i, --input <filename> 输入 (替代 <entry file>)  
-m, --sourcemap 生成 sourcemap (`-m inline` 生成行内 map)  
-n, --name <name> UMD 导出的名字  
-o, --file <output> 单个的输出文件(如果不存在,就打印到标准输出)  
-p, --plugin <plugin> 使用指定的插件(可能重复)  
-v, --version 显示版本号  
-w, --watch 监听 bundle 中的文件并在文件改变时重新构建  

--amd.id <id> AMD 模块 ID(默认是匿名的)  
--amd.define <name> 代替 `define` 使用的功能  
--assetFileNames <pattern> 构建的资源命名模式  
--banner <text> 插入 bundle 顶部(包装器之外)的代码  
--chunkFileNames <pattern> 次要构建块的命名模式  
--compact 压缩包装器代码  
--context <variable> 指定顶层的 `this` 值  
--entryFileNames <pattern> 入口构建块的命名模式  
--environment <values> 设置传递到配置文件 (看示例)  
--no-esModule 不增加 __esModule 属性  
--exports <mode> 指定导出的模式 (auto, default, named, none)  
--extend 通过 --name 定义,拓展全局变量  
--no-externalLiveBindings 不生成实施绑定的代码  
--footer <text> 插入到 bundle 末尾的代码(包装器外部)  
--no-freeze 不冻结命名空间对象  
--no-hoistTransitiveImports 不提升传递性的导入到入口构建块  
--no-indent 结果中不进行缩进  
--no-interop 不包含互操作块  
--inlineDynamicImports 使用动态导入时创建单个 bundle  
--intro <text> 在 bundle 顶部插入代码(包装器内部)  
--minifyInternalExports 强制或者禁用内部导出的压缩  
--namespaceToStringTag 为命名空间创建正确的 `.toString` 方法  
--noConflict 为 UMD 全局变量生成 noConflict 方法  
--outro <text> 在 bundle 的末尾插入代码(包装器内部)  
--preferConst 使用 `const` 代替 `var` 进行导出  
--no-preserveEntrySignatures 避免表面的构建块作为入口  
--preserveModules 保留模块结构  
--preserveSymlinks 解析文件时不要遵循符号链接  
--shimMissingExports 给丢失的导出创建填充变量  
--silent 不打印警告  
--sourcemapExcludeSources source map 中不包含源码  
--sourcemapFile <file> source map 中指定 bundle 的路径  
--no-stdin 不从标准输入中读取 "-"  
--no-strict 在生成的模块中不使用 `"use strict";`  
--strictDeprecations 不推荐使用的特性抛出错误  
--systemNullSetters 用 `null` 替换空的 SystemJS setter  
--no-treeshake 禁用 tree-shaking 优化  
--no-treeshake.annotations 忽略纯的调用注释  
--no-treeshake.moduleSideEffects 假设模块没有副作用  
--no-treeshake.propertyReadSideEffects 忽略属性访问的副作用  
--no-treeshake.tryCatchDeoptimization 不关闭 try-catch-tree-shaking  
--no-treeshake.unknownGlobalSideEffects 假设未知的全局变量不抛出  
--waitForBundleInput 等待 bundle 的输入文件  
--watch.buildDelay <number> 监听重新构建的延时  
--no-watch.clearScreen 重新构建时不进行清屏  
--watch.skipWrite 监听时不写入文件到磁盘  
--watch.exclude <files> 监听时排除的文件  
--watch.include <files> 限制监听指定的文件

下面我们从一个 Demo 入手,一步一步扩展和熟悉 Rollup 实战应用。

二、搭建环境

1、初始化工程并安装 rollup:

mkdir rollup-demo && cd rollup-demo && yarn init -y && yarn add rollup -D

2、新建入口文件:

// src/index.js
export const fn = () => {
  console.log('hello rollup');
}

3、命令执行打包:
和 webpack 相似,在没有配置文件情况下,可以通过 cli 进行最低配置基础打包。

npx rollup -i src/index.js -o dist/bundle.js -f cjs

上面命令解释为:

  • -i 指定要打包的文件,-i是--input的缩写。
  • src/index.js 是打包入口文件。
  • -o 指定输出的文件,是 --output.file 或 --file 的缩写。(如果没有这个参数,则直接输出到控制台)
  • dist/bundle.js 是输出文件。
  • -f 指定打包文件的格式,-f 是 --format 的缩写,esm 表示使用 ES6 模块规范,cjs 表示 CommonJS,格式种类有:amd, cjs, esm, iife, umd。

4、配置文件:
由于 cli 方式打包配置比较薄弱,下面我们在工程根目录下新建 rollup.config.js 作为 Rollup 配置文件:

export default {
  input: "./src/index.js",
  output: [
    {
      file: './dist/my-lib-umd.js',
      format: 'umd',
      name: 'myLib', // umd 格式必须指定 name
    },
    {
      file: './dist/my-lib-es.js',
      format: 'es'
    },
    {
      file: './dist/my-lib-cjs.js',
      format: 'cjs'
    }
  ],
  plugins: [], // 配置插件
}

5、配置脚本:
在 package.json 下配置打包执行脚本。

"scripts": {
  "build": "rollup -c rollup.config.js"
},

现在,我们就可以通过 yarn build 或者 npm run build 进行打包。

至此,基础的环境已经搭建完成,下面我们通过 Plugin 插件来完善我们的工程。

三、常用插件配置

1、@rollup/plugin-node-resolve

用于处理 import 一个模块时的解析规则,模块可以是本工程内的文件,也可以是 node_modules 第三方依赖模块,有了它,import 一个目录时会默认指向目录下的 index 文件。

// 不配置 @rollup/plugin-node-resolve 插件引入方式
import module from './module/index';

// 配置了 @rollup/plugin-node-resolve 插件引入方式
import module from './module';
  • 安装:
yarn add @rollup/plugin-node-resolve -D 
  • 配置:
import resolve from '@rollup/plugin-node-resolve';
const extensions = [ '.ts', '.tsx', '.js', '.jsx'];

plugins: [
  resolve({
    extensions, // 指定 import 模块后缀解析规则
  }),
  ...
]

2、@rollup/plugin-commonjs

rollup 默认只支持 ES6+ 模块方式的 import / export,由于 node_modules 中大多数包都是 CommonJS 方式,导致无法直接引入使用,这就需要配置 CommonJS 插件。

  • 安装:
yarn add @rollup/plugin-commonjs -D
  • 配置:
import commonjs from '@rollup/plugin-commonjs';

plugins: [
  ...
  commonjs(),
],

3、@rollup/plugin-babel

这个大家都很熟悉,babel,就是转换 ES6+ 语法、api 为浏览器都识别的基础语法。

  • 安装:
yarn add @rollup/plugin-babel @babel/core @babel/cli @babel/preset-env -D
yarn add @babel/plugin-transform-runtime -D
yarn add @babel/runtime
  • 配置:
import { babel } from '@rollup/plugin-babel';
const extensions = ['.js','.jsx','.ts','.tsx'];

export default {
  input: 'src/index.js',
  output: {
    dir: 'dist',
    format: 'umd',
    name: 'myLib',
  },
  external: [
    /@babel\/runtime/
  ],
  plugins: [
    ...
    babel({
      extensions,
      presets: [
        '@babel/preset-env'
      ],
      plugins: [
        '@babel/transform-runtime'
      ],
      babelrc: false, // 忽略工程内的 babel 配置文件,使用 rollup 这里的配置
      babelHelpers: 'runtime', // 当工程作为程序应用时推荐使用 bundled(默认值),当构建库时推荐使用 runtime。
    })
  ]
};

注意这三个插件顺序:@rollup/plugin-commonjs 要在 @rollup/plugin-babel 配置之前,在 @rollup/plugin-node-resolve 配置之后。

4、@rollup/plugin-alias

进行路径别名。类似于 webpack.resolve.alias,一般我们会配置 @ 代替 src 目录来简化 import Path。

  • 安装:
yarn add @rollup/plugin-alias -D
  • 配置:
import alias from '@rollup/plugin-alias';

plugins: [
  ...
  alias({
    entries: [
      { find: '@', replacement: path.resolve(__dirname, 'src') },
    ],
  }),
]
  • 使用:
import module1 from '@/module1'; // src/module1.js

另外,如果想让编辑器(VSCode)鼠标移动在路径上通过 command + 点击快速进入到模块文件,可以新建 jsconfig.json 进行配置:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

5、rollup-plugin-terser

用于压缩代码,减少代码体积(代码压缩至一行、删除代码内注释)。

  • 安装:
yarn add rollup-plugin-terser -D
  • 配置:
import { terser } from "rollup-plugin-terser";

plugins: [
  ...
  terser(),
]

6、@rollup/plugin-replace

埋入环境变量。比如让打包资源支持对 process.env.NODE_ENV 变量的访问。

  • 安装:
yarn add @rollup/plugin-replace -D
  • 配置:
import replace from '@rollup/plugin-replace';

plugins: [
  ...
  replace({
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  }),
]

四、支持 TypeScript

Rollup 团队提供了一个无缝连接 TypeScript 插件:@rollup/plugin-typescript,但自己在使用过程中发现编译工作会与 @rollup/plugin-eslint 配置的 rules 产生冲突,如:no-var,最后决定采用 babel 来转换 TS。

  • 安装:
yarn add typescript @babel/preset-typescript -D
  • 配置:
babel({
  presets: [
    ...
    ["@babel/preset-typescript", {
      "isTSX": true,
      "allExtensions": true,
    }]
  ],
}),
  • 注意: 将 ts 转换工作交给 babel 处理后,它不会输出 .d.ts 类型声明文件,这一工作需要手动执行 ts 命令进行输出,特别注意在 tsconfig.json 中需要进行以下配置:
{
  "compilerOptions": {
    "noEmit": false, // 关闭不输出开关
    "emitDeclarationOnly": true, // 仅输出类型声明文件
    "declaration": true, /* Generates corresponding '.d.ts' file. */
    "declarationDir": "types", // 类型声明文件输出目录
    ...
  },
}
  • 执行脚本:
"scripts": {
  "build:types": "yarn run tsc",
}

五、接入 React

由于团队内选用的组件库技术栈为 React,下面我们通过配置让 Rollup 打包支持 React。

  • 安装:
yarn add react react-dom
yarn add @babel/preset-react @types/react @types/react-dom -D
  • 配置:
plugins: [
  babel({
    ...
    presets: [
      ...
      '@babel/preset-react'
    ],
  }),
]
  • 使用:
import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
  return (
    <div>
      Hello React!
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'));

注意,React JSX 语法必须在 .jsx / .tsx 文件内使用,否则 Rollup 在打包时会无法识别 JSX 语法。

六、外部扩展

外部扩展(external),和 webpack external 配置一致,防止将某些 import 的第三方以来,打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖。

比如:react、react-dom,如果不排除,默认会将它们全量代码进行打包。

一般构建一个 library,而非 application 时,这个配置非常有用。

  • 配置:
output: {
  dir: 'dist',
  format: 'cjs',
  name: 'myLib',
  globals: {
    'react': 'React', // key 为包名,value 为包暴露给 window 上的变量名
    'react-dom': 'ReactDOM',
  },
},
external: [
  'react',
  'react-dom',
],

七、ESLint

一个好的工程,离不开 ESLint 去约束开发者的编程习惯。下面我们配置 ESLint 接入到 Rollup 打包流程中,并且支持 React、TypeScript 相关的 ESLint 校验。

  • 安装:
yarn add eslint @rollup/plugin-eslint -D
yarn add @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
yarn add eslint-plugin-react -D
  • Rollup 接入 ESLint:
import eslint from '@rollup/plugin-eslint';

plugins: [
  eslint(), // 使用 .eslintrc.js 配置文件
]
  • ESLint 配置文件:
// .eslintrc.js
module.exports = {
  "env": { // 指定代码的运行环境
    "browser": true,
    "es6": true
  },
  // 使用 eslint-plugin-react 插件时需要指定 React 版本
  "settings": {
    "react": {
      "version": "17" // or "detect"
    }
  },
  "extends": [
    "plugin:react/recommended"
  ],
  "plugins": [
    "react",
    "@typescript-eslint"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": "latest", // 支持 ES 最新版本的语法校验
    "sourceType": "module" // 设置 ECMAScript modules
  },
  // rules: https://eslint.bootcss.com/docs/rules/
  "rules": {
    // 禁止出现定义了,但未使用过的变量
    "no-unused-vars": "warn",
    // 阻止 var 的使用,推荐用 let 和 const
    "no-var": "warn",
  }
};


// .eslintignore
node_modules
dist

注意:关于 .eslintignore 在 VSCode 编辑器下不生效情况:如果 eslintignore 配置文件,不在 VSCode 打开的工程跟目录下,可能造成不生效。

  • 执行脚本:
"scripts": {
  "lint": "eslint src --ext .js,.jsx,.ts,.tsx",
  "lint-fix": "eslint src --ext .js,.jsx,.ts,.tsx --fix"
}

八、本地服务

通常我们会有一个本地服务来运行和调试代码。需要注意的是:rollup serve 并不会像 webpack-dev-server 那样开发环境下将打包资源生成在内存中,而是需要先进行打包,然后 HTML 文件中引入打包后的资源。

  • 安装:
yarn add rollup-plugin-serve rollup-plugin-livereload -D
  • 配置:
import serve from 'rollup-plugin-serve';
import livereload from 'rollup-plugin-livereload';

const isEnvProduction = process.env.NODE_ENV === 'production';

plugins: [
  !isEnvProduction && serve({
    contentBase: '',  // 服务器启动的文件夹,默认是项目根目录,需要在该文件下创建index.html
    port: 3000
  }),
  !isEnvProduction && livereload('dist'), // watch dist目录,当目录中的文件发生变化时,刷新页面
],

注意:我们需要提供 index.html template 文件,并且需要手动引入打包后的 js 和 css 资源。

九、样式处理

Rollup 提供了 rollup-plugin-postcss 插件来解析 css 文件。

  • 安装:
yarn add postcss rollup-plugin-postcss -D
  • 配置:
import postcss from 'rollup-plugin-postcss';

plugins: [
  ...
  postcss(),
]
  • 提取 CSS: 默认 Rollup 会将样式通过 style 标签注入到 head 标签内,如果想提取 CSS,可配置:
plugins: [
  postcss({
    minimize: true, // 压缩 css
    extract: true, // 默认提取的样式文件名为:index.css
    // extract: path.resolve('dist/my.css'), // 自定义样式文件名
  }),
]
  • 支持 sass: 只需要安装 node-sass,即可支持 .scss/.sass 文件解析:
yarn add node-sass@^6.0.0 -D
  • 支持浏览器厂商前缀:

    • 安装:
    yarn add autoprefixer -D
    
    • 配置:
    import autoprefixer from 'autoprefixer';
    
    plugins: [
      ...
      postcss({
        plugins: [
          autoprefixer(),
        ]
      }),
    ]
    

    autoprefixer 除了上面的配置,还需要指定 browserslist 来确定我们程序运行的目标环境,避免去兼容程序不考虑的浏览器环境。我们在 package.json 下:

    "browserslist": [
      "> 1%", // 全球超过 1% 人使用的浏览器
      "last 2 versions", // 所有浏览器兼容到最后两个版本,比如 IE 的最新版本为 11,向后兼容两个版本即为 10、11
      "not ie <= 8" // 排除指定的浏览器版本
    ]
    
  • 自动引入 css 文件: 在打包后,如果希望打包出的 js 资源自动去引入 css 文件,可以在 output 配置:

output: {
  format: 'es',
  dir: 'dist',
  banner: 'import "index.css"', // 自动引入样式文件
},

十、图片资源

类似于 webpack url-loader,在 Rollup 中可以使用 @rollup/plugin-url 处理图片 base64 资源。

  • 安装:
yarn add @rollup/plugin-url -D
  • 配置:
import url from '@rollup/plugin-url';

plugin: [
  url({
    limit: 10240, // 大于10k,打包生成单独静态资源,否则处理成 base64
    fileName: 'assets/[name].[hash][extname]',
  }),
]

十一、环境变量

cross-env 是跨平台设置和使用环境变量的脚本。由于在大多数 Windows 命令行中在使用 NODE_ENV = production 设置环境变量时会报错,使用 cross-env 可以设置在不同的平台上有相同的 NODE_ENV 参数。

  • 安装:
yarn add cross-env -D
  • 执行脚本:
"scripts": {
  "build": "cross-env NODE_ENV=production rollup -c rollup.config.js",
},
  • 配置中读取环境变量:
const isEnvProduction = process.env.NODE_ENV === 'production';

plugins: [
  isEnvProduction && terser(),
]

十二、代码分割,将每个模块文件作为独立文件输出

按照源码的目录结构进行输出,常用于构建组件库实现按需加载。使用示例如下:

import { babel } from "@rollup/plugin-babel";
const extensions = [".js", ".jsx", ".ts", ".tsx"];

export default {
  input: "lib/index.js",
  output: {
    dir: "es/",
    format: "es",
    preserveModules: true, // 关键
  },
  plugins: [
    babel({
      extensions,
      presets: ["@babel/preset-env"],
      babelrc: false, // 忽略工程内的 babel 配置文件,使用 rollup 这里的配置
    }),
  ],
};

preserveModules 的使用参考:rollup.nodejs.cn/configurati…

最后

可以看到,一个完整的工程配置还是很多的,比如还可以加入 Git Commit 相关规范校验、组件库快速生成文档等功能。

感谢阅读。