解析ElementPlus打包源码

突然好奇了下ElementPlus打包的源码,今天想着去看一下,顺便记录一下看这块代码的过程

去github下载源码到本地,如果没有🪜,gitee去下载一下element-plus: 🎉 Vue 3 的桌面端组件库 (gitee.com)(😀我在公司我也没🪜)

拿到源码这么多目录,我特么看啥啊❓❓❓ 冷静一下先别急,我要看打包,找下package.json打包的scripts命令。有了:"build": "pnpm run -C internal/build start",ok懂了我要去internal/build目录下面看start命令。我继续找啊找,发现了"start": "gulp --require @esbuild-kit/cjs-loader -f gulpfile.ts"

万能的deepseek啊🙏🙏🙏,@esbuild-kit/cjs-loader这个是啥我没见过。

好的我懂了💡~ 啊不对,gulp文档打开一下先,看下说明

其实ts文件gulp没法识别啊,所以--require @esbuild-kit/cjs-loader,这个就是Node.js 参数,要求在执行脚本前先加载 @esbuild-kit/cjs-loader,这样gulpfile就可以是ts文件了

ok那我们可以去看gulpfile.ts写的啥了

如果不了解gulp的,请看这里gulp.js - 基于流(stream)的自动化构建工具 | gulp.js中文网 (gulpjs.com.cn)

gulpfile.ts看一下

typescript 复制代码
import path from 'path'
import { copyFile, mkdir } from 'fs/promises'
import { copy } from 'fs-extra'
import { parallel, series } from 'gulp'
import {
  buildOutput,
  epOutput,
  epPackage,
  projRoot,
} from '@element-plus/build-utils'
import { buildConfig, run, runTask, withTaskName } from './src'
import type { TaskFunction } from 'gulp'
import type { Module } from './src'

export const copyFiles = () =>
  Promise.all([
    copyFile(epPackage, path.join(epOutput, 'package.json')),
    copyFile(
      path.resolve(projRoot, 'README.md'),
      path.resolve(epOutput, 'README.md')
    ),
    copyFile(
      path.resolve(projRoot, 'typings', 'global.d.ts'),
      path.resolve(epOutput, 'global.d.ts')
    ),
  ])

export const copyTypesDefinitions: TaskFunction = (done) => {
  const src = path.resolve(buildOutput, 'types', 'packages')
  const copyTypes = (module: Module) =>
    withTaskName(`copyTypes:${module}`, () =>
      copy(src, buildConfig[module].output.path, { recursive: true })
    )

  return parallel(copyTypes('esm'), copyTypes('cjs'))(done)
}

export const copyFullStyle = async () => {
  await mkdir(path.resolve(epOutput, 'dist'), { recursive: true })
  await copyFile(
    path.resolve(epOutput, 'theme-chalk/index.css'),
    path.resolve(epOutput, 'dist/index.css')
  )
}

export default series(
  withTaskName('clean', () => run('pnpm run clean')),
  withTaskName('createOutput', () => mkdir(epOutput, { recursive: true })),

  parallel(
    runTask('buildModules'),
    runTask('buildFullBundle'),
    runTask('generateTypesDefinitions'),
    runTask('buildHelper'),
    series(
      withTaskName('buildThemeChalk', () =>
        run('pnpm run -C packages/theme-chalk build')
      ),
      copyFullStyle
    )
  ),

  parallel(copyTypesDefinitions, copyFiles)
)

export * from './src'

我们直接定位到series这个任务的方法

less 复制代码
export default series(
  withTaskName('clean', () => run('pnpm run clean')),
  withTaskName('createOutput', () => mkdir(epOutput, { recursive: true })),

  parallel(
    runTask('buildModules'),
    runTask('buildFullBundle'),
    runTask('generateTypesDefinitions'),
    runTask('buildHelper'),
    series(
      withTaskName('buildThemeChalk', () =>
        run('pnpm run -C packages/theme-chalk build')
      ),
      copyFullStyle
    )
  ),

  parallel(copyTypesDefinitions, copyFiles)
)

好多封装的函数,我们一个一个看下

internal\build\src\utils\process.ts的run函数说明

typescript 复制代码
import { spawn } from 'child_process'
import chalk from 'chalk'
import consola from 'consola'
import { projRoot } from '@element-plus/build-utils'

export const run = async (command: string, cwd: string = projRoot) =>
  new Promise<void>((resolve, reject) => {
    const [cmd, ...args] = command.split(' ')
    consola.info(`run: ${chalk.green(`${cmd} ${args.join(' ')}`)}`)
    const app = spawn(cmd, args, {
      cwd,
      stdio: 'inherit',
      shell: process.platform === 'win32',
    })

    const onProcessExit = () => app.kill('SIGHUP')

    app.on('close', (code) => {
      process.removeListener('exit', onProcessExit)

      if (code === 0) resolve()
      else
        reject(
          new Error(`Command failed. \n Command: ${command} \n Code: ${code}`)
        )
    })
    process.on('exit', onProcessExit)
  })

spawn是干啥的呢,创建子进程执行命令的。

chalk,改终端字符串颜色的,这样输出更好看噻,做命令行工具常用的东西

consola,是一个日志输出的,比咋们常用的console更友好

projRoot,明显就是我们项目根目录(我有信心,我不点进去@element-plus/build-utils就能看出来😏😏😏)

这段代码这个 run 函数,就是帮助在 Node.js 中执行 shell 命令

里面会打印一下日志,这个consola.info(`run: ${chalk.green(`${cmd} ${args.join(' ')}`)}`)就是打印日志

紧接着下面这个代码,其实就是使用 spawn 创建子进程执行我们的命令。stdio: 'inherit'就是子进程共享父进程的终端。shell: process.platform === 'win32'使得构建脚本能够在 Windows 和非 Windows 平台上一致地工作(就是兼容一下大家的系统嘛,我是用的windows💻)

ini 复制代码
const app = spawn(cmd, args, {
  cwd,
  stdio: 'inherit',
  shell: process.platform === 'win32',
})

主进程退出,肯定要杀死子进程,所以有这个代码

dart 复制代码
const onProcessExit = () => app.kill('SIGHUP')
process.on('exit', onProcessExit)

子进程退出,要移除主进程的监听,以及正常退出就resolve,异常就直接报错

javascript 复制代码
app.on('close', (code) => {
  process.removeListener('exit', onProcessExit)

  if (code === 0) resolve()
  else
    reject(
      new Error(`Command failed. \n Command: ${command} \n Code: ${code}`)
    )
})

总结一下,run函数就是帮我们运行命令的,并且输出一下运行日志。(自己的项目可以也可以偷过来用一下😼😼😼我们争做代码的搬运工)

internal\build\src\utils\gulp.ts的两个函数withTaskNamerunTask函数说明

typescript 复制代码
import { buildRoot } from '@element-plus/build-utils'
import { run } from './process'

import type { TaskFunction } from 'gulp'

export const withTaskName = <T extends TaskFunction>(name: string, fn: T) =>
  Object.assign(fn, { displayName: name })

export const runTask = (name: string) =>
  withTaskName(`shellTask:${name}`, () =>
    run(`pnpm run start ${name}`, buildRoot)
  )

我们先来看下withTaskName,这个函数传入一个name(string类型)和一个fn(要是TaskFunction的类型),然后给这个函数的displayName字段添加上name。经过我的分析,其实就是给对应的gulp任务定义一下打印日志的名称。我们可以写个demo看一下,修改一下gulpfile.tsexport default series这里的代码

javascript 复制代码
export const gege = async () => {
  console.log('唱、跳、rap、篮球')
}
gege.displayName = '鸽鸽'
export default series(gege)

然后我们执行pnpm build看下效果,可以看到gulp帮我们输出了对应的任务名称

然后看下runTask,参数有一个name,函数里面调用withTaskName,给对应函数添加上了displayName为shellTask:${name},这个函数是调用上面已经说过的run方法,执行了一下pnpm run start ${name},执行的目录是buildRoot(/internal/build)。

举例说明:例如这个runTask('buildModules'),这里的/internal/build下package.json的start命令是"start": "gulp --require @esbuild-kit/cjs-loader -f gulpfile.ts",,也就是会执行gulp --require @esbuild-kit/cjs-loader -f gulpfile.ts "buildModules"。也就会执行gulpfile.ts导出的buildModules任务

在gulpfile.ts中有一行导出export * from './src',一层一层查找可以找到对应的buildModules这个任务的定义,在这个文件internal\build\src\tasks\modules.ts

cleancreateOutput任务

scss 复制代码
export default series(
  withTaskName('clean', () => run('pnpm run clean')),
  withTaskName('createOutput', () => mkdir(epOutput, { recursive: true }))

  // parallel(
  //   runTask('buildModules'),
  //   runTask('buildFullBundle'),
  //   runTask('generateTypesDefinitions'),
  //   runTask('buildHelper'),
  //   series(
  //     withTaskName('buildThemeChalk', () =>
  //       run('pnpm run -C packages/theme-chalk build')
  //     ),
  //     copyFullStyle
  //   )
  // )

  // parallel(copyTypesDefinitions, copyFiles)
)

clean任务呢,在项目根目录目录帮我们执行了pnpm run clean命令,

这个命令先执行了pnpm run clean:dist,也就是rimraf dist清理一下dist目录,打包之前一般都要先干的事情。然后pnpm run -r --parallel clean-r表示对所有子项目也都执行clean命令,--parallel表示并行执行。这里其实清理项目和项目的子包下面的打包文件。

然后是createOutput任务,这个任务就是创建一下文件夹,这个文件夹epOutputdist/element-plus

我们只保留这两个任务运行一下pnpm build,可以看到删除了原来的dist,也创建了对应的目录


🥱🥱🥱🥱🥱🥱🥱🥱🥱🥱🥱🥱🥱🥱🥱🥱累了我先更新到这,后续请看下回分解

相关推荐
狮子座的男孩7 分钟前
解决:给整个 Vue 项目添加鼠标点击、鼠标移动、鼠标滚轮(DOM)事件,以达到后台延迟退出
javascript·vue.js·经验分享·dom树·鼠标移动事件·鼠标点击事件·鼠标滚轮事件
小赵学鸿蒙17 分钟前
用Uniapp开发鸿蒙项目 五
前端
小lan猫19 分钟前
【实战】 Vue 3、Anything LLM + DeepSeek本地化项目(五)
前端·vue.js
星使bling19 分钟前
基于Baidu JSAPI Three的卫星轨道三维可视化Demo
前端·javascript
Oder_C21 分钟前
自定义指令-优化v-if和v-show上的使用
前端·javascript·vue.js
小赵学鸿蒙22 分钟前
用Uniapp开发鸿蒙项目 八(上)
前端
拾光拾趣录23 分钟前
TypeScript 数组与对象类型定义
前端
小赵学鸿蒙23 分钟前
用Uniapp开发鸿蒙项目 四
前端
程序猿阿伟38 分钟前
《深入解析:如何通过CSS集成WebGPU实现高级图形效果》
前端·css
Monster411 小时前
鸿蒙性能引擎:ArkCompiler实战精要
前端