• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

从 0 到 1 搭建 Vue 组件库框架2. 在 monorepo 模式下集成 Vite 和 TypeScript - 上

武飞扬头像
蘑菇王
帮助41

导航

导航:0. 导论

上一章节: 1. 基于 pnpm 搭建 monorepo 工程目录结构

下一章节:2. 在 monorepo 模式下集成 Vite 和 TypeScript - 下

本章节示例代码仓:Github

在上一章节中,我们完成了“新建文件夹”的操作,使用 pnpm 为我们的组件库搭建了 monorepo 目录结构。在本章节,我们的组件库工程会开始初成体系——加入构建工具 ViteTypeScript 类型支持,能够真正通过构建生成产物,并充分发挥出 monorepo 模式下开发的灵活性。

Vite 选型与简介

Vite 是一种新型前端构建工具,能够显著提升前端开发体验。

很多文章都分析了 Vite 如何地快速,如何有极致的热更新效率,但从我个人的体验出发,Vite 在开发服务器上的优化并不都是积极的,HMR 热更新效率确实大幅提高,但是其他方面也牺牲了很多。例如大量 es 模块在 HTTP 1.1 的请求并发数限制下导致首次启动白屏时间过长;预构建的机制不仅使依赖增多后首次启动速度降低,还使开发环境下调试体验(断点进入依赖代码)变得不好。

相比之下,Vite 的快捷和优秀体验更多地体现在配置与操作上,而非开发服务器的效率

  • 无需插件,工具本身能够抹平 cjsesm 的模块化差异。
  • 无需插件,几乎无需配置就能够天然支持 TypeScriptSass(CSS 预处理器)。
  • 无需插件,支持各种图片、jsonworker 等静态资源的加载。

在我看来,Vite 的快更多地体现在了配置的快、上手的快。对于绝大多数复杂度有限的场景,Vite 都可谓实现了“构建的最佳实践”,大部分工程能力几乎都是开箱即用,极大地减少了构建配置的成本。即使你有深度使用的需求,Vite 也具有 自定义插件 的能力以及良好的 周边生态

在当下,如果你想要接触一款前端构建工具,我强烈推荐从 Vite 入手。Vite 对于构建工具的初学者而言,能够大幅减少使用传统构建工具时繁琐的概念理解、插件选型、插件文档阅读的困难,快速建立起正反馈,这样才有不断深入学习下去的动力。

我们的组件库项目只有文档示例会涉及开发服务器,绝大部分场景都是依赖于 Vite 的构建模式。在构建产物质量方面,Vite 也是值得信赖的——它是基于成熟的打包工具 Rollup 实现的产物构建。

element-plus 采用的混合使用 RollupVite 的方案不同,我们的组件库将全部使用 Vite 进行构建。

(实战环节开始)基本依赖安装

在更多操作开始前,我们先强调一下系列分享编写代码所使用的 IDE 为 VSCode。在操作过程中会涉及相当多的 IDE 使用技巧,如果使用 WebStorm 等其他 IDE 的话,这些知识可能是无法即刻应用的,需要读者自行变通。

.npmrc 文件

在安装依赖前,先给大家介绍一下 .npmrc 文件,我们试着在项目的根目录下建立 .npmrc 文件。

mkdir .npmrc

这个 .npmrc 文件相当于项目级的 npm 配置,

registry=https://registry.npm.taobao.org

上面的配置相当于切换 npm 镜像源为 https://registry.npm.taobao.org,只不过配置只在当前项目目录下生效,优先级高于用户设置的本地配置

这个效果是不是很像 npm set config registry https://registry.npm.taobao.org ?是的,npm set config 的本质是修改用户级.npmrc 文件,这个文件在 C:\Users\${用户名}\.npmrc(Windows 系统) 或者 ~/.npmrc(Linux 系统) 位置。

我推荐大家将一些必要的配置放在这个项目级的 .npmrc 文件中,并且将这个文件提交到代码仓,这样就可以使后续贡献的其他用户免去许多环境配置的麻烦,尤其是在公司的内网环境下,各式各样的 npm 私仓和代理配置让很多新人头疼。

当然,如果你是开源项目,就不是很推荐你在里面做网络环境相关的配置了,因为每个贡献者的网络环境是多样化的,这时项目级的 .npmrc 就只适合放一些与包管理相关的配置。

安装公共构建依赖

接下来,我们要在根目录下安装所需的构建工具:ViteTypeScript

pnpm i -wD vite typescript

因为每个包都需要用到 ViteTypeScript 进行构建,我们在上一章 1. 基于 pnpm 搭建 monorepo 工程目录结构 讲过,公共开发依赖统一安装在根目录下,是可以被各个子包正常使用的。

由于我们要构建的是 Vue 组件库,Vue 推荐的组件开发范式 单文件组件 SFC 并不是原生的 Web 开发语法,而是 Vue 方面定义的“方言”,需要经过一个编译为原生 js 的过程。因此我们需要引入相关的 Vite 插件 @vitejs/plugin-vue,这个插件集成了 vue 编译器的能力,使得构建工具能够理解 Vue SFC 模板。

pnpm i -wD @vitejs/plugin-vue

另外需要注意,vue 应该被安装到根目录下的 dependencies,因为几乎所有子包的 peerDependencies 中都具有 vue(peerDependencies 相关可以复习1. 基于 pnpm 搭建 monorepo 工程目录结构,我们结合 pnpmresolve-peers-from-workspace-root 机制,可以统一所有子包中 vue 的版本。在执行这一步前,建议删除 node_modules 目录以及 pnpm-lock.yaml 文件,确保依赖重新被解析安装

pnpm i -wS vue

关于 resolve-peers-from-workspace-root 的说明:

默认为 true。 启用后,将会使用根工作区项目的 dependencies 解析工作区中任何项目的 peer dependencies。这是一个有用的功能,因为你可以只在工作区的根目录中安装 peer dependencies,并且确保工作区中的所有项目都使用相同版本的 peer dependencies。

这一步操作正确的标志,是根目录 node_modules/.pnpm 路径下的 vue 系列包只有一个版本。如果同时出现 3.0.03.x.xvue,说明操作未到位,3.0.0 版本的 vue 将导致构建过程中出现错误。

学新通

另外,如果我们喜欢用 CSS 预处理器 的话,也要在根目录下进行安装,这里我选择安装 Sass。大家也可以根据自己的偏好选择 Less 或者 Stylus

pnpm i -wD sass

Vite 集成

为了成功集成 Vite,让我们的组件库能够构建出产物,这里我们需要完成三个步骤,分别是:

  • 编写构建目标源码。因为文章的重点是工程化而非组件库的开发,代码预备部分我们不会实现组件的实际功能,只给出能够体现构建要点的 demo 代码
  • 准备 vite.config 配置文件。
  • package.json 中设置构建脚本。

我们先回顾一下上一章节所规划的 monorepo 目录结构,在集成 Vite 的过程中,我们会对它做非常多的拓展:

  1. 对于 packages 目录下的每一个组件包,我们制定了更细的源码组织规则:
  • 各种配置文件,如 package.jsonvite.config.ts(js),都放在模块根目录下。
  • src 目录下存放源码,其中 src/index.ts(js) 作为这个模块的总出口,所有需要暴露给外部供其他模块使用的方法、对象都要在这里声明导出。
  • dist 目录作为产物输出目录,当然如果没执行过构建命令,这个目录是不会生成的。
  1. packages 目录下新建统一出口包,命名为 @openxui/ui。正如 element-plus 主包负责集合各个子包,并统一导出其中内容一般。
  2. 我们还没走到搭建 demo 文档的阶段,但又迫不及待地想看到组件的实际效果,为了满足这个需求,我们要在根目录下建立 demo 模块,这个模块是一个 Web 应用,用来展示组件,同时验证我们的 monorepo 架构是否能立即响应子模块的更新修改。
  3. 关于 tsconfig,我们会在集成 TypeScript 的阶段进行说明。
📦openx-ui
 ┣ 📂docs
 ┃ ┗ 📜package.json
 ┣ 📂demo               # 展示组件效果的 Web 应用
 ┃ ┣ 📂node_modules
 ┃ ┣ 📂dist
 ┃ ┣ 📂public
 ┃ ┣ 📂src
 ┃ ┣ 📜index.html
 ┃ ┣ 📜vite.config.ts
 ┃ ┣ 📜tsconfig.json
 ┃ ┗ 📜package.json
 ┣ 📂packages
 ┃ ┣ 📂button
 ┃ ┃ ┣ 📂node_modules
 ┃ ┃ ┣ 📂dist           # 组件产物目录
 ┃ ┃ ┣ 📂src            # 组件源码目录
 ┃ ┃ ┃ ┣ 📜Button.vue
 ┃ ┃ ┃ ┗ 📜index.ts
 ┃ ┃ ┣ 📜package.json
 ┃ ┃ ┗ 📜vite.config.ts
 ┃ ┣ 📂input
 ┃ ┃ ┗ 📜...
 ┃ ┣ 📂shared
 ┃ ┃ ┗ 📜...
 ┃ ┗ 📂ui               # 组件库主包,各组件的统一出口
 ┃   ┗ 📜...
 ┣ 📜package.json
 ┣ 📜tsconfig.json
 ┣ 📜tsconfig.base.json
 ┣ 📜tsconfig.node.json
 ┣ 📜tsconfig.src.json
 ┣ 📜pnpm-workspace.yaml
 ┗ 📜README.md

因为我们规定了每个模块的 dist 都作为产物输出目录,而输出产物是不需要入仓的(clone 代码后执行构建命令就能生成),所以要注意在根目录的 .gitignore 中添加产物目录 dist

# .gitignore
node_modules
 dist

公共方法代码预备

在上一章的规划中(1. 基于 pnpm 搭建 monorepo 工程目录结构),我们安排 @openxui/shared 作为公具方法包,将成为所有其他模块的依赖项。

我们假定工具方法中有一个打印 Hello World 的方法。另外,为了演示引入外部依赖的工程能力,我们还要导出一个方法 useLodash,这个方法原封不动地返回 lodash 实例对象。index.ts 会作为出口统一导出这些方法。下面展示操作步骤。

// 模块源码目录
📦shared
 ┣ ...
 ┣ 📂src
 ┃ ┣ 📜hello.ts
 ┃ ┣ 📜index.ts
 ┃ ┗ 📜useLodash.ts
 ┣ ...

# 为 shared 包安装 lodash 相关依赖
pnpm --filter @openxui/shared i -S lodash @types/lodash

正在学习 TypeScript 的同学,如果对语法存在疑惑,可以及时前往 TypeScript 官网 补充知识。

// packages/shared/src/hello.ts
export function hello(to: string = 'World') {
  const txt = `Hello ${to}!`;
  alert(txt);
  return txt;
}

// packages/shared/src/useLodash.ts
import lodash from 'lodash'

export function useLodash() {
  return lodash
}

// packages/shared/src/index.ts
export * from './hello';
export * from './useLodash'

代码块 code-snippets 介绍

在为 Vue 组件准备代码前,向大家穿插介绍一个 IDE 方面的小技巧:代码块 code-snippets

在编写大量 Vue 组件时,我们总是要重复编写这样的语法:

<script lang="ts">

const props = withDefaults(defineProps<{
  prop1?: string;
}>(), {
  prop1: '',
});

const emit = defineEmits<{
  (event: 'event1'): void;
}>();

</script>

<template>
  <div>content</div>
</template>

<style scoped lang="scss">

</style>

重复编写这些模板化内容没有任何意义,而且有时忘记了写法还需要到其他文件中搜索,容易让人产生厌烦情绪而效率低下,充分利用 IDE 的代码块功能可以大幅提高编码体验。代码块的具体使用方式并不复杂,大家花 5 分钟时间快速阅读这篇文章就能够掌握:VSCode 利用 Snippets 设置超实用的代码块

我们在项目根目录下建立 .vscode 目录,.vscode 目录代表项目级的 VSCode 编辑器设置项,其中的配置文件只会在本项目下生效。

.vscode 文件是否应该入仓呢,这是一把双刃剑,不合理的项目配置会绑架其他贡献者的使用习惯,带来负面的体验。我建议项目的 Owner 还是要将其推入仓库,但是要设身处地地为贡献者的使用体验考虑,仅在里面设置与代码规范、必备插件、快捷使用相关的选项,其他个人偏好的配置应该交由贡献者自己决定。

下面我们仅演示 code-snippets 代码块的设置,.vscode 的更多效用我们在后续的分享中还会介绍。我们建立 vue.code-snippets 文件,写入最常用的“单文件组件定义”、“Props 定义”、“emits 事件定义”的代码块:

// .vscode/vue.code-snippets
{
  "Vue SFC Template": {
    // 代码块搜索前缀
    "prefix": "sfc",
    "body": [
      "<script setup lang=\"${1:ts}\">",
      "import { ref } from 'vue'",
      "</script>",
      "",
      "<template>",
      "  <div>${3:contents}</div>",
      "</template>",
      "",
      "<style scoped lang=\"${4:scss}\">",
      "</style>"
    ],
    "description": "初始化 vue3 SFC 模板"
  },
  "Vue Define Props": {
    "prefix": "props",
    "body": [
      "const props = withDefaults(defineProps<{",
      "  prop1?: string;",
      "}>(), {",
      "  prop1: '',",
      "});"
    ],
    "description": "初始化 vue3 props 定义"
  },
  "Vue Define Emits": {
    "prefix": "emits",
    "body": [
      "const emit = defineEmits<{",
      "  (event: 'event1'): void;",
      "}>();"
    ],
    "description": "初始化 vue3 emits 定义"
  }
}

之后,我们就可以通过 sfcpropsemits 直接呼出对应的代码。当然,大家也可以根据自身的偏好,加入更多便捷的代码块,可以借助一款自动生成代码块的工具 snippet generator 更快地完成这个过程。

学新通

vue 组件代码预备

button 组件

我们先设置 button 组件的初始代码,为了演示 monorepo 工程中内部模块之间互相依赖的特性,我们假定按钮组件的用例为点击之后打印 Hello ${props.hello},那么 button 将依赖于先前定义的 shared 模块中的 hello 方法。我们先通过 pnpm workspace 命令声明内部模块关联:

pnpm --filter @openxui/button i -S @openxui/shared

当然,我们也可以先在子模块下的 package.json 中按照 workspace 协议 手动声明内部依赖,然后通过 pnpm -w i 执行全局安装,也能达到和上面那条命令一样的效果,两种方式二选一即可。

// packages/button/package.json
{
  // ...
  "dependencies": {
    "@openxui/shared": "workspace:^"
  }
}

接下来,实现 button 组件代码,并在 index.ts 中导出。

📦button
 ┣ ...
 ┣ 📂src
 ┃ ┣ 📜button.vue
 ┃ ┗ 📜index.ts
 ┣ ...

正在学习 Vue 框架的同学,如果对 demo 代码存在疑惑,可以及时前往 Vue 官网 补充知识。

注意,这里 import from '@openxui/shared' 语句在 IDE 中可能会出现 ts 报错,这个不影响构建,我们暂时忽略它,在后续 TypeScript 实操部分会解决。

<script setup lang="ts">
// packages/button/src/button.vue
import { hello } from '@openxui/shared';

const props = withDefaults(defineProps<{
  text?: string;
}>(), {
  text: 'World',
});

function clickHandler() {
  hello(props.text);
}
</script>

<template>
  <button class="openx-button" @click="clickHandler">
    <slot></slot>
  </button>
</template>

// packages/button/src/index.ts
import Button from './button.vue';

export { Button }

input 组件

按照类似的方式,也简单实现处理一下 input 模块。我们假定 input 输入框组件实现的用例是监听内容变化,调用 hello 方法打印当前输入的内容。

pnpm --filter @openxui/input i -S @openxui/shared

<script setup lang="ts">
// packages/input/src/input.vue
import { hello } from '@openxui/shared';

withDefaults(defineProps<{
  modelValue?: string;
}>(), {
  modelValue: '',
});

const emit = defineEmits<{
  (event: 'update:modelValue', val: string): void;
}>();

function inputHandler(e: any) {
  const value: string = e.target.value;
  emit('update:modelValue', value);
  hello(value);
}

</script>

<template>
  <input 
    class="openx-input" 
    type="text" 
    :value="modelValue" 
    @input="inputHandler" />
</template>

// packages/input/src/index.ts
import Input from './input.vue';

export { Input }

组件主包 ui

ui 模块作为各个组件的统一出口,需要在 package.json 中正确声明与所有组件的依赖关系,之后每增加一个新组件,都应该在 ui 模块中补充导出代码。

// packages/ui/package.json
{
  // 其他字段完全参考 button 即可
  "dependencies": {
    "@openxui/button": "workspace:^",
    "@openxui/input": "workspace:^",
    "@openxui/shared": "workspace:^"
  },
}

完成声明后,执行 pnpm -w i 更新依赖。

之后在 src/index.ts 文件中将各个模块的内容导出即可。

// packages/ui/src/index.ts
export * from '@openxui/button';
export * from '@openxui/input';
export * from '@openxui/shared';

构建公共方法模块(纯 js/ts 代码)

按照 Vite 官方介绍的配置方法,我们在公共方法模块 shared 包中添加 vite.config.ts,这个配置文件将告诉 vite 如何构建这个模块。

组件库项目的模块自然要以库模式构建,Vite 能够很好地支持这种构建方式,官方文档也对这种用法进行了 说明

我们编写一个库模式下最简单的 vite.config 文件,并将 package.json 中的 build 脚本修改成 Vite 构建指令。

// packages/shared/vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    // 产物输出目录,默认值就是 dist。我们使用默认值,注释掉此字段。
    // outDir: 'dist',

    // 参考:https://cn.vitejs.dev/config/build-options.html#build-lib
    lib: {
      // 构建的入口文件
      entry: './src/index.ts',

      // 产物的生成格式,默认为 ['es', 'umd']。我们使用默认值,注释掉此字段。
      // formats: ['es', 'umd'],

      // 当产物为 umd、iife 格式时,该模块暴露的全局变量名称
      name: 'OpenxuiShared',
      // 产物文件名称
      fileName: 'openxui-shared',
    },
    // 为了方便学习,查看构建产物,将此置为 false,不要混淆产物代码
    minify: false,
  }
})

// packages/shared/package.json
{
  // ...
  "scripts": {
-   "build": "echo build",
    "build": "vite build",
    "test": "echo test"
  },
}

之后,执行 shared 包的构建指令,Vite 会自动读取对应的 vite.config 文件,生成构建产物。

pnpm --filter @openxui/shared run build

# 以下为指令输出
> @openxui/shared@0.0.0 build D:\learning\openx-ui\packages\shared
> vite build

vite v4.4.4 building for production...
✓ 6 modules transformed.
dist/openxui-shared.mjs  213.46 kB │ gzip: 41.73 kB
dist/openxui-shared.umd.js  224.92 kB │ gzip: 42.24 kB
✓ built in 751ms

在默认情况下,Vite 会为我们生成 .mjs.umd.js 后缀的产物,可以满足绝大多数情况下对于产物格式的要求。其中 .mjs 对应 esm 格式的可用产物,.umd.js 对应 cjs 格式的可用产物。我们按照产物的路径,在 package.json 中修改对应的入口字段(回顾:1. 基于 pnpm 搭建 monorepo 工程目录结构):

// packages/shared/package.json
{
  // 省略其他无关配置 ...
  "main": "./dist/openxui-shared.umd.js",
  "module": "./dist/openxui-shared.mjs",
  "exports": {
    ".": {
      "require": "./dist/openxui-shared.umd.js",
      "module": "./dist/openxui-shared.mjs",
      // ...
    }
  },
}

但是,我们仅仅导出了两个很短小的方法,生成的产物却有 200K 之大,这是为什么呢?这时因为我们安装的 npmlodash 的代码也被打包进来了。

构建工具打包时默认行为,是将所有涉及模块的代码都一并集合到产物中。这在打包 Web 应用的时候是没问题的,因为浏览器并不能识别 npm 模块,所以产物就需要包含所有代码。可我们正在打包的东西将作为 npm 包给其他应用安装,在工程环境下,构建工具是可以识别模块引入语法的。

因此,我们在为 库 / npm 包 构建产物时,在实践中通常会将依赖项(package.jsondependenciespeerDependencies 字段下的依赖)声明为 external(外部依赖),使这个依赖相关的源码不被整合进产物,而是保留着 import xxx from 'pkg' 的导入语句。

我们修改 vite.config 文件,增加 rollupOptions 选项,将依赖项 lodash 声明为外部模块:

// packages/shared/vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    // 其他配置...

    // 参考:https://cn.vitejs.dev/config/build-options.html#build-rollupoptions
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: [/lodash.*/],

      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量。即使不设置,构建工具也会为我们自动生成。个人倾向于不设置
        /*
        globals: {
          lodash: 'lodash'
        }
        */
      }
    },
  }
})

再次执行构建命令,产物的大小总算正常了。

pnpm --filter @openxui/shared run build

# 以下为指令输出
> @openxui/shared@0.0.0 build D:\learning\openx-ui\packages\shared
> vite build

vite v4.4.4 building for production...
✓ 3 modules transformed.
dist/openxui-shared.mjs  0.20 kB │ gzip: 0.15 kB
No name was provided for external module "lodash" in "output.globals" – guessing "lodash".
dist/openxui-shared.umd.js  0.69 kB │ gzip: 0.36 kB
✓ built in 93ms

检查 dist 目录下的产物可以发现,外部依赖 lodash 不再被打包进源码,而是保持原本的引入语句。外部化处理依赖对于库的开发者而言是一件非常严肃的事情,产物的大小会直接影响下游用户的使用体验。

学新通

构建 Vue 组件模块

不同于公共方法模块的纯 ts/js 代码,Vue 组件通常要涉及到对开发范式 单文件组件 SFC 的识别。幸运的是,Vite 提供了官方插件 @vitejs/plugin-vue 来处理这个复杂的编译任务。

我们以 button 模块为例子来演示 Vue 组件的构建配置:

  • 在先前 shared 模块的基础上,我们需要进一步加入插件 @vitejs/plugin-vue 来处理 .vue 文件。
  • 依赖外部化处理也要注意,button 模块依赖的 shared 模块因为在 dependencies 字段中已声明,因此将其排除。同时 peerDependencies vue 也应当被排除。
  • package.json 中的 build 命令更换为 vite build
// packages/button/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  // 增加插件的使用
  plugins: [vue()],
  build: {
    lib: {
      entry: './src/index.ts',
      name: 'OpenxuiButton',
      fileName: 'openxui-button',
    },
    minify: false,
    rollupOptions: {
      external: [
        // 除了 @openxui/shared,未来可能还会依赖其他内部模块,不如用正则表达式将 @openxui 开头的依赖项一起处理掉
        /@openxui.*/, 
        'vue'
      ],
    },
  }
})

完成上述操作后,执行 button 包的构建命令,输出产物。

pnpm --filter @openxui/button run build

# 以下为指令输出
> @openxui/button@0.0.0 build D:\learning\openx-ui\packages\button
> vite build

vite v4.4.4 building for production...
✓ 5 modules transformed.
dist/style.css           0.06 kB │ gzip: 0.08 kB
dist/openxui-button.mjs  0.94 kB │ gzip: 0.50 kB
No name was provided for external module "vue" in "output.globals" – guessing "vue".
No name was provided for external module "@openxui/shared" in "output.globals" – guessing "shared".
dist/openxui-button.umd.js  1.44 kB │ gzip: 0.71 kB
✓ built in 622ms

构建成功后,根据产物路径修改 package.json 的入口字段。

// packages/button/package.json
{
  // 省略其他无关配置 ...
  "main": "./dist/openxui-button.umd.js",
  "module": "./dist/openxui-button.mjs",
  "exports": {
    ".": {
      "require": "./dist/openxui-button.umd.js",
      "module": "./dist/openxui-button.mjs",
      // ...
    }
  },
}

整体构建

最后还剩下统一出口 ui 模块,由于只有导出语句,暂不需要 @vitejs/plugin-vue 插件,它的处理方式与 shared 模块类似。

到此为止,所有组件的构建都完成了,我们可以通过 路径过滤器 选中 packages 目录下所有包进行构建。

pnpm --filter ./packages/** run build

由于 @openxui/ui 是组件库的统一出口包,它的 package.jsondependencies 字段中声明了所有其他模块,我们也可以用依赖过滤器 ...,构建 ui 以及其所有的依赖项,达到整体构建的效果。不过如果不能确保所有包都在 ui 中再进行一次导出,还是采用前者更佳。

pnpm --filter @openxui/ui... run build

执行结果如下:

packages/shared build$ vite build
[1 lines collapsed]
│ transforming...
│ ✓ 3 modules transformed.
│ rendering chunks...
│ [vite:dts] Start generate declaration files...
│ computing gzip size...
│ dist/openxui-shared.mjs  0.20 kB │ gzip: 0.15 kB
│ [vite:dts] Declaration files built in 1150ms.
│ No name was provided for external module "lodash" in "output.globals" – guessing "lodash".
│ dist/openxui-shared.umd.js  0.69 kB │ gzip: 0.36 kB
│ ✓ built in 1.24s
└─ Done in 2.6s

packages/button build$ vite build
[3 lines collapsed]
│ rendering chunks...
│ [vite:dts] Start generate declaration files...
│ computing gzip size...
│ dist/style.css           0.04 kB │ gzip: 0.06 kB
│ dist/openxui-button.mjs  0.66 kB │ gzip: 0.38 kB
│ [vite:dts] Declaration files built in 1509ms.
│ No name was provided for external module "vue" in "output.globals" – guessing "vue".
│ No name was provided for external module "@openxui/shared" in "output.globals" – guessing "shared".
│ dist/openxui-button.umd.js  1.14 kB │ gzip: 0.58 kB
│ ✓ built in 1.84s
└─ Done in 3.2s

packages/input build$ vite build
[3 lines collapsed]
│ rendering chunks...
│ [vite:dts] Start generate declaration files...
│ computing gzip size...
│ dist/style.css          0.04 kB │ gzip: 0.06 kB
│ dist/openxui-input.mjs  0.78 kB │ gzip: 0.42 kB
│ [vite:dts] Declaration files built in 1543ms.
│ No name was provided for external module "vue" in "output.globals" – guessing "vue".
│ No name was provided for external module "@openxui/shared" in "output.globals" – guessing "shared".
│ dist/openxui-input.umd.js  1.27 kB │ gzip: 0.63 kB
│ ✓ built in 1.91s
└─ Done in 3.3s

packages/ui build$ vite build
[3 lines collapsed]
│ rendering chunks...
│ [vite:dts] Start generate declaration files...
│ computing gzip size...
│ dist/openxui.mjs  0.10 kB │ gzip: 0.07 kB
│ [vite:dts] Declaration files built in 987ms.
│ No name was provided for external module "@openxui/button" in "output.globals" – guessing "button".
│ No name was provided for external module "@openxui/input" in "output.globals" – guessing "input".
│ No name was provided for external module "@openxui/shared" in "output.globals" – guessing "shared".
│ dist/openxui.umd.js  1.32 kB │ gzip: 0.43 kB
│ ✓ built in 1.06s
└─ Done in 2.3s

观察命令行输出,可以发现整个打包执行顺序(shared -> button & input(并行) -> ui) 是符合依赖树的拓扑排序的(回顾:1. 基于 pnpm 搭建 monorepo 工程目录结构)。由于我们用 rollupOptions.external 外部化了依赖,这个特性现在对我们而言无关紧要,但在未来定制完善的打包体系,需要研究全量构建时,拓扑排序的特性就会变得非常关键。

flowchart TB
  ui --> shared
  ui --> button --> shared
  ui --> input --> shared

我们将组件库的整体构建命令写入根目录下的 package.json,后续我们将通过 pnpm run build:ui 更快捷地执行构建。

// package.json
{
  // ...
  "scripts": {
-   "hello": "echo 'hello world'"
    "build:ui": "pnpm --filter ./packages/** run build"
  },
}

搭建 demo 应用演示组件效果

我们正好借助创建 demo 演示模块来给大家分享如何在 monorepo 项目里建立一个网站应用模块。我们先设置好 demo 模块的目录结构:

📦openx-ui
 ┣ 📂...
 ┣ 📂demo
 ┃ ┣ 📂node_modules
 ┃ ┣ 📂dist
 ┃ ┣ 📂public
 ┃ ┣ 📂src
 ┃ ┃ ┣ 📂main.ts
 ┃ ┃ ┗ 📜App.vue
 ┃ ┣ 📜index.html
 ┃ ┣ 📜vite.config.ts
 ┃ ┣ 📜tsconfig.json
 ┃ ┗ 📜package.json

由于 demo 是新添加的模块,需要在 pnpm-workspace.yaml 中补充声明这个工作空间:

// pnpm-workspace.yaml
packages:
  - docs
  - packages/*
  - demo

编辑 demo 模块的 package.json

  • @openxui/ui 声明为依赖项,导入所有组件;
  • script 脚本中添加 vite dev 命令用于运行应用;
  • private 设置为 true 避免被当做 npm 包发布。 完成编辑后,不要忘记执行 pnpm i -w 更新整个项目的依赖。
// demo/package.json
// 注意实操时删除注释,package.json 不支持注释
{
  "name": "@openxui/demo",
  "private": true,
  "scripts": {
    // 启动 vite 开发服务器运行应用
    "dev": "vite dev"
  },
  "dependencies": {
    "@openxui/ui": "workspace:^"
  },
  "devDependencies": {
  }
}

Web 应用是以 index.html 作为入口的。

<!-- demo/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DEMO</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

vite.config 采取默认配置即可,只需加入 vue 插件。

// demo/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
})

之后编辑源码。对于 ts 的报错我们暂不理会,在集成完 TypeScript 后会报错都会消失。

// demo/src/main.ts
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

<script setup lang="ts">
// demo/src/App.vue
import {
  Button,
  Input
} from '@openxui/ui'
</script>

<template>
  <div>
    <Button>1111</Button>
    <Input />
  </div>
</template>

完成全部步骤后,启动开发服务器运行 demo 演示应用:

pnpm --filter @openxui/demo run dev

# 输出内容
> @openxui/demo@ dev D:\learning\openx-ui\demo
> vite dev


  VITE v4.4.4  ready in 534 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

学新通

结尾与资料汇总

由于篇幅原因,上半篇只完成了 Vite 的集成,TypeScript 的实践部分放在下半篇完成。

本章涉及到的相关资料汇总如下:

官网与文档:

VSCode

WebStorm

pnpm

Vite

Vue

Rollup

TypeScript

CSS 预处理器

Sass

Less

Stylus

@vitejs/plugin-vue

代码块生成工具 snippet generator

分享博文:

VSCode 利用 Snippets 设置超实用的代码块

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfffjii
系列文章
更多 icon
同类精品
更多 icon
继续加载