Element Plus 源码 : 搭建 Element Plus 组件库打包配置(一)

994 阅读11分钟

一、前言

Element Plus 源码的打包过程比较复杂,初次看很难理解源码中部分代码的用途。阶段性手写源码难度较大。通过这篇文章,我将让你理解 Element Plus 源码中的 组件打包、类型打包、主文件打包、样式打包 整体框架逻辑。 下篇文章我将从零搭建打包配置, 这篇文章只当作了解目录。后面还会继续介绍全部流程

二、Element Plus 打包源码介绍

一)、Element Plus 源码打包目的

1、模块打包目的

将每个组件打包成独立的模块,开发者在使用时可以按需引入,减少不必要的代码加载,提升应用的性能。

import {ElButton} from 'element-plus'

这个就只会引入所需的 ElButton 组件的代码和样式,不会加载整个组件库。

Element Plus 将组件打包成多种格式 ( ESM 和 CommonJS ) ,确保组件库在浏览器和 Node.js 环境下都可以使用。

2、主文件打包目的

简化导入路径:主文件打包后的统一入口文件,使开发者可以直接导入整个库或单个模块,而无需管理多个路径。

import { ElButton, ElInput } from 'element-plus';

开发者可以通过一个入口文件引入库中的任意组件或功能,提升代码的整洁性。

  • 兼容性:生成的多种格式文件(如 ESM、CommonJS)可以兼容不同的模块加载系统,如浏览器环境中的 ESM 和 Node.js 环境中的 CommonJS。

3、类型文件打包目的

生成的 .d.ts 文件为使用 TypeScript 的开发者提供类型提示和检查。开发者在使用组件时,可以获得准确的类型信息,避免因错误的 props 或事件类型导致的运行时错误。


import { ElButtonProps } from 'element-plus';

const buttonProps: ElButtonProps = {
  type: 'primary',
  size: 'large',
};

在 IDE 中会自动提示组件的 props 属性和其对应的类型。

4、样式文件打包目的

通过打包的样式文件,开发者能够按需加载、压缩优化,还可以自定义主题,增强项目的性能和可扩展性 ,可以对不同浏览器和平台进行样式的兼容性处理 。

二)、主文件目录初始化及核心代码

1、初始化主文件项目目录

执行下面命令完成 fz-mini 初始化项目,在按照图片创建剩余的文件。

ps:fz-mini\packages\fz-mini> pnpm init

2、fz-mini 文件下的核心代码介绍

1)、component.ts
import { FzIcon } from '@fz-mini/components/icon'
import type { Plugin } from 'vue'

export default [FzIcon] as Plugin[]

这个文件里引入了所有组件库中的组件,批量引入组件的好处是方便在 install 方法中集中进行注册。

2)、make-installer.ts
import { INSTALLED_KEY } from '@fz-mini/constants'
import { version } from './version'
import type { App, Plugin } from '@vue/runtime-core'
export const makeInstaller = (components: Plugin[]) => {
  const install = (app: App) => {
    if ((app as any)[INSTALLED_KEY]) return
    ;(app as any)[INSTALLED_KEY] = true
    components.forEach((c) => app.use(c))
  }
  return { install, version }
}

这个函数是一个高阶函数用于创建组件库的安装器,当用户调用install 函数就会将整个组件库注册到 vue 中的实例上。

  • install 方法:接受两个参数 app(Vue 应用实例)和 options(配置选项)。
  • 首先,app[INSTALLED_KEY] 用于判断是否已经安装过组件库,防止重复安装。
  • 其次,遍历 components 数组中的每个插件,并调用 app.use(c) 来完成注册。
  • makeInstaller 返回一个对象 { version, install },便于用户在 Vue 应用中调用 install
3)、index.ts
import installer from './defaults'
export * from './make-installer'
export * from '@fz-mini/components'
export default installer
export const install = installer.install
export const version = installer.version

在 index.ts 中导入所有的组件,用户可以在一个路径下导入多个组件,export * from '@fz-mini/components' 会将 @fz-mini/components 中导出的所有组件模块都在 index.ts 中重新导出,这样用户就可以通过 import { FzIcon } from 'element-plus' 直接导入组件,而无需指定组件的具体路径 。

导入 make-installer.ts 的作用是可以实现安装 FzMini 会注册全部组件。在实际的 Vue 项目中,用户可以这样引入并安装 FzMini:

import { createApp } from 'vue'
import FzMini from 'fz-mini'
import App from './App.vue'

const app = createApp(App)

app.use(FzMini, { locale: zhCn })  // 传入配置选项
app.mount('#app')

这里 app.use(FzMini) 会自动调用 index.ts 中的 install 方法,从而注册所有组件和指令,使得用户能够在应用中自由使用 Element Plus 的组件和功能

三)、Element Plus 源码中打包目录介绍

internal/
├── build/
│   ├── dist/
│   ├── node_modules/
│   ├── src/
│   │   ├── plugins/
│   │   │   └── element-plus-alias.ts
│   │   ├── tasks/
│   │   │   ├── full-bundle.ts
│   │   │   ├── helper.ts
│   │   │   ├── index.ts
│   │   │   ├── modules.ts
│   │   │   └── types-definitions.ts
│   │   └── utils/
│   │       ├── gulp.ts
│   │       ├── index.ts
│   │       ├── pkg.ts
│   │       ├── process.ts
│   │       └── rollup.ts
│   ├── build-info.ts
│   ├── build.config.ts
│   ├── gulpfile.ts
│   └── package.json
├── build-constants/
│   ├── dist/
│   ├── node_modules/
│   ├── src/
│   │   ├── index.ts
│   │   ├── pkg.ts
│   │   └── repo.ts
│   ├── build.config.ts
│   └── package.json
├── build-utils/
│   ├── dist/
│   ├── node_modules/
│   ├── src/
│   │   ├── fs.ts
│   │   ├── index.ts
│   │   ├── log.ts
│   │   ├── paths.ts
│   │   └── pkg.ts
│   ├── build.config.ts
│   └── package.json

1、build 目录文件介绍

internal\build\src文件下主要是处理主文件打包、模块文件打包、类型打包文件和 rollup 自定义插件。自己搭建打包文件目录也可以按照这个结构来搭建。

1)、初始化 build 目录

需要在 internal 目录下创建 build 目录,再终端执行下面命令初始化这个包。

//需要切换到 build-utils 目录下执行
ps:fz-mini\internal\build> pnpm init

按照这个目录创建文件,所有 src 目录下的文件都在每层文件下的 index.ts 文件中重新导出改文件下的模块。这样做的目的是想导入某个模块可以不必导入全部路径名。

最后在整个项目目录下安装这个 package.json 中的包名。

// 在项目根目录下执行
ps:element-plus>pnpm i @element-plus/build -D -w

2)、internal\build\src\plugins

internal\build\src\plugins\element-plus-alias.ts :

这个文件声明的是 rollup 中的插件自定义函数,具体分析这篇先不讲,后面也会介绍。 这个插件的主要作用是将开发过程中使用的主题样式路径 @element-plus/theme-chalk 替换为发布后的路径 element-plus/theme-chalk。这样可以确保在打包时,样式文件的引用是正确的 。

在 Element Plus 文档中导入样式的就能看出来,源码中打包的路径是 @element-plus/theme-chalk ,所以开发者导入这个路径要重定向到打包后的路径。

3)、internal\build\src\tasks

Element Plus 绝大部分打包源码在这个文件下。

  • internal\build\src\tasks\modules.ts: 文件是用 rollup 打包组件模块的核心代码
  • internal\build\src\tasks\full-bundle.ts:文件时用 rollup 打包主文件模块的核心代码。
  • ****internal\build\src\tasks\helper.ts: 用于生成和处理 Element Plus 组件的文档数据。它通过解析组件的 .md 文档文件,自动生成 Web Types 数据以便于在编辑器中显示组件的文档、属性和类型提示信息。它是一个 gulp 任务。
  • internal\build\src\tasks\types-definitions.ts: 打包类型文件的核心代码。
4)、internal\build\src\utils

internal\build\src\utils\rollup.ts:用来声明 rollup 打包时的工具函数文件。

  • generateExrernal:这个模块打包用到排除第三方库函数,作用是在打包时,是否是部分打包引入的第三方库还是全量打包。generateExrernal 函数后面会具体分析,目前先做个了解。
  • writeBundle: 用来并行执行 rollup 的输出配置,rollup 输出可以通过返回 Promise 的方式用 gulp 执行自动化构建,rollupjs.org/tools/ 详细介绍参考这个。Element Plus 源码中如何使用 rollup 和 gulp 完成打包会详细介绍。
  • formatBundleFilename: 用来打包格式化 rollup 打包输出文件。

internal\build\src\utils\gulp.ts: 用来声明 gulp 定义 gulp 任务名称和自动化构建自定义的工具函数。

internal\build\src\utils\pkg.ts:

1)、internal\build-utils

internal\build-utils\src\paths.ts:用来存放在打包过程中各种打包的路径变量。

internal\build-utils\src\pkg.ts:用来声明一些处理路径的处理函数,其中打包模块文件使用的是排除一些不需要打包的文件路径(excludeFiles)。

为什么可以这样引入 build-utils 目录文件下的变量?

需要在 internal 目录下创建 build-utils 目录,再终端执行下面命令初始化这个包。

//需要切换到 build-utils 目录下执行
ps:element-plus\internal\build-utils> pnpm init

按照这个目录创建文件,所有 src 目录下的文件都在 index.ts 文件中重新导出。最后在整个项目目录下安装这个 package.json 中的包名。

// 在项目根目录下执行
ps:element-plus>pnpm i @element-plus/build-utils -D -w

2)、internal\build\src\utils

internal\build\src\utils\rollup.ts:用来声明 rollup 打包时的工具函数文件。

  • generateExrernal:这个模块打包用到排除第三方库函数,作用是在打包时,是否是部分打包引入的第三方库还是全量打包。generateExrernal 函数后面会具体分析,目前先做个了解。
  • writeBundle: 用来并行执行 rollup 的输出配置,rollup 输出可以通过返回 Promise 的方式用 gulp 执行自动化构建,rollupjs.org/tools/ 详细介绍参考这个。Element Plus 源码中如何使用 rollup 和 gulp 完成打包下一章节会详细介绍。
  • formatBundleFilename: 用来打包格式化 rollup 打包输出文件。

internal\build\src\utils\gulp.ts: 用来声明 gulp 定义 gulp 任务名称和自动化构建自定义的工具函数。

internal\build\src\utils\process.ts: 定义了一个名为 run 的异步函数,用于在指定目录下执行给定的命令。该函数通过创建子进程的方式运行命令,并提供了对执行过程的控制和日志记录 。

internal\build\src\utils\pkg.ts: 定义了一个用于路径重写的函数 pathRewriter,其主要目的是在构建过程中生成类型文件时,将路径从开发环境的格式重写为打包后的发布环境格式 。

3)、internal\build\src\build-info.ts

配置模块打包需要的 rollup 配置常量以及一些模块类型。

4)、internal\build\gulp.ts

全局组件的 gulp 配置项,整个组件打包的 gulp 自动化执行任务都是在这个文件中配置的

5)、internal\build\build.config.ts

这个文件主要是 unbuild 打包库的配置文件,unbuild 是一个构建打包 JavaScript 和 TypeScript 库的工具,因为Element Plus 组件库中有多个包构建,而 unbuild 用来打包 internal\build 这个项目,它能迅速的打包这个项目,可以输出多种模块格式的打包内容。

2、build-constants 目录文件介绍

1)、internal\build-constants\src\repo.ts

定义了与项目的 GitHub 仓库相关的一些常量 , 这些常量为项目提供了灵活的仓库信息。在项目构建、自动化部署、生成文档、发布等流程中,很多场景需要仓库的路径和分支信息,通过定义这些常量可以避免硬编码。

2)、internal\build-constants\src\pkg.ts

定义了一些与 Element Plus 组件库相关的重要常量,主要用于构建和打包过程中以及文档和品牌标识的管理。每个常量都代表了 Element Plus 项目的一部分信息,帮助在代码中更加统一和灵活地引用这些信息。

3、build-utils 文件目录介绍

1)、internal\build-utils\src\fs.ts

writeJson: 将 JavaScript 对象写入到指定路径的 JSON 文件中 。

await writeJson('data.json', { name: 'Alice', age: 30 }, 2);

这会将对象 { name: 'Alice', age: 30 } 写入到 data``.json 文件中,格式化为缩进 2 的 JSON 字符串。

ensureDir: 确保指定的目录存在,如果不存在则创建它 。

2)、internal\build-utils\src\log.ts

errorAndExit:

  • 记录错误信息到控制台,提供清晰的反馈。
  • 以非零状态码退出进程,表示发生了错误。
3)、internal\build-utils\src\paths.ts

要用于定义和解析项目中各种重要目录和文件的路径,通过这些路径,开发者可以方便地引用和管理项目结构中的不同部分。

4)、internal\build-utils\src\pkg.ts

getWorkspacePackages:获取当前工作区内的所有包(packages),调用 findWorkspacePackages(projRoot),传入项目根目录,返回一个数组,包含所有在该工作区内的包的信息(如目录和包的元数据)。

getWorkspaceNames:获取当前工作区内的所有包名,且只返回指定目录下的包名。

getPackageManifest:获取指定包的元数据(package.json 的内容),使用 require(pkgPath) 加载指定路径的包,并将其类型断言为 ProjectManifest(定义在 PNPM 类型中的类型)。

getPackageDependencies:获取指定包的依赖项和对等依赖项。

excludeFiles: 从文件列表中过滤掉特定的文件和目录。

四、总结

本篇文章主要是了解了打包文件的核心文件的作用,让我们有个更清楚的了解。能够快速的针对性读懂源码。下篇文章详细介绍打包的具体逻辑。

  • Element Plus 源码打包目的
  • Element Plus 打包文件介绍