如何使用pnpm在Vue3中搭建一个高效的monorepo长尾词开发环境?

2026-04-01 13:051阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计3128个文字,预计阅读时间需要13分钟。

如何使用pnpm在Vue3中搭建一个高效的monorepo长尾词开发环境?

目录+前言+Pnpm 与 Monorepo 搭建开发环境+创建项目+配置 monorepo+安装依赖+初始化 Typescript+准备两个模块+shared, reactivity+编写构建脚本+完成第一次调试+小结+前言+Vue3 源码阅读系列,计划从环境搭建开始

目录
  • 前言
  • Pnpm 和 Monorepo
  • 搭建开发环境
    • 创建项目
    • 配置 monorepo
    • 安装依赖
    • 初始化Typescript
    • 准备两个模块
      • shared
      • reactivity
    • 编写构建脚本
      • 完成第一次调试
      • 小结

        前言

        Vue3 源码阅读系列,计划从环境搭建开始,将 Vue3 的响应式模块,运行时模块和编译器模块,以及状态库 Pinia、路由库 Vue-Router的核心原理做一个梳理。这大概是一个漫长的过程。祝自己不要烂尾,祝大家有所收获。

        Pnpm 和 Monorepo

        Pnpm 是新一代的 nodejs 包管理工具。第一个 “P”意为 Performance,代表着更佳的性能。

        它的主要优点有两个,一是采用了 hard-link 机制,避免了包的重复安装,节省了空间,同时能提高项目依赖的安装速度。二是对monorepo 的支持非常友好,只需要一条配置就能实现。

        Monorepo 是一种新的仓库管理方式。过去的项目,大多采用一个仓库维护一个项目的方案。对于一个庞大复杂的项目,哪怕只进行一处小小的修改,影响的也是整体。而采用 monorepo 的形式,我们可以在一个仓库中管理多个包。每个包都可以单独发布和使用,就好像是一个仓库中又有若干个小仓库。

        Vue3 源码采用 monorepo 方式进行管理,将众多模块拆分到 packages 目录中。

        这带来的最直观的好处,就是方便管理和维护。而且,它不像 Vue2 那样将源码整体打包对外暴露。Vue3的这种组织形式,方便的实现了 Tree-shaking,需要哪个功能就引入对应的模块,能大大减少打包后项目的体积。

        搭建开发环境

        创建项目

        首先全局安装 pnpm

        npm install -g pnpm

        新建一个目录并进行初始化:

        mkdir vue3-learn cd vue3-learn pnpm init mkdir packages

        配置 monorepo

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

        packages: - 'packages/*'

        意思是,将 packages 目录下所有的目录都作为单独的包进行管理。

        通过这样一个简单的配置,Monorepo 开发环境搭建好了。

        如果大家之前接触过 lerna + yarn workspace的方案,就会深有体会,使用 pnpm 的确方便。Vue3Element Plus以前采用的方案就是前者,现在都已经改用后者了。

        安装依赖

        如果你使用过 Vite,就一定体验过它的快。因为 Vite 内置了 esbuild 作为开发阶段的构建工具。esbuild 的特点就是快。

        Vue3 采用了和 vite 一致的选择,开发阶段使用 esbuild 作为构建工具,在生产阶段采用 rollup 进行打包。

        如何使用pnpm在Vue3中搭建一个高效的monorepo长尾词开发环境?

        我们先安装一些依赖:

        # 源码采用 typescript 编写 pnpm add -D -w typescript # 构建工具,命令行参数解析工具 pnpm add -D -w esbuild rollup rollup-plugin-typescript2 @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-commonjs minimist execa

        说明:

        -D:作为开发依赖安装

        -wmonorepo 环境默认会认为应该将依赖安装到具体的 package中。使用 -w 参数,告诉 pnpm 将依赖安装到 workspace-root,也就是项目的根目录。

        依赖说明:

        依赖描述typescript项目使用 typescript 进行开发esbuild开发阶段的构建工具rollup生产阶段的构建工具rollup-plugin-typescript2rollup 编译 ts 的插件@rollup/plugin-jsonrollup 默认采用 esm 方式解析模块,该插件将 json 解析为 esm 供 rollup 处理@rollup/plugin-node-resolverollup 默认采用 esm 方式解析模块,该插件可以解析安装在 node_modules 下的第三方模块@rollup/plugin-commonjs将 commonjs 模块 转化为 esm 模块minimist解析命令行参数execa生产阶段开启子进程

        初始化Typescript

        pnpm tsc --init

        pnpm 的使用基本和 npm 一致。这里的用法就相当于 npm 中的 npx

        npx tsc --init

        意思是,去 node_modules 下的 .bin 目录中找到tsc 命令,并执行它。

        执行完该命令,会在项目根目录生成一个 tsconfig.json 文件,进行一些配置:

        { "compilerOptions": { "outDir": "dist", // 输出的目录 "sourceMap": true, // 开启 sourcemap "target": "es2016", // 转译的目标语法 "module": "esnext", // 模块格式 "moduleResolution": "node", // 模块解析方式 "strict": false, // 关闭严格模式,就能使用 any 了 "resolveJsonModule": true, // 解析 json 模块 "esModuleInterop": true, // 允许通过 es6 语法引入 commonjs 模块 "jsx": "preserve", // jsx 不转义 "lib": ["esnext", "dom"], // 支持的类库 esnext及dom "baseUrl": ".", // 当前目录,即项目根目录作为基础目录 "paths": { // 路径别名配置 "@my-vue/*": ["packages/*/src"] // 当引入 @my-vue/时,去 packages/*/src中找 }, } }

        准备两个模块

        我们先在 packages 目录下新建两个模块,分别是 reactivity 响应式模块 和 shared 工具库模块。然后编写构建脚本进行第一次的开发调试。

        shared

        packages 下新建 shared 目录,并初始化:

        pnpm init

        然后修改 package.json

        { "name": "@my-vue/shared", "version": "1.0.0", "description": "@my-vue/shared", "main": "dist/shared.cjs.js", "module": "dist/shared.esm-bundler.js" }

        注意 name 字段的值,我们使用了一个 @scope 作用域,它相当于 npm 包的命名空间,可以使项目结构更加清晰,也能减少包的重名。

        编写该模块的入口文件:

        // src/index.ts /** * 判断对象 */ export const isObject = (value) =>{ return typeof value === 'object' && value !== null } /** * 判断函数 */ export const isFunction= (value) =>{ return typeof value === 'function' } /** * 判断字符串 */ export const isString = (value) => { return typeof value === 'string' } /** * 判断数字 */ export const isNumber =(value)=>{ return typeof value === 'number' } /** * 判断数组 */ export const isArray = Array.isArray

        reactivity

        packages 下新建 reactivity 目录,并初始化:

        pnpm init

        然后修改 package.json

        { "name": "@my-vue/reactivity", "version": "1.0.0", "description": "@my-vue/reactivity", "main": "dist/reactivity.cjs.js", "module": "dist/reactivity.esm-bundler.js", "buildOptions": { "name": "VueReactivity" } }

        在浏览器中以 IIFE 格式使用响应式模块时,需要给模块指定一个全局变量名字,通过 buildOptions.name 进行指定,将来打包时会作为配置使用。

        main 指定的文件支持 commonjs 规范进行导入,也就是说在nodejs 环境中,通过 require 方法导入该模块时,会导入 main 指定的文件。

        同理,module 指定的是使用 ES Module 规范导入模块时的入口文件。

        编写该模块的入口文件:

        // src/index.ts import { isObject } from '@my-vue/shared' const obj = {name: 'Vue3'} console.log(isObject(obj))

        reactivity 包中用到了另一个包 shared ,需要安装才能使用:

        pnpm add @my-vue/shared@workspace --filter @my-vue/reactivity

        意思是,将本地 workspace 内的 @my-vue/shared 包,安装到 @my-vue/reactivity包中去。

        此时,查看 reactivity 包的依赖信息:

        "dependencies": { "@my-vue/shared": "workspace:^1.0.0" }

        编写构建脚本

        在根目录下新建 scripts 目录,存放项目构建的脚本。

        新建 dev.js,作为开发阶段的构建脚本。

        // scripts/dev.js // 使用 minimist 解析命令行参数 const args = require('minimist')(process.argv.slice(2)) const path = require('path') // 使用 esbuild 作为构建工具 const { build } = require('esbuild') // 需要打包的模块。默认打包 reactivity 模块 const target = args._[0] || 'reactivity' // 打包的格式。默认为 global,即打包成 IIFE 格式,在浏览器中使用 const format = args.f || 'global' // 打包的入口文件。每个模块的 src/index.ts 作为该模块的入口文件 const entry = path.resolve(__dirname, `../packages/${target}/src/index.ts`) // 打包文件的输出格式 const outputFormat = format.startsWith('global') ? 'iife' : format === 'cjs' ? 'cjs' : 'esm' // 文件输出路径。输出到模块目录下的 dist 目录下,并以各自的模块规范为后缀名作为区分 const outfile = path.resolve(__dirname, `../packages/${target}/dist/${target}.${format}.js`) // 读取模块的 package.json,它包含了一些打包时需要用到的配置信息 const pkg = require(path.resolve(__dirname, `../packages/${target}/package.json`)) // buildOptions.name 是模块打包为 IIFE 格式时的全局变量名字 const pgkGlobalName = pkg?.buildOptions?.name console.log('模块信息:\n', entry, '\n', format, '\n', outputFormat, '\n', outfile) // 使用 esbuild 打包 build({ // 打包入口文件,是一个数组或者对象 entryPoints: [entry], // 输入文件路径 outfile, // 将依赖的文件递归的打包到一个文件中,默认不会进行打包 bundle: true, // 开启 sourceMap sourcemap: true, // 打包文件的输出格式,值有三种:iife、cjs 和 esm format: outputFormat, // 如果输出格式为 IIFE,需要为其指定一个全局变量名字 globalName: pgkGlobalName, // 默认情况下,esbuild 构建会生成用于浏览器的代码。如果打包的文件是在 node 环境运行,需要将平台设置为node platform: format === 'cjs' ? 'node' : 'browser', // 监听文件变化,进行重新构建 watch: { onRebuild (error, result) { if (error) { console.error('build 失败:', error) } else { console.log('build 成功:', result) } } } }).then(() => { console.log('watching ...') })

        使用该脚本,会使用 esbuildpackages 下的包进行构建,打包的结果放到各个包的 dist 目录下。

        在开发阶段,我们默认打包成 IIFE 格式,方便在浏览器中使用 html 文件进行测试。在生产阶段,会分别打包成 CommonJSES ModuleIIFE 的格式。

        完成第一次调试

        给项目增加一条 scripts 命令:

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

        意思是,以 IIFE 的格式,打包 reactivity 模块,打包后的文件可以运行在浏览器中。

        在终端中执行:

        pnpm dev

        输出:

        PS D:\vue3-learn> pnpm dev
        > vue3-learn@1.0.0 dev D:\vue3-learn
        > node scripts/dev.js reactivity -f global
        模块信息:
        D:\vue3-learn\packages\reactivity\src\index.ts
        global
        iife
        D:\demo3\vue3-learn\packages\reactivity\dist\reactivity.global.js
        watching ...

        编写一个 html 文件进行测试:

        // packages/reactivity/test/index.html <body> <div id="app"></div> <script src="../dist/reactivity.global.js"></script> </body>

        打开浏览器控制台:

        小结

        到此,一个基本的 monorepo 开发环境就搭建完毕了。

        代码已上传至 Github ,点击访问。

        更多关于Vue3 pnpm搭建monorepo的资料请关注易盾网络其它相关文章!

        本文共计3128个文字,预计阅读时间需要13分钟。

        如何使用pnpm在Vue3中搭建一个高效的monorepo长尾词开发环境?

        目录+前言+Pnpm 与 Monorepo 搭建开发环境+创建项目+配置 monorepo+安装依赖+初始化 Typescript+准备两个模块+shared, reactivity+编写构建脚本+完成第一次调试+小结+前言+Vue3 源码阅读系列,计划从环境搭建开始

        目录
        • 前言
        • Pnpm 和 Monorepo
        • 搭建开发环境
          • 创建项目
          • 配置 monorepo
          • 安装依赖
          • 初始化Typescript
          • 准备两个模块
            • shared
            • reactivity
          • 编写构建脚本
            • 完成第一次调试
            • 小结

              前言

              Vue3 源码阅读系列,计划从环境搭建开始,将 Vue3 的响应式模块,运行时模块和编译器模块,以及状态库 Pinia、路由库 Vue-Router的核心原理做一个梳理。这大概是一个漫长的过程。祝自己不要烂尾,祝大家有所收获。

              Pnpm 和 Monorepo

              Pnpm 是新一代的 nodejs 包管理工具。第一个 “P”意为 Performance,代表着更佳的性能。

              它的主要优点有两个,一是采用了 hard-link 机制,避免了包的重复安装,节省了空间,同时能提高项目依赖的安装速度。二是对monorepo 的支持非常友好,只需要一条配置就能实现。

              Monorepo 是一种新的仓库管理方式。过去的项目,大多采用一个仓库维护一个项目的方案。对于一个庞大复杂的项目,哪怕只进行一处小小的修改,影响的也是整体。而采用 monorepo 的形式,我们可以在一个仓库中管理多个包。每个包都可以单独发布和使用,就好像是一个仓库中又有若干个小仓库。

              Vue3 源码采用 monorepo 方式进行管理,将众多模块拆分到 packages 目录中。

              这带来的最直观的好处,就是方便管理和维护。而且,它不像 Vue2 那样将源码整体打包对外暴露。Vue3的这种组织形式,方便的实现了 Tree-shaking,需要哪个功能就引入对应的模块,能大大减少打包后项目的体积。

              搭建开发环境

              创建项目

              首先全局安装 pnpm

              npm install -g pnpm

              新建一个目录并进行初始化:

              mkdir vue3-learn cd vue3-learn pnpm init mkdir packages

              配置 monorepo

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

              packages: - 'packages/*'

              意思是,将 packages 目录下所有的目录都作为单独的包进行管理。

              通过这样一个简单的配置,Monorepo 开发环境搭建好了。

              如果大家之前接触过 lerna + yarn workspace的方案,就会深有体会,使用 pnpm 的确方便。Vue3Element Plus以前采用的方案就是前者,现在都已经改用后者了。

              安装依赖

              如果你使用过 Vite,就一定体验过它的快。因为 Vite 内置了 esbuild 作为开发阶段的构建工具。esbuild 的特点就是快。

              Vue3 采用了和 vite 一致的选择,开发阶段使用 esbuild 作为构建工具,在生产阶段采用 rollup 进行打包。

              如何使用pnpm在Vue3中搭建一个高效的monorepo长尾词开发环境?

              我们先安装一些依赖:

              # 源码采用 typescript 编写 pnpm add -D -w typescript # 构建工具,命令行参数解析工具 pnpm add -D -w esbuild rollup rollup-plugin-typescript2 @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-commonjs minimist execa

              说明:

              -D:作为开发依赖安装

              -wmonorepo 环境默认会认为应该将依赖安装到具体的 package中。使用 -w 参数,告诉 pnpm 将依赖安装到 workspace-root,也就是项目的根目录。

              依赖说明:

              依赖描述typescript项目使用 typescript 进行开发esbuild开发阶段的构建工具rollup生产阶段的构建工具rollup-plugin-typescript2rollup 编译 ts 的插件@rollup/plugin-jsonrollup 默认采用 esm 方式解析模块,该插件将 json 解析为 esm 供 rollup 处理@rollup/plugin-node-resolverollup 默认采用 esm 方式解析模块,该插件可以解析安装在 node_modules 下的第三方模块@rollup/plugin-commonjs将 commonjs 模块 转化为 esm 模块minimist解析命令行参数execa生产阶段开启子进程

              初始化Typescript

              pnpm tsc --init

              pnpm 的使用基本和 npm 一致。这里的用法就相当于 npm 中的 npx

              npx tsc --init

              意思是,去 node_modules 下的 .bin 目录中找到tsc 命令,并执行它。

              执行完该命令,会在项目根目录生成一个 tsconfig.json 文件,进行一些配置:

              { "compilerOptions": { "outDir": "dist", // 输出的目录 "sourceMap": true, // 开启 sourcemap "target": "es2016", // 转译的目标语法 "module": "esnext", // 模块格式 "moduleResolution": "node", // 模块解析方式 "strict": false, // 关闭严格模式,就能使用 any 了 "resolveJsonModule": true, // 解析 json 模块 "esModuleInterop": true, // 允许通过 es6 语法引入 commonjs 模块 "jsx": "preserve", // jsx 不转义 "lib": ["esnext", "dom"], // 支持的类库 esnext及dom "baseUrl": ".", // 当前目录,即项目根目录作为基础目录 "paths": { // 路径别名配置 "@my-vue/*": ["packages/*/src"] // 当引入 @my-vue/时,去 packages/*/src中找 }, } }

              准备两个模块

              我们先在 packages 目录下新建两个模块,分别是 reactivity 响应式模块 和 shared 工具库模块。然后编写构建脚本进行第一次的开发调试。

              shared

              packages 下新建 shared 目录,并初始化:

              pnpm init

              然后修改 package.json

              { "name": "@my-vue/shared", "version": "1.0.0", "description": "@my-vue/shared", "main": "dist/shared.cjs.js", "module": "dist/shared.esm-bundler.js" }

              注意 name 字段的值,我们使用了一个 @scope 作用域,它相当于 npm 包的命名空间,可以使项目结构更加清晰,也能减少包的重名。

              编写该模块的入口文件:

              // src/index.ts /** * 判断对象 */ export const isObject = (value) =>{ return typeof value === 'object' && value !== null } /** * 判断函数 */ export const isFunction= (value) =>{ return typeof value === 'function' } /** * 判断字符串 */ export const isString = (value) => { return typeof value === 'string' } /** * 判断数字 */ export const isNumber =(value)=>{ return typeof value === 'number' } /** * 判断数组 */ export const isArray = Array.isArray

              reactivity

              packages 下新建 reactivity 目录,并初始化:

              pnpm init

              然后修改 package.json

              { "name": "@my-vue/reactivity", "version": "1.0.0", "description": "@my-vue/reactivity", "main": "dist/reactivity.cjs.js", "module": "dist/reactivity.esm-bundler.js", "buildOptions": { "name": "VueReactivity" } }

              在浏览器中以 IIFE 格式使用响应式模块时,需要给模块指定一个全局变量名字,通过 buildOptions.name 进行指定,将来打包时会作为配置使用。

              main 指定的文件支持 commonjs 规范进行导入,也就是说在nodejs 环境中,通过 require 方法导入该模块时,会导入 main 指定的文件。

              同理,module 指定的是使用 ES Module 规范导入模块时的入口文件。

              编写该模块的入口文件:

              // src/index.ts import { isObject } from '@my-vue/shared' const obj = {name: 'Vue3'} console.log(isObject(obj))

              reactivity 包中用到了另一个包 shared ,需要安装才能使用:

              pnpm add @my-vue/shared@workspace --filter @my-vue/reactivity

              意思是,将本地 workspace 内的 @my-vue/shared 包,安装到 @my-vue/reactivity包中去。

              此时,查看 reactivity 包的依赖信息:

              "dependencies": { "@my-vue/shared": "workspace:^1.0.0" }

              编写构建脚本

              在根目录下新建 scripts 目录,存放项目构建的脚本。

              新建 dev.js,作为开发阶段的构建脚本。

              // scripts/dev.js // 使用 minimist 解析命令行参数 const args = require('minimist')(process.argv.slice(2)) const path = require('path') // 使用 esbuild 作为构建工具 const { build } = require('esbuild') // 需要打包的模块。默认打包 reactivity 模块 const target = args._[0] || 'reactivity' // 打包的格式。默认为 global,即打包成 IIFE 格式,在浏览器中使用 const format = args.f || 'global' // 打包的入口文件。每个模块的 src/index.ts 作为该模块的入口文件 const entry = path.resolve(__dirname, `../packages/${target}/src/index.ts`) // 打包文件的输出格式 const outputFormat = format.startsWith('global') ? 'iife' : format === 'cjs' ? 'cjs' : 'esm' // 文件输出路径。输出到模块目录下的 dist 目录下,并以各自的模块规范为后缀名作为区分 const outfile = path.resolve(__dirname, `../packages/${target}/dist/${target}.${format}.js`) // 读取模块的 package.json,它包含了一些打包时需要用到的配置信息 const pkg = require(path.resolve(__dirname, `../packages/${target}/package.json`)) // buildOptions.name 是模块打包为 IIFE 格式时的全局变量名字 const pgkGlobalName = pkg?.buildOptions?.name console.log('模块信息:\n', entry, '\n', format, '\n', outputFormat, '\n', outfile) // 使用 esbuild 打包 build({ // 打包入口文件,是一个数组或者对象 entryPoints: [entry], // 输入文件路径 outfile, // 将依赖的文件递归的打包到一个文件中,默认不会进行打包 bundle: true, // 开启 sourceMap sourcemap: true, // 打包文件的输出格式,值有三种:iife、cjs 和 esm format: outputFormat, // 如果输出格式为 IIFE,需要为其指定一个全局变量名字 globalName: pgkGlobalName, // 默认情况下,esbuild 构建会生成用于浏览器的代码。如果打包的文件是在 node 环境运行,需要将平台设置为node platform: format === 'cjs' ? 'node' : 'browser', // 监听文件变化,进行重新构建 watch: { onRebuild (error, result) { if (error) { console.error('build 失败:', error) } else { console.log('build 成功:', result) } } } }).then(() => { console.log('watching ...') })

              使用该脚本,会使用 esbuildpackages 下的包进行构建,打包的结果放到各个包的 dist 目录下。

              在开发阶段,我们默认打包成 IIFE 格式,方便在浏览器中使用 html 文件进行测试。在生产阶段,会分别打包成 CommonJSES ModuleIIFE 的格式。

              完成第一次调试

              给项目增加一条 scripts 命令:

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

              意思是,以 IIFE 的格式,打包 reactivity 模块,打包后的文件可以运行在浏览器中。

              在终端中执行:

              pnpm dev

              输出:

              PS D:\vue3-learn> pnpm dev
              > vue3-learn@1.0.0 dev D:\vue3-learn
              > node scripts/dev.js reactivity -f global
              模块信息:
              D:\vue3-learn\packages\reactivity\src\index.ts
              global
              iife
              D:\demo3\vue3-learn\packages\reactivity\dist\reactivity.global.js
              watching ...

              编写一个 html 文件进行测试:

              // packages/reactivity/test/index.html <body> <div id="app"></div> <script src="../dist/reactivity.global.js"></script> </body>

              打开浏览器控制台:

              小结

              到此,一个基本的 monorepo 开发环境就搭建完毕了。

              代码已上传至 Github ,点击访问。

              更多关于Vue3 pnpm搭建monorepo的资料请关注易盾网络其它相关文章!