likes
comments
collection
share

element-plus源码与二次开发:构建与发布流程分析

作者站长头像
站长
· 阅读数 19

前言

对于中后台项目的基础组件,一般element-plusant design就够用了,但有时我们还是想对功能做一些补充和调整,如使用功能更强大的vxe-table替代element-plustable,或者很多公司的UI会有自己的一套设计理念,对默认的样式并不认可(我司也是其中之一),就需要基于现有组件库进行二次开发。

二次开发的方案有两种:

  • 作为依赖引入:新建组件库,将原组件库作为依赖引入,在不入侵原组件库源码的基础上进行二次开发和导出。
  • 源码开发:fork原组件库源码,直接修改原组件库。
优点缺点
作为依赖引入- 上手难度低,无需了解源码- 后期容易同步原组件库更新- 较复杂的改动难以实现
源码开发- 完全可控,可自由改造任何功能与样式- 后期同步原组件库更新需根据更新日志手动同步代码- 需要掌握原组件库源码及其开发规范

我这里目前使用的vue,改造element-plus,首先尝试上手更简单的方案一,对于基础一些样式来说还好搞定,但是遇到很常见的需要改动dom结构的改造,这种方式就很难操作。于是放弃方案一,直接fork源码进行开发。

另外方案二的主要缺点是当原组件库有更新时,无法快速合并,需要手动介入,但是考虑到基础组件库其实有比较强的稳定性,还是可以接受的。

本篇对element-plus二次开发的第一步:构建与发布流程进行分析。主要是对package.json中命令的解读,可以参照我fork出的源码进行阅读,里面有一些更为详细的注释。

对于实际的二次开发及发布流程,可以阅读下篇文章:element-plus源码与二次开发:开发规范与发布

解析

基于v2.3.7版本

首先拉取element-plus源码,执行pnpm i安装依赖。这里可以拉取我fork出的版本,添加了一些详细的注释。

monorepo

element-plus使用pnpm来搭建monorepo工程。 比较大的仓库一般会拆解为多个包,为了方便管理和维护这些互相依赖的包,就有了monorepo(单一代码仓库)的概念,一方面可以将这些包集中在一个git仓库中,同时在发布时各个包又保持独立性。

在之前一般使用yarn workspace+lerna来实现monorepo,如今pnpmworkspace成为主流。

element-plus源码与二次开发:构建与发布流程分析

在根目录下有文件pnpm-workspace.yaml,这个文件内声明了当前项目内部可以引用的包,在根目录执行pnpm i后,会在node_modules中创建这些包的软链,无需再手动link。

packages:
  - packages/*
  - docs
  - play
  - internal/*

/internal下有build-utils文件夹,该文件夹中的package.json中声明了包名@element-plus/build-utils,那么在当前项目的任意位置可以直接使用

import { epRoot } from '@element-plus/build-utils'

peerDependencies

"peerDependencies": {
  "vue": "^3.2.0"
}

开发过组件库的应该明白这个字段的意义,组件库依赖vue, 但vue包一般由使用者在项目中安装,组件库在被使用时应该引用主项目的vue,因此在开发组件库时将vue声明到peerDependencies中,在主项目安装组件库时不会额外安装vue,若主项目没有符合版本条件的vue,会提示警告(但不会报错)。

scripts命令

对一些关键命令做下说明

cz - 提交commit

根据git规范,提交commit

dev - 启动调试项目

"dev": "pnpm -C play dev"

npm -C <directory> <command> 指定目录作为工作目录,即在<directory>中执行<command>命令,这里是执行play文件夹中的dev命令。

play子包只是一个简单的开发调试包,是一个最简的vite+vue3工程,引入了本地的组件库。当要调试某个组件时,可启动该项目进行调试(我个人一般在docs文档中直接调试,同时修改文档)

gen - 创建新组件

"gen": "bash ./scripts/gc.sh"

使用模板快速创建新组建,使用方式:

pnpm gen <component-name>

如: pnpm gen input-list

会在packages/components下创建出input-list文件夹,里面包含组件的基础模板,可以自行创建一个测试组件来查看一下组件模板。

gen:version - 生成版本号文件

"gen:version": "tsx scripts/gen-version.ts"

执行 scripts/gen-version.ts ,根据环境变量或 packages/element-plus/package.json 中的version字段,在 packages/element-plus/ 下生成 version.ts 文件, 只有一句,导出当前组件库版本号。

export const version = '0.0.0-dev.1'

用于在构建时(full-bundle.ts)提供rollup的banner参数

update:version - 部署前更新版本号

从环境变量中获取TAG_VERSIONTAG_VERSION,写入到:

  • element-plus 或 @element-plus/nightly
  • @element-plus/eslint-config
  • @element-plus/metadata

三个包的package.json中的versiongitHead字段。 这里的环境变量在执行CI/CD时会写入。

clean - 清除dist目录

"clean": "pnpm run clean:dist && pnpm run -r --parallel clean"

pnpm run -r --parallel <command>是以并行(parallel)方式执行所有子包的<command>命令(如果有这个命令的话)

删除根目录下的dist目录,并执行所有子包的clean命令,即删除所有包的dist 目录

build - 构建文档和组件库

关键命令,在下面进行详解

build:theme - 编译样式文件

编译样式文件,具体参考下文build命令的buildThemeChalk任务

docs:dev - 调试文档项目

"docs:dev": "pnpm run -C docs dev"

启动组件库文档docs项目,基于vitepress

docs中的命令如下,生成国际化语言对象并启动vitepress文档:

"dev": "pnpm gen-locale && vitepress dev ."
"gen-locale": "rimraf .vitepress/i18n && tsx .vitepress/build/crowdin-generate.ts"

即实际执行命令:

  • 清空历史语言文件.vitepress/i18n
  • 执行tsx .vitepress/build/crowdin-generate.ts将多种语言文件合并为一个语言文件对象
  • 执行vitepress dev .启动本地开发

合并语言文件, 如:

// 英语源文件:en-US/pages/home.json
{
  "title": "component doc"
}

// 中文源文件:zh-CN/pages/home.json
{
  "title": "组件文档"
}

// 经`crowdin-generate.ts`处理后,生成`i18n/pages/home.json`
{
  "en-US": {
    "title": "component doc"
  },
  "zh-CN": {
    "title": "组件文档"
  }
}

在文档代码中使用时,会取页面访问路径的第一段路由来判断语言

<script lang="ts" setup>
  import { useLang } from '../../composables/lang'
  import homeLocale from '../../../i18n/pages/home.json'

  const lang = useLang() // 从页面路径判断语言 如 https://element-plus.org/zh-CN 则 lang = 'zh-CN'
  const homeLang = computed(() => homeLocale[lang.value]) // => { "title": "组件文档" }
</script>
<template>
  <h4>{{ homeLang['title'] }}</h4>
</template>

element-plus.org/zh-CN 则取zh-CN语言,默认是en-US。

实际上源码中只有en-US的英语语言文件,使用crowdin处理多语言翻译,其他版本的语言文件是使用CI/CD命令在crowdin上下载进来的,所以要先将其他语言(如中文)下载下来,再执行这个命令,否则是只有英文的。

crowdin

在crowdin创建项目 -> 上传需要翻译的文件到项目中 -> 在crowdin上自动或手动翻译 -> 下载翻译后的其他语言文件到本地

简单介绍下crowdin的基本使用流程:

  1. 首先在官网上创建出项目,配置原文件的语言和需要翻译的语言。

  2. 使用@crowdin/cli上传。 @crowdin/cli 是使用crowdin官方出的帮助上传和下载翻译文件的辅助工具。使用@crowdin/cli对本地项目进行初始化,生成crowdin.yml配置文件:

'project_id': '473874'
'api_token': 'API_TOKEN_PLACEHOLDER'
'base_path': '.'
'base_url': 'https://api.crowdin.com'
'preserve_hierarchy': true
# files字段配置需要翻译(上传)的源文件,和翻译后的文件下载路径
files: [
    {
        # source 需要翻译(上传)的源文件
        'source': '.vitepress/crowdin/en-US/**/*.json',
        # translation 需要下载的翻译后的文件 
        # %locale% 是翻译的语言如`zh-CN`,`%original_file_name%`是原文件名如`home.json`
        'translation': '.vitepress/crowdin/%locale%/**/%original_file_name%',
    },
    {
        'source': 'en-US/**/*.md',
        'translation': '%locale%/**/%original_file_name%',
    },
]

上面配置的意思是将

  • .vitepress/crowdin/en-US/下的所有json文件
  • en-US/下的所有markdown文件

全部上传到crowndin平台进行翻译,将翻译后的文件下载回到对应的目录。

crowdin upload sources # 上传需要翻译的源文件到平台
crowdin download # 下载翻译后的文件
crowdin download -l zh-CN # 只下载中文

源文件上传后在平台上进行操作,可以自动翻译,如果觉得不够准确也可以进入具体文件的编辑页手动辅助翻译,最后生成翻译后的文件,再使用命令将翻译后的文件下载到本地。

当前element-plus项目的crowdin地址:element-plus/zh-CN

在进行CI/CD操作时,会执行uploaddownload操作,相关流程见workflows/staging-docs.yml(github action)

docs:build - 构建组件文档

构建生产环境docs文档项目

docs:serve

本地启动构建出的生产环境docs进行测试

docs:gen-locale - 生成多语言文件

"gen-locale": "rimraf .vitepress/i18n && tsx .vitepress/build/crowdin-generate.ts"

参见上文

docs:crowdin-credentials - 生成CROWDIN_TOKEN

从环境变量中获取CROWDIN_TOKEN的值,将docs/crowdin.yml中的API_TOKEN_PLACEHOLDER占位字符串进行替换。

crowdin.yml是上文提到的@crowdin/cli生成的配置文件。

stub - 构建internal下的包

"stub": "pnpm run -r --parallel stub"

执行各包的stub命令, 各个包(internal文件夹下的buildbuild-constantbuild-utils三个工具包)实际执行的命令是 unbuild --stub

unbuild

unbuild是一个打包工具,它基于rollup,支持typescript,支持生成commonjsesmodule和类型声明。这些功能不需要额外配置,因为内部集成了一些rollup插件,另外它使用esbuild来转换js代码,比原生rollup要快。

--stub

一般在开发时,我们通常使用监听模式,监听文件变化重新触发编译,如rollup,每次修改文件都会重新打包,非常耗时。--stub模式使用了jiti,在运行时实时编译源文件,而不是传统的预编译,因此执行一次unbuild --stub打包命令后,只会对入口文件进行编译,使用jiti接管入口文件,然后进程就会退出,不会监听源文件变化。在代码执行时,入口被jiti接管,jiti会在运行时实时读取源文件,实时编译。

使用--stub并不会将源码完全编译,只是简单将入口使用编译为使用jiti运行。

internal/build/包为例,原入口文件是index.ts(文件具体内容并不重要),执行unbuild --stub,查看dist中的产物,只有三个文件:

element-plus源码与二次开发:构建与发布流程分析

分别是cjs、esm规范和类型声明文件,也对应package.json中指定的包入口:

"main": ".dist/index.cjs",
"module": ".dist/index.mjs",
"types": ".dist/index.d.ts",

常规预编译的模式来说,index.cjs和index.mjs应该包含所有的代码,而看一下dist/index.mjs

import jiti from "file:///xxx/node_modules/jiti/lib/index.js";
export default jiti(null, { interopDefault: true })('/xxx/internal/build/src/index');

只是引入jiti,将原入口文件进行了包装。 在这个文件执行时,jiti会接管后续所有的importrequire,在运行时进行实时编译。 这样,在本地开发时,就只需初始化时编译一次入口文件(postinstall命令会在项目装包后自动执行此初始化工作),后续不用监听文件变化,每次调用时也能保证是使用的最新代码。

jiti

传统模式开发使用了tsesmodule语法的node应用时,都是采用预编译的方案,监听文件变化,每次修改文件进行编译。

jiti 可以让 node在 运行时直接支持typescriptesmodule 代码,底层原理是它在运行时拦截了模块加载请求(requireimport),将代码转换为可执行函数并进行缓存。

基本使用方式:

a.ts:

export default 'aaa'

main.ts:

import a from './a'
const b: number = 123
console.log(a, b)

index.js:

const jiti = require("jiti")(__filename);
jiti("./main.ts");

执行

node index.js

aaa 123 # 输出

无需配置,可经过jiti包装后,可直接使用tsesm语法,因为在执行时jiti会对他们实时编译。因此也无需监听文件变化每次重新编译了。

prepare - Husky钩子脚本

安装Husky钩子脚本

postinstall - 自动预执行命令

执行pnpm i 后会自动执行,主要是执行了pnpm stub,编译internal下的三个包入口

"postinstall": "pnpm stub && concurrently \"pnpm gen:version\" \"pnpm run -C internal/metadata dev\""

先执行pnpm stub命令,再并行执行gen:versionpnpm run -C internal/metadata dev

pnpm stub即如上面所解释,使用jiti编译三个包的入口文件。

internal/metadata 中的scripts:

"build": "run-p "build:*"",
"build:contributor": "tsx src/contributor.ts",
"build:components": "tsx src/components.ts",
"dev": "DEV=1 pnpm run build"

设置环境变量DEV=1,然后以并行方式执行

  • build:contributor

生成贡献者到metadata/dist/contrbutors.json

  • build:components

生成组件名数组到metadata/dist/components.json

build - 构建文档和组件库

使用rollup执行构建

"build": "pnpm run -C internal/build start"

执行internal/build目录里的start命令

"start": "gulp --require @esbuild-kit/cjs-loader -f gulpfile.ts"

gulp默认不支持esm和ts,@esbuild-kit/cjs-loader通过使用esbuild实时将esmts转换为CommonJS

gulpfile.ts中, 分别以串行和并行的方式执行了一些gulp命令(这些命令的具体逻辑在internal/build/src/tasks/下):

  1. 清除dist目录
  2. 创建dist/element-plus目录
  3. 并行执行任务
    1. buildModules:
    • 编译出package.jsonmainmodule对应的文件
    • packages下的文件(排除test、mock等相关文件夹),以.js.ts.vue文件作为入口,使用rollup编译为esmcjs,保持原有文件目录结构。「所有第三方包作为外部模块(internal)不进行打包」;
    • element-plus作为根目录,其下的文件被提到顶层,其他文件夹作为子文件夹,保持原有目录结构
    • theme-chalk下都是样式文件(.scss),因此未被打包
    1. buildFullBundle: 打包完整bundle,除了vue,全部打包进bundle,并在package.json中声明unpkgjsDelivr字段,以作为这两个公共CDN的默认入口。
    2. generateTypesDefinitions: 生成packages下的类型声明文件到dist/types/packages
    3. buildHelper 为组件生成代码提示文件(veturwebstorm)
    4. 串行任务
      1. buildThemeChalk: 复制源文件(scss)、编译组件的样式文件(css)到dist/theme-chalk下;生成全量样式文件(index.css)
      2. copyFullStyle: 将全量样式文件index.css移动到dist目录
  4. 并行执行任务
    1. copyTypesDefinitions: 将上面生成的类型声明文件,按照目录结构同时拷贝到es(esm)lib(cjs)
    2. copyFiles: 将element-pluspackage.json、根目录README.mdglobal.d.ts拷贝到dist/element-plus
export default series(
  // withTaskName只是给函数加上displayName属性,这样在gulp执行时,就可以显示出函数的名字
  withTaskName('clean', () => run('pnpm run clean')),
  withTaskName('createOutput', () => mkdir(epOutput, { recursive: true })),

  parallel(
    // runTask: 在 internal/build目录下,使用child_process执行 pnpm run start xxx 即执行src/tasks中的gulp的xxx任务
    runTask('buildModules'),
    runTask('buildFullBundle'),
    runTask('generateTypesDefinitions'),
    runTask('buildHelper'),
    series(
      withTaskName('buildThemeChalk', () =>
        run('pnpm run -C packages/theme-chalk build')
      ),
      copyFullStyle
    )
  ),

  parallel(copyTypesDefinitions, copyFiles)
)

buildModules

将packages下的所有js、ts、vue文件使用rollup打包,保持原有文件目录结构。 这里需要注意的是,使用vue-marcrosvue3的语法做了扩展和兼容

// 将packages下的所有js、ts、vue文件使用rollup打包,保持原有文件目录结构
export const buildModules = async () => {
  // excludeFiles 排除['node_modules', 'test', 'mock', 'gulpfile', 'dist']下的文件
  const input = excludeFiles(
    await glob('**/*.{js,ts,vue}', {
      cwd: pkgRoot,
      absolute: true,
      onlyFiles: true,
    })
  )

  // 使用rollup的[JavaScript API](https://www.rollupjs.com/guide/javascript-api)
  const bundle = await rollup({
    input,
    plugins: [
      ElementPlusAlias(), // 将@element-plus/theme-chalk开头的import路径替换为element-plus/theme-chalk,并标记为外部模块
      VueMacros({
        // [官方文档](https://vue-macros.sxzz.moe/zh-CN/guide/getting-started.html)
        // 用于实现尚未被 Vue 正式实现的提案或想法。提供更多宏和语法糖到 Vue 中,其中某些功能在新版本的Vue中已经官方实现了
        // 具体扩展的宏,参阅: https://vue-macros.sxzz.moe/zh-CN/macros/
        setupComponent: false,
        setupSFC: false,
        plugins: {
          vue: vue({
            isProduction: false,
          }),
          vueJsx: vueJsx(),
        },
      }),
      nodeResolve({
        // 查找到外部(node_modules)模块
        // [Node resolution algorithm](https://nodejs.org/api/modules.html?spm=a2c6h.24755359.0.0.4f4461advo8dzO#modules_all_together)
        extensions: ['.mjs', '.js', '.json', '.ts'], // 自动查找扩展名
      }),
      commonjs(), // 将CommonJS模块转换为ES6,以便rollup可以处理它们
      esbuild({
        // 使用 esbuild 执行代码转换和压缩
        sourceMap: true,
        target,
        loaders: {
          '.vue': 'ts',
        },
      }),
    ],
    // 标记所有element-plus的dependencies、peerDependencies以及'@vue'开头的包为external(外部包)
    // 在使用时,babel一般会忽略node_modules里的包,但是因为这里已经转为了es5语法,所以语法是没有问题的。另外虽然babel不会处理,但是webpack、rollup等打包工具仍会处理依赖关系,最终将这里忽略的外部包打包到最终产物里。
    external: await generateExternal({ full: false }), // 标记外部模块,不打包进bundle
    treeshake: false,
  })

  // 使用Promise.all, 遍历buildConfigEntries(esm和cjs)生成的options,多次调用bundle.write()方法生成文件
  await writeBundles(
    bundle,
    buildConfigEntries.map(([module, config]): OutputOptions => {
      return {
        format: config.format, // esm | cjs
        dir: config.output.path, // dist/element-plus/(es | lib)
        exports: module === 'cjs' ? 'named' : undefined,
        preserveModules: true, // 保留模块结构,打包产物和源码文件结构一致。使用原始模块名作为文件名,为所有模块创建单独的 chunk,而不是创建尽可能少的 chunk
        preserveModulesRoot: epRoot, // 以element-plus为产物根目录,打包后原element-plus作为根目录,其他模块在此目录下保持原目录结构
        sourcemap: true,
        entryFileNames: `[name].${config.ext}`, // chunks入口文件名
      }
    })
  )
}

element-plus源码与二次开发:构建与发布流程分析buildModules任务打包的产物

Vue Macros

在使用rollup打包时,使用了一个插件unplugin-vue-macros,这个插件用来实现一些尚未被Vue正式实现的提案或想法,提供更多宏和语法糖到Vue中。其中某些功能在新版本的Vue中已经获得了官方支持。

defineOptions,可以通过 defineOptions 宏在 <script setup> 中使用选项式 API,也就是说可以在一个宏函数中设置 name, props, emits, render,此功能在Vue 3.3版本开始官方支持,在 Vue >= 3.3 中,vue macros会默认关闭此功能兼容,使用官方实现。

<script setup lang="ts">
import { useSlots } from 'vue'
defineOptions({
  name: 'Foo',
  inheritAttrs: false,
})
const slots = useSlots()
</script>

更多宏请查阅全部宏

buildFullBundle

构建浏览器可直接引用的完整bundle,与buildModules逻辑类似,只是将除peerDependencies(即vue)外的所有依赖都打包进去。另外,字体文件(local文件夹)是单独打包的,CDN引用的时候单独引用。

async function buildFullEntry(minify: boolean) {
  // 打包完整bundle
  // minify控制是否执行minify,生成.min.js文件
  // 具体逻辑解释可参考buildModules
}

async function buildFullLocale(minify: boolean) {
  // 打包字体文件
}

element-plus源码与二次开发:构建与发布流程分析buildFullBundle打包产物

在package.json中声明unpkgjsDelivr字段,以作为这两个公共CDN的默认入口

"unpkg": "dist/index.full.js",
"jsdeliver": "dist/index.full.js"

jsDelivr & unpkg

jsDelivrunpkg是免费的开源公共CDN,可以按照固定的规则直接通过他们加载公开的npm和github静态资源文件。

以jsDelivr加载npm资源为例,//cdn.jsdelivr.net/npm/<package>[@version][/file], 若不指定版本号和文件,默认会首先查找package.json中的jsDelivr字段指定的文件

<link
  rel="stylesheet"
  href="//cdn.jsdelivr.net/npm/element-plus/dist/index.css"
  />
<script src="//cdn.jsdelivr.net/npm/element-plus"></script>

generateTypesDefinitions

使用ts-morph,将/typings/env.d.tspackages/下的所有文件生成类型声明文件,生成到dist/types

import { Project } from 'ts-morph'
import * as vueCompiler from 'vue/compiler-sfc'

/**
 * 生成类型定义文件,包括/packages下的所有文件和/typings/env.d.ts
 * [ts-morph](https://ts-morph.com/) 是一个用来访问和操作typescript AST的库,可以重构、生成、检查和分析ts代码、生成类型定义等
 *
 * 1. 将packages下的所有文件作为源文件,并且对vue文件做处理,只提取script部分(对setup类型的脚本使用@vue/compiler-sfc编译为普通脚本)
 * 2. 类型检查
 * 3. 生成类型声明文件到dist/types 目录结构以types为根目录与源文件一致
 */
export const generateTypesDefinitions = async () => {
  // 创建ts-morph项目
  const project = new Project({})

  // 处理(vue文件)和添加源文件,包括/typings/env.d.ts和packages/下的所有文件
  const sourceFiles = await addSourceFiles(project) 

  // 类型检查 ts-morph提供的api
  typeCheck(project)

  // emit 生成js和类型声明文件,emitOnlyDtsFiles: true 指定只生成类型声明文件
  await project.emit({
    emitOnlyDtsFiles: true,
  })

  // 对生成的类型文件内容做后续处理
  // 将 @element-plus/theme-chalk 替换为 element-plus/theme-chalk
  // 将 @element-plus 替换为 element-plus/es
  
}

// 添加和处理源文件,包括/typings/env.d.ts和packages/下的所有文件
async function addSourceFiles(project: Project) {
  // 加载源文件/typings/env.d.ts
  project.addSourceFileAtPath(path.resolve(projRoot, 'typings/env.d.ts'))

	// ...
    
  // 如果是vue文件,需要将其转换为js文件
  // 1. 对于vue文件,只提取script脚本
  // 2. 如果不是setup脚本,直接输出脚本内容
  // 3. 如果是setup脚本,将其编译为标准js内容输出
  
    // 将vue setup语法转为标准js语法 如:
    /**
        <script setup>
          import {ref} from 'vue'
          const a = ref(1)
        </script>
     */
    // 转换后:
    /**
        "import {ref} from 'vue'\n" +
        '\n' +
        'export default {\n' +
        '  setup(__props, { expose: __expose }) {\n' +
        '  __expose();\n' +
        '\n' +
        'const a = ref(1)\n' +
        '\n' +
        'const __returned__ = { a, ref }\n' +
        "Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n" +
        'return __returned__\n' +
        '}\n' +
        '\n' +
        '}',
     */

  // 将package/下的所有文件添加到project中
}

element-plus源码与二次开发:构建与发布流程分析生成类型声明文件到dist/types中

buildHelper

使用components-helper读取markdown文档,自动生成组件代码提示文件

  • 生成vetur的组件代码提示文件 attributes.jsontags.json
  • 生成webstorm的组件代码提示文件web-types.json

element-plus源码与二次开发:构建与发布流程分析element-plus源码与二次开发:构建与发布流程分析

volar可直接使用tsconfig.json中的类型声明配置,因此有类型声明文件就够了,根据element-plus的官方文档中volar配置,在tsconfig.json中配置,读取element-plus/global

"compilerOptions": {
  "types": ["element-plus/global"]
}

这个文件来自根目录的global.d.ts

element-plus源码与二次开发:构建与发布流程分析global.d.ts

这里引入了全部组件的类型定义,但是并没有找到自动更新该文件的地方(如执行gen命令创建新组件),那么在创建新组件时需要在这里手动引入新组件的类型声明。

buildThemeChalk

所有组件的样式文件并没有主动引入,单独存放在package/theme-chalk下。

buildThemeChalk任务复制源样式文件(scss),编译为css,压缩,文件名加el-前缀,输出到packages/theme-chalk/dist/

样式文件目录的规则:

  • 所有组件的样式文件入口都在src的根目录下,以[name].scss命名,如button.scss
  • index.scss引入所有组件的样式文件入口,编译出的css是全量样式
  • 若组件样式比较复杂,则创建[name]文件夹,将样式文件拆分到该文件夹下,如date-picker

element-plus源码与二次开发:构建与发布流程分析编译后结果,src内是源scss文件,dark是黑暗模式的变量