因为我们是解析vue3源码,所以用到的尽可能的和vue3的编写保持一致
使用的语言是TypeScript
,放心这里都是很基础的ts语法,即使你不会ts语法也不用担心,只需要跟着写就行。
使用的打包工具是rollup
,因为rollup更专注于纯粹的js打包
代码管理方式使用的是monorepo
使用的模块管理器是 yarn
,因为yarn支持monorepo
因为我们使用的是monorepo,所以使用yarn,如果你没有yarn,需要安装一下
npm install yarn -g
构建package.json
# 注意如果你的根文件夹是有中文的 就不能使用 -y
yarn init -y
package.json添加属性
packages目录下
{
"private": "true",
"workspaces": [
"packages/*"
],
...
}
规定:
包
packages
目录下src/index.ts
创建包
这里我们先创建两个包reactivity、shared(共享)
,因为我们先开发的是reactivity包,后面开发其他的,按照规范创建即可,目录结构如下
vue3 exercise
├─ package.json
├─ packages # 存放模块
│ ├─ reactivity
│ │ ├─ package.json
│ │ └─ src
│ │ └─ index.ts
│ └─ shared
│ ├─ package.json
│ └─ src
│ └─ index.ts
└─ scripts #脚本文件夹
├─ build.js
└─ dev.js
配置reactivity包的package.json
@vue/reactivity
,@vue是自定义的,这样到时候我们自己写的包软链到最上层root package的node_modules的时候都会在@vue文件夹下,方便我们管理module
字段
main
:对应commonjs引入方式的程序入口文件module
:对应esmodule引入方式的程序入口文件dist/reactivity.esm-bundler.js
这是我们自己定义该模块打包时候的位置buildOptions
{
"name": "@vue/reactivity",
"version": "1.0.0",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
"license": "MIT",
"buildOptions": {
"name": "VueReactivity",
"formats": [
"es",
"cjs",
"iife"
]
}
}
配置shared包的package.json
{
"name": "@vue/shared",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"buildOptions": {
"name": "VueShared",
"formats": [
"es",
"cjs",
"iife"
]
}
}
yarn add [email protected] [email protected] [email protected] [email protected] [email protected] @rollup/[email protected] @rollup/[email protected] -D -W
根目录
的package.json添加命令{
"scripts": {
"build": "node scripts/build.js",
"dev": "node scripts/dev.js"
},
}
这里你需要了解一下rollup的打包命令官网解析
还需要了解execa的使用方法,ececa使用方法
等我们把包都开发好了就可以直接执行
yarn build
命令
// 把packages目录下所有的包全部打包
const fs = require('fs')
const execa = require('execa')
// 1. 首先找到packages目录下的所有文件夹,且要排除packages下不是文件夹的文件
// 比如packages下有一个readme.md 那么就要排除
const targets = fs.readdirSync('packages').filter((f) => {
if (!fs.statSync(`packages/${f}`).isDirectory()) return false
return true
})
// 2. 对找到的文件夹依次进行打包,这里使用并行的方式,不需要等待上一个包打包完在打包当前的包
// 2.1 构建函数
async function build(target) {
// 利用node子进程 进行打包
// 下面的操作相当于在命令行中输入 rollup -c --bundleConfigAsCjs --environment TARGET:xxx
// --bundleConfigAsCjs 因为我们在rollup.config.js中使用esm的导入语法,如果不添加这个会有问题
// 官网解析 https://cn.rollupjs.org/command-line-interface/#bundleconfigascjs
// --environment 后面的值我们可以通过process.ENV.xxx 获取到 在配置文件中会用到
// 官网解析 https://cn.rollupjs.org/command-line-interface/#environment-values
await execa(
'rollup',
['-c', '--bundleConfigAsCjs', '--environment', `TARGET:${target}`],
{
stdio: 'inherit', // 子进程打包信息共享给父进程
}
)
}
// 2.2.并行所有构建函数
// 这里其实我们直接用forEach遍历然后执行就行了
// 但是我们为了和vue3源码保持一致就用了这种方法,vue3源码会用到当前函数的返回值,我们这里用不到
function runParallel(targets, iteraotrFn) {
const res = []
for (let target of targets) {
const p = iteraotrFn(target)
res.push(p)
}
return Promise.all(res)
}
// 进行打包构建
runParallel(targets, build)
在开发某个包的时候执行yarn dev
会实时监控修改然后打包,很方便
// 只针对具体的某个包打包
const execa = require('execa')
// 开发那个包 将包名替换即可
const target = 'reactivity'
// 对目标进行依次打包 并行打包
async function build(target) {
await execa(
'rollup',
['-cw', '--bundleConfigAsCjs', '--environment', `TARGET:${target}`],
{
stdio: 'inherit', // 子进程打包信息共享给父进程
}
)
}
build(target)
这需要你了解rollup的配置 官网解析
import path from 'path'
import ts from 'rollup-plugin-typescript2'
import resolvePlugin from '@rollup/plugin-node-resolve'
import json from '@rollup/plugin-json'
/** 1. 首先需要知道哪个模块进行打包,我们需要拿到对应包的package.json文件
开始我们在里面自定义了一个buildOptions字段 现在需要拿到这个字段 **/
// 所有的包都在packages下,将这个目录作为根目录
const packagesDir = path.resolve(__dirname, 'packages')
// 获取我们刚刚在build/dev中传入的环境变量 其实就是包的名称
const packageDir = path.resolve(packagesDir, process.env.TARGET)
// 创建一个函数 用于获取packageDir下的文件 这样可以偷懒
const resolve = (p) => path.resolve(packageDir, p)
// 获取不同包的package.json json文件可以直接引入
const pkg = require(resolve('package.json'))
// 获取包的文件名 作为打包出口的文件名 也可以使用process.env.TARGET 反正都是一样的
const name = path.basename(packageDir)
// 创建一个映射表,将我们在package.json中的自定义字段buildOptions中的内容映射成rollup中的配置
const outputConfig = {
es: {
file: resolve(`dist/${name}.esm-boundler.js`),
format: 'es',
},
cjs: {
file: resolve(`dist/${name}.cjs.js`),
format: 'cjs',
},
global: {
file: resolve(`dist/${name}.global.js`),
format: 'iife',
},
}
/**
* 2.创建rollup的配置
*/
const options = pkg.buildOptions
function createConfig(format, output) {
output.name = options.name
output.sourcemap = true
return {
input: resolve('src/index.ts'),
output,
plugins: [
json(),
ts({
// tsconfig文件的位置
tsconfig: path.resolve(__dirname, 'tsconfig.json'),
}),
resolvePlugin(),
],
}
}
// 导出配置
export default options.formats.map((format) => {
createConfig(format, outputConfig[format])
})
因为我们使用的是ts编写的,所以需要ts的配置文件,
生成ts配置文件
在根目录输入以下命令
tsc --init
配置ts.config.json
这里不过多讲述ts的语法,自行查阅 按照以下配置即可
{
"compilerOptions": {
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library
/* Modules */
"module": "ESNext", /* Specify what module code is generated. */
"noImplicitAny": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"sourceMap": true,
"strict": true,
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@vue/*": [
"packages/*/src"
]
}
}
}
之前我们虽然创建了每个包,但是里面是没有内容的,打包的时候会报错。所以要想验证我们的打包能否成功,先要在每个包加点内容即可,等我们正式开放的时候在替换掉即可
比如我们在shared包中导出了一个a变量,我们在reactivity中如何引入呢?
引入之前我们需要在根目录命令行中执行如下命令,因为我们采用的是monorepo的方式,所以下面的命令会在node_modules中的@vue中创建我们声明的包的软链
yarn install
那么我们直接向引入三方库那样引入即可
// 比如我们引入shared包中导出的info
import {info} from '@vue/shared'
如果打包的时候遇到no such file or directory,unlink xxx
这样的问题,那么尝试在命令行中输入yarn install
命令,如果还是没用尝试把node_modules删除然后执行yarn