一、前言
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 打包文件介绍