【话说Vue3】开发环境的搭建

105 阅读3分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

前言

在上一篇我们主要分析了 vue3 的目录结构划分及 package.json 文件几个主要属性,了解到 Vue3 通过 ”npm scripts“ 生命周期钩子函数,在 ”preinstall“ 阶段,来检测用户是否使用的 pnpm 作为包管理器,并通过 pnpm 来实现 monorepo 架构。这个章节我们将搭建一个简易的开发环境,方便后续工作的进行。来,撸起袖子,加油干吧😁

初始化pnpm环境

npm install -g pnpm
mkdir mini-vue && cd mini-vue && pnpm init -y

构建monorepo环境

在项目根目录新建 pnpm-workspace.yaml 文件

 packages
- 'packages/*'

必需的几个npm包

  1. minimist 解析node命令参数;
  2. esbuild 用于开发环境的打包;
  3. typescript
pnpm install -w minimist esbuild typescript // -w 是--workspace-root的缩写 代表根目录中启动 pnpm ,而不是当前的工作目录

这里简单的讲一下 esbuild

Esbuild 是由 Figma 的 CTO 「Evan Wallace」基于 Golang 开发的一款打包工具,相比传统的打包工具,主打性能优势,在构建速度上可以快 10~100 倍。

支持:

  • ES6 和 CommonJS 模块
  • ES6 模块的 Tree Shaking
  • JavaScript 和 Go的API
  • TypeScriptJSX语法

划分目录结构

这里我们与源码保持一致:

├── package.json
├── packages
├── scripts

初始化ts环境

tsc init

在生成的tsconfig.json文件中添加

{
    "baseUrl": ".",
    "paths": {
      "@mini-vue/*": ["packages/*/src/"] // 这里的@mini-vue是我们定义在 package.json 文件中的 name 属性
    }
}

这样配置的目的是防止我们在引入模块文件时,ts 提示异常。比如 image.png

完善 package.json

{
  "name": "mini-vue",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "node scripts/dev.js shared -f global"
  },
  "buildOptions": {
    "name": "VueShared",
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "esbuild": "^0.14.42",
    "minimist": "^1.2.6",
    "typescript": "^4.7.2"
  }
}

这里需要注意两个地方:

  1. 新增 dev 脚本命令 shared 表示需要打包的目录;-f 表示打包输入的文件格式(支持三种:cjs、esm、iife);
  2. 添加自定义属性:buildOptions ,在后续打包环节中会读取这个属性,因为每个子包的打包产物可能不同,因此在这里可以定义一些个性化的参数。

编写开发环境脚本

在项目根目录创建scripts目录,专门存放脚本文件

|-scripts                    -- 专门存放脚本文件
    |--dev.js                -- 开发环境脚本
// scripts/dev.js
const minimist = require("minimist");

const esbuild = require("esbuild");

const path = require("path");

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

const pkgName = args._[0]; // 获取命令行中指定的包名

// 这里的 global 其实就是 iife(自执行函数) 的输出格式
const f = args.f || "global"; // 获取打包输出格式

const pkg = require(path.resolve(
  __dirname,
  `../packages/${pkgName}/package.json`
));

const entry = path.resolve(__dirname, `../packages/${pkgName}/src/index.ts`);

// 打包输出路径
const outfile = path.resolve(
  __dirname,
  `../packages/${pkgName}/dist/${pkgName}.${f}.js`
);

const format = f.startsWith("global") ? "iife" : f;

esbuild
  .build({
    entryPoints: [entry],
    outfile,
    bundle: true,
    sourcemap: true,
    format, // 打包输出的文件格式
    globalName: pkg.buildOptions.name, // iife格式的文件定义的全局变量
    platform: f.startsWith("cjs") ? "node" : "browser",
    watch: { // 这里监听文件变化后再进行重构代码
      onRebuild(error, result) {
        if (error) console.error("watch build failed:", error);
        else console.log("watch build succeeded:", result);
      },
    },
  })
  .then(() => {
    console.log("watch...");
  })
  .catch(() => process.exit(1));

测试一下

在 packages 目录下新建 shared 子包,主要存放一些共享模块。

packages
└── shared
    ├── package.json
    └── src
        └── index.ts

在 shared/index.ts 文件中定义几个工具方法。

// packages/shared/index.ts
export const isObject = (obj) => {
  return typeof obj === "object" && obj !== null;
};
export const isFunction = (obj) => {
  return typeof obj === "function";
};
export const isArray = Array.isArray;

接下来运行 image.png 此时在 shared 目录下会生成一个 dist 文件夹,这个就是打包后的产物

image.png 加下来在 packages/shared/dist 文件夹下新建一个 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 这里引入刚刚打包出来的产物 -->
    <script src="./shared.global.js"></script>
    <script>
      // vueShared 其实就是 esbuild 的 globalName 属性
      const { isFunction } = vueShared;
      console.log(
        "shared",
        isFunction(() => {})
      );
    </script>
  </body>
</html>

运行结果:

image.png

总结

工欲善其事必先利其器,这个章节我们搭建了一个简易的开发环境,完善了 ts.config.json 与 package.json 文件,并编写了基于 esbuild 的开发环境构建脚本。麻雀虽小,但五脏俱全,到目前为止尽量还原了 Vue3 源码的主干部分。下一章节进入 Vue3 响应式系统💪🏻