Rollup:打造NPM包提高开发效率!

1,526 阅读10分钟

前言

1.在云在前端公众号中发送rollup领取源码地址

2.本章内容涉及到package.json,如果对其相关字段不熟悉,可以先参考我的上一篇文章# 中高级前端必须掌握的package.json最新最全指南

起步

1. rollup与webpack区别

Rollup 更适合构建库和组件,追求更高的代码优化和性能;

Webpack 更适合构建复杂的应用,提供了更多的功能和灵活性。

2. 安装 Rollup

  1. 初始化 package.json

yarn init -y

  1. 安装 rollup

yarn add rollup -D

  1.  新建src/foo.js
export default 'hello world!';
  1. 新建src/index.js
import foo from './foo.js';
export default function () {
    console.log(foo);
}
  1. 修改 package.json
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"browser": "dist/index.umd.js",
"scripts": {
  "build": "rollup -c",
  "serve": "rollup -c -w"
},

-c:代表读取配置去打包,默认读取根目录下的rollup.config.mjs

-w:代表了watch监听,调试的时候可以用

  1. 新建 rollup.config.mjs
import { defineConfig } from "rollup";
import pkg from "./package.json" assert { type: "json" }; //断言导出json模块

export default defineConfig([
  {
    input: "src/index.js", //入口文件
    output: [
      {
        file: pkg.main, //出口文件
        format: "cjs", //打包成CommonJS模块
      },
      {
        file: pkg.module, //出口文件
        format: "es", //打包成es module模块
      },
      {
        name: "myUtils", //打包成UMD模式,需提供name
        file: pkg.browser, //出口文件
        format: "umd", //打包成UMD模块
      },
    ],
  },
]);
  1. 运行 yarn run build 进行打包,项目根目录会生成一个dist文件夹。

  2. 根目录新建index.html引入打包后的文件,进行测试

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 测试es module模块 -->
    <script type="module">
      import main from "./dist/index.es.js";
      main();
    </script>
    <!-- 测试umd模块 -->
    <script src="./dist/index.umd.js"></script>
    <script>
      window.myUtils();
    </script>
  </body>
</html>

提示:使用esm模块时,直接访问,浏览器会报跨域错误。

解决方式:安装vscode扩展 Live Server,然后使用open with Live Server打开。可以看到浏览器打印出两个hello world!,至此使用rollup已打包成功。

插件

插件列表:github.com/rollup/awes…

rollup有丰富的插件,可以让我们做更多的处理,这里列举常用的插件使用方式。

1. @rollup/plugin-json(处理JSON文件)

  1. 安装

yarn add @rollup/plugin-json -D

  1. 修改src/index.js 文件
import { version } from "../package.json";

export default function () {
  console.log("version " + version);
}
  1. 在 rollup.config.mjs 文件中加入 JSON plugin
import json from '@rollup/plugin-json';

export default defineConfig([
  {
    //...
    plugins: [
      json(),
    ],
  },
]);
  1. 使用 yarn run build 运行 Rollup。dist/index.cjs.js文件内容应该如下所示
'use strict';

var version = "1.0.0";

function index () {
    console.log('version ' + version);
}

module.exports = index;

结果中JSON文件已成功被处理,并且只导入了我们实际需要的数据version,其他内容例如name、devDependencies都被忽略了。这就是 tree shaking 的作用

2. @rollup/plugin-terser(压缩文件)

  1. 安装

yarn add @rollup/plugin-terser -D

  1. 修改rollup.config.mjs
import terser from "@rollup/plugin-terser";

export default defineConfig([
  {
    //...
    plugins: [terser()],
  },
]);
  1. 使用 yarn run build 进行打包,生成的打包文件将全部被压缩

3. @rollup/plugin-node-resolve(处理外部依赖)

  1. 安装

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

  1. 修改rollup.config.mjs
import resolve from "@rollup/plugin-node-resolve";

export default defineConfig([
  {
    //...
    plugins: [resolve()],
  },
]);
  1. 运行 yarn add lodash-es,安装lodash-es包进行测试

  2. 修改src/index.js

import { add } from "lodash-es"; //引入第三方包

export default function () {
  console.log("sum " + add(2 + 4));
}
  1. 使用 yarn run build 打包。这里如果不使用@rollup/plugin-node-resolve,会报错:Uncaught TypeError: Cannot read properties of undefined (reading 'add')

4. @rollup/plugin-commonjs(将第三方包CommonJS转ES)

  1. 安装

yarn add @rollup/plugin-commonjs -D

  1. 修改rollup.config.mjs
import commonjs from "@rollup/plugin-commonjs";

export default defineConfig([
  {
    //...
    plugins: [commonjs()],
  },
]);
  1. 运行 yarn add ms,安装ms包进行测试

  2. 修改src/index.js

import ms from "ms"; //引入CommonJS类型包

export default function () {
  console.log(ms("2 days"));
}
  1. 使用 yarn run build 进行打包

5. @rollup/plugin-alias(路径别名)

  1. 安装

yarn add @rollup/plugin-alias -D

  1. 修改rollup.config.mjs
import alias from "@rollup/plugin-alias";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export default defineConfig([
  {
    //...
    plugins: [
      //之前的插件保留,在plugins数组末尾添加alias
      alias({
        entries: [{ find: "@", replacement: path.resolve(__dirname, "src") }],
      }),
    ],
  },
]);
  1. 新建src/utils/index.js
export function Add(a, b) {
  return a + b;
}
  1. 修改src/index.js
import { Add } from "@/utils";

export default () => {
  console.log(Add(1, 2));
};
  1. 使用 yarn run build 进行打包

代码分割

1. 自动拆分代码块

rollup对于使用import()方式引入的文件,会自动将代码拆分成块,并以chunk-[hash].js的格式命名文件,其中[hash]是基于内容的哈希字符串。

由于umd模块不支持代码分割,并且打包后从一个文件变为一个文件夹,因此需要做一些调整

  1. 修改package.json
"main": "dist/cjs", 
"module": "dist/es",
  1. 修改rollup.config.mjs
export default defineConfig([
  {
    //...
    output: [
      {
        dir: pkg.main, //出口文件夹
        format: "cjs", //打包成CommonJS模块
      },
      {
        dir: pkg.module, //出口文件夹
        format: "es", //打包成es module模块
      },
    ],
  },
]);
  1. 修改src/index.js
export default function () {
  import("./foo.js").then(({ default: foo }) => console.log(foo));
}
  1. 运行yarn run build

  2. 修改index.html,进行测试

<!-- 测试es module模块 -->
<script type="module">
  import main from "./dist/es/index.js";
  main();
</script>

2. 显式拆分代码块

通过配置 output.manualChunks 显式地将模块拆分成单独的块。常用于拆分第三方包。

  1. 修改rollup.config.mjs
export default defineConfig([
  {
    //...
    output: [
      {
        dir: pkg.main, //出口文件
        format: "cjs", //打包成CommonJS模块
        manualChunks: {
          lodash: ["lodash-es"],
        },
      },
      {
        dir: pkg.module, //出口文件
        format: "es", //打包成es module模块
        manualChunks: {
          lodash: ["lodash-es"],
        },
      },
    ],
  },
]);
  1. 修改src/index.js
import { add } from "lodash-es";

export default function () {
  console.log("sum " + add(2 + 4));
}
  1. 使用 yarn run build 运行 Rollup,可以看到lodash已被拆分出来,形成一个单独的文件

使用 babel

将es6代码转es5,以兼容旧版浏览器、特定的移动设备等

  1. 安装

@rollup/plugin-babel:在 Rollup 打包过程中使用 Babel 进行代码转换

@babel/core:babel核心库

@babel/preset-env:将ES6转换为向后兼容的JavaScript

@babel/plugin-transform-runtime:处理async,await、import()等语法关键字的帮助函数

yarn add @rollup/plugin-babel -D
yarn add @babel/core -D
yarn add @babel/preset-env -D
yarn add @babel/plugin-transform-runtime -D
  1. 修改rollup.config.mjs
import { babel } from "@rollup/plugin-babel";

export default defineConfig([
  {
    //...
    plugins: [
      babel({
        babelHelpers: "runtime",
        presets: ["@babel/preset-env"],
        plugins: [["@babel/plugin-transform-runtime", { useESModules: true }]],
      }),
    ],
  },
]);

注意:如果使用了@rollup/plugin-commonjs,@rollup/plugin-commonjs一定要在@rollup/plugin-babel之前调用

import { babel } from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';

const config = {
  ...
  plugins: [
    commonjs(),
    babel(...)
  ],
};
  1. 修改src/index.js
import foo from "./foo";

export default () => {
  console.log(foo);
};
  1. 使用 yarn run build 运行 Rollup,在dist/es/index.js文件已将es6转es5

处理 Sass

1. 打包支持sass文件

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

  1. 安装
yarn add sass -D
yarn add postcss rollup-plugin-postcss -D
  1. 修改rollup.config.mjs
import postcss from "rollup-plugin-postcss";

export default defineConfig([
  {
    //...
    plugins: [
      postcss(),
    ],
  },
]);
  1. 新建src/foo.scss
$color: red;

body {
  background-color: $color;
  display: flex;
}
  1. 修改src/index.js
import foo from "./foo.js";
import "./foo.scss";

export default function () {
  console.log(foo);
}
  1. 使用 yarn run build 运行 Rollup,在dist/es/index.js中可以看到样式被打包了进去

2. css加前缀

  1. 安装

yarn add autoprefixer -D

  1. 更新 packages.json
"browserslist": [
    "defaults",
    "not ie < 8",
    "last 2 versions",
    "> 1%",
    "iOS 7",
    "last 3 iOS versions"
  ]
  1. 修改rollup.config.mjs
import postcss from "rollup-plugin-postcss";
import autoprefixer from "autoprefixer";

export default defineConfig([
  {
    //...
    plugins: [
      postcss({
        plugins: [autoprefixer()],
      }),
    ],
  },
]);

3. css压缩

  1. 安装

yarn add cssnano -D

  1. 修改rollup.config.mjs
import postcss from "rollup-plugin-postcss";
import autoprefixer from "autoprefixer";
import cssnano from "cssnano";

export default defineConfig([
  {
    //...
    plugins: [
      postcss({
        plugins: [autoprefixer(), cssnano()],
      }),
    ],
  },
]);

4. 抽离单独的css文件

修改rollup.config.mjs

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

使用 yarn run build 运行 Rollup,在dist/es中可以看到样式已被拆分出去,单独在css文件夹下

处理 Typescript

1. typescript 插件

  1. 安装

yarn add rollup-plugin-typescript2 typescript tslib -D

  1. 新建src/bar.ts
const str = "hello ts!";

export default str;
  1. 修改src/index.js
import bar from "./bar.ts";
import "./foo.scss";

export default function () {
  console.log(bar);
}
  1. 修改rollup.config.mjs
import typescript from "rollup-plugin-typescript2";

export default defineConfig([
  {
    //...
    plugins: [typescript()]
  },
]);

注意点:

  1. 如果使用了@rollup/plugin-node-resolve,@rollup/plugin-node-resolve要在rollup-plugin-typescript2之前调用
const config = {
    ...
    plugins: [
      resolve(),
      typescript(...)
    ],
  };
  1. 如果使用了@rollup/plugin-babel,需配置babel扩展
import { DEFAULT_EXTENSIONS } from '@babel/core';

export default defineConfig([
  {
    //...
    plugins: [
      babel({
        babelHelpers: 'runtime',
        presets: ["@babel/preset-env"],
        plugins: [["@babel/plugin-transform-runtime", { useESModules: true }]],
        extensions: [...DEFAULT_EXTENSIONS, ".ts", ".tsx"], //增加配置
      }),
    ],
  },
]);

2. 导出类型声明文件

新建tsconfig.json

{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "declaration": true, //生成声明文件
    "outDir": "dist",
    "rootDir": "src"
  },
  "exclude": ["node_modules", "dist"]
}

tsconfig.json配置会与typescript()配置合并,并覆盖其默认配置

使用 yarn run build 运行 Rollup,在dist/cjs与dist/es下会分别生成一份ts声明文件

优化

1. 打包前清空原打包目录

  1. 安装 rimraf 和 rollup-plugin-delete
yarn add rimraf -D //删除打包目录
yarn add rollup-plugin-delete -D //设置要删除的文件或目录
  1. 修改rollup.config.mjs
import { rimrafSync } from "rimraf";
import del from 'rollup-plugin-delete';

rimrafSync("dist");// 删除打包目录

export default defineConfig([
  {
    //...
    plugins: [
      del({ targets: "dist/*" }),
    ],
  },
]);

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

  1. 安装

yarn add @rollup/plugin-strip -D

  1. 修改rollup.config.mjs
import strip from "@rollup/plugin-strip";

export default defineConfig([
  {
    //...
    plugins: [
      strip()
    ],
  },
]);
  1. 修改src/index.js
import bar from "./bar.ts";
import "./foo.scss";

export default function () {
  console.log(bar);
  document.querySelector("body").innerHTML = bar;
}

使用 yarn run build 运行 Rollup,可以看到console.log部分的代码,在打包的时候已被删除

调试

1. 建立链接

例如当前项目名为rollup,想作为一个第三方包

在项目中运行npm link,将rollup项目创建成本地依赖包

在项目例如名为my-app中,运行 npm link rollup 建立软链接,link后面的rollup为rollup项目中的package.json的name值

建立软链接之后,可以在项目my-app 的node_models中找到rollup,之后修改rollup包里的内容会实时更新,不用再运行npm link

在项目my-app中使用

import main from 'rollup'; 
main();

2. 解除链接

解除项目my-app 的rollup依赖,在项目 my-app 中运行

npm unlink rollup

发布

1. 发布到npm

  1. 如果以前改过npm的镜像地址,比如使用了淘宝的镜像,就先改回来

npm config set registry https://registry.npmjs.org/

  1. 如果没有npm账户,先去官网注册www.npmjs.com/ ,注册完去邮箱点击链…

  2. 在项目终端登录 npm login,输入用户信息后会提示二次验证,最后把邮件的验证码输入即可

  3. 修改package.json

"files": [
    "dist/es/*",
    "dist/cjs/*"
  ],

配置files,设置需要推送到npm的文件

  1. 进行发布

npm publish

(如果包名有@符号,例如@username/component,默认为私有包,需要加上 --access public参数)

备注:这里包名为rollup,肯定会与npm上现有的包名重复,发布时会报错。需要修改package.json的name属性,重新设置属于自己的包名

  1. 可在npm个人主页查看上传的包

上传成功后,使用npm进行下载,可在node_modules中看到我们只上传了打包后的代码

EBA505CD05AA43D8AA38FE5B49F8C525.jpg

2. 发布npm包时可能会遇到的一些坑

  1. 邮箱未验证
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! you must verify your email before publishing a new package: https://www.npmjs.com/email-edit : your-package

去邮箱验证

  1. 没有权限发布
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! You do not have permission to publish "your-package". Are you logged in as the correct user? : your-package

包和别人的包重名了。修改包名

  1. 需要登录
npm ERR! code ENEEDAUTH
npm ERR! need auth auth required for publishing
npm ERR! need auth You need to authorize this machine using `npm adduser`

使用 npm login 登录

  1. 只有管理员才有权限发布
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! [no_perms] Private mode enable, only admin can publish this module [no_perms] Private mode enable, only admin can publish this module: your-package

源设置成第三方源,比如设置了淘宝镜像。只要把源设为默认的即可

npm config set registry http://registry.npmjs.org

  1. 包名过于类似
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! Package name too similar to existing packages; try renaming your package to '@hopgoldy/auto-git' and publishing with 'npm publish --access=public' instead : your-package
  1. 无法发布私有包
npm ERR! publish Failed PUT 402
npm ERR! code E402
npm ERR! You must sign up for private packages :

这个当你的包名为@your-name/your-package时才会出现,原因是当包名以@your-name开头时,npm publish会默认发布为私有包,但是 npm 的私有包需要付费,所以需要添加如下参数进行发布:

npm publish --access public

或者在package.json中添加

"publishConfig": {
    "access": "public"
  },
  1. 包的命名空间

lerna ERR! E404 Scope not found

点击链接创建包的命名空间:www.npmjs.com/org/create