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

前言

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

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

最后实现的效果是:

  • main.js 引入

    当然按需引入也是支持的.

  • vue文件使用:

项目结构

less 复制代码
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

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

typescript 复制代码
新建文件夹
├─ 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:[] 用于指定项目中那些包是依赖包。比如我写了:

    css 复制代码
    import a from "a"

    如果a这个包是外部依赖包而不是自己写的,则需要配置在此, 当然你可以用专门的插件来处理这个问题,这里我用的依赖包暂时不是很多,可以手 动排除

  • input:String | [] 指定打包入口,也就是组件库的入口文件

  • ouput:[] 指定打包的输出口,可以根据不同的打包格式,输出到不同的目录中。下面是我的配置项:

    java 复制代码
    [
    
        {
            format: "es", //指定打包格式
            entryFileNames: "[name].mjs", //指定输出文件名(不用改动)
            preserveModules: true, //保留原目录结构(不动)
            exports: "named", //(指定导出方式,老实说你删了这个配置项都行)
            dir: "es",   //配置打包根目录
    
        },
        {
            format: "cjs",
            entryFileNames: "[name].js",
            preserveModules: true,
            exports: "named",
            dir: "lib",
        },
    ],

打包

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

也就是执行一下代码:

php 复制代码
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"],
        }


    },
})

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

当我们展开子目录会发现,ts代码全都没了,这好么?这不好!

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

vite-plugin-dts

npm i vite-plugin-dts -D
import dts from "vite-plugin-dts";

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

修改plugin配置项:

css 复制代码
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中的配置项:

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

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

发现有了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文件,这个文件储存所有的样式文件。 配置如下:

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样式的浏览器兼容性。

最后会在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如下:

css 复制代码
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保持不变

这之后你会得到:

rollup-plugin-terser

npm i rollup-plugin-terser -D
import {terser} from "rollup-plugin-terser";

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

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

请看这样的效果:

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

是的,我们需要保留的便是这些特殊的注释

只需要在plugin中加入:

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

    },
}),

完整的配置文件

php 复制代码
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 之后我们会用到
perl 复制代码
{
    "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", 
    "author": {  //作者信息
      "name": "laster",
      "email": "lasterxin@163.com"
    },
    "description": "适用于vue3的组件库",  //项目描述
    "keywords": [   //项目关键词(用于npm官网查找)
      "UI",
      "Vue3",
      "typescript"
    ],
    "license": "MIT",  //项目遵循协议
  "scripts": {},
  "dependencies": {},
  "devDependencies": {}
}

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

global.d.ts

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

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

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

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

typescript 复制代码
// 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

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

相关推荐
学术头条2 小时前
清华团队开源SCAIL-2:角色动画告别骨骼依赖,端到端还原视频中动作细节
人工智能·科技·机器学习·ai·开源·音视频·agi
comcoo3 小时前
电脑自动干活不用值守!OpenClaw 本地部署完整实操流程
windows·开源·github·open claw部署·open claw部署包
代码不加糖3 小时前
js中不会冒泡的事件有哪些?
前端·javascript·vue.js
懂懂tty3 小时前
Vue2与Vue3之间API差异
前端·javascript·vue.js
老毛肚4 小时前
软件测试期末考试
vue.js
杨若瑜4 小时前
本地开发环境慢?localhost的锅!
vue.js
namexingyun6 小时前
开源前端生态如何成为 AI UI 生成的“燃料“:shadcn/ui、Tailwind CSS、Storybook 技术价值全解剖
java·前端·人工智能·python·ui·开源·ai编程
字节跳动的猫6 小时前
2026年国内开源商城系统推荐:LikeShop、CRMEB、ShopXO、Mall4j、TigShop深度对比
开源
Hommy886 小时前
【开源剪映小助手】添加特效接口(Add Effects)
开源·github·剪映小助手·视频剪辑自动化