Skip to content

这是 Vue3.x 源码实现的第一篇,介绍 vue3 开发环境的搭建工作

1、项目初始化

shell
mkdir mini-vue3-core
pnpm init -y

2、monorepo 架构

vue3 源码采用 monorepo 架构,在一个仓库里面管理多个包,每个包可作为单独依赖存在

margin-right

根目录创建 pnpm-workspace.yaml 文件

packages:
  - 'packages/*'

3、安装依赖

shell
pnpm install
typescript
rollup
rollup-plugin-typescript2
@rollup/plugin-json
@rollup/plugin-node-resolve
@rollup/plugin-commonjs
minimist
execa@4 -D -w

创建 ts 配置: pnpm tsc --init, 修改配置如下

json
{
  "compilerOptions": {
    "outDir": "dist",
    "sourceMap": true,
    "target": "es2016", // 目标语法
    "module": "esnext", // 模块格式
    "moduleResolution": "node", // 模块解析方式
    "resolveJsonModule": true, // 解析 json 模块
    "esModuleInterop": true, // 允许通过 es6 语法引入 commonjs 模块
    "strict": false,
    "jsx": "preserve", // jsx 不转译
    "lib": ["ESNext", "DOM"], // 支持的类库
    "baseUrl": ".",
    "paths": {
      "@vue/*": ["packages/*/src"]
    }
  }
}

4、创建 reactivity , shared 模块

每个模块都是单独的一个项目

json
// packages/reactivity/package.json
{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "main": "index.js",
  "module": "dist/reactivity.esm-bundler.js", // 给 webpack 使用
  "upkg": "dist/reactivity.global.js", // 全局使用
  "buildOptions": {
    "name": "VueReactivity",
    "formats": ["esm-bundler", "cjs", "global"]
  }
}
json
// packages/shared/package.json
{
  "name": "@vue/shared",
  "version": "1.0.0",
  "main": "index.js",
  "module": "dist/shared.esm-bundler.js",
  "buildOptions": {
    "formats": ["esm-bundler", "cjs"]
  }
}

模块 @vue/shared 中集成一些公共方法等,可在其它模块中使用

js
// packages/shared/src/index.ts
export function isObject(value: unknown) {
  return typeof value === 'object' && value !== null
}

模块 @vue/reactivity 中想要使用模块 @vue/shared 中导出的方法前,需要安装 @vue/shared 模块

在项目根目录执行

shell
pnpm install @vue/shared@workspace --filter @vue/reactivity
  • @workspace: 指向当前空间
  • --filter: 指定安装目录

@vue/reactivity 模块中之间导入就可以使用了

import { isObject } from "@vue/shared";

5、打包配置

json
"scripts": {
  "dev": "node scripts/dev.js reactivity -f global -s",
},

新增 dev 命令, 执行 scripts/dev.js, 并传递参数

  • 只打包 reactivity 模块,
  • -f:输出格式 global,
  • -s: 开启 sourceMap
js
// path: scripts/dev.js
const minimist = require('minimist')
const execa = require('execa')

const args = minimist(process.argv.slice(2))

// 获取执行命令时候的打包参数
const target = args._.length ? args._[0] : 'reactivity'
const formats = args.f || 'global'
const sourceMap = args.s || false

execa(
  'rollup',
  [
    '-wc', // --watch --config
    '--environment', // 传参
    [
      `TARGET:${target}`,
      `FORMATS:${formats}`,
      sourceMap ? `SOURCE_MAP:true` : '',
    ]
      .filter(Boolean)
      .join(','),
  ],
  {
    stdio: 'inherit', // 子进程的输出在当前命令行中输出
  }
)

脚本 dev.js 文件中会通过 execa 开启单独线程执行 rollup 进行打包,并传递了参数, rollup配置会默认从根目录查找

js
// rollup.config.js

import path from 'path'
import ts from 'rollup-plugin-typescript2'
import json from '@rollup/plugin-json'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'

const packageFormats = process.env.FORMATS && process.env.FORMATS.split(',')
const sourcemap = process.env.SOURCE_MAP

// 1、需要根据 target 找到要打包的模块
const packagesDir = path.resolve(__dirname, 'packages')
// 打包入口
const packageDir = path.resolve(packagesDir, process.env.TARGET)
// 入口文件
const resolve = (p) => path.resolve(packageDir, p)
const name = path.basename(packageDir)

const pkg = require(resolve('package.json'))

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 pkgConfigs = packageFormats || pkg.buildOptions.formats
function createConfig(format, output) {
  output.sourcemap = sourcemap
  output.exports = 'named'
  let external = []
  if (format === 'global') {
    output.name = pkg.buildOptions.name
  } else {
    external = [...Object.keys(pkg.dependencies)]
  }
  return {
    input: resolve('src/index.ts'),
    output,
    external,
    plugins: [json(), ts(), commonjs(), nodeResolve()],
  }
}

// 返回数组,会依次进行打包
export default pkgConfigs.map((format) =>
  createConfig(format, outputConfig[format])
)

此时,执行 npm run dev 就会在 packages/reactivity/dist/ 目录下生成 reactivity.global.js 文件

自此,vue3 源码开发环境初步搭建完成,拜 👋