乐趣区

关于前端:我想写一个-Vue3-组件库我该怎么开始

如何从零开始搭建一个 Vue3 组件库?

最近有一些小伙伴问雨声:想要本人做一个组件库,但不晓得如何开始?看到他人那些组件库各种各样、形形色色的配置文件和文件构造,基本不晓得该从何下手。

别慌,其实再简单的构造与配置,也是从一开始非常简单的我的项目,在开发过程中依据须要,经验一直的迭代、重构与欠缺后才逐步形成的。

咱们想要开始的时候,其实最重要的第一步就是口头起来,先开一个最简略的我的项目,写一个最简略的组件,实现一次最简略的打包。

开始☕!

创立我的项目

首先是包管理工具,举荐大家选用 pnpm,相较于其余包治理,它更快、更节约空间,还有一个很重要的劣势就是能十分不便地创立和治理 monorepo,装置可自行查阅 官网。

而后就是脚手架的抉择,既然是 Vue3 组件库,那 Vite 必然是不二之选了。首先它简直能够说是 Vue3 的官配,其次在做库我的项目方面,Vite 在打包时没 Webpack 这么麻烦,在开发时也比 Rollup 更容易搭建开发服务。

咱们间接应用 Vite 官网提供的 vue-ts 模版来疾速创立一个原始我的项目:

pnpm create vite demo-ui --template vue-ts

之后咱们把 Git 初始化一下:

git init

接着把 package.jsonvue-tsc 的内容去掉,因为类型谬误的查看咱们能够间接借助编辑器的 Volar 插件实时进行。

package.json

{
  "name": "demo-ui",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {"vue": "^3.2.25"},
  "devDependencies": {
    "@vitejs/plugin-vue": "^2.3.3",
    "typescript": "^4.5.4",
    "vite": "^2.9.9"
  }
}

最初装置一下依赖,我的项目创立就 OK 了。

pnpm i

第一个组件

有了我的项目之后,咱们不论其余乌七八糟的,先来个最简略的组件。

咱们先对目录构造做简略的革新,使其适应 Lib 型我的项目。

这是咱们原始创立后的构造:

咱们把 publicsrc/assetssrc/components/HelloWorld.vue 都删掉,增加 src/index.tssrc/components/button.vue

其中的 src/App.vuesrc/main.ts 留着用于开发组件的时候随时看成果。

接着咱们快快地写一个按钮组件,不论其余的,页面上能看到就行。

src/components/button.vue

<template>
  <button class="d-button">
    <slot></slot>
  </button>
</template>

<script lang="ts">
import {defineComponent} from 'vue'

export default defineComponent({name: 'DButton'})
</script>

src/App.vue

<template>
  <DButton>
    一个按钮
  </DButton>
</template>

<script setup lang="ts">
import DButton from './components/button.vue'
</script>

而后咱们启动开发服务:

pnpm run dev

关上浏览器:

平平无奇,没有任何款式的按钮组件进去了。

不过通常的组件库应该是以 app.use(DemoUI) 的模式来装置的,咱们再来改装一下,导出一个具备 install 办法的对象。

src/index.ts

import DButton from './components/button.vue'

import type {App} from 'vue'

const components = [DButton]

export function install(app: App) {
  components.forEach(component => {app.component(component.name, component)
  })
}

export default {install}

export {DButton}

src/main.ts

import {createApp} from 'vue'
import App from './App.vue'
import DemoUI from './index'

createApp(App).use(DemoUI).mount('#app')

src/App.vue

<template>
  <DButton>
    一个按钮
  </DButton>
</template>

<script setup lang="ts">
// import DButton from './components/button.vue'
</script>

打包组件

下一步咱们就来实现咱们的组件打包。

借助 vitebuild.lib 配置疾速实现库打包,留神打包的时候要排除 Vue 自身。

vite.config.ts

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.ts',
      formats: ['cjs', 'es']
    },
    rollupOptions: {external: ['vue']
    }
  },
  plugins: [vue()]
})

而后咱们执行打包命令:

pnpm run build

最初咱们为 package.json 增加上 mainmodule 字段,那么一个可公布的组件库雏形就进去了(留神把 private 去掉或者改为 false)。

package.json

{
  "name": "demo-ui",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "main": "dist/demo-ui.cjs.js",
  "module": "dist/demo-ui.es.js",
  "dependencies": {"vue": "^3.2.25"},
  "devDependencies": {
    "@vitejs/plugin-vue": "^2.3.3",
    "typescript": "^4.5.4",
    "vite": "^2.9.9"
  }
}

这个时候如果你执行 pnpm publish 那它就真的能够公布进来了(版本号要更新),不过它还有点薄弱,还是先不要焦急。

增加款式

通过下面的步骤,咱们失去一个最简略的组件库,不过是光溜溜的,咱们还须要为这个组件增加款式。

这里咱们选用 sass 作为 css 的预处理语言,当然你也能够抉择你喜爱的计划,不过雨声认为 sasscss 的过渡足够平滑,而且性能也足够弱小,所以比拟喜爱用。

pnpm i -D sass

而后咱们在 src 下创立一个 style 目录,用来专门寄存款式文件,并增加 index.scssbutton.scss

接着咱们给 button.scss 写上一些按钮的款式,并在 index.scss 中导出。

src/style/button.scss

.d-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  padding: 0 14px;
  color: #fff;
  background-color: #339af0;
  border: 1px solid #339af0;
  border-radius: 4px;
  outline: 0;
}

src/style/index.scss

@use './button.scss';

随后咱们在 src/index.ts 中将款式也引入进来。

src/index.ts

import './style/index.scss'

import DButton from './components/button.vue'

// 省略上面 

这时咱们就失去一个具备自定义款式的按钮组件了:

再执行 pnpm run build 就能够看到打包后的文件有了 style.css 的款式文件。

类型申明

作为一个 Vue3 + TypeScript 的我的项目,天然不能少了主动生成类型申明文件。

这里咱们借助 vite-plugin-dts 插件来实现打包时主动生成类型申明文件。

pnpm i -D vite-plugin-dts

而后在 Vite 配置文件中增加插件:

vite.config.ts

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from 'vite-plugin-dts'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.ts',
      formats: ['cjs', 'es']
    },
    rollupOptions: {external: ['vue']
    }
  },
  plugins: [vue(),
    dts({insertTypesEntry: true, copyDtsFiles: false})
  ]
})

咱们再一次执行 pnpm run build 进行打包,能够看到打包后的文件也蕴含类型申明文件了:

最初在 package.json 中增加 types 字段:

package.json

{
  // ...
  "main": "dist/demo-ui.cjs.js",
  "module": "dist/demo-ui.es.js",
  "types": "dist/index.d.ts",
  // ...
}

Lint 工具

就和惯例我的项目一样,组件库的我的项目也须要像是 ESLint 和 Stylelint 这样的来标准对立代码格调的工具的。

在我的项目的搭建上,雨声感觉到了这一步才是最乏味的,就如同拼乐高一样,有了主体之后,就能够开始往上拼上各种各样的模块,而后看着我的项目变得丰盛起来,十分有意思。

咱们先来装置 Eslint 全家桶,蕴含 Vue 和 TypeScript 相干的:

pnpm i -D eslint eslint-plugin-import eslint-plugin-n eslint-plugin-node eslint-plugin-promise
pnpm i -D eslint-plugin-vue @vue/eslint-config-typescript vue-eslint-parser

这里咱们根底 ESLint 配置采纳 eslint-config-standard 作为根底,你能够抉择你喜爱的:

pnpm i -D eslint-config-standard

接着在根目录创立 .eslintrc.js.eslintignore 两个文件,配置大部分来自继承,在这根底上将一小部分不太适宜组件库状况的做一些调整。

.eslintrc.js

module.exports = {
  root: true,
  env: {node: true},
  parser: 'vue-eslint-parser',
  extends: [
    'plugin:vue/vue3-essential',
    'plugin:vue/vue3-strongly-recommended',
    'plugin:vue/vue3-recommended',
    'standard',
    '@vue/typescript/recommended'
  ],
  parserOptions: {ecmaVersion: 'latest'},
  rules: {
    'no-console':
      process.env.NODE_ENV === 'production'
        ? [
            'error',
            {allow: ['warn', 'error']
            }
          ]
        : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'space-before-function-paren': [
      'error',
      {
        anonymous: 'always',
        named: 'never',
        asyncArrow: 'always'
      }
    ],
    'vue/match-component-file-name': 'off',
    'vue/html-self-closing': 'off'
  }
}

.eslintignore

dist/
node_modules/

.*rc.js
*.config.js
*.css
*.pcss
*.scss
*.json

同时记得在编辑器上装置 ESLint 相干的插件,而后回过头看看先前的代码有没有报错,有的话就调整一下。

下一个是 Stylelint 全家桶~

当然了,如果你喜爱 Free Style 的话,齐全能够不装 Stylelint 系列。

pnpm i -D postcss postcss-html postcss-preset-env
pnpm i -D stylelint stylelint-config-html stylelint-config-recess-order stylelint-config-recommended-vue
pnpm i -D stylelint-config-standard stylelint-config-standard-scss stylelint-order

在根目录创立 .stylelintrc.js.stylelintignore 两个文件,Stylelint 为了适应 sass 的书写习惯,这里做了比拟多的定制化,有趣味深刻理解的能够自行拓展浏览。

.stylelintrc.js

module.exports = {
  defaultSeverity: 'error',
  extends: [
    'stylelint-config-standard',
    'stylelint-config-standard-scss',
    'stylelint-config-html',
    'stylelint-config-recommended-vue',
    'stylelint-config-recess-order'
  ],
  plugins: ['stylelint-order'],
  rules: {
    'no-empty-source': process.env.NODE_ENV === 'production' ? true : null,
    'block-no-empty': process.env.NODE_ENV === 'production' ? true : null,
    'string-quotes': 'single',
    'at-rule-no-unknown': null,
    'at-rule-no-vendor-prefix': true,
    'declaration-property-value-disallowed-list': {'/^transition/': ['/all/'],
      '/^background/': ['http:', 'https:'],
      '/^border/': ['none'],
      '/.+/': ['initial']
    },
    'media-feature-name-no-vendor-prefix': true,
    'property-no-vendor-prefix': true,
    'selector-no-vendor-prefix': true,
    'value-no-vendor-prefix': true,
    'at-rule-empty-line-before': [
      'always',
      {except: ['first-nested'],
        ignore: [
          'after-comment',
          'blockless-after-same-name-blockless',
          'blockless-after-blockless'
        ],
        ignoreAtRules: ['else']
      }
    ],
    'no-descending-specificity': null,
    'custom-property-empty-line-before': null,
    'selector-class-pattern': ['^([#a-z][$#{}a-z0-9]*)((-{1,2}|_{2})[$#{}a-z0-9]+)*$',
      {message: 'Expected class selector to be kebab-case'}
    ],
    'keyframes-name-pattern': ['^([#a-z][$#{}a-z0-9]*)((-{1,2}|_{2})[$#{}a-z0-9]+)*$',
      {message: 'Expected keyframe name to be kebab-case'}
    ],
    'color-function-notation': null,
    'scss/at-import-partial-extension': 'always',
    'function-no-unknown': null,
    'alpha-value-notation': 'percentage',
    'scss/dollar-variable-empty-line-before': null,
    'scss/operator-no-newline-after': null
  },
  ignoreFiles: [/* see .stylelintignore */]
}

.stylelintignore

dist/
node_modules/

*.js
*.ts
*.tsx
*.svg
*.gif
*.md

一样别忘了在编辑器上装置 Stylelint 相干的插件,以取得编辑器的谬误提醒,之前的款式如果有报错那也跟着调整一下。

至此,咱们就把最要害的两个 Lint 工具初始化了。

提交

提交 Message 的规范化和提交时的主动格式化代码,咱们采纳 Commitlint、lint-staged 和 husky 配合实现。

尽管 Commitlint 也属于 Lint 工具,不过因为波及到 Git 提交,就专门放到这个章节一起讲。

老样子,咱们先来装置相干的依赖:

pnpm i -D @commitlint/cli @commitlint/config-conventional
pnpm i -D husky is-ci lint-staged && pnpm dlx husky install

# 在 windows 零碎下只须要把 && 改为 ; 即可拼接多条命令
pnpm i -D husky is-ci lint-staged; pnpm dlx husky install

其中 pnpm dlx husky install 会帮忙咱们主动实现 husky 的初始化。

之后咱们在 package.jsonscripts 下找到 prepare 这个命令,并做一些小调整:

package.json

{
  "scripts": {"prepare": "is-ci || husky install"}
}

装置实现后,咱们开始配置 Commitlint,在根目录创立 .commitlintrc.js,看到咱们下面装置 Commitlint 的时候还多装置了一个配置,咱们就继承这个配置再做一些拓展:

.commitlintrc.js

module.exports = {extends: ['@commitlint/config-conventional'],
  rules: {'body-leading-blank': [2, 'always'],
    'footer-leading-blank': [1, 'always'],
    'header-max-length': [2, 'always', 108],
    'subject-empty': [2, 'never'],
    'type-empty': [2, 'never'],
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'perf',
        'style',
        'docs',
        'test',
        'refactor',
        'build',
        'ci',
        'chore',
        'revert',
        'wip',
        'workflow',
        'types',
        'release'
      ]
    ]
  }
}

这里能够略微锁一下 type-enum 外面的内容,能够看到有很多的提交类型,这些类型来自于 Augular 提交标准,并在其根底上做了一些拓展,是目前开源社区里比拟风行的计划,感兴趣的小伙伴能够自行拓展浏览。

接着咱们开始配置 husky,它能够让咱们不便地创立一些 Git 钩子脚本,使的咱们能够在提交过程的各个阶段执行一些命令,其中就包含调用各种 Lint 工具来查看代码和提交 Message。

先前咱们执行了 pnpm dlx husky install 这个命令后,能够看到根目录下多了一个 .husky 的文件夹,这外面就是搁置相干配置文件的中央。

咱们应用 husky 提供的命令创立 commoncommit-msgpre-commit 三个脚本文件,或者间接在 .husky 目录下手动创立也能够:

pnpm dlx husky add .husky/common
pnpm dlx husky add .husky/commit-msg
pnpm dlx husky add .husky/pre-commit

接着别离调整外面的内容:

.husky/common

command_exists () {command -v "$1" >/dev/null 2>&1}

# Workaround for Windows 10, Git Bash and Yarn
if command_exists winpty && test -t 1; then
  exec < /dev/tty
fi

.husky/commit-msg

#!/bin/sh
. "$(dirname"$0")/_/husky.sh"
. "$(dirname"$0")/common"

npx --no-install commitlint --edit "$1"

.husky/pre-commit

#!/bin/sh
. "$(dirname"$0")/_/husky.sh"
. "$(dirname"$0")/common"

[-n "$CI"] && exit 0

pnpm run precommit

husky 这样就算是配置好了,除去 common 是一个补丁脚本外,另外两个脚本会别离在咱们进行提交 Message 解决时,以及行将开始提交时执行,咱们也别离放入了调用 Commitlint 查看提交 Message 和一条 pnpm run precommit 的命令,不过这条命令在咱们的我的项目里临时还没有。

下一步,咱们来配置 lint-staged 的同时把这条命令补全。

lint-staged 的配置文件为 .lintstagedrc,雨声习惯会把它一起放入 .husky 便于管理,你也能够放到本人喜爱的地位,比方根目录下。

.husky/.lintstagedrc

{"*.{js,jsx,ts,tsx}": ["eslint --fix"],
  "*.{css,scss,html}": ["stylelint --fix"],
  "*.vue": [
    "eslint --fix",
    "stylelint --fix"
  ]
}

而后咱们在 package.json 中增加 precommit 命令:

{
  "scripts": {"precommit": "lint-staged -c ./.husky/.lintstagedrc -q"}
}

这样一来,在每次 pre-commit 脚本执行时,就会调用 lint-staged 对提交的文件执行 Lint 相干的命令,并且咱们想要手动查看要提交的文件时也能够间接执行这条命令。

事不宜迟,咱们马上来进行咱们的第一次提交~

git add .

为了验证一下 Commitlint 是否能失常工作,咱们先来一个不符合规范的提交 Message:

git commit -m "init"

能够看到控制台输入了一些错误信息,并且提交失败了:

确认了 Commitlint 能够失常工作后咱们就来一次符合规范的提交:

git commit -m "chore: init"

提交胜利,功败垂成。

当初,咱们的我的项目构造长这个样子:

告一段落

本篇介绍了从零开始搭建一个 Vue3 组件库的第一步,实现了一个最简略的组件库我的项目,并且可能打包。

同时还介绍了三个 Lint 工具的拆卸,以及如何实现提交规范化及主动格式化代码。

只管咱们没有真的公布到 npm 上,不过咱们如果想试试的话,能够应用 link 协定在本地的其余我的项目上试一试。

同样用 pnpm create vite 创立一个我的项目,在 package.jsondependencies 上手动增加 link 协定的依赖:

{
  "dependencies": {
    "demo-ui": "link: 到 demo-ui 的根目录的物理门路"
    // 如 "link:D:/ui-library/demo-ui"
  }
}

执行 pnpm i 后就能够像应用其余库一样,来应用本地的 demo-ui 了。

未完待续

🚧 组件库我的项目的搭建还远不止这些,随着深刻咱们还会遇到诸如:

  • 组件的开发工作流
  • 组件库的公布流程设计
  • 组件的单元测试
  • 组件的款式的治理
  • css 变量设计与主题
  • 实现间接的按需加载
  • 组件库文档的构建
  • 组件库 Playground 的实现
  • 应用 monorepo 治理组件库的多个模块
  • 等等 …

👀 下一篇 ,咱们持续围绕着从零开始搭建一个 Vue3 组件库会遇到的问题,持续讲讲如何欠缺组件库我的项目的配套设施。

如果有不同的见解或倡议,欢送在评论区感性探讨,或者如果下面列举的点中有哪些更想晓得的也能够留言哦。

最初,举荐一下雨声的开源组件库 Vexip-UI – GitHub(小伙伴们赏一个🌟)

现正招募小伙伴来尝试应用或者保护与倒退这个我的项目。

如果你对写组件感兴趣的话,十分欢送来试一试,还有许多组件的性能等着你来开发与欠缺,你能够随时回复感兴趣的 issue 参加探讨或发动一个新的 issue。

与大家共勉,独特学习提高🎉~

退出移动版