【由浅入深】vue组件库实战开发总结分享

广告:宝塔Linux面板高效运维的服务器管理软件 点击【 https://www.bt.cn/p/uNLv1L 】立即购买

【由浅入深】vue组件库实战开发总结分享

很庆幸标题能够赶上2022结束的脚步。本文由浅入深层层递进,对组件库的开发过程做个了小结。

由于篇幅有限,阴影部分的内容将在中/下篇介绍。

话不多说,直入主题。

yarn workspace + lerna: 管理组件库及其生态项目

考虑到组件库整体需要有多边资源支持,比如组件源码,库文档站点,color-gen等类库工具,代码规范配置,vite插件,脚手架,storybook等等,需要分出很多packages,package之间存在彼此联系,因此考虑使用monorepo的管理方式,同时使用yarn作为包管理工具,lerna作为包发布工具。【相关推荐:vuejs视频教程、web前端开发】

在monorepo之前,根目录就是一个workspace,我们直接通过yarn add/remove/run等就可以对包进行管理。但在monorepo项目中,根目录下存在多个子包,yarn 命令无法直接操作子包,比如根目录下无法通过yarn run dev启动子包package-a中的dev命令,这时我们就需要开启yarn的workspaces功能,每个子包对应一个workspace,之后我们就可以通过yarn workspace package-a run dev启动package-a中的dev命令了。

你可能会想,我们直接cd到package-a下运行就可以了,不错,但yarn workspaces的用武之地并不只此,像auto link,依赖提升,单.lock等才是它在monorepo中的价值所在。

启用yarn workspaces

我们在根目录packge.json中启用yarn workspaces:

{  "private": true,  "workspaces": [    "packages/*"  ]}
登录后复制

packages目录下的每个直接子目录作为一个workspace。由于我们的根项目是不需要发布出去的,因此设置private为true。

安装lerna并初始化

不得不说,yarn workspaces已经具备了lerna部分功能,之所以使用它,是想借用它的发布工作流以弥补workspaces在monorepo下在这方面的不足。下面我们开始将lerna集成到项目中。

首先我们先安装一下lerna:

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g
登录后复制

执行lerna init初始化项目,成功之后会帮我们创建了一个lerna.json文件

lerna init
登录后复制
// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}
登录后复制

$schema指向的lerna-schema.json描述了如何配置lerna.json,配置此字段后,鼠标悬浮在属性上会有对应的描述。注意,以上的路径值需要你在项目根目录下安装lerna。

useWorkspaces定义了在lerna bootstrap期间是否结合yarn workspace。

由于lerna默认的工作模式是固定模式,即发布时每个包的版本号一致。这里我们修改为independent独立模式,同时将npm客户端设置为yarn。如果你喜欢pnpm,just do it!

// lerna.json{  "version": "independent",  "npmClient": "yarn"}
登录后复制

至此yarn workspaces搭配lerna的monorepo项目就配置好了,非常简单!

额外的lerna配置

By the way!由于项目会使用commitlint对提交信息进行校验是否符合Argular规范,而lerna version默认为我们commit的信息是"Publish",因此我们需要进行一些额外的配置。

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}
登录后复制

可以看到,我们使用符合Argular团队提交规范的"chore(release): publish"代替默认的"Publish"。

conventionalCommits表示当我们运行lerna version,实际上会运行lerna version --conventional-commits帮助我们生成CHANGELOG.md。

小结

在lerna刚发布的时候,那时的包管理工具还没有可用的workspaces解决方案,因此lerna自身实现了一套解决方案。时至今日,现代的包管理工具几乎都内置了workspaces功能,这使得lerna和yarn有许多功能重叠,比如执行包pkg-a的dev命令lerna run dev --stream --scope=pkg-a,我们完全可以使用yarn workspace pkg-a run dev代替。lerna bootstrap --hoist将安装包提升到根目录,而在yarn workspaces中直接运行yarn就可以了。

Anyway, 使用yarn作为软件包管理工具,lerna作为软件包发布工具,是在monorepo管理方式下一个不错的实践!

集成Lint工具规范化代码

很无奈,我知道大部分人都不喜欢Lint,但对我而言,这是必须的。

集成eslint

packages目录下创建名为@argo-design/eslint-config(非文件夹名)的package

1. 安装eslint
cd argo-eslint-configyarn add eslintnpx eslint --init
登录后复制

注意这里没有-D或者--save-dev。选择如下:

安装完成后手动将devDependencies下的依赖拷贝到dependencies中。或者你手动安装这一系列依赖。

2. 使用
// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}
登录后复制

运行yarn lint:script,将会自动修复代码规范错误警告(如果可以的话)。

3. VSCode保存时自动修复

安装VSCode Eslint插件并进行如下配置,此时在你保存代码时,也会自动修复代码规范错误警告。

// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}
登录后复制4. 集成到项目全局

argo-eslint-config中新建包入口文件index.js,并将.eslintrc.js的内容拷贝到index.js中

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}
登录后复制

确保package.json配置main指向我们刚刚创建的index.js。

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g0
登录后复制

根目录package.json新增如下配置

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g1
登录后复制

最后运行yarn重新安装依赖。

注意包命名与extends书写规则;root表示根配置,对eslint配置文件冒泡查找到此为止。

集成prettier

接下来我们引入formatter工具prettier。首先我们需要关闭eslint规则中那些与prettier冲突或者不必要的规则,最后由prettier代为实现这些规则。前者我们通过eslint-config-prettier实现,后者借助插件eslint-plugin-prettier实现。比如冲突规则尾逗号,eslint-config-prettier帮我们屏蔽了与之冲突的eslint规则:

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g2
登录后复制

通过配置eslint规则"prettier/prettier": "error"让错误暴露出来,这些错误交给eslint-plugin-prettier收拾。

prettier配置我们也新建一个package@argo-design/prettier-config

1. 安装
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g3
登录后复制2. 使用
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g4
登录后复制

完整配置参考官网 prettier配置

3. 配置eslint

回到argo-eslint-config/index.js,只需新增如下一条配置即可

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g5
登录后复制

plugin:prettier/recommended指的eslint-plugin-prettierpackage下的recommended.js。该扩展已经帮我们配置好了

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g6
登录后复制4. 集成到项目全局

根目录package.json新增如下配置

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g7
登录后复制

运行yarn重新安装依赖。

5. VSCode安装prettier扩展并将其设置成默认格式化工具
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g8
登录后复制集成stylelint

stylelint配置我们也新建一个package@argo-design/stylelint-config

1. 安装
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g9
登录后复制

对于结合prettier这里不在赘述。

stylelint-order允许我们自定义样式属性名称顺序。而stylelint-config-rational-order为我们提供了一套合理的开箱即用的顺序。

值得注意的是,stylelint14版本不在默认支持less,sass等预处理语言。并且stylelint14依赖postcss8版本,可能需要单独安装,否则vscode 的stylellint扩展可能提示报错TypeError: this.getPosition is not a function at LessParser.inlineComment....

2. 使用
lerna init0
登录后复制3. 集成到项目全局

根目录package.json新增如下配置

lerna init1
登录后复制

运行yarn重新安装依赖。

4. VSCode保存时自动修复

VSCode安装Stylelint扩展并添加配置

lerna init2
登录后复制

修改settings.json之后如不能及时生效,可以重启一下vscode。如果你喜欢,可以将eslint,prettier,stylelint配置安装到全局并集成到编辑器。

集成husky

为防止一些非法的commitpush,我们借助git hooks工具在对代码提交前进行 ESLint 与 Stylelint的校验,如果校验通过,则成功commit,否则取消commit。

1. 安装
lerna init3
登录后复制2. 使用
lerna init4
登录后复制

至此,当我们执行git commit -m "xxx"时就会先执行lint校验我们的代码,如果lint通过,成功commit,否则终止commit。具体的lint命令请自行添加。

集成lint-staged: 仅校验staged中文件

现在,当我们git commit时,会对整个工作区的代码进行lint。当工作区文件过多,lint的速度就会变慢,进而影响开发体验。实际上我们只需要对暂存区中的文件进行lint即可。下面我们引入·lint-staged解决我们的问题。

1. 安装

在根目录安装lint-staged

lerna init5
登录后复制2. 使用

在根目录package.json中添加如下的配置:

lerna init6
登录后复制

在monorepo中,lint-staged运行时,将始终向上查找并应用最接近暂存文件的配置,因此我们可以在根目录下的package.json中配置lint-staged。值得注意的是,每个glob匹配的数组中的命令是从左至右依次运行,和webpack的loder应用机制不同!

最后,我们在.husky文件夹中找到pre-commit,并将yarn lint修改为npx --no-install lint-staged

lerna init7
登录后复制

至此,当我们执行git commit -m "xxx"时,lint-staged会如期运行帮我们校验staged(暂存区)中的代码,避免了对工作区的全量检查。

集成commitlint: 规范化commit message

除了代码规范检查之后,Git 提交信息的规范也是不容忽视的一个环节,规范精准的 commit 信息能够方便自己和他人追踪项目和把控进度。这里,我们使用大名鼎鼎的Angular团队提交规范

commit message格式规范

commit message 由 HeaderBodyFooter 组成。其中Herder时必需的,Body和Footer可选。

Header

Header 部分包括三个字段 typescopesubject

lerna init8
登录后复制type

其中type 用于说明 commit 的提交类型(必须是以下几种之一)。

值描述featFeature) 新增一个功能fixBug修复docsDocumentation) 文档相关style代码格式(不影响功能,例如空格、分号等格式修正),并非css样式更改refactor代码重构perfPerforment) 性能优化test测试相关build构建相关(例如 scopes: webpack、gulp、npm 等)ci更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等chore变更构建流程或辅助工具,日常事务revertgit revertscope

scope 用于指定本次 commit 影响的范围。

subject

subject 是本次 commit 的简洁描述,通常遵循以下几个规范:

用动词开头,第一人称现在时表述,例如:change 代替 changed 或 changes

第一个字母小写

结尾不加句号.

Body(可选)

body 是对本次 commit 的详细描述,可以分成多行。跟 subject 类似。

Footer(可选)

如果本次提交的代码是突破性的变更或关闭Issue,则 Footer 必需,否则可以省略。

集成commitizen(可选)

我们可以借助工具帮我们生成规范的message。

1. 安装
lerna init9
登录后复制2. 使用

安装适配器

// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}0
登录后复制

这行命令做了两件事:

安装cz-conventional-changelog到开发依赖

在根目录下的package.json中增加了:

// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}1
登录后复制

添加npm scriptscm

// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}2
登录后复制

至此,执行yarn cm,就能看到交互界面了!跟着交互一步步操作就能自动生成规范的message了。

集成commitlint: 对最终提交的message进行校验1. 安装

首先在根目录安装依赖:

// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}3
登录后复制2. 使用

接着新建.commitlintrc.js:

// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}4
登录后复制

最后向husky中添加commit-msg钩子,终端执行:

// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}5
登录后复制

执行成功之后就会在.husky文件夹中看到commit-msg文件了:

// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}6
登录后复制

至此,当你提交代码时,如果pre-commit钩子运行成功,紧接着在commit-msg钩子中,commitlint会如期运行对我们提交的message进行校验。

关于lint工具的集成到此就告一段落了,在实际开发中,我们还会对lint配置进行一些小改动,比如ignore,相关rules等等。这些和具体项目有关,我们不会变更package里的配置。

千万别投机取巧拷贝别人的配置文件!复制一时爽,代码火葬场。

图标库

巧妇难为无米之炊。组件库通常依赖很多图标,因此我们先开发一个支持按需引入的图标库。

假设我们现在已经拿到了一些漂亮的svg图标,我们要做的就是将每一个图标转化生成.vue组件与一个组件入口index.ts文件。然后再生成汇总所有组件的入口文件。比如我们现在有foo.svg与bar.svg两个图标,最终生成的文件及结构如下:

相应的内容如下:

// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}7
登录后复制
// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}8
登录后复制
// lerna.json{  "$schema": "node_modules/lerna/schemas/lerna-schema.json",  "useWorkspaces": true,  "version": "0.0.0"}9
登录后复制
// lerna.json{  "version": "independent",  "npmClient": "yarn"}0
登录后复制

之所以这么设计是由图标库最终如何使用决定的,除此之外argoIcon.ts也将会是打包umd的入口文件。

// lerna.json{  "version": "independent",  "npmClient": "yarn"}1
登录后复制

图标库的整个构建流程大概分为以下3步:

1. svg图片转.vue文件

整个流程很简单,我们通过glob匹配到.svg拿到所有svg的路径,对于每一个路径,我们读取svg的原始文本信息交由第三方库svgo处理,期间包括删除无用代码,压缩,自定义属性等,其中最重要的是为svg标签注入我们想要的自定义属性,就像这样:

// lerna.json{  "version": "independent",  "npmClient": "yarn"}2
登录后复制

之后这段svgHtml会传送给我们预先准备好的摸板字符串:

// lerna.json{  "version": "independent",  "npmClient": "yarn"}3
登录后复制

为摸板字符串填充数据后,通过fs模块的writeFile生成我们想要的.vue文件。

2. 打包vue组件

在打包构建方案上直接选择vite为我们提供的lib模式即可,开箱即用,插件扩展(后面会讲到),基于rollup,能帮助我们打包生成ESM这是按需引入的基础。当然,commonjsumd也是少不了的。整个过程我们通过Vite 的JavaScript API实现:

// lerna.json{  "version": "independent",  "npmClient": "yarn"}4
登录后复制
// lerna.json{  "version": "independent",  "npmClient": "yarn"}5
登录后复制
// lerna.json{  "version": "independent",  "npmClient": "yarn"}6
登录后复制
// lerna.json{  "version": "independent",  "npmClient": "yarn"}7
登录后复制

可以看到,我们通过type区分组件库和图标库打包。实际上打包图标库和组件库都是差不多的,组件库需要额外打包国际化相关的语言包文件。图标样式内置在组件之中,因此也不需要额外打包。

3. 打包声明文件

我们直接通过第三方库 vite-plugin-dts 打包图标库的声明文件。

// lerna.json{  "version": "independent",  "npmClient": "yarn"}8
登录后复制

关于打包原理可参考插件作者的这片文章。

lequ7.com/guan-yu-qia…

4. 实现按需引入

我们都知道实现tree-shaking的一种方式是基于ESM的静态性,即在编译的时候就能摸清依赖之间的关系,对于"孤儿"会残忍的移除。但是对于import "icon.css"这种没导入导出的模块,打包工具并不知道它是否具有副作用,索性移除,这样就导致页面缺少样式了。sideEffects就是npm与构建工具联合推出的一个字段,旨在帮助构建工具更好的为npm包进行tree-shaking。

使用上,sideEffects设置为false表示所有模块都没有副作用,也可以设置数组,每一项可以是具体的模块名或Glob匹配。因此,实现图标库的按需引入,只需要在argo-icons项目下的package.json里添加以下配置即可:

// lerna.json{  "version": "independent",  "npmClient": "yarn"}9
登录后复制

这将告诉构建工具,图标库没有任何副作用,一切没有被引入的代码或模块都将被移除。前提是你使用的是ESM。

指定入口

Last but important!当图标库在被作为npm包导入时,我们需要在package.json为其配置相应的入口文件。

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}0
登录后复制引入storybook:是时候预览我们的成果了!

顾名思义,storybook就是一本"书",讲了很多个"故事"。在这里,"书"就是argo-icons,我为它讲了3个故事:

基本使用

按需引入

使用iconfont.cn项目

初始化storybook

新建@argo-design/ui-storybookpackage,并在该目录下运行:

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}1
登录后复制

-t (即--type): 指定项目类型,storybook会根据项目依赖及配置文件等推算项目类型,但显然我们仅仅是通过npm init新创建的项目,storybook无法自动判断项目类型,故需要指定type为vue3,然后storybook会帮我们初始化storybook vue3 app。

-b (--builder): 指定构建工具,默认是webpack4,另外支持webpack5, vite。这里指定webpack5,否则后续会有类似报错:cannot read property of undefine(reading 'get')...因为storybook默认以webpack4构建,但是@storybook/vue3依赖webpack5,会冲突导致报错。这里是天坑!!

storybook默认使用yarn安装,如需指定npm请使用--use-npm。

这行命令主要帮我们做以下事情:

注入必要的依赖到packages.json(如若没有指定-s,将帮我们自动安装依赖)。

注入启动,打包项目的脚本。

添加Storybook配置,详见.storybook目录。

添加Story范例文件以帮助我们上手,详见stories目录。

其中1,2步具体代码如下:

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}2
登录后复制

接下来把目光放到.storybook下的main.js与preview.js

preview.js

preview.js可以具名导出parameters,decorators,argTypes,用于全局配置UI(stories,界面,控件等)的渲染行为。比如默认配置中的controls.matchers:

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}3
登录后复制

它定义了如果属性值是以background或color结尾,那么将为其启用color控件,我们可以选择或输入颜色值,date同理。

除此之外你可以在这里引入全局样式,注册组件等等。更多详情见官网 Configure story rendering

main.js

最后来看看最重要的项目配置文件。

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}4
登录后复制

stories, 即查找stroy文件的Glob。

addons, 配置需要的扩展。庆幸的是,当前一些重要的扩展都已经集成到@storybook/addon-essentials。

framework和core即是我们初识化传递的-t vue3 -b webpack5

更多详情见官网 Configure your Storybook project

配置并启动storybookless配置

由于项目使用到less因此我们需要配置一下less,安装less以及相关loader。来到.storybook/main.js

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}5
登录后复制配置JSX

storybook默认支持解析jsx/tsx,但你如果需要使用jsx书写vue3的stories,仍需要安装相关插件。

在argo-ui-storybook下安装 @vue/babel-plugin-jsx

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}6
登录后复制

新建.babelrc

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}7
登录后复制

关于如何书写story,篇幅受限,请自行查阅范例文件或官网。

配置完后终端执行yarn storybook即可启动我们的项目,辛苦的成果也将跃然纸上。

对于UI,在我们的组件库逐渐丰富之后,将会自建一个独具组件库风格的文档站点,拭目以待。

组件库组件通信

在Vue2时代,组件跨层级通信方式可谓“百花齐放”,provide/inject就是其中一种。时至今日,在composition,es6,ts加持下,provide/inject可以更加大展身手。

provide/inject原理

在创建组件实例时,会在自身挂载一个provides对象,默认指向父实例的provides。

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}8
登录后复制

appContext.provides即createApp创建的app的provides属性,默认是null

在自身需要为子组件供数据时,即调用provide()时,会创建一个新对象,该对象的原型指向父实例的provides,同时将provide提供的选项添加到新对象上,这个新对象就是实例新的provides值。代码简化就是

// lerna.json{  "command": {    "version": {      "message": "chore(release): publish",      "conventionalCommits": true    }  }}9
登录后复制

而inject的实现原理则时通过key去查找祖先provides对应的值:

cd argo-eslint-configyarn add eslintnpx eslint --init0
登录后复制

你可能会疑惑,为什么这里是直接去查父组件,而不是先查自身实例的provides呢?前面不是说实例的provides默认指向父实例的provides么。但是请注意,是“默认”。如果当前实例执行了provide()是不是把instance.provides“污染”了呢?这时再执行inject(key),如果provide(key)的key与你inject的key一致,就从当前实例provides取key对应的值了,而不是取父实例的provides!

最后,我画了2张图帮助大家理解

新增button组件并完成打包

篇幅有限,本文不会对组件的具体实现讲解哦,简单介绍下文件

__demo__组件使用事例constants.ts定义的常量context.ts上下文相关interface.ts组件接口TEMPLATE.md用于生成README.md的模版button/style下存放组件样式style下存放全局样式打包esm与commonjs模块

关于打包组件的esmcommonjs模块在之前打包图标库章节已经做了介绍,这里不再赘述。

打包样式

相对于图标库,组件库的打包需要额外打包样式文件,大概流程如下:

生成总入口components/index.less并编译成css。

编译组件less。

生成dist下的argo.css与argo.min.css。

构建组件style/index.ts。

1. 生成总入口components/index.less
cd argo-eslint-configyarn add eslintnpx eslint --init1
登录后复制

代码很简单,值得一提就是为什么不将lessContent初始化为空,glob中将ignore移除,这不是更简洁吗。这是因为style/index.less作为全局样式,我希望它在引用的最顶部。最终将会在components目录下生成index.less内容如下:

cd argo-eslint-configyarn add eslintnpx eslint --init2
登录后复制2. 打包组件样式
cd argo-eslint-configyarn add eslintnpx eslint --init3
登录后复制3. 生成dist下的argo.css与argo.min.css
cd argo-eslint-configyarn add eslintnpx eslint --init4
登录后复制

其中最重要的就是使用clean-css压缩css。

4. 构建组件style/index.ts

如果你使用过babel-plugin-import,那一定熟悉这项配置:

["import", { "libraryName": "antd", "style": true }]: import js and css modularly (LESS/Sass source files)["import", { "libraryName": "antd", "style": "css" }]: import js and css modularly (css built files)

通过指定style: true,babel-plugin-import可以帮助我们自动引入组件的less文件,如果你担心less文件定义的变量会被覆盖或冲突,可以指定'css',即可引入组件的css文件样式。

这一步就是要接入这点。但目前不是很必要,且涉及到vite插件开发,暂可略过,后面会讲。

来看看最终实现的样子。

其中button/style/index.js内容也就是导入less:

cd argo-eslint-configyarn add eslintnpx eslint --init5
登录后复制

button/style/css.js内容也就是导入css:

cd argo-eslint-configyarn add eslintnpx eslint --init6
登录后复制

最后你可能会好奇,诸如上面提及的compileComponentcompileStyle等函数是如何被调度使用的,这其实都归功于脚手架@argo-design/scripts。当它作为依赖被安装到项目中时,会为我们提供诸多命令如argo-scripts geniconargo-scripts compileComponent等,这些函数都在执行命令时被调用。

配置sideEffects
cd argo-eslint-configyarn add eslintnpx eslint --init7
登录后复制国际化基本实现
cd argo-eslint-configyarn add eslintnpx eslint --init8
登录后复制

添加需要支持的语言包,这里默认支持中文和英文。

cd argo-eslint-configyarn add eslintnpx eslint --init9
登录后复制
// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}0
登录后复制

button组件中接入

// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}1
登录后复制

Button的国际化仅做演示,实际上国际化在日期日历等组件中才有用武之地。

国际化演示

argo-ui-storybook/stories中添加locale.stories.ts

// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}2
登录后复制

.preview.js中全局引入组件库样式

// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}3
登录后复制

终端启动项目就可以看到效果了。

实现config-provider组件

通常组件库都会提供config-provider组件来使用国际化,就像下面这样

// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}4
登录后复制

下面我们来实现一下config-provider组件:

// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}5
登录后复制
// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}6
登录后复制

修改locale/index.ts中计算属性i18nMessage的获取逻辑

// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}7
登录后复制

编写stories验证一下:

// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}8
登录后复制

以上stories使用到了jsx,请确保安装并配置了@vue/babel-plugin-jsx

可以看到,Button默认是英文的,表单控件也接收到enUS语言包了,符合预期。

自动引入组件样式

值得注意的是,上面提到的按需引入只是引入了组件js逻辑代码,但对于样式依然没有引入。

下面我们通过开发vite插件vite-plugin-auto-import-style,让组件库可以自动引入组件样式。

效果演示

现在我们书写的代码如下,现在我们已经知道了,这样仅仅是加载了组件而已。

// argo-eslint-config/package.json{  scripts: {    "lint:script": "npx eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./"  }}9
登录后复制

添加插件之前:

添加插件之后:

// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}0
登录后复制

插件实现

实践之前浏览一遍官网插件介绍是个不错的选择。插件API

vite插件是一个对象,通常由name和一系列钩子函数组成:

// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}1
登录后复制常用钩子config

vite.config.ts被解析完成后触发。常用于扩展配置。可以直接在config上定义或返回一个对象,该对象会尝试与配置文件vite.config.ts中导出的配置对象深度合并。

configResolved

在解析完所有配置时触发。形参config表示最终确定的配置对象。通常将该配置保存起来在有需要时提供给其它钩子使用。

resolveId

开发阶段每个传入模块请求时被调用,常用于解析模块路径。返回string或对象将终止后续插件的resolveId钩子执行。

load

resolveId之后调用,可自定义模块加载内容

transform

load之后调用,可自定义修改模块内容。这是一个串行钩子,即多个插件实现了这个钩子,下个插件的transform需要等待上个插件的transform钩子执行完毕。上个transform返回的内容将传给下个transform钩子。

为了让插件完成自动引入组件样式,我们需要完成如下工作:

过滤出我们想要的文件。

对文件内容进行AST解析,将符合条件的import语句提取出来。

然后解析出具体import的组件。

最后根据组件查找到样式文件路径,生成导入样式的语句字符串追加到import语句后面即可。

其中过滤我们使用rollup提供的工具函数createFilter;

AST解析借助es-module-lexer,非常出名,千万级周下载量。

// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}2
登录后复制
// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}3
登录后复制换肤与暗黑风格换肤

在我们的less样式中,会定义一系列如下的颜色梯度变量,其值由color-palette函数完成:

// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}4
登录后复制

基于此,我们再演化出具体场景下的颜色梯度变量:

// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}5
登录后复制

有了具体场景下的颜色梯度变量,我们就可以设计变量供给组件消费了:

// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}6
登录后复制
// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}7
登录后复制

在使用组件库的项目中我们通过 Less 的 ·modifyVars 功能修改变量值:

Webpack配置
// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}8
登录后复制vite配置
// settings.json{  "editor.defaultFormatter": "dbaeumer.vscode-eslint",  "editor.codeActionsOnSave": {    "source.fixAll.eslint": true  }}9
登录后复制设计暗黑风格

首先,颜色梯度变量需要增加暗黑风格。也是基于@blue-6计算,只不过这里换成了dark-color-palette函数:

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}0
登录后复制

然后,在相应节点下挂载css变量

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}1
登录后复制

紧接着,组件消费的less变量更改为css变量:

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}2
登录后复制

此外,我们还设置了--color-bg,--color-text等用于设置body色调:

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}3
登录后复制

最后,在消费组件库的项目中,通过编辑body的argo-theme属性即可切换亮暗模式:

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}4
登录后复制在线动态换肤

前面介绍的是在项目打包时通过less配置修改less变量值达到换肤效果,有了css变量,我们可以实现在线动态换肤。默认的,打包过后样式如下:

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}5
登录后复制

在用户选择相应颜色后,我们只需要更改css变量--primary-6的值即可:

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}6
登录后复制文档站点

还记得每个组件目录下的TEMPLATE.md文件吗?

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}7
登录后复制

它是如何一步步被渲染出我们想要的界面呢?

TEMPLATE.md的作用

TEMPLATE.md将被解析并生成中英文版READE.md(组件使用文档),之后在vue-router中被加载使用。

这时当我们访问路由/button,vite服务器将接管并调用一系列插件解析成浏览器识别的代码,最后由浏览器渲染出我们的文档界面。

1. 解析TEMPLATE 生成 README

简单起见,我们忽略国际化和使用例子部分。

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}8
登录后复制

其中button.vue就是我们的组件,interface.ts就是定义组件的一些接口,比如ButtonProps,ButtonType等。

解析button.vue

大致流程如下:

读取TEMPLATE.md,正则匹配出button.vue;

使用vue-doc-api解析vue文件; let componentDocJson = VueDocApi.parse(path.resolve(__dirname, "button.vue"));

componentDocJson转换成md字符串,md字符串替换掉占位符%%API(button.vue)%%,写入README.md;

关于vue文件与解析出来的conponentDocJson结构见 vue-docgen-api

解析interface.ts

由于VueDocApi.parse无法直接解析.ts文件,因此借助ts-morph解析ts文件并转换成componentDocJson结构的JSON对象,再将componentDocJson转换成md字符串,替换掉占位符后最终写入README.md;

读取TEMPLATE.md,正则匹配出interface.ts;

使用ts-morph解析inerface.ts出interfaces;

interfaces转componentDocJson;

componentDocJson转换成md字符串,md字符串替换掉占位符%%API(button.vue)%%,写入README.md;

module.exports = {  env: {    browser: true,    es2021: true,    node: true  },  extends: ['plugin:vue/vue3-essential', 'standard-with-typescript'],  overrides: [],  parserOptions: {    ecmaVersion: 'latest',    sourceType: 'module'  },  plugins: ['vue'],  rules: {}}9
登录后复制

最终生成README.zh-CN.md如下

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g00
登录后复制2. 路由配置
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g01
登录后复制3. README是如何被渲染成UI的

首先我们来看下README.md(为方便直接省略.zh-CN)以及其中的demos.md的样子与它们最终的UI。

可以看到,README就是一系列demo的集合,而每个demo都会被渲染成一个由代码示例与代码示例运行结果组成的代码块。

开发vite-plugin-vue-docs解析md

yarn create vite快速搭建一个package

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g02
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g03
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g04
登录后复制

开发之前我们先看看插件对README.md源码的解析转换流程。

1. 源码转换

首先我们来实现第一步: 源码转换。即将

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g05
登录后复制

转换成

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g06
登录后复制

转换过程我们借助第三方markdown解析工具marked完成,一个高速,轻量,无阻塞,多平台的markdown解析器。

众所周知,md2html规范中,文本默认会被解析渲染成p标签。也就是说,README.md里的@import ./__demo__/basic.md会被解析渲染成<p>@import ./__demo__/basic.md</p>,这不是我想要的。所以需要对marked进行一下小小的扩展。

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g07
登录后复制

我们新建了一个mdImport的扩展,用来自定义解析我们的md。在tokenizer 中我们定义了解析规则并返回一系列自定义的tokens,其中raw就是# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g05,filename就是./__demo__/basic.md,basename就是basic,我们可以通过marked.lexer(code)拿到这些tokens。在renderer中我们自定义了渲染的html,通过marked.parser(tokens)可以拿到html字符串了。因此,我们开始在插件中完成第一步。

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g08
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g09
登录后复制

其中change-case是一个名称格式转换的工具,比如basic-demo转BasicDemo等。

transformMain返回的vueCode就是我们的目标vue模版了。但浏览器可不认识vue模版语法,所以我们仍要将其交给官方插件@vitejs/plugin-vuetransform钩子函数转换一下。

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g10
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g11
登录后复制

这里使用getVueId修改扩展名为.vue是因为vuePlugin.transform会对非vue文件进行拦截就像我们上面拦截非md文件一样。

configResolved钩子函数中,形参resolvedConfig是vite最终使用的配置对象。在该钩子中拿到其它插件并将其提供给其它钩子使用,是vite插件开发中的一种“惯用伎俩”了。

2. 处理basic.md

在经过vuePlugin.transform及后续处理过后,最终vite服务器对readme.md响应给浏览器的内容如下

对于basic.md?import响应如下

可以看到,这一坨字符串可没有有效的默认导出语句。因此对于解析语句import DemoBasic from "/src/__demo__/basic.md?import";浏览器会报错

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g12
登录后复制

在带有module属性的script标签中,每个import语句都会向vite服务器发起请求进而继续走到插件的transform钩子之中。下面我们继续,对/src/__demo__/basic.md?import进行拦截处理。

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g13
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g14
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g15
登录后复制

现在已经可以在浏览器中看到结果了,水平线和示例代码。

3. 虚拟模块

那如何实现示例代码的运行结果呢?其实在对tokens遍历(filter)的时候,我们是可以拿到vue模版字符串的,我们可以将其缓存起来,同时手动构造一个import请求import Result from "${virtualPath}";这个请求用于返回运行结果。

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g16
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g17
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g18
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g19
登录后复制

最后为示例代码加上样式。安装prismjs

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g20
登录后复制
# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g21
登录后复制

项目入口引入css

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g22
登录后复制

重启预览,以上就是vite-plugin-vue-docs的核心部分了。

遗留问题

最后回到上文构建组件style/index.ts遗留的问题,index.ts的内容很简单,即引入组件样式。

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g23
登录后复制

index.ts在经过vite的lib模式构建后,我们增加css插件,在generateBundle钩子中,我们可以对最终的bundle进行新增,删除或修改。通过调用插件上下文中emitFile方法,为我们额外生成用于引入css样式的css.js。

# W指workspace-root,即在项目根目录下安装,下同yarn add lerna -D -W# 由于经常使用lerna命令也推荐全局安装yarn global add lernaornpm i lerna -g24
登录后复制结语

下篇暂定介绍版本发布,部署站点,集成到在线编辑器,架构复用等,技术涉及linux云服务器,站点服务器nginx,docker,stackblitz等。

(学习视频分享:vuejs入门教程、编程基础视频)

以上就是【由浅入深】vue组件库实战开发总结分享的详细内容,更多请关注9543建站博客其它相关文章!

广告:SSL证书一年128.66元起,点击购买~~~

9543建站博客
一个专注于网站开发、微信开发的技术类纯净博客。

作者头像
admin创始人

肥猫,知名SEO博客站长,14年SEO经验。

上一篇:S5让分层屏幕适配
下一篇:DW怎么插JavaScript

发表评论

关闭广告
关闭广告