rollup
是一个 JavaScript 模块打包器,可以将许多 JavaScript 库和应用程序打包成少量的捆绑包,从而提高了应用程序的性能。本文详细描述如何通过 rollup
实现多模块打包。
前提
项目的目录结构
先看下项目的 package.json
文件夹:
{"private": true,"workspaces": ["packages/"],"type": "module"
}
workspaces
这里我们使用 workspaces
字段定义 npm 工作空间。它可以让你在同一项目中使用多个独立的 npm 包,这些包可以共享相同的依赖项和环境,使得管理依赖关系变得更加方便。
同时你可以指定一个或多个目录,它们将包含在工作空间中。例子中,将 packages/
目录包含在工作空间中,意味着在该目录下的所有子目录都将被视为 npm 包,并共享相同的依赖项和环境。
使用工作空间可以大大简化具有多个独立模块或应用的项目的依赖关系管理。你可以在每个模块或应用中独立运行 npm 命令,而无需担心它们之间的依赖关系和环境影响。
需要注意的是,使用工作空间需要你的项目目录结构符合一定的规范,例如每个模块或应用都需要位于 packages/
目录的子目录中。此外,使用工作空间需要在命令行中运行 npm 命令时,需要使用 “–workspace” 标志来指定要操作的工作空间,例如 npm install rollup -W
。
同时 “workspaces” 只能在私有项目中使用。这里需要配置 private: true
包使用
通过目录结构可以看到在 packages 里面是有多个 npm 包的那么如何在去引用这些文件呢,例如在 reactivity
里面通过 import xxx from '@vue/shared'
去引用 shared 文件下的某个功能呢?我们经行如下修改:
// tsconfig.json
{"baseUrl": "./","module": "ESNext","moduleResolution": "node",// 省略部分代码"paths": {"@vue/*": ["packages/*/src"]}
}
在 shared 文件夹中补充如下测试代码:
// packages/shared/src/index.ts
export const isArray = Array.isArray
在 reactivity 文件夹下直接引入就行了:
// packages/reactivity/src/index.ts
import { isArray } from '@vue/shared'
console.log(isArray)
打包
既然每个文件都有属于一个独立 npm 模块,也会有一个属于自己的 package.json
以reactivity
为例:
package.json
{"name": "@vue/reactivity","version": "1.0.0","main": "index.js","license": "MIT","buildOptions": {"name": "VueReactivity","formats": ["esm-bundler","cjs","global"]}
}
package.json
里面定义了包的名称以及对应的打包方式,分别是 esm
、 cjs
、 global
不同标准的打包方式。
execa
在 packages/
目录包含了多个 npm 包那么如何对每个文件经行打包呢,这里我们可以使用 execa
import { readdirSync, statSync } from 'fs'
import { execa } from 'execa'// 1、获取打包目录
const dirs = readdirSync('packages').filter(dir => statSync(`packages/${dir}`).isDirectory()) // [ 'reactivity', 'shared' ]async function build(TARGET) {await execa('rollup', ['-c', '--environment', `TARGET:${TARGET}`], { stdio: 'inherit' })
}function runParaller(dirs) {let result = []for (const dir of dirs) {result.push(build(dir))}return Promise.all(result) // 存放打包的promise
}
// 2、进行打包
runParaller(dirs).then(() => {console.log('success')
})
rollup.config.js
上面代码通过 execa
去执行 rollup
的打包逻辑,这里的 −c
参数表示使用配置文件(默认是 rollup.config.js
),−−environment
参数用于指定环境变量, stdio: 'inherit'
表示将命令的标准输入、输出和错误流重定向到父进程的流(即 Node.js 进程的流)。
import { defineConfig } from 'rollup'
import { createRequire } from 'node:module'
import ts from 'rollup-plugin-typescript2'
import json from '@rollup/plugin-json'
import resolvePlugin from '@rollup/plugin-node-resolve' // 解析第三方插件
import path from 'node:path'
import { fileURLToPath } from 'node:url'const require = createRequire(import.meta.url)
const __dirname = path.dirname(fileURLToPath(import.meta.url))// 2.获取文件路径
const packagesDir = path.resolve(__dirname, 'packages')// 2.1 获取需要打包的文件
const packageDir = path.resolve(packagesDir, process.env.TARGET)
const resolve = p => path.resolve(packageDir, p)
// 2.2 获取每个包的配置项
const pkg = require(resolve('package.json')) // 获取 jsonconst name = path.basename(packageDir) // reactivity
// 3、创建一个映射表
const outputConfigs = {'esm-bundler': {file: resolve(`dist/${name}.esm-bundler.js`),format: 'esm'},'cjs': {file: resolve(`dist/${name}.cjs.js`),format: 'cjs'},'global': {file: resolve(`dist/${name}.global.js`),format: 'iife'}
}const packageOptions = pkg.buildOptions || {}// 1、创建一个打包配置
function createConfig(format, output) {output.name = packageOptions.nameoutput.sourcemap = false// 生成rollup配置const config = {input: resolve('src/index.ts'), // 输入output, // 输出plugins: [json(),ts({tsconfig: path.resolve(__dirname, 'tsconfig.json')}),resolvePlugin()]}return config
}const packageFormats = packageOptions.formatsconst packageConfigs = packageFormats.map(format => createConfig(format, outputConfigs[format]))
export default defineConfig(packageConfigs)
测试
在 example
在新建一个测试文件:
<script src="../packages/shared/dist/shared.global.js"></script>
<script>let { isArray } = VueShared;console.log(isArray([])); // true
</script>
总结
以上大体实现了通过 rollup 实现多模块打包的功能