卷起来 —— Rollup 入门指南

666 阅读7分钟

通常前端开发过程中,大家使用的打包工具都是 webpack 或 vite,但近期一个需求需要我开发一个js库。但是webpack在开发js库上并不是很适用。通过一番搜索,最终确定使用 rollup 来进行打包处理。

什么是 Rollup

Rollup 是一个用于 JavaScript 的模块打包工具,它将小的代码片段编译成更大、更复杂的代码,例如库或应用程序。

它的优点如下:

  • 支持多种输入格式:ES Module、CommonJS、UMD、SystemJS等,打包后的bundle不仅适用于web,还适用于其他许多平台
  • Tree-Shaking:静态分析你导入的代码,并将排除任何实际上没有使用的内容。这使你可以在现有的工具和模块的基础上构建,而不需要添加额外的依赖项或使项目的大小变得臃肿
  • 强大的插件
  • ……

下面,我们就来看看如何使用 rollup。

一、基础使用

  1. 首先我们创建一个文件夹 “rollup-test”,然后切换到该文件夹下执行项目初始化命令:
npm init --yes
  1. 根目录下创建 src 目录,并在该文件夹下创建 index.js 文件,文件内容如下:
export function sayHelloWorld() {
  console.log('hello world');
}
  1. 安装 rollup 以及相关插件
npm install --save-dev @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup
  • @rollup/plugin-node-resolve:用于解析 node_modules 中的第三方依赖
  • @rollup/plugin-commonjs用于将CommonJS模块转换为ES6模块。这样可以使得那些使用CommonJS规范编写的模块能够在Rollup中正常工作。它允许开发者将基于Node.js的模块引入到他们的Rollup项目中,并以ES6模块的形式进行处理和打包。

安装好后,在根目录下创建 rollup 的配置文件 rollup.config.js,并写入如下基本配置:

// rollup.config.js
import path from 'path'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'

const config = {
  // 入口文件,src/index.ts 
  input: path.resolve(__dirname, 'src/index.js'),
  // 输出文件 
  output: {
    file: 'dist/index.js',
    format: 'cjs'
  },
  // 插件
  plugins: [
    resolve(),
    commonjs(),
  ]
}

export default config

至此,一个基本的项目就构建好了,大致结构如图所示:

image.png

然后我们运行打包命令:

rollup --config

打包后就会在 dist 目录下生成 index.js 文件:

image.png

然后我们在命令行进行测试,可以看到我们可以成功调用 sayhelloWorld 函数:

image.png

1.1 输出多种格式

rollup 支持多种输出格式,我们可以通过配置 format 字段来指定输出的格式:

import path from 'path';
...
import pkg from './package.json'

const config = {
  // 入口文件,src/index.ts 
  input: path.resolve(__dirname, 'src/index.ts'),
  // 输出文件 
  outputoutput: [
    // commonjs
    {
      file: pkg.main,
      format: 'cjs'
    },
    // es module
    {
      file: pkg.module,
      format: 'es'
    },
    // umd
    {
      // umd 导出文件的全局变量 
      name: 'rollupTest'
      file: pkg.umd,
      format: 'umd'
    },
  ],
  plugins: [
    ...
  ]
}

export default config

package.json 文件中加入对应的字段:

"main": "./dist/index.js",
"module": "./dist/index.esm.js",
"umd": "./dist/index.umd.js",

打包后就会在 dist 目录下生成三个不同格式的文件:

image.png

1.2 自动清空 dist

如果希望每次打包能够自动清空 dist 文件夹,可以使用 rollup-plugin-clear 插件:

npm install --save-dev rollup-plugin-clear

安装好后,在 rollup.config.js 中加入如下配置:

...
import clear from 'rollup-plugin-clear';

const config = {
  ...
  // 插件
  plugins: [
    ...
    clear({
      target: ['dist']
    })
  ]
}

2. 使用 Babel

为了兼容低版本的的浏览器或其他环境,我们需要将高版本的JavaScript代码转换成向后兼容的JavaScript代码。但是 rollup 本身是不会进行这些处理的,例如我们在src/index.js文件中写入 ES6 的箭头函数代码。

const sayHelloWorld = () => {
  console.log('hello world');
}

exports.sayHelloWorld = sayHelloWorld;

如下图,可以看到打包后的代码中并没有对箭头函数进行转换。

image.png

对此,我们需要使用 Babel 对代码进行转换。

Babel 是一个广泛使用的 JavaScript 编译器,它可以将最新的 ECMAScript 语言特性转换为向后兼容的 JavaScript 版本,以便在旧版本的浏览器或环境中运行。

首先安装 babel 相关的库:

npm install --save-dev @babel/core @babel/preset-env @babel/plugin-transform-runtime
  • @babel/core:Babel 的核心包,用于配置和初始化 Babel
  • @babel/preset-env:配置目标环境,babel会根据环境来转换那些它不支持的语法

然后安装 rollup 的 babel 插件@rollup/plugin-babel

npm install --save-dev @rollup/plugin-babel

安装好后,在 rollup.config.js 中加入如下配置:

...
import { babel } from '@rollup/plugin-babel'

const config = {
  ...
  // 插件
  plugins: [
    ...
    babel({
      // 编译库
      babelHelpers: 'runtime',
      // 不转义依赖
      exclude: 'node_modules/**',
    }),
  ]
}

export default config

并添加babel配置文件 babel.config.js:

// babel.config.js
module.exports = {
  "presets": [
    [
      "@babel/preset-env",
      {
        /* Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS,导致 Rollup 的一些处理失败 */
        "modules": false
      }
    ]
  ],
  "plugins": [
    [
      // 与 babelHelpers: 'runtime' 配合使用 
      "@babel/plugin-transform-runtime"
    ]
  ]
}

可以看到,打包后的代码中 ES6 语法已经进行了转换: image.png

3. 支持 Typescript

安装typescript:

npm i --save-dev typescript

安装 rollup 的 typescript 插件 rollup-plugin-typescript2

npm i --save-dev rollup-plugin-typescript2

rollup.config.js 中加入如下配置:

...
import { babel } from '@rollup/plugin-babel'

const config = {
  ...
  // 插件
  plugins: [
    ...
    typescript(),
    babel({
      ...
      // babel 默认不会转换ts
      extensions: ['js', 'ts']
    }),
  ]
}

export default config

根目录下添加 tsconfig.json 配置文件

{
  "compilerOptions": {
    /* 基础选项 */
    "target": "esnext", /* 指定 ECMAScript 目标版本:'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "esnext", /* 输出的代码使用什么方式进行模块化: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "lib": [ /* 指定引用的标准库 */
      "esnext",
      "dom",
      "dom.iterable",
    ],
    "declaration": "true", /* 自动生成类型声明 */
    "sourceMap": true,
    "allowJs": true, /* 允许编译 js 文件 */
    "removeComments": true, /* 输出不包含注释 */
    /* 严格类型检查选项 */
    "strict": true, /* 启用所有严格类型检查选项 */
    "noImplicitAny": true, /* 检查隐含 any 类型的表达式和声明 */
    "strictNullChecks": false, /* 严格空检查. */
    /* 额外检查 */
    "noUnusedLocals": true, /* 检查无用的变量. */
    /* Module Resolution Options */
    "moduleResolution": "node", /* 指定模块查找策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6) */
    "baseUrl": "./", /* 查找模块的基础目录 */
    "paths": {
      "@/*": [
        "src/*"
      ]
    }, /* 记录 baseUrl 的模块路径查找别名 */
    "types": [], /* 类型声明文件 */
  },
  "include": [
    /* 指定编译处理的文件列表 */
    "src/*.ts",
    "src/types.ts"
  ],
}

Tips: typescript 配置可根据自己需求修改。

4. 压缩代码

当我们是在生产(production)环境中时,我们可以使用 rollup-plugin-terser 插件对代码进行压缩

npm install --save-dev rollup-plugin-terser

rollup.config.js 中加入如下配置:

...
import {terser} from 'rollup-plugin-terser';
const env = process.env.NODE_ENV;

const config = {
  ...
}

// 生产环境,压缩代码 
if (env === 'production') {
  config.plugins.push(terser({
    compress: {
      pure_getters: true,
      unsafe: true,
      unsafe_comps: true,
      warnings: false
    }
  }))
}

export default config

我们可以使用 corss-env用于设置环境变量。并在 package.json中加入如下script:

"scripts": {
  "build": "cross-env NODE_ENV=production rollup --config",
},

然后执行 npm run build 进行打包。

5. server 和 热更新

当我们在开发环境(development)时可以使用rollup-plugin-dev插件开启本地server,并使用 rollup-plugin-livereload 插件进行热更新。

npm install  --save-dev rollup-plugin-dev rollup-plugin-livereload

rollup.config.js 中加入如下配置:

...
import {terser} from 'rollup-plugin-terser';
const env = process.env.NODE_ENV;

const config = {
  ...
}

...

// 开发环境
if (env === 'development') {
  config.plugins.push(
    livereload(),
    dev({
      port: 3000,
    })
  )
}

export default config

package.json 中加入如下 script:

"scripts": {
    "dev": "cross-env NODE_ENV=development rollup --config -w"
},

此外我们还需要在根目录下创建一个 index.html 文件,并引入打包后的index.esm.js文件。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="dist/index.esm.js" type="module"></script>
</body>
</html>

然后运行 npm run dev 即可在本地进行调试啦。

  • 本地调试并不一定要按本文的方式
  • 在本地开启 server 也可以使用 rollup-plugin-serve插件。

有关 Rollup 的基本使用就介绍到这里了,当然还有很多方面还没涉及到。最后附上本文中测试项目中的相关代码:

  • pcakage.json
{
  "name": "rollup-test",
  "version": "1.0.0",
  "description": "",
  "main": "./dist/index.js",
  "module": "./dist/index.esm.js",
  "umd": "./dist/index.umd.js",
  "scripts": {
    "build": "cross-env NODE_ENV=production rollup --config",
    "dev": "cross-env NODE_ENV=development rollup --config -w"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.23.7",
    "@babel/plugin-transform-runtime": "^7.23.6",
    "@babel/preset-env": "^7.23.8",
    "@rollup/plugin-babel": "^6.0.4",
    "@rollup/plugin-commonjs": "^25.0.7",
    "@rollup/plugin-node-resolve": "^15.2.3",
    "cross-env": "^7.0.3",
    "rollup": "^2.79.1",
    "rollup-plugin-clear": "^2.0.7",
    "rollup-plugin-dev": "^2.0.4",
    "rollup-plugin-livereload": "^2.0.5",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.36.0",
    "typescript": "^5.3.3"
  }
}
  • rollup.config.js
import path from 'path'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import clear from 'rollup-plugin-clear';
import { babel } from '@rollup/plugin-babel'
import typescript from 'rollup-plugin-typescript2'
import {terser} from 'rollup-plugin-terser';
import dev from 'rollup-plugin-dev';
import livereload from 'rollup-plugin-livereload'
import pkg from './package.json'

const env = process.env.NODE_ENV;

const config = {
  // 入口文件,src/index.ts 
  input: path.resolve(__dirname, 'src/index.ts'),
  // 输出文件 
  output: [
    {
      file: pkg.main,
      format: 'cjs'
    },
    {
      file: pkg.module,
      format: 'es'
    },
    {
      file: pkg.umd,
      format: 'umd'
    },
  ],
  // 插件
  plugins: [
    resolve(),
    commonjs(),
    typescript(),
    babel({
      // 编译库
      babelHelpers: 'runtime',
      // 不转义依赖
      exclude: 'node_modules/**',
    }),
    clear({
      targets: ['dist']
    })
  ]
}

// 开发环境
if (env === 'development') {
  config.plugins.push(
    livereload(),
    dev({
      port: 3000,
    })
  )
}

// 生产环境,压缩代码 
if (env === 'production') {
  config.plugins.push(terser({
    compress: {
      pure_getters: true,
      unsafe: true,
      unsafe_comps: true,
      warnings: false
    }
  }))
}

export default config