【实战篇】最详细的Rollup打包项目教程

17,165 阅读8分钟

我报名参加金石计划 1 期挑战——瓜分 10 万奖池,这是我的第 5 篇文章,点击查看活动详情

介绍

本文带你一起使用 Rollup 打包项目,实现以下功能:

  • 自动将 dependencies 依赖声明为 externals
  • 支持处理外部 npm 依赖
  • 支持基于 CommonJS 模块引入
  • 支持 typescript,并导出声明文件
  • 支持 scss,并添加前缀
  • 支持自动清除调试代码
  • 打包输出文件保留原始模块结构
  • 支持按需加载

一、什么是 rollup

rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。

二、为什么是 rollup

为什么是 rollup 而不是 webpack 呢?

rollup的特色是 ES6 模块和代码 Tree-shaking,这些 webpack 同样支持,除此之外 webpack 还支持热模块替换、代码分割、静态资源导入等更多功能。

当开发应用时当然优先选择的是 webpack,但是若你项目只需要打包出一个简单的 bundle 包,并是基于 ES6 模块开发的,可以考虑使用 rollup

rollup 相比 webpack,它更少的功能和更简单的 api,是我们在打包类库时选择它的原因。

三、支持打包的文件格式

rollup 支持的打包文件的格式有 amd, cjs, es\esm, iife, umd。其中,amd 为 AMD 标准,cjs 为 CommonJS 标准,esm\es 为 ES 模块标准,iife 为立即调用函数, umd 同时支持 amd、cjs 和 iife。

四、快速开始

1. 安装

npm install --global rollup

2. 基础打包

新增文件 src/main.js

// src/main.js
import foo from "./foo.js";
export default function () {
  console.log(foo);
}

新增文件 src/foo.js

export default "hello world!";

项目根目录下新增文件 rollup.config.js

export default {
  input: "src/main.js",
  output: {
    file: "bundle.js",
    format: "cjs",
  },
};

运行命令:

rollup -c

得到产物 bundle.js

"use strict";

var foo = "hello world!";

// src/main.js
function main() {
  console.log(foo);
}

module.exports = main;

这时我们使用 Rollup 将 src/main.jssrc/foo.js打包成功,完成了第一个 bundle,。

image.png

3. 引入外部资源

更新 src/main.js,添加外部资源 lodash-es 引入:

// src/main.js
import foo from "./foo.js";

import { sum } from "lodash-es";

export default function () {
  console.log(foo);
  console.log(sum[(1, 2)]);
}

再次打包 rollup -c,发现有报错 (!) Unresolved dependencies

image.png

这是因为当项目中引入外部资源时,如 npm 包,rollup 不知道如何打破常规去处理这些依赖。

有 2 种方法引入外部资源:

  • 添加插件 @rollup/plugin-node-resolve 将我们编写的源码与依赖的第三方库进行合并;
  • 配置 external 属性,告诉 rollup.js 哪些是外部的类库。

3.1 resolve 插件

@rollup/plugin-node-resolve 插件让 rollup 能够处理外部依赖。

安装:

yarn add @rollup/plugin-node-resolve -D

更新 rollup.config.js

import resolve from "@rollup/plugin-node-resolve";
export default {
  plugins: [resolve()],
};

重新打包得到产物,已经包含了 lodash-es

"use strict";

var foo = "hello world!";

/**
 * This method returns the first argument it receives.
 *
 * @static
 * @since 0.1.0
 * @memberOf _
 * @category Util
 * @param {*} value Any value.
 * @returns {*} Returns `value`.
 * @example
 *
 * var object = { 'a': 1 };
 *
 * console.log(_.identity(object) === object);
 * // => true
 */
function identity(value) {
  return value;
}

/**
 * The base implementation of `_.sum` and `_.sumBy` without support for
 * iteratee shorthands.
 *
 * @private
 * @param {Array} array The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {number} Returns the sum.
 */
function baseSum(array, iteratee) {
  var result,
    index = -1,
    length = array.length;

  while (++index < length) {
    var current = iteratee(array[index]);
    if (current !== undefined) {
      result = result === undefined ? current : result + current;
    }
  }
  return result;
}

/**
 * Computes the sum of the values in `array`.
 *
 * @static
 * @memberOf _
 * @since 3.4.0
 * @category Math
 * @param {Array} array The array to iterate over.
 * @returns {number} Returns the sum.
 * @example
 *
 * _.sum([4, 2, 8, 6]);
 * // => 20
 */
function sum(array) {
  return array && array.length ? baseSum(array, identity) : 0;
}

// src/main.js

function main() {
  console.log(foo);
  console.log(sum([1, 2]));
}

module.exports = main;

成功运行:

image.png

3.2 external 属性

有些场景下,虽然我们使用了 resolve 插件,但可能我们仍然想要某些库保持外部引用状态,这时我们就需要使用 external 属性,来告诉 rollup.js 哪些是外部的类库。

更新 rollup.config.js

import commonjs from "@rollup/plugin-commonjs";
import { nodeResolve } from "@rollup/plugin-node-resolve";

export default {
  input: "src/main.js",
  output: {
    file: "bundle.js",
    format: "esm",
    name: "test",
  },
  plugins: [nodeResolve(), commonjs()],
  external: ["react"],
};

3.3 external 插件

每个类库都要手动添加至 externals 未免太麻烦,这时候可以用 rollup-plugin-node-externals 插件,自动将外部类库声明为 externals。

安装:

yarn add rollup-plugin-node-externals -D

更新 rollup.config.js

import externals from "rollup-plugin-node-externals";

export default [
  {
    plugins: [
      externals({
        devDeps: false, // devDependencies 类型的依赖就不用加到 externals 了。
      }),
    ],
  },
];

4. 引入 CommonJs 模块

4.1 CommonJs 插件

rollup.js 编译源码中的模块引用默认只支持 ES6+的模块方式 import/export。然而大量的 npm 模块是基于 CommonJS 模块方式,这就导致了大量 npm 模块不能直接编译使用。

需要添加 @rollup/plugin-commonjs 插件来支持基于 CommonJS 模块方式 npm 包。

安装:

yarn add @rollup/plugin-commonjs -D

更新 rollup.config.js:

import commonjs from "@rollup/plugin-commonjs";

export default {
  plugins: [commonjs()],
};

更新 src/foo.js:

module.exports = {
  text: "hello world!",
};

重新打包,打包成功:

image.png

5. 引入 Sass 资源

rollup-plugin-postcss 默认集成了对 scss、less、stylus 的支持。

5.1 打包支持 sass 文件

新增 src/foo.scss

body {
  background-color: red;
  display: flex;
}

更新 src/main.js

// src/main.js
import foo from "./foo.js";
import "./foo.scss";

export default function () {
  console.log(foo.text);
}

安装:

yarn add rollup-plugin-postcss -D

更新 rollup.config.js:

import commonjs from "@rollup/plugin-commonjs";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import postcss from "rollup-plugin-postcss";

export default {
  input: "src/main.js",
  output: {
    file: "bundle.js",
    format: "esm",
    name: "test",
  },
  plugins: [nodeResolve(), commonjs(), postcss()],
  external: ["react"],
};

打包产物:

"use strict";

var foo = {
  text: "hello world!",
};

function styleInject(css, ref) {
  if (ref === void 0) ref = {};
  var insertAt = ref.insertAt;

  if (!css || typeof document === "undefined") {
    return;
  }

  var head = document.head || document.getElementsByTagName("head")[0];
  var style = document.createElement("style");
  style.type = "text/css";

  if (insertAt === "top") {
    if (head.firstChild) {
      head.insertBefore(style, head.firstChild);
    } else {
      head.appendChild(style);
    }
  } else {
    head.appendChild(style);
  }

  if (style.styleSheet) {
    style.styleSheet.cssText = css;
  } else {
    style.appendChild(document.createTextNode(css));
  }
}

var css_248z = "body {\n  background-color: red;\n}";
styleInject(css_248z);

// src/main.js

function main() {
  console.log(foo.text);
}

module.exports = main;

效果如图:

image.png

5.2 css 加前缀

安装:

yarn add autoprefixer -D

更新 packages.json:


  "browserslist": [
    "defaults",
    "not ie < 8",
    "last 2 versions",
    "> 1%",
    "iOS 7",
    "last 3 iOS versions"
  ]

更新 rollup.config.js:

import commonjs from "@rollup/plugin-commonjs";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import autoprefixer from "autoprefixer";
import postcss from "rollup-plugin-postcss";

export default {
  input: "src/main.js",
  output: {
    file: "bundle.js",
    format: "umd",
    name: "test",
  },
  plugins: [
    nodeResolve(),
    commonjs(),
    postcss({
      plugins: [autoprefixer()],
    }),
  ],
  external: ["react"],
};

效果如图:

image.png

5.3 css 压缩

安装:

yarn add cssnano -D

更新 rollup.config.js:

import commonjs from "@rollup/plugin-commonjs";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import autoprefixer from "autoprefixer";
import cssnano from "cssnano";
import postcss from "rollup-plugin-postcss";

export default {
  input: "src/main.js",
  output: {
    file: "bundle.js",
    format: "umd",
    name: "test",
  },
  plugins: [
    nodeResolve(),
    commonjs(),
    postcss({
      plugins: [autoprefixer(), cssnano()],
    }),
  ],
  external: ["react"],
};

效果如图:

image.png

5.4 抽离单独的 css 文件

更新 rollup.config.js

export default [
  {
    plugins: [
      postcss({
        plugins: [autoprefixer(), cssnano()],
        extract: "css/index.css",
      }),
    ],
  },
];

效果如图:

image.png

6. 引入 Typescript 资源

6.1 typescript 插件

修改 src/foo.js -> src/foo.ts

export default {
  text: "hello world!",
};

更新 src/main.js

// src/main.js
import foo from "./foo.ts";
import "./foo.scss";

export default function () {
  console.log(foo.text);
}

安装:

yarn add @rollup/plugin-typescript -D

更新 rollup.config.js:

import typescript from "@rollup/plugin-typescript";
export default [
  {
    plugins: [typescript()];
  }
];

成功支持 Ts 文件导出:

image.png

6.2 导出类型声明文件

更新 rollup.config.js:

import typescript from "@rollup/plugin-typescript";
export default [
  {
    plugins: [
        typescript({
            outDir: "dist",
            declaration: true,
            declarationDir: "dist",
        })
    ];
  }
];

成功支持类型声明文件导出:

image.png

7. 打包产物清除调试代码

插件 @rollup/plugin-strip 用于从代码中删除 debugger 语句和函数。包括 assert.equal、console.log 等等。

安装:

yarn add @rollup/plugin-strip -D

更新 rollup.config.js:

import strip from "@rollup/plugin-strip";
export default [
  {
    plugins: [
        strip()
    ];
  }
];

8. 打包输出文件保留原始模块结构

上面我们的 output 配置是这样的:

output: {
    dir: path.dirname('dist/bundle.js'),
    format: 'es',
  }

打包产物如下:

image.png

那么怎么才能把 index.js、index2.js 改成 foo/index.js、hello/index.js 呢?

修改 output,更新 rollup.config.js:

output: {
    dir: path.dirname('dist/bundle.js'),
    format: 'es',
    exports: 'named', // 指定导出模式(自动、默认、命名、无)
    preserveModules: true, // 保留模块结构
    preserveModulesRoot: 'src', // 将保留的模块放在根级别的此路径下
  },

这时打包产物就和源码的结构一致了:

image.png

9. 按需加载

rollup 支持输出格式为 es 模块化,就会按模块输出。

所以我们上面的配置已经实现了按需加载了。

五、一个真实的组件库的 rollup 打包配置

项目地址:github.com/jiaozitang/…

该项目支持:

  • 打包输出文件保留原始模块结构
  • 自动将 dependencies 依赖声明为 externals
  • 支持处理外部 npm 依赖
  • 支持基于 CommonJS 模块引入
  • 支持 typescript,并导出声明文件
  • 支持 scss,并添加前缀
  • 支持自动清除调试代码
  • 支持按需加载

1. 安装

npm i rollup -g

yarn add typescript postcss @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-strip @rollup/plugin-typescript rollup-plugin-postcss rollup-plugin-node-externals autoprefixer -D

2. 配置

项目根目录下新增 rollup.config.js

import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import strip from "@rollup/plugin-strip";
import typescript from "@rollup/plugin-typescript";
import autoprefixer from "autoprefixer";
import path from "path";
import externals from "rollup-plugin-node-externals";
import postcss from "rollup-plugin-postcss";

import pkg from "./package.json";

export default [
  {
    input: "./src/index.ts", // 入口文件
    output: [
      {
        // 出口文件
        dir: path.dirname(pkg.module),
        format: "es", // es模块导出,支持按需加载
        name: pkg.name,
        exports: "named", // 指定导出模式(自动、默认、命名、无)
        preserveModules: true, // 保留模块结构
        preserveModulesRoot: "src", // 将保留的模块放在根级别的此路径下
      },
    ],
    plugins: [
      // 自动将dependencies依赖声明为 externals
      externals({
        devDeps: false,
      }),
      // 处理外部依赖
      resolve(),
      // 支持基于 CommonJS 模块引入
      commonjs(),
      // 支持 typescript,并导出声明文件
      typescript({
        outDir: "es",
        declaration: true,
        declarationDir: "es",
      }),
      // 支持 scss,并添加前缀
      postcss({
        plugins: [autoprefixer()],
      }),
      // 清除调试代码
      strip(),
    ],
  },
];

更新 packages.json:

{
  "module": "es/index.js",
  "types": "es/index.d.ts",
  "files": ["es"]
}

新增 tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
  },
  "include": [
    "src"
  ],
  "exclude": [
      "src/**/stories.*",
      "src/**/.spec.*",
      "src/**/.mdx"
  ]
}

项目结构:

image.png

打包产物:

image.png

小结

本文介绍了 rollup 的各个功能的使用方法,rollup 自身能力较弱,依靠插件完成完整的组件库打包。

可以直接拷贝文中配置,实现一个按需加载的组件库打包。

项目地址:github.com/jiaozitang/…

希望能对你有所帮助,感谢阅读~

别忘了点个赞鼓励一下我哦,笔芯 ❤️

往期精彩

参考资料