likes
comments
collection
share

邂逅Vu3源码--1.开工准备

作者站长头像
站长
· 阅读数 13

1.说点啥

因为我们是解析vue3源码,所以用到的尽可能的和vue3的编写保持一致

使用的语言是TypeScript,放心这里都是很基础的ts语法,即使你不会ts语法也不用担心,只需要跟着写就行。

使用的打包工具是rollup,因为rollup更专注于纯粹的js打包

代码管理方式使用的是monorepo

使用的模块管理器是 yarn,因为yarn支持monorepo

2.初始化项目

2.1.构建package.json

因为我们使用的是monorepo,所以使用yarn,如果你没有yarn,需要安装一下

npm install yarn -g

构建package.json

# 注意如果你的根文件夹是有中文的 就不能使用 -y
yarn init -y

package.json添加属性

  • 添加private属性表明我们这个包是私有包,因为我们这个包是用来维护其他包的
  • workspaces:添加工作区域,规定所有的包都在packages目录下
  • 在workspaces声明目录下的包会软链到最上层root package的node_modules中。
{
  "private": "true",
  "workspaces": [
    "packages/*"
  ],
  ...
}

2.2.构建目录结构

规定:

  • 所有的模块都要是一个
  • 所有的包都要在packages目录下
  • 所有的包的入口都要统一 src/index.ts
  • 所有的包都要用自己的package.json文件

创建包

这里我们先创建两个包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字段

    • 说一下module字段和main字段的区别,
    • main:对应commonjs引入方式的程序入口文件
    • module:对应esmodule引入方式的程序入口文件
    • dist/reactivity.esm-bundler.js 这是我们自己定义该模块打包时候的位置
  • 添加自定义字段buildOptions

    • 此字段是自定义的,使用rollup打包的时候会用到这些字段
    • formats:打包的类型官网解析
    • name: 全局使用时的名称。官网解析
{
  "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"
    ]
  }
}

2.3.安装依赖

  • 请务必安装以下对应版本 否则可能会出问题
  • 至于下面的依赖什么意思这里就不在讲解了 可以自行查询
  • 注意:是在根目录安装,而不是在某个包安装
yarn add typescript@5.1.3 tslib@2.5.3 execa@5.1.1 rollup@3.25.1 rollup-plugin-typescript2@0.34.1 @rollup/plugin-json@6.0.0  @rollup/plugin-node-resolve@15.1.0 -D -W

2.4.添加打包命令

  • 首先需要在根目录的package.json添加命令
  • 这里对应的命令是文件,因为这样方便我们写更多的逻辑
{

  "scripts": {
    "build": "node scripts/build.js",
    "dev": "node scripts/dev.js"
  },

}

2.5.编写打包命令文件

这里你需要了解一下rollup的打包命令官网解析

还需要了解execa的使用方法,ececa使用方法

build.js

等我们把包都开发好了就可以直接执行

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)

dev.js

在开发某个包的时候执行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)

2.6.rollup配置文件

这需要你了解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])
})

2.7.ts.config.json的配置

因为我们使用的是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"
      ]
    }
  }
}

2.8.给包中添加内容

之前我们虽然创建了每个包,但是里面是没有内容的,打包的时候会报错。所以要想验证我们的打包能否成功,先要在每个包加点内容即可,等我们正式开放的时候在替换掉即可

2.9.包合包之间引入的问题

比如我们在shared包中导出了一个a变量,我们在reactivity中如何引入呢?

引入之前我们需要在根目录命令行中执行如下命令,因为我们采用的是monorepo的方式,所以下面的命令会在node_modules中的@vue中创建我们声明的包的软链

yarn install

那么我们直接向引入三方库那样引入即可

// 比如我们引入shared包中导出的info
import {info} from '@vue/shared'

3.打包报错问题

如果打包的时候遇到no such file or directory,unlink xxx这样的问题,那么尝试在命令行中输入yarn install命令,如果还是没用尝试把node_modules删除然后执行yarn