likes
comments
collection
share

实现一个vue3组件库 - 打包和发布

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

实现一个vue3组件库 - 打包和发布

前言

咱辛辛苦苦用typescript写个组件库为了啥?不就是为了方便开发嘛。

  • 通过npm实现一键安装,下载即玩✨(bushi
  • 允许按需加载组件,打包大小 down down down 💟!!!
  • 通过ts轻轻松松获取代码提示,写代码直接tab tab tab 😋...

最后实现的效果是:

  • main.js 引入

    实现一个vue3组件库 - 打包和发布 当然按需引入也是支持的.

  • vue文件使用:

    实现一个vue3组件库 - 打包和发布

    实现一个vue3组件库 - 打包和发布

项目结构

sss-ui-plus-master
├─ examples    //测试用的文件
│    ├─ App.vue
│    └─ main.ts
├─ packages  //组件
│    ├─ SButton (按钮组件)
│    │    ├─ index.ts //导出单个组件,比如这里导出SButton
│    │    └─ src
│    │           ├─ button.less //样式文件
│    │           ├─ button.ts  //声明组件要用的props、emits
│    │           └─ button.vue 
│    ├─ SIcon  (图标组件)
│    ├─ ... //其他组件
│    └─ index.ts //用于导出所有的组件
└─ src //资源文件,前面应该说过了,不重点介绍
       ├─ hooks
       ├─ styles
       │    ├─ animate.css
       │    ├─ global.less
       │    ├─ icons
       │    └─ variable.less
       ├─ types
       │    └─ index.ts
       └─ utils
              ├─ decorator
              └─ managers

我们最后要打包出来的目录结构

新建文件夹
├─ dist
│    ├─ @types    //存放d.ts文件,作为编译器提示的入口文件
│    └─ index.css    //所有的样式文件最终会编译在一起
├─ es      //ES6打包格式
│    ├─ index.d.ts
│    ├─ index.mjs
│    ├─ node_modules
│    ├─ packages
│    │    ├─ SButton
│    │    ├─ SIcon
│    │    ├─ ...
│    │    ├─ index.d.ts
│    │    └─ index.mjs
│    └─ src
└─ lib      //commonJS打包格式
|      ├─ index.d.ts
|      ├─ index.js
|      ├─ node_modules
|      ├─ packages
|      │    ├─ SButton
|      │    ├─ SIcon
|      │    ├─ ...
|      │    ├─ index.d.ts
|      │    └─ index.js
|      └─ src
└─ global.d.ts 全局提示   

初识vite.config.ts

再用vite开发时,执行vite build就是以此文件作为配置文件。这个文件实际上是暴露一个json格式的对象。现在介绍一些这个对象的属性。

  • plugin:[] 这个属性用于配置在打包时用的的插件
  • build:{}
    • rollupOptions:{} 配置rollup打包时的配置信息(事实上,vite打包用的是rollup, 而不是webpack。简单来说,rollup更适合工具库的打包,webpack更适合web应用的打包)
    • lib: {} 老实说,我没明白他有啥用😫

这次重点不再lib这个配置项中,在前两者,关于rollupOptions:

  • external:[] 用于指定项目中那些包是依赖包。比如我写了:
    import a from "a"
    
    如果a这个包是外部依赖包而不是自己写的,则需要配置在此, 当然你可以用专门的插件来处理这个问题,这里我用的依赖包暂时不是很多,可以手 动排除
  • input:String | [] 指定打包入口,也就是组件库的入口文件
  • ouput:[] 指定打包的输出口,可以根据不同的打包格式,输出到不同的目录中。下面是我的配置项:
    [
    
        {
            format: "es", //指定打包格式
            entryFileNames: "[name].mjs", //指定输出文件名(不用改动)
            preserveModules: true, //保留原目录结构(不动)
            exports: "named", //(指定导出方式,老实说你删了这个配置项都行)
            dir: "es",   //配置打包根目录
    
        },
        {
            format: "cjs",
            entryFileNames: "[name].js",
            preserveModules: true,
            exports: "named",
            dir: "lib",
        },
    ],
    

打包

再不使用插件的情况下打包

也就是执行一下代码:

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import DefineOptions from 'unplugin-vue-define-options/vite'
import {resolve} from "path";


export default defineConfig({
    plugins: [
        vue(),
        DefineOptions(),
    ],

    build: {
        rollupOptions: {
            external: ["vue", '@vueuse/core', '@floating-ui/vue'],
            input: './index.ts',

            output: [

                {
                    format: "es",
                    entryFileNames: "[name].mjs",
                    //让打包目录和我们目录对应
                    preserveModules: true,
                    exports: "named",
                    //配置打包根目录
                    dir: "es",
                },
                {
                    //打包格式
                    format: "cjs",
                    //打包后文件名
                    entryFileNames: "[name].js",
                    //让打包目录和我们目录对应
                    preserveModules: true,
                    exports: "named",
                    //配置打包根目录
                    dir: "lib",
                },
            ],

        },
        lib: {
            entry: "./index.ts",
            name: 'sss-ui-plus',
            fileName: 'sss-ui-plus',
            formats: ["es", "umd", "cjs"],
        }


    },
})

最终会形成以下输出目录:

实现一个vue3组件库 - 打包和发布 当我们展开子目录会发现,ts代码全都没了,这好么?这不好!

所以我们需要插件来就我们。

vite-plugin-dts

npm i vite-plugin-dts -D

import dts from "vite-plugin-dts";

这个插件会在打包之后为我们生成.d.ts的类型声明文件。有了类型声明文件,编译器才能获取到类型提示。

修改plugin配置项:

plugins: [
    vue(),
    DefineOptions(),
    dts({
        outDir: ['es', "lib", 'dist/@types'],
        tsConfigFilePath: resolve(__dirname, "tsconfig.json"),
    }),
],
  • outDir是ts文件的输出目录,es,lib两个可有可无,就是我们打包生成的两个目录es,lib,也就是在原目录下生成类型声明文件.

    dist/@types则表示单独生成类型声明文件到此处,这也会是我们项目的声明文件入口。

  • tsConfigFilePath 用于执行那些地方的文件需要为其产生d.ts文件,这里我们直接读取tsconfig.json中的配置项:

    修改其中的include配置项:
    需要注意的是,需要根据自身项目的目录结构来修改
    "include": [
          "src/**/*.ts",
          "src/**/*.d.ts",
          "src/**/*.tsx",
          "src/**/*.vue",
    
          "packages/**/*.ts",
          "packages/**/*.d.ts",
          "packages/**/*.tsx",
          "packages/**/*.vue",
    
          "index.ts"
    ],
    
    

修改完成后vite build输出如下:

实现一个vue3组件库 - 打包和发布 发现有了d.ts文件

rollup-plugin-postcss

npm i rollup-plugin-postcss autoprefixer -D

import postcss from 'rollup-plugin-postcss'

import autoprefixer from "autoprefixer"

仔细观察之前生成的项目目录,会发现之后在es下面才会有一个style.css文件,而这显然不是我们自己生成的。

现在我们需要生成一个index.css文件,这个文件储存所有的样式文件。 配置如下:

plugins: [
    vue(),
    DefineOptions(),
    dts({
        outDir: ['es', "lib", 'dist/@types'],
        tsConfigFilePath: resolve(__dirname, "tsconfig.json"),
    }),
    postcss({
         extract: 'index.css',
         plugins: [autoprefixer()],
    
    }),

],
  • extract 指定生成的文件地址
  • plugins 指定生成样式文件是使用的插件,这里我们使用的autoprefixer用于适配特殊的css属性给不同的浏览器,也就是不需要我们考虑css样式的浏览器兼容性。

实现一个vue3组件库 - 打包和发布 最后会在es lib两输出目录下生成index.css文件

rollup-plugin-copy & rollup-plugin-delete

npm i rollup-plugin-copy rollup-plugin-delete -D

import copy from "rollup-plugin-copy"

import del from "rollup-plugin-delete"

上面生成的index.css很显然应该是共用的,而不是在两个输出目录下都有一个,并且原本生成的style.css还在

这时候我们需要吧两个index.css保留一个并且移动到dist目录下,并且吧style.css删除掉

  • rollup-plugin-copy 帮助我们复制文件
  • rollup-plugin-delete帮助我们删除文件

修改plugin如下:

plugins: [
    vue(),
    DefineOptions(),
    dts({
        outDir: ['es', "lib", 'dist/@types'],
        tsConfigFilePath: resolve(__dirname, "tsconfig.json"),
    }),
    postcss({
        extract: 'index.css',
        plugins: [autoprefixer()],

    }),
    copy({
        targets: [
            {src: 'es/*.css', dest: 'dist'},
        ],
        verbose: true,
        hook: 'generateBundle'

    }),
    del({
        targets: [
            // 设置删除规则,删除原来位置的 CSS 文件
            'es/*.css',
            'lib/*.css',
            'dist/style.css',
        ],
        hook: 'closeBundle', // 在 writeBundle 钩子时执行删除操作
    }),
],
  • copy.targets:[] 配置需要复制的文件,src代表文件原位置,dest代表文件新位置。
  • copy.verbose:Boolean 代表执行copy插件是要不要打印信息(建议开启)
  • copy.hook 代表创建的执行时期, generateBundle代表在打包期间执行
  • del.targets:[] 需要删除的文件位置
  • del.hook 执行的时期, closeBundle代表在打包完成后执行

注意这两插件的hook保持不变

这之后你会得到:

实现一个vue3组件库 - 打包和发布

rollup-plugin-terser

npm i rollup-plugin-terser -D

import {terser} from "rollup-plugin-terser";

打包之后需要压缩体积么? 选他就对了!, 这个插件可以帮你自定义压缩代码的规则

但是我们现在不是需要压缩,我们要的是保留某些特定的注释。

请看这样的效果:

实现一个vue3组件库 - 打包和发布 可以看到当鼠标移动到timeout上面之后,会有通知的存活时间这个提示。这是怎么做到的?其实就是一段注释而已:

实现一个vue3组件库 - 打包和发布 是的,我们需要保留的便是这些特殊的注释

只需要在plugin中加入:

terser({
    format: {
        comments: 'all', // 保留所有注释(为了简单我全部保留了)

    },
}),

完整的配置文件

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import DefineOptions from 'unplugin-vue-define-options/vite'
import {resolve} from "path";
import dts from "vite-plugin-dts";
import postcss from 'rollup-plugin-postcss'
import autoprefixer from "autoprefixer"
import copy from "rollup-plugin-copy"
import del from "rollup-plugin-delete"
import {terser} from "rollup-plugin-terser";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        DefineOptions(),
        dts({
            outDir: ['es', "lib", 'dist/@types'],
            tsConfigFilePath: resolve(__dirname, "tsconfig.json"),
        }),
        postcss({
            extract: 'index.css',
            plugins: [autoprefixer()],

        }),
        terser({
            format: {
                comments: 'all', 
            },
        }),
        copy({
            targets: [
                {src: 'es/*.css', dest: 'dist'},
            ],
            verbose: true,
            hook: 'generateBundle'

        }),
        del({
            targets: [
                // 设置删除规则,删除原来位置的 CSS 文件
                'es/*.css',
                'lib/*.css',
                'dist/style.css',
            ],
            hook: 'closeBundle', // 在 writeBundle 钩子时执行删除操作
        }),
    ],

    build: {

        rollupOptions: {
            external: ["vue", '@vueuse/core', '@floating-ui/vue'],
            input: './index.ts',

            output: [
                {
                    format: "es",
                    entryFileNames: "[name].mjs",
                    //让打包目录和我们目录对应
                    preserveModules: true,
                    exports: "named",
                    //配置打包根目录
                    dir: "es",
                },
                {
                    //打包格式
                    format: "cjs",
                    //打包后文件名
                    entryFileNames: "[name].js",
                    //让打包目录和我们目录对应
                    preserveModules: true,
                    exports: "named",
                    //配置打包根目录
                    dir: "lib",
                },
            ],

        },
        lib: {
            entry: "./index.ts",
            name: 'sss-ui-plus',
            fileName: 'sss-ui-plus',
            formats: ["es", "umd", "cjs"],
        }


    },


})

.npmignore

在打包好我们的代码之后便是发布啦,再次之前我们需要创建一个.npmignore,代表我们不是所有的文件都需要上传的。 我们需要上传的结构有:

  • dist 存放了类型声明文件和样式文件
  • es ES6模式下的打包产出
  • lib CommonJS模式下的打包产出
  • package.json 项目的描述文件
  • README.md 项目的介绍文件
  • global.d.ts 之后我们会用到

根据自己的内容设定

package.json

主要用于指定包的信息,其中,main types字段指定入口

{
    "name": "sss-ui-plus",  //项目名字,自己来吧
    "main": "lib/index.js",  //通过commonJS形式引入的入口文件
    "module": "es/index.mjs",  //通过ES6形式引入的入口文件
    "types": "dist/@types/index.d.ts",  //类型声明文件入口
    "private": false,  //是否是私有项目(要发布当然不能是私有)
    "version": "0.0.0",  //项目版本号(注意每次发布版本号不能是一样的)
    "type": "module", 
    "sideEffects": false,    //设置此项目无副作用
    "author": {  //作者信息
      "name": "laster",
      "email": "lasterxin@163.com"
    },
    "description": "适用于vue3的组件库",  //项目描述
    "keywords": [   //项目关键词(用于npm官网查找)
      "UI",
      "Vue3",
      "typescript"
    ],
    "license": "MIT",  //项目遵循协议
  "scripts": {},
  "dependencies": {},
  "devDependencies": {}
}

关于sideEffects

简单说就是在打包时告诉编译器这个项目是没有副作用的, 可以安全tree shaking。 (建议找篇文章看看这部分内容, 这里不做解释)。

再此之后,登录你的npm账号发布就行啦!

global.d.ts

等发布之后你就会发现一个问题,明明我已经在全局引入并注册了组件,为什么在(App.vue)中使用的时候报错没有声明呢?并且只是报错没有声明但是浏览器会正常显示。

当我们使用element-plus并进入某一个组件时会看到:

实现一个vue3组件库 - 打包和发布 此时你会看到有一个global.d.ts文件,并且里面对@vue/runtime-core这个包进行了类型拓展,那么我们可不可以也这样干呢?当然可以!

在项目的根目录下创建global.d.ts文件,写入:

// GlobalComponents for Volar
declare module '@vue/runtime-core' {
    export interface GlobalComponents {
        SButton: typeof import('sss-ui-plus')['SButton'],
        SInput: typeof import('sss-ui-plus')['SInput'],
        SIcon: typeof import('sss-ui-plus')['SIcon'],
        SLink: typeof import('sss-ui-plus')['SLink'],
        SDialog: typeof import('sss-ui-plus')['SDialog'],
        SDrawer: typeof import('sss-ui-plus')['SDrawer'],
        
    }
    interface ComponentCustomProperties {
        $message: typeof import('sss-ui-plus')['message'],
        $notify: typeof import('sss-ui-plus')['notify'],
        $confirm: typeof import('sss-ui-plus')['confirm'],
    }


}

export {}

之后发布后,就能解决问题啦!

写在最后

首先,这个项目地址是lastertd/sss-ui-plus: 适用于vue3的组件库 (github.com) 在这里求一个star✨

其次,若是在打包发布的过程中遇到什么问题,欢迎在讨论区写下,大伙一起掉头发hhh

最后,感谢看到最后💟💟💟