VUE3+TS组件库开发与发布

657 阅读7分钟

1. 创建项目

使用 vue-cli 创建一个 vue3 项目,假设项目名为 vue-ts-components

> vue create vue-ts-components

image-20211014170125300.png

选择自定义Manually select features,回车进入下一步

image-20211014170422210.png

选中Choose Vue version、Babel 、TypeScript、 CSS Pre-processors 这4项,回车进入下一步

  • Choose a version of Vue.js that you want to start the project with 选择 3.x (Preview)
  • Use class-style component syntax? 输入n
  • Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? 输入y
  • Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default) 选择 Sass/SCSS (with dart-sass)
  • Where do you prefer placing config for Babel, ESLint, etc.? 选择 In dedicated config files
  • Save this as a preset for future projects? 输入y,回车后输入模板名保存模板 image.png

最后回车,等待项目创建完成。创建完成,目录结构如图:

image.png

2. 规划目录

├─ build         // 编辑打包脚本目录,用于存放脚本文件
│  ├─ rollup.config.js
├─ docs          // 文档目录,用于生成 vuepress 文档页面
│  ├─ .vuepress
│  ├─ guide
│  ├─ README.md      
├─ examples      // 原 src 目录,改成 examples 用于示例展示
│  ├─ App.vue
│  ├─ main.ts
├─ packages      // 新增 packages 目录,用于编写存放组件,如button
│  ├─ button
│  ├─ index.ts
├─ typings      // 新增 typings 目录, 用于存放 .d.ts 文件,把 shims-vue.d.ts 移动到这里
│  ├─ shims-vue.d.ts
├─ .npmignore    // 新增 .npmignore 配置文件
├─ vue.config.js // 新增 vue.config.js 配置文件

src 目录改为 examples ,并将里面的 assetscomponents 目录删除,移除 App.vue 里的组件引用。

调整后的目录结构如图

image.png

3. 项目配置

3.1 vue.config.js

新增 vue.config.js 配置文件,适配重新规划后的项目目录

const path = require('path')

module.exports = {
  // 修改 pages 入口
  pages: {
    index: {
      entry: "examples/main.ts", //入口
      template: "public/index.html", //模板
      filename: "index.html" //输出文件
    }
  },
  // 扩展 webpack 配置
  chainWebpack: (config) => {
    // 新增一个 ~ 指向 packages 目录, 方便示例代码中使用
    config.resolve.alias
      .set('~', path.resolve('packages'))
  }
}

3.2 .npmignore

新增 .npmignore 配置文件,组件发布到 npm 中,只有编译后的发布目录(例如lib)、package.json、README.md才是需要被发布的,所以我们需要设置忽略目录和文件

# 忽略目录
.idea
.vscode
build/
docs/
examples/
packages/
public/
node_modules/
typings/

# 忽略指定文件
babel.config.js
tsconfig.json
tslint.json
vue.config.js
.gitignore
.browserslistrc
*.map

3.3 tsconfig.json

修改 tsconfig.json 中 paths 的路径

    "paths": {
      "@/*": [
        "src/*"
      ]
    }

改为

    "paths": {
      "~/*": [
        "packages/*"
      ]
    }

修改 include 的路径

  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ]

改为

  "include": [
    "examples/**/*.ts",
    "examples/**/*.tsx",
    "examples/**/*.vue",
    "packages/**/*.ts",
    "packages/**/*.tsx",
    "packages/**/*.vue",
    "typings/**/*.ts",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ]

3.4 package.json

修改 package.json 中发布到 npm 的字段

  • name:包名,该名字是唯一的。可在 npm 官网搜索名字,如果存在则需换个名字。
  • version:版本号,每次发布至 npm 需要修改版本号,不能和历史版本号相同。
  • description:描述。
  • main:入口文件,该字段需指向我们最终编译后的包文件。
  • typings:types文件,TS组件需要。
  • keyword:关键字,以空格分离希望用户最终搜索的词。
  • author:作者信息
  • private:是否私有,需要修改为 false 才能发布到 npm
  • license: 开源协议

参考设置:

  "name": "vue-ts-components",
  "version": "0.1.2",
  "private": false,
  "description": "测试vue-ts搭建组件库",
  "main": "lib/index.min.js",
  "module": "lib/index.esm.js",
  "typings": "lib/index.d.ts",
  "keyword": "vue3 vant",
  "license": "MIT",

在 package.json 的 scripts 新增编译和发布的命令

"scripts": {
    "build": "yarn build:clean && yarn build:lib && yarn build:esm-bundle && rimraf lib/demo.html",
    "build:clean": "rimraf lib",
    "build:lib": "vue-cli-service build --target lib --name index --dest lib packages/index.ts",
    "build:esm-bundle": "rollup --config ./build/rollup.config.js"
}

其中 build:lib 是利用 vue-cli 进行 umd 方式打包,build:esm-bundle 是利用 rollup 进行 es 方式打包,具体参数解析如下:

  • --target: 构建目标,默认为应用模式。改为 lib 启用库模式。
  • --name: 输出文件名
  • --dest : 输出目录,默认 dist。改成 lib
  • [entry]: 入口文件路径,默认为 src/App.vue。这里我们指定编译 packages/ 组件库目录。

以下是完整package.json参考示例

{
  "name": "vue-ts-components",
  "version": "0.1.2",
  "private": false,
  "description": "测试vue-ts搭建组件库",
  "main": "lib/index.min.js",
  "module": "lib/index.esm.js",
  "typings": "lib/index.d.ts",
  "keyword": "vue3 vant",
  "license": "MIT",
  "scripts": {
    "serve": "vue-cli-service serve",
    "docs:dev": "vuepress dev docs",
    "docs:build": "vuepress build docs",
    "build": "yarn build:clean && yarn build:lib && yarn build:esm-bundle && rimraf lib/demo.html",
    "build:clean": "rimraf lib",
    "build:lib": "vue-cli-service build --target lib --name index --dest lib packages/index.ts",
    "build:esm-bundle": "rollup --config ./build/rollup.config.js"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "rollup-plugin-vue": "^6.0.0",
    "vue": "^3.2.13"
  },
  "devDependencies": {
    "@rollup/plugin-node-resolve": "^13.3.0",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-typescript": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "rollup-plugin-dts": "^4.2.2",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.31.2",
    "sass": "^1.32.7",
    "sass-loader": "^12.0.0",
    "tslib": "^2.4.0",
    "typescript": "^4.7.2"
  }
}

3.5 rollup.config.js

新增 rollup.config.js,rollup 打包脚本

import { nodeResolve } from '@rollup/plugin-node-resolve'
import path from 'path'
import { terser } from 'rollup-plugin-terser'
import typescript from 'rollup-plugin-typescript2'
import pkg from '../package.json'
import  dts from 'rollup-plugin-dts'
import scss from 'rollup-plugin-scss'

const deps = Object.keys(pkg.dependencies)
const vue = require('rollup-plugin-vue')

export default [
  {
    input: path.resolve(__dirname, '../packages/index.ts'),
    output: [
      {
        format: 'es',
        file: pkg.module,
      }
    ],
    plugins: [
      terser(),
      nodeResolve(),
      scss(),
      // commonjs(),
      vue({
        target: 'browser',
        css: false,
        exposeFilename: false,
      }),
      typescript({
        tsconfigOverride: {
          compilerOptions: {
            declaration: true,
          },
          'include': [
            'packages/**/*',
            'typings/shims-vue.d.ts',
          ],
          'exclude': [
            'node_modules',
            'packages/**/__tests__/*',
          ],
        },
        abortOnError: false,
      }),
    ],
    external(id) {
      return /^vue/.test(id)
        || deps.some(k => new RegExp('^' + k).test(id))
    },
  },
  {
    input: path.resolve(__dirname, '../packages/index.ts'),
    output: [
      {
        format: 'es',
        file: 'index.d.ts',
      }
    ],
    plugins: [
      vue({
        target: 'browser',
        css: false,
        exposeFilename: false,
      }),
      dts({ respectExternal: true }),
      scss(),
    ],
  }
]

4. 开发组件

下面以Button组件作为开发示例,在 packages 目录下新建 index.ts 文件和 button 文件夹,在 button 下新建 index.ts 和 src/button.vue,结构如图

image.png

button.vue

<template>
  <button class="s-btn">
    <span v-if="$slots.default"><slot></slot></span>
  </button>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  name: "s-button"
})
</script>

<style scoped>
</style>

button/index.ts,单独组件的入口文件,在其他项目可以使用 import { SButton } from 'vue-ts-components' 方式进行单个组件引用

import { App } from 'vue'
import Button from './src/button.vue'

// 定义 install 方法, App 作为参数
Button.install = (app: App): void => {
    app.component(Button.name, Button)
}

export default Button>

index.ts 作为组件库的入口文件,可以在其他项目的 main.ts 引入整个组件库,内容如下

import { App } from 'vue'
import SButton from './button'

// 所有组件列表
const components = [ SButton ]

// 定义 install 方法, App 作为参数
const install = (app: App): void => {
    // 遍历注册所有组件
    components.map((component) => app.component(component.name, component))
}

export {
    SButton
}

export default {
    install
}

这样,我们就完成一个简单的 button 组件,后续需要扩展其他组件,按照 button 的结构进行开发,并且在 index.ts 文件中 components 组件列表添加即可。

5. 编写示例

组件开发完成后,我们本地先测试一下,没有问题再发布到 npm 仓库。在示例入口 main.ts 引用我们的组件库

import { createApp } from 'vue'
import App from './App.vue'
import SocComponents from '~/index'  // 这里 ~ 就是在 tsconfig.json 以及 vue.config.js 配置的 packages 路径

const app = createApp(App)
app.use(SocComponents)
app.mount('#app')

App.vue 删除项目初始化的 HelloWorld 组件

<template>
  <div>组件示例</div>
  <div>{{ count }}</div>
  <s-button @click="handleClick">按钮</s-button>
</template>

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

export default defineComponent({
  name: 'App',
  data() {
    return {
      count: 0
    }
  },
  methods: {
    handleClick() {
      this.count ++
    }
  }
});
</script>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

启动项目,测试一下

> npm run serve

image.png

6. 发布组件到npm官网

组件开发并测试通过后,就可以发布到 npm 仓库提供给其他项目使用了,首先执行编译库命令,生成 lib 目录

> npm run build

image.png

6.1 注册npm账号

前往官网注册 npm 账号,如果已注册过,则跳过此步骤

6.2 登录npm账号

在项目中 terminal 命令窗口登录 npm 账号

> npm login
Username:
Password:
Email:(this IS public)

输入在 npm 注册的账号、密码、邮箱

6.3 发布

确保 registry 是 registry.npmjs.org

> npm config get registry

如果不是则先修改 registry

> npm config set registry=https://registry.npmjs.org

然后执行命令

> npm publish

如果需要删除已发布的组件(不推荐删除已发布的组件),则执行以下命令(加 --force 强制删除)

> npm unpublish --force

删除指定版本的包,比如包名为 vue-ts-components 版本 0.1.0

> npm unpublish vue-ts-components@0.1.0

如果24小时内有删除过同名的组件包,那么将会发布失败,提示

npm ERR! code E403
npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/vue-ts-components - vue-ts-components cannot be republished until 24 hours have passed.
npm ERR! 403 In most cases, you or one of your dependencies are requesting
npm ERR! 403 a package version that is forbidden by your security policy.

npm ERR! A complete log of this run can be found in:
npm ERR!     D:\tools\nodejs\node_cache_logs\2021-10-18T09_58_58_933Z-debug.log

只能换一个名称发布或者等24小时之后发布,所以不要随便删除已发布的组件(万一有项目已经引用)

7. 测试

另外创建一个测试项目 vue-demo

7.1 查看 registry

> npm config get registry

7.2 测试 install

这里是为了测试 npm 私服库是否能正常 install 代理库上的依赖,选择一个 vue 项目,把 node_modules 目录删掉,然后再重新 install

> yarn // 或者使用 yarn install; 或者 npm i

如果能正常把 vue、element-ui、core-js这些关键包 install 下来,就说明 npm 私服库可用

然后测试一下我们发布的组件

> yarn add vue-ts-components // 或者使用 npm i -S vue-ts-components

看看 package.json 和 node_modules 是否有vue-ts-components的数据和包,然后在 main.ts 和 App.vue 引用组件(类似示例中的代码),启动项目,测试通过,这样我们的组件库给其他项目使用也没问题了。

8. 踩坑记录

本文配置按照手把手教学:VUE3+TS组件库开发与发布该文进行配置,但后续在别的项目引入组件会遇到 发布之后,在项目中引入开发的组件库时,报找不到模块“xxxxxx”或其相应的类型声明的错误,
具体的解决方案是:(1)在rollup.config.js文件中引入rollup-plugin-dts插件;(2)在导出模块中添加index.d.ts文件;(3)在packge.json文件中添加

"typings": "lib/index.d.ts",

image.png

image.png