Rollup配置实战:多产物与多入口的完美结合 (2)

常用配置

多产物配置

我们可以将 output 改造成一个数组,对外暴露出不同格式的产物供他人使用,不仅包括 ESM,也需要包括诸如CommonJSUMD等格式,保证良好的兼容性

import { defineConfig } from 'rollup'

export default defineConfig({
  input: 'src/index.js',
  output: [
    {
      file: 'dist/bundle-iife.js',
      format: 'iife'
    },
    {
      file: 'dist/bundle-esm.js',
      format: 'esm'
    },
    {
      file: 'dist/bundle-cjs.js',
      format: 'cjs'
    },
    {
      file: 'dist/bundle-umd.js',
      format: 'umd',
      name: 'bundle'
    }
  ],
})

多入口配置

除了多产物配置,Rollup 中也支持多入口配置

main.js

// src/main.js
import util from "./util.js";
const r = util.getRandomNum(1, 10)
console.log("🚀 ~ r:", r)

const obj = {
  a: 1,
  b: {
    c: 3
  }
}
const cloneObj = util.deepClone(obj)
cloneObj.b.c = 4;
console.log("🚀 ~ obj:", obj)
console.log("🚀 ~ cloneObj:", cloneObj)

rollup.config.js

import { defineConfig } from 'rollup'

export default defineConfig({
  input: ['src/index.js','src/main.js'],
  output: [
    {
      dir: 'dist',
      format: 'cjs'
    }
  ],
})

通常情况下多产物和多入口两者会被结合起来使用

import { defineConfig } from 'rollup'
export default defineConfig({
  input: ['src/index.js','src/main.js'],
  output: [
    {
      dir: 'cjs',
      format: 'cjs'
    },
    {
      dir: 'esm',
      format: 'esm'
    }
  ],
})

当然,上面这样的写的话,生成的产物会把两个入口一起进行构建,我们可能的想法是一个入口一种构建方式:

import { defineConfig } from 'rollup'
/**
 * @type {import('rollup').RollupOptions}
 */
const buildIndexOptions = {
  input: 'src/index.js',
  output: {
    dir: 'dist/umd/',
    format: 'umd',
    name: 'bundle'
  }
}

/**
 * @type {import('rollup').RollupOptions}
 */
const buildMainOptions = {
  input: 'src/main.js',
  output: {
    dir: 'dist/esm/',
    format: 'esm',
  }
}
export default [buildIndexOptions, buildMainOptions];

动态导入与默认代码分割

使用路由来说明懒加载是大家喜闻乐见的方式,估计大多数同学对于懒加载都只是停留在路由的懒加载,其实,任何时候,我们都可以使用import动态懒加载的方式。重新编辑一下main.js入口:

// src/main.js
function run() { 
  // 如果不清楚import动态导入返回的是什么,可以先打印一下下面结果
  // import("./util.js").then(chunk => console.log("🚀 ~ chunk:", chunk));

  import("./util.js").then(({ default: foo }) => { 
    const r = foo.getRandomNum(1, 10);
    console.log("🚀 ~ r:", r) 
  })
}
run();

重新运行可以看到dist目录形成了下面的结构:

.
├── dist
│   ├── esm
│   │   ├── main.js
│   │   └── util-371e3ef9.js
│   └── umd
│       └── index.js
└── ...

Rollup 将使用动态导入创建一个仅在需要时加载的单独块。所以你可以看到这里多了一个util-371e3ef9.js的文件

注意:为了让 Rollup 知道在哪里放置第二个块,我们不使用 --file 选项,而是使用 --dir 选项设置一个输出文件夹

其中,util-371e3ef9.js是自动生成的chunk-[hash].js的名字,[hash] 是基于内容的哈希字符串。你可以通过指定 output.chunkFileNames (chunk文件)和 output.entryFileNames (打包入口文件)选项来提供自己的命名模式。

/**
 * @type {import('rollup').RollupOptions}
 */
const buildMainOptions = {
  input: 'src/main.js',
  output: {
    dir: 'dist/esm/',
    entryFileNames: '[name].js',
    chunkFileNames: 'chunk-[name]-[hash].js',
    format: 'esm',
  }
}

而且,很智能的是,如果这个时候,我定义了又多个入口点都调用了util.js文件,会自动的引入分割出来的文件

/**
 * @type {import('rollup').RollupOptions}
 */
const buildMainOptions = {
  input: ['src/main.js', 'src/main2.js'],
  output: {
    dir: 'dist/esm/',
    entryFileNames: '[name].js',
    chunkFileNames: 'chunk-[name]-[hash].js',
    format: 'esm',
  }
}

在打包后的main2.js中,可以看到这样的引用:

import util from './chunk-util-371e3ef9.js';

使用插件

到目前为止,我们已经用入口文件和通过相对路径导入的模块打了一个简单的包。随着你需要打包更复杂的代码,通常需要更灵活的配置,例如导入使用 NPM 安装的模块、使用 Babel 编译代码、处理 JSON 文件等等。

插件列表 : the Rollup Awesome List

@rollup/plugin-node-resolve

比如我们现在引入lodash-es库

pnpm add lodash-es -D

在index.js中使用

import { chunk } from "lodash-es";
const r = chunk([1, 2, 3, 4], 2);
console.log("🚀 ~ r:", r)

现在直接打包

/**
 * @type {import('rollup').RollupOptions}
 */
const buildIndexOptions = {
  input: 'src/index.js',
  output: {
    dir: 'dist/esm/',
    format: 'esm',
  }
}
export default buildIndexOptions

会出现下面的警告:

src/index.js → dist/esm/...
(!) Unresolved dependencies
https://rollupjs.org/troubleshooting/#warning-treating-module-as-external-dependency
lodash-es (imported by "src/index.js")
created dist/esm/ in 13ms

意思是,不能解析lodash-es这个依赖,报出了警告,问你是不是需要external,并提示你点击链接有这个警告的解释。当我们点击这个链接,按照提示解决这个external警告问题,很简单,就加上external,也就是把lodash-es这个库给排除出去

const buildIndexOptions = {
  input: 'src/index.js',
  output: {
    dir: 'dist/esm/',
    format: 'esm',
  },
+  external: ['lodash-es']
}
export default buildIndexOptions

再此打包,果然没警告了,而且我们在nodejs环境中确实也能正常运行了

到这里,问题似乎已经解决了,但我要问你一个问题:这个问题是如何解决的,依据是什么?

这里虽然看似一个很小的问题,但是却有很多基本理论点

1、rollup默认只能解析导入的相对路径,也就是/./或者../开头的路径,对于bare import,也就是import { chunk } from 'lodash-es';这种直接导入的第三方包的格式,并不支持

2、rollup相比webpack最大的优势并不是构建一个足够大的应用打包,大多是情况下,我们使用rollup用来构建工具库,因此,这里导入的lodash-es并没有报错,而仅仅报出警告,因为rollup认为lodash-es这个库并没有加入构建,那么你的意思是将来用作第三方库来使用,因此将lodash-es使用配置external排除掉就好。

3、lodash-es这个包本身就是支持ESM的

4、最后打包好的index.js文件只所以能在nodejs环境下运行,是因为nodejs可以帮我们解析bare import,我们可以试着将index.js放入到html文件中运行,你就可以看到其中的问题所在,在html环境中就会报错了:index.html:1 Uncaught TypeError: Failed to resolve module specifier "lodash-es". Relative references must start with either "/", "./", or "../".

如果希望在最后的打包中,将lodash-es内容解析打包进去,就可以使用@rollup/plugin-node-resolve

安装:

pnpm add @rollup/plugin-node-resolve --save-dev

使用:

import { nodeResolve } from '@rollup/plugin-node-resolve';
/**
 * @type {import('rollup').RollupOptions}
 */
const buildIndexOptions = {
  input: 'src/index.js',
  output: {
    dir: 'dist/esm/',
    format: 'esm',
  },
  plugins: [nodeResolve()]
  // external: ['lodash-es']
}
export default buildIndexOptions

现在,再此进行打包,无论是打包时间,还是打包内容和之前都很不一样了,把lodash-es中,关于chunk()函数的内容,都打包进了index.js文件中

output.manualChunks

上面讲了对于动态加载模块,rollup帮我们自动做了代码分割,其实我们也可以手动的操作,直接在rollup配置中声明

const buildIndexOptions = {
  input: 'src/index.js',  
  output: {
    dir: 'dist/esm/',
    format: 'esm',
    entryFileNames: '[name].js',
    chunkFileNames: 'chunk-[name]-[hash].js',
    manualChunks: {
      'lodash-es': ['lodash-es'],
    }
    //也可以是函数形式
    // manualChunks(id){
    //   if(id.includes('lodash-es')){
    //     return 'lodash-es'
    //   }
    // }
	},
  plugins: [nodeResolve()]
}

@rollup/plugin-commonjs

上面最开始导入lodash-es,没有加入externalplugins,之所以还能直接运行,还因为lodash-es本身就是支持ESM的,因为rollup默认并不支持Commonjs模块化,比如将lodash-es换位lodash,马上就能看到不一样的效果,直接打包失败

[!] RollupError: "chunk" is not exported by "node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/lodash.js", imported by "src/index.js".
https://rollupjs.org/troubleshooting/#error-name-is-not-exported-by-module

这个错误在官网上解释的很清楚了,无非就是commonjs没有这种导出,因此我们需要@rollup/plugin-commonjs帮我们处理commonjs模块化的问题

安装:

pnpm add @rollup/plugin-commonjs --save-dev

使用:

import { nodeResolve } from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
/**
 * @type {import('rollup').RollupOptions}
 */
const buildIndexOptions = {
  input: 'src/index.js',
  output: {
    dir: 'dist/esm/',
    format: 'esm',
  },
+  plugins: [nodeResolve(), commonjs()]
}
export default buildIndexOptions

@rollup/plugin-babel

使用 Babel 来使用尚未被浏览器和 Node.js 支持的最新 JavaScript 特性。

使用 Babel 和 Rollup 最简单的方法是使用 @rollup/plugin-babel

安装:

pnpm add @rollup/plugin-babel -D

使用:

import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
+import babel from '@rollup/plugin-babel';
/**
 * @type {import('rollup').RollupOptions}
 */
const buildIndexOptions = {
  input: 'src/index.js',
  output: {
    dir: 'dist/esm/',
    format: 'esm',
  },
  plugins: [
    nodeResolve(),
    commonjs(),
+    babel({ babelHelpers: 'bundled' })
  ]
}
export default buildIndexOptions

不过这么做之后,打包代码并不会有什么变化,因为我们都知道babel是需要预设的.

安装 babel-coreenv 预设

pnpm add -D @babel/core @babel/preset-env

在 Babel 实际编译代码之前,需要进行配置。在项目根目录创建一个名为 .babelrc.json 的新文件

{
  "presets": ["@babel/preset-env"]
}

具体的babel设置,可以参考babel文档

题外话:@babel/runtime

我们在index.js代码中加入如下的api

import { getRandomNum } from "./util.js";
const r = getRandomNum(1, 10)
console.log(r)

const arr = [1, 2, 3, 4].map(item => item * item);
console.log("🚀 ~ arr:", arr)

Promise.resolve(1).then(res => {
  console.log(res);
});

我们通过babel处理之后会发现一些问题:

@babel/preset-env 只转换了语法,也就是我们看到的箭头函数、const一类,但是对于进一步需要转换内置对象、实例方法等等API,就显得无能为力了,这些代码需要polyfill(兼容性垫片)。所以这个我需要@babel/runtime来帮我们处理。

@babel/runtime是一个核心, 一种实现方式,但是在实现polyfill垫片的过程中,可能会产生很多重复的代码,所以需要@babel/plugin-transform-runtime防止污染全局, 抽离公共的 helper function , 防止冗余,当然在处理polyfill的时候,我们还需要core-js的辅助,基于babel,我们可以使用@babel/runtime-corejs3

安装:

pnpm add @babel/plugin-transform-runtime -D
pnpm add @babel/runtime @babel/runtime-corejs3

要使用@babel/plugin-transform-runtime@rollup/plugin-babelbabelHelper处理必须改为runtime

import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import typescript from '@rollup/plugin-typescript';
/**
 * @type {import('rollup').RollupOptions}
 */
const buildIndexOptions = {
  input: 'src/index.js',
  output: {
    dir: 'dist/esm/',
    format: 'esm',
  },
  plugins: [
    nodeResolve(),
    commonjs(),
    babel({
      babelHelpers: 'runtime',
      include: 'src/**',
      exclude: 'node_modules/**',
      extensions:['.js', '.ts']
    }),
    typescript(),
  ]
}
export default buildIndexOptions

.babelrc.json:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "> 0.25%, not dead",
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 3
      }
    ]
  ]
}

这个时候你再进行构建,会发现打包后的文件多出了一大堆,这其实就是polyfill中的代码。

@rollup/plugin-typescript

要使用typescript就需要依赖这个插件,当然这个插件本身还依赖了typescripttslib,因此我们需要导入3个包

pnpm add typescript tslib @rollup/plugin-typescript -D

util.ts

/**
 * 深拷贝
 * @param obj 需要深拷贝的对象
 * @returns 深拷贝对象
 */
export const deepClone = <T>(obj: T): T => {
  if(typeof obj !== 'object' || obj === null) {
    return obj
  }
  const result:any = Array.isArray(obj) ? [] : {};
  for(let key in obj) {
    if(obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key])
    }
  }
  return result
}

export const getRandomNum = (min: number, max: number): number => {
  let num = Math.floor(Math.random() * (min - max) + max);
  return num;
}

Index.ts

import { getRandomNum,deepClone } from './util.ts';
const r = getRandomNum(1, 10);
console.log(r);

const obj = { a: 1, b: { c: 3 } };
const obj2 = deepClone(obj);
obj2.b.c = 4;

console.log(obj);
console.log(obj2);

当然,配置文件我们也完全可以是ts的

rollup.config.ts

import { RollupOptions } from "rollup"
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import typescript from '@rollup/plugin-typescript';

const config: RollupOptions = {
  input: 'src/index.ts',
  output: {
    file: 'dist/umd/index.js',
    format: 'umd',
    name: 'rollupDemo',
  },
  plugins: [
    nodeResolve(),
    commonjs(),
    babel({
      babelHelpers: 'runtime',
      include: 'src/**',
      exclude: 'node_modules/**',
      extensions:['.js', '.ts']
    }),
    typescript(),
  ],
}
export default config;

tsconfig.json

{
  "compilerOptions": {
    "module": "esnext",
    "target": "es5",
    "lib": ["esnext", "dom", "dom.iterable"],
    "skipLibCheck": true,

    "moduleResolution": "bundler",
    "noEmit": true,
    "allowImportingTsExtensions":true,
    "resolveJsonModule": true,
    "isolatedModules": true,
  },
  "include": ["src/**/*","rollup.config.ts"],
}

**注意:**别忘记tsconfig.json文件中需要加入rollup.config.ts配置文件,不然要报错

{
  "compilerOptions": {
    ......
  },
+  "include": ["src/**/*","rollup.config.ts"],
}

运行:

npx rollup -c rollup.config.ts --configPlugin typescript
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值