Vue3源码分析之打包原理
如果之前你已经看过我的《Vue3源码分析之入门》,那么你可以直接阅读此篇文章
一、配置环境
1. 全局安装yarn
Monorepo 管理项目中多个包是依赖yarn的
$ npm install yarn -g
2. 初始化项目
新建文件夹,如:mycore,进入新建的文件夹,并运行如下命令,进行项目初始化,生成package.json文件
$ yarn init -y
3. 配置项目的package.json
{
"private":true, // 因为是一个项目管理多个包,所以将项目设置为私有的
"workspaces":[
"packages/*" // 指定工作目录: 指定该项目下的所有的包都放在 packages下的文件夹
],
"name": "myCore",
"version": "1.0.0",
"main": "index.js",
"license": "MIT"
}
4. 在根目录中新建包管理文件夹packages,并新建子文件夹包reactivity和share文件夹
- 重复步骤2,初始化reactivity和share包
- 新建src/index.ts 入口文件
- 配置reactivity和share包的package.json
{
// 表示vue里面的包 就可以如此使用该包 import reactivity from '@vue/reactivity'
"name": "@vue/reactivity",
"version": "1.0.0",
"license": "MIT",
// node commonJS引入的入口
"main": "index.js",
// ES6 或者webpack引入的入口 会默认查找module
"module": "dist/reactivity.esm-bundler.js",
// 自定义打包配置
"buildOptions" : {
// 打包后的名称
"name" : "VueReactivity",
// 表示:当前模块/包 可以构建成CommonJS、ES6、全局 模块
"formats" : [
"cjs", // commonJS
"esm-bundler",
"global" // shared包里面,是不需要声明可以构建全局的
]
}
}
5. 安装依赖包
安装依赖一定要使用yarn
yarn add typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa
此时会报如下错误
表示 需要添加声明:声明当前依赖包是给根目录的package.json安装的
yarn add typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa --ignore-workspace-root-check
6. 在根目录下package.json,配置打包脚本
- 新建scripts文件夹,并新建dev.js,build.js
- 在根目录下package.json,配置打包脚本
{
"private": true,
"workspaces": [
"packages/*"
],
// 使用脚本配置打包命令
"scripts": {
"dev" : "node scripts/dev.js", // 使用node执行当前项目下的scripts的dev.js
"build" : "node scripts/build.js"
},
"name": "mysore",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.3.0",
"execa": "^6.1.0",
"rollup": "^2.72.0",
"rollup-plugin-typescript2": "^0.31.2",
"typescript": "^4.6.4"
}
}
二、build.js
这个文件的作用是:把packages目录下的所有包 都进行打包 ,具体配置如下
1.build.js配置
// 把packages目录下的所有包 进行打包
// 1、获取packages目录下的所有包
const fs = require('fs');
const execa = require('execa') // 开启子进程,使用rollup进行打包
// 同步读取packages目录下的内容
// 返回一个数组,默认会读取packages下的所有文件夹和文件
// 这里过滤掉文件
const targets = fs.readdirSync('packages').filter(f=>{
// 返回目录
return fs.statSync(`packages/${f}`).isDirectory()
})
// 2、对所获取的包目录,进行并行依次打包
// 打包的构建方式
// 打包是异步的
async function build(target) {
// 参数一:执行的打包命令
// 参数二:执行的打包参数
// '-c' 表示采用某个配置文件
// --environment 声明采用环境变量
// `TARGET:${target}` 传的具体环境变量 可以在rollup.config.js文件中通过process.env.TARGET访问到环境变量target
// 参数三: 子进程打包的信息共享给父进程
await execa(
'rollup',
['-c','--environment',`TARGET:${target}`],
{stdio:'inherit'}
)
}
// 轮循目录,依次打包
function runParallel(targets,iteratorFn) {
let res = [];
for(const item of targets) {
// 打包每一个目录包
// const p = await iteratorFn(item) 注意:这里不能加await 否则会变成同步打包,而不是并行打包
const p = iteratorFn(item) // 并行打包
res.push(p)
}
return Promise.all(res)
}
// 调用
runParallel(targets,build)
3. 运行bug解决
此时,使用yarn run build 进行打包会出现以下错误:
解决如下
- 在根package.json中添加type为module的配置
{
...
"type": "module",
...
}
- 更改包的导入方式
import fs from 'fs';
import {execa} from 'execa'
...
三、rollup配置
在根目录下,创建rollup.config.js,并进行如下配置
// rollup配置
import path from 'path'
import json from '@rollup/plugin-json';
import nodeResolve from '@rollup/plugin-node-resolve';
import ts from 'rollup-plugin-typescript2'
// 根据环境变量中的TARGET属性,获取对应模块的package.json
// 获取包的绝对路径
const packagesDir = path.resolve(__dirname,'packages');
// 获取要打包的目录
// console.log(process.env.TARGET,'cc')
const packageDir = path.resolve(packagesDir,process.env.TARGET)
// 针对某个模块 拼接路径
const resolve = (p) => path.resolve(packageDir,p)
// 获取当前被打包的package.json
const pkg = require(resolve('package.json'));
// 获取文件名
const name = path.basename(packageDir)
// 对打包类型 做映射表 ,根据提供的formats,格式化需要打包的内容
// 自定义配置
const outputConfig = {
'esm-bundler' : {
file : resolve(`dist/${name}.esm-bundler.js`),
format : 'es'
},
'cjs' : {
file : resolve(`dist/${name}.cjs.js`),
format : 'cjs'
},
'global' : {
file : resolve(`dist/${name}.global.js`),
format : 'iife' // 立即执行函数
},
}
// 获取包的package.json 的 自定义选项buildOptions
const options = pkg.buildOptions;
// 打包
function createConfig(formatItem,outputItem) {
// 给outputConfig 每一项添加参数
outputItem.name = options.name;
outputItem.sourcemap = true; // 生成sourcemap
// 生成rollup配置
return {
input : resolve('src/index.ts'), // 入口
output : outputItem, // 出口
plugins : [
json(),
// 先解析ts
ts({ // ts插件
tsconfig : path.resolve(__dirname,'tsconfig.json')
}),
// 再解析第三方
nodeResolve() // 解析第三方模块
]
}
}
// rollup 最终需要导出配置
export default options.formats.map(item=>{
// 调用打包方法
// 这块如果没有写在一行 需要加return
return createConfig(item,outputConfig[item])
})
四、配置tsconfig.json
初始化ts配置文件 npx tsc --init
将打包和模块配置成ESNEXT
"target": "ESNEXT", // 打包
"module": "ESNEXT", // 模块
五、使用yarn run build打包
打包成功后的截图如下:
六、dev.js
// 只针对某个具体的包进行打包
// 把packages目录下的所有包 进行打包
// 1、获取packages目录下的所有包
import fs from 'fs';
import {execa} from 'execa' // 开启子进程,使用rollup进行打包
const target = 'reactivity'
// 2、对所获取的包目录,进行并行依次打包
// 打包的构建方式
// 打包是异步的
async function build(target) {
// 参数一:执行的打包命令
// 参数二:执行的打包参数
// '-cw' 监听文件变化
// --environment 声明采用环境变量
// `TARGET:${target}` 传的具体环境变量 可以在rollup.config.js文件中通过process.env.TARGET访问到环境变量target
// 参数三: 子进程打包的信息共享给父进程
await execa(
'rollup',
['-cw','--environment',`TARGET:${target}`],
{stdio:'inherit'}
)
}
build(target)
七、packages目录下子包的互相引入
比如在reactivity中引入shared
import { share } from "@vue/share"
此时会报错,需要我们在tsconfig.json中,进行如下配置
"moduleResolution":"Node",
"baseUrl": ".",
"paths": {
"@vue/*": [
"packages/*/src"
]
}