本文介绍了Vue3的架构和开发环境搭建
1.Vue3 架构介绍
1.1 Monorepo 管理项目
Monorepo 是管理项目代码的一个方式,指在一个项目仓库(repo
)中管理多个模块/包(package
)。 Vue3
源码采用 monorepo
方式进行管理,将模块拆分到 package
目录中。
- 一个仓库可维护多个模块,不用到处找仓库
- 方便版本管理和依赖管理,模块之间的引用,调用都非常方便
1.2 Vue3 项目结构
模块拆分说明:
- 编译时模块(
compiler-core、compiler-dom、compiler-sfc、compiler-ssr
) - 运行时模块(
runtime-core、runtime-dom
) - 响应式系统(
reactivity-transform、reactivity
) - vue2兼容(
vue-compat
) - 共享模块(
shared
) - 服务端渲染(
server-renderer
) - 模版编译(
template-explorer
) - Vue的运行时和编译器(
vue
)
1.3 Vue3 采用 Typescript
作为类型系统
Vue2
采用Flow
来进行类型检测 (Vue2
中对TS
支持并不友好),Vue3
源码采用Typescript
来进行重写 , 对TS
的支持更加友好。
2. Vue3 开发环境搭建
2.1 搭建 Monorepo 环境
Vue3 中使用pnpm
workspace
来实现monorepo
(pnpm是快速、节省磁盘空间的包管理器。主要采用符号链接的方式管理模块)
2.1.1 全局安装 pnpm
bash
npm install pnpm -g # 全局安装pnpm
bash
pnpm init -y # 初始化配置文件
2.1.2 创建.npmrc
文件
ini
shamefully-hoist = true
这里您可以尝试一下安装
Vue3
,pnpm install vue@next
此时默认情况下vue3
中依赖的模块不会被提升到node_modules
下。 添加shamefully-hoist 可以将 Vue3,所依赖的模块提升到node_modules
中
2.1.3 配置 workspace
新建 pnpm-workspace.yaml
yaml
packages:
- "packages/*"
将 packages 下所有的目录都作为包进行管理。这样我们的 Monorepo 就搭建好了。确实比
lerna + yarn workspace
更快捷
2.2 环境搭建
打包项目,Vue3 是采用
rollup
进行打包代码,因此需要安装打包所需要的依赖
依赖 | |
---|---|
typescript | 在项目中支持 Typescript |
rollup | JavaScript 模块打包器 |
rollup-plugin-typescript2 | rollup 和 ts 的 桥梁 |
@rollup/plugin-json | 支持引入 json |
@rollup/plugin-node-resolve | 解析 node 第三方模块 |
@rollup/plugin-commonjs | 将 CommonJS 转化为 ES6Module |
minimist | 命令行参数解析 |
execa@4 | 开启子进程 |
bash
pnpm init -y
bash
pnpm install typescript rollup rollup-plugin-typescript2 @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-commonjs minimist execa@4 esbuild -D -w
2.2.1 初始化 TS 配置
bash
pnpm tsc --init
先添加些常用的
ts-config
配置,后续需要其他的在继续增加
json
{
"compilerOptions": {
"outDir": "dist", // 输出的目录
"sourceMap": true, // 采用sourcemap
"target": "es2016", // 目标语法
"module": "esnext", // 模块格式
"moduleResolution": "node", // 模块解析方式
"strict": false, // 严格模式
"resolveJsonModule": true, // 解析json模块
"esModuleInterop": true, // 允许通过es6语法引入commonjs模块
"jsx": "preserve", // jsx 不转义
"lib": ["esnext", "dom"] // 支持的类库 esnext及dom
}
}
2.2.2 创建模块
我们先创建
packages
目录,然后在packages
目录下新建两个package
reactivity
响应式模块shared
共享模块
所有包的入口均为src/index.ts
这样可以实现统一打包
初始化两个模块的包管理配置文件 package.json
:
-
reactivity/package.json
json{ "name": "@vue/reactivity", "version": "1.0.0", "main": "index.js", "module": "dist/reactivity.esm-bundler.js", "unpkg": "dist/reactivity.global.js", "buildOptions": { "name": "VueReactivity", "formats": ["esm-bundler", "cjs", "global"] } }
-
reactivity/src/index.ts
ts
export const str = 'reactivity';
console.log(str);
shared/package.json
shared
是vue3内部共享的模块包,不需要暴露出去使用,因此不需要打包成 立即执行函数(IIFE) 的格式
json
{
"name": "@vue/shared",
"version": "1.0.0",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",
"buildOptions": {
"formats": ["esm-bundler", "cjs"]
}
}
shared/src/index.ts
ts
export const str = 'shared';
console.log(str);
formats为自定义的打包格式
esm-bundler
: 构建工具中使用的格式esm-browser
: 浏览器中使用的格式cjs
: node 中使用的格式global
: 立即执行函数(IIFE)的格式
bash
pnpm install @vue/shared@workspace --filter @vue/reactivity
tsconfig.json
配置ts
引用关系
json
"baseUrl": ".",
"paths": {
"@vue/*": ["packages/*/src"]
}
2.2.3 开发环境esbuild
打包
创建开发时执行脚本,参数为要打包的模块
解析用户参数
json
"scripts": {
"dev": "node scripts/dev.js reactivity -f global"
}
创建打包文件:scripts/dev.js
js
const { build } = require("esbuild");
const { resolve } = require("path");
const args = require("minimist")(process.argv.slice(2));
const target = args._[0] || "reactivity";
const format = args.f || "global";
const pkg = require(resolve(__dirname, `../packages/${target}/package.json`));
const outputFormat = format.startsWith("global") // 输出的格式
? "iife"
: format === "cjs"
? "cjs"
: "esm";
const outfile = resolve(
// 输出的文件
__dirname,
`../packages/${target}/dist/${target}.${format}.js`
);
build({
entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],
outfile,
bundle: true,
sourcemap: true,
format: outputFormat,
globalName: pkg.buildOptions?.name,
platform: format === "cjs" ? "node" : "browser",
watch: {
// 监控文件变化执行重新构建
onRebuild(error) {
if (!error) console.log(`rebuilt~~~~`);
},
},
}).then(() => {
console.log("watching~~~");
});
命令行执行 pnpm run dev
or yarn dev
然后再更改下reactivity/src/index.ts
文件内容,看看终端效果:
再看下我们打包后的代码:
2.2.4 生产环境rollup
打包
2.2.4.1 新建 rollup 打包配置 📦 文件
rollup.config.js
js
import path from "path";
// 获取packages目录
const packagesDir = path.resolve(__dirname, "packages");
// 获取对应的模块
const packageDir = path.resolve(packagesDir, process.env.TARGET);
// 全部以打包目录来解析文件
const resolve = (p) => path.resolve(packageDir, p);
const pkg = require(resolve("package.json"));
const name = path.basename(packageDir); // 获取包的名字
// 配置打包信息
const outputConfigs = {
"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",
},
};
// 获取formats
const packageFormats = process.env.FORMATS && process.env.FORMATS.split(",");
const packageConfigs = packageFormats || pkg.buildOptions.formats;
import json from "@rollup/plugin-json";
import commonjs from "@rollup/plugin-commonjs";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import tsPlugin from "rollup-plugin-typescript2";
function createConfig(format, output) {
output.sourcemap = process.env.SOURCE_MAP;
output.exports = "named";
let external = [];
if (format === "global") {
output.name = pkg.buildOptions.name;
} else {
// cjs/esm 不需要打包依赖文件
external = [...Object.keys(pkg.dependencies || {})];
}
return {
input: resolve("src/index.ts"),
output,
external,
plugins: [json(), tsPlugin(), commonjs(), nodeResolve()],
};
}
// 开始打包...
export default packageConfigs.map((format) =>
createConfig(format, outputConfigs[format])
);
2.2.4.2 新建自动打包脚本 build.js
scripts/build.js
js
const fs = require("fs");
const execa = require("execa");
const targets = fs.readdirSync("packages").filter((f) => {
if (!fs.statSync(`packages/${f}`).isDirectory()) {
return false;
}
return true;
});
async function runParallel(source, iteratorFn) {
const ret = [];
for (const item of source) {
const p = Promise.resolve().then(() => iteratorFn(item));
ret.push(p);
}
return Promise.all(ret);
}
async function build(target) {
await execa("rollup", ["-c", "--environment", `TARGET:${target}`], {
stdio: "inherit",
});
}
runParallel(targets, build);
2.2.4.3 添加打包命令
json
"scripts": {
"build": "node scripts/build.js"
}
命令行执行 pnpm run build
or yarn build
打包 📦 结果如下:
总结
Vue3
采用了Monorepo
管理项目,将模块拆分到package
目录中,主要包括:
- 编译时模块(
compiler-core、compiler-dom、compiler-sfc、compiler-ssr
) - 运行时模块(
runtime-core、runtime-dom
) - 响应式系统(
reactivity-transform、reactivity
) - vue2兼容(
vue-compat
) - 共享模块(
shared
) - 服务端渲染(
server-renderer
) - 模版编译(
template-explorer
) - ...
这样是方便版本管理和依赖管理。
同时,Vue3
采用Typescript
作为类型系统,对TS的支持更加友好。 在开发环境搭建方面,Vue3
使用pnpm workspace
实现Monorepo
,使用esbuild
进行开发环境打包,使用rollup
进行生产环境打包。