vue3源码解析:1.2 vue3构建流程搭建-monorepo

1,143 阅读2分钟

搭建 monorepo

实现对 es6 语法、ts、项目特定的编译, 尽可能还原 vue3 对多个模块的构建方式.

yarn 安装项目依赖

vue3 采用 menorepo 的方式,目前只有 yarn 支持

yarn init -y
yarn add typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa -D
# execa: 同时打包多个项目,是 node 的子进程
# p.s. —ignore-workspace-root-check 给根模块安装而不是子模块
依赖
typescript支持typescript
rollup打包工具
rollup-plugin-typescript2rollup 和 ts的 桥梁
@rollup/plugin-node-resolve解析node第三方模块
@rollup/plugin-json支持引入json
execa开启子进程方便执行命令

目录结构

├── package.json            # 配置运行命令 
├── packages                # n个repo
│   ├── reactivity          # 响应式系统
│   │   ├── dist
│   │   ├── package.json
│   │   └── src
│   ├── runtime-core        # 与平台无关的运行时核心
│   │   └── dist
│   ├── runtime-dom
│   │   └── dist
│   └── shared
│       ├── dist
│       ├── package.json
│       └── src
├── rollup.config.js       # rollup配置文件
├── scripts                # 打包命令
│   ├── build.js
│   └── dev.js
└── tsconfig.json

package.json

作用: 维护packages/*目录下的多个包

  • private:true:表示是个私有包
  • 指定工作空间:workspace:["packages/*"],所有包都管理到 packages 目录下
  • 执行yarn install时,会将packages/*生成软链到 node_modules 下,这样就可以在当前任意模块引入其他模块,即多个包可以相互引用
  • 一个包可以依赖另一个包:yarn workspace @vue/reactivity add @vue/shared@1.0.0
{
+  "private": true,
+  "workspaces": [
+    "packages/*"
+  ],
  "name": "crx-vue3-core-code",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "@rollup/plugin-json": "^4.1.0",
    "@rollup/plugin-node-resolve": "^13.0.0",
    "execa": "^5.1.1",
    "rollup": "^2.52.3",
    "rollup-plugin-typescript2": "^0.30.0",
    "typescript": "^4.3.4"
  }
}

package.json

每个子模块需要遵循一定机制:

packages/reactivity/package.json

"name": "@vue/reactivity",
"main": "index.js", // node commonjs
"module": "dist/reactivity.esm-bundle.js",// import '@vue/reactivity' --> 找dist/reactivity.esm-bundle.js文件
"buildOptions":{
  "name":"VueReactivity",// 给全局的包起名 <script src=".../dist/reactivity.esm-bundle.js"></script>引入后可以使用VueReactivity变量
  // 配置当前模块支持 node、es6、全局模块
  "formats":[
    "cjs",
    "esm-bundler",
    "global"
  ]
}

packages/reactivity/package.json

{
  "name": "@vue/shared",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "module": "dist/shared.esm-bundle.js",
  "buildOptions":{
    "name":"VueShared",
    "formats":[
      "cjs",
      "esm-bundler"
    ]
  },
  "scripts": {},
  "keywords": [],
  "author": "",
  "license": "ISC"
}

编译脚本、rollup、ts配置

package.json

  "scripts": {
    "dev": "node scripts/dev.js",
    "build": "node scripts/build.js"
  }
  • dev.js 只针对具体的某个包打包 作用: 给 rollup 传入 target 环境变量,生成 rollup 配置,可以根据后面的参数 打包的类型 做一些忽略 忽略一些文件
  • build.js 打包package目录下的所有包

scripts/build.js

const fs = require("fs");
const execa = require("execa"); // 开启子进程打包,最终还是使用rollup来进行打包

// 过滤packages目录下的文件,保留文件夹
const targets = fs.readdirSync("packages").filter((f) => {
  if (!fs.statSync(`packages/${f}`).isDirectory()) {
    return false;
  }
  return true;
});

// 对目标依次、并行打包
async function build(target) {
  // rollup  -c --environment TARGET:shated
  await execa("rollup", ["-c", "--environment", `TARGET:${target}`], {
    stdio: "inherit",
  }); // 当子进程打包的信息共享给父进程
}

function runParallel(targets, iteratorFn) {
  const res = [];
  for (const item of targets) {
    const p = iteratorFn(item);
    res.push(p);
  }
  return Promise.all(res);
}

runParallel(targets, build);

scripts/dev.js

const execa = require("execa");
const target = "reactivity";
build(target);
async function build(target) {
  await execa("rollup", ["-cw", "--environment", `TARGET:${target}`], {
    stdio: "inherit",
  });
}

rollup.config.js

import path from "path";
import json from "@rollup/plugin-json";
import resolvePlugin from "@rollup/plugin-node-resolve";
import ts from "rollup-plugin-typescript2";

// 1.根据环境变量中的target属性 获取对应模块中的 pakcage.json
const packagesDir = path.resolve(__dirname, "packages");
const packageDir = path.resolve(packagesDir, process.env.TARGET); // packageDir 找到要打包的某个包/打包的基准目录
const resolve = (p) => path.resolve(packageDir, p);
const pkg = require(resolve("package.json"));
const name = path.basename(packageDir); // 取文件名 或者process.env.TARGET

// 对打包类型的映射表,根据提供的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", // 立即执行函数
  },
};
const options = pkg.buildOptions;

// 生成rollup配置
function createConfig(format, output) {
  output.name = options.name;
  output.sourcemap = true; // 生成sourcemap

  return {
    input: resolve(`src/index.ts`),
    output,
    plugins: [
      json(),
      ts({
        // ts 插件
        tsconfig: path.resolve(__dirname, "tsconfig.json"),
      }),
      resolvePlugin(), // 解析第三方模块插件
    ],
  };
}
// rollup 最终需要到出配置
export default options.formats.map((format) =>
  createConfig(format, outputConfig[format])
);

tsconfig.json

{
  "compilerOptions": {
    "target": "ESNEXT",
    "module": "ESNEXT",
    "strict": false,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@vue/*": ["packages/*/src"]
    }
  }
}

github