更多

Vue3+Vite+TypeScript 组件库打包发布NPM

最近在做问卷设计器(Free Survey Form Builder)的时候,遇到了打包发布NPM的问题,看了很多教程但是说的都不是很清楚,最后做了各种尝试,才搞明白怎么正确打包并发布TypeScript类型声明。

1、打包产物

众所周知现在的JS包一般采用ES Module或者CommonJS/AMD方案,现在虽然都在向ES方案过渡,但是一般还是会提供CommonJS的包,具体ES和CommonJS的区别就不做赘述了,网上已经有大量的帖子了。此外还有UMD模式,它提供了一种能够兼容所有常见方案的方案。

所以在打包时,打包的产物就比较明确了,提供ES和CommonJS两种包,并且提供TypeScript类型声明。

2、组件代码组织

Free Survey Form Builder 中,组件被放在 packages 目录当中,Free Survey Form Builder 目前只有一个free-survey-form-builder组件,所有的组件都会通过 packages/index.ts 的 FreeSurveyFormBuilderPlugin 进行全局注册。

package.json
vite.config.ts
packages
├─index.ts
└─free-survey-form-builder
    ├─index.ts
    └─src
        ├─assets
        ├─components
        ├─elements
        │  ├─page-block
        │  ├─question-group-block
        │  ├─radio-group-question
        │  └─single-text-question
        ├─layout
        ├─scripts
        ├─index.vue
        └─types

Free Survey Form Builder的组件是采用SFC开发的,开发完毕后,也通过组件包里的index.ts进行导出,这样方便后续按需导出的需求。所以不同的index文件有不同的作用:packages/index.ts 负责导出所有包模块的组件和各种内容,提供了库的整体入口,packages/fress-survey-form-builder/index.ts 负责导出包模块中各种内容,例如使用SFC开发的组件(虽然直接导出SFC文件也可以但是个人认为这种方法不太整洁,最好是每个包模块各自组织好自己部分要导出的内容,然后再交给packages/index导出),packages/fress-survey-form-builder/src/index.vue 则是组件本体。

因此,如果要进行库的构建,则只需要构建packages/index.ts即可,因为该文件就是整个库的入口,它导出了库中对外提供的所有组件。

3、构建配置1

Vite 使用 Rollup作为底层构建工具,可以直接使用Vite提供的库构建模式进行统一构建,这样做的话Vite会采用一种Rollup预设选项进行构建,最终的产物是ES模式和UMD模式两种构建产物,并且各自只有一个文件。

而Free Survey Form Builder期望的构建输出则是ES Module和CommonJS,因此在配置中的 lib.formats 设置为 ["es", "cjs"] 就可以改变构建时采用的方案。此外,Free Survey Form Builder 的构建产物希望能够保持原始的目录结构,不希望产生一个过大的构建输出(只是个人的偏好和实际需求而已,也可以直接采用Vite的默认预设进行构建),并且指定好不同构建输出的输出路径,此时lib中的设置就无法很好的满足需求了,所以最终直接配置 rollupOptions.output 来实现。

还要说明的一点是,Vite构建的产物,一般是*.mjs和*.js文件这种形式(对应CommonJS和ES Module),但如果package.json中的type设置为module,则产物是*.js和*.cjs文件(对应ES Module和CommonJS)。

总而言之,Free Survey Form Builder没有采用Vite预设的Rollup构建选项,而是采用了以下的构建选项:

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      external: [
        'vue',
        'dayjs',
        'free-survey-core',
        'lodash',
        'mitt',
        'tdesign-icons-vue-next',
        'tdesign-vue-next',
        'vuedraggable'
      ],
      output: [
        {
          format: 'es',
          dir: 'dist/es',
          entryFileNames: '[name].js',
          preserveModulesRoot: 'packages',
          preserveModules: true
        },
        {
          format: 'cjs',
          dir: 'dist/lib',
          entryFileNames: '[name].js',
          preserveModulesRoot: 'packages',
          preserveModules: true,
          exports: 'named'
        }
      ]
    },
    lib: {
      entry: resolve(__dirname, 'packages/index.ts')
    }
  },
  plugins: [
    vue({
      script: {
        defineModel: true
      }
    }),
    vueJsx(),
    dtsPlugin({
      entryRoot: 'packages',
      outDir: ['dist/es', 'dist/lib'],
      include: ['packages/**/*.ts']
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
});

注意:更新TS版本后使用以上dtsPlugin配置可能导致构建错误,要解决报错,建议使用 tsconfigPath: resolve(__dirname, 'tsconfig.app.json') 而不是 include 进行配置。同时,在构建Vue组件库时,高版本TS中,tsconfig.node.json的module和moduleResolution建议设置为ESNext和Bundler。

其中,external配置的是不打包进构建产物中的依赖,也就是说package.json中的运行时依赖dependencies中的都可以写进来。最终构建时,会根据代码依赖,将大部分的依赖的库的代码刨除,从而减少构建产物的大小。

最后,为了同时构建出TypeScript类型声明文件,这里直接采用了vite-plugin-dts这个Vite插件,它会在构建时根据配置自动生成TypeScript类型声明文件,这里生成了两份,分别放入es和lib两个目录(对应ES Module和CommonJS两种构建方案)。

4、NPM包配置

构建完成后,还需要在package.json文件中声明库的入口,这样才能让他人引用包的时候知道如何引用。

在packages.json中,需要配置以下几个字段完成2

  1. files:是一个字符串数组,指示在上传至NPM仓库时需要上传哪些文件,一般需要包含构建产物目录、README文件、LICENSE文件。在Free Survey Form Builder中构建产物目录为dist目录。
  2. type:在 node 支持 ES 模块后,要求 ES 模块采用 .mjs 后缀文件名。只要遇到 .mjs 文件,就认为它是 ES 模块。如果不想修改文件后缀,就可以在 package.json文件中,指定 type 字段为 module,这样所有 .js 后缀的文件,node 都会用 ES 模块解释。
  3. types:指定库的TypeScript类型声明文件。
  4. main:指定的是项目的入口文件,在 browser 和 Node 环境中都可以使用,这是早期只有 CommonJS 模块规范时,指定项目入口的唯一属性,因此个人推荐还是继续使用 CommonJS 构建产物入口作为该字段的值。
  5. module:指定 ES 模块的入口文件。main和module的意义是各种打包工具会根据这些配置和项目设置及环境自动采用不同的模块方案。
  6. exports:可以配置不同环境对应的模块入口文件,并且当它存在时,它的优先级最高;它还能够限制库用户导入其它未公开的模块;此外,它还可以对导出的文件路径进行封装。

所以最终Free Survey Form Builder的package.json的部分配置如下:

{
  "files": [
    "dist",
    "README.md",
    "LICENSE",
    "readme-files"
  ],
  "type": "module",
  "main": "dist/lib/index.js",
  "module": "dist/es/index.js",
  "types": "dist/es/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "default": "./dist/es/index.js",
        "types": "./dist/es/index.d.ts"
      },
      "require": {
        "default": "./dist/lib/index.js",
        "types": "./dist/lib/index.d.ts"
      }
    },
    "./style": {
      "import": "./dist/es/style.css"
    }
  },
}

虽然exports的优先级最高,但是为了能够最大化兼容,所以依然配置了main、module、types等字段。最后,ES Module方案构建出的产物js中css是单独作为一个文件的,所以在使用库的时候需要单独进行导入,这里就对其进行了导出,使用时只需要如下一行即可导入:

import 'free-survey-form-builder/style';

如果希望直接把css合入模块中,需要自行查阅其它资料,不是本文重点,不再过多讨论。

参考

  1. 参考vue3组件库搭建-23-使用vite构建cjs和esm组件包vue3组件库搭建-24-使用vite构建umd组件包 ↩︎
  2. 参考package.json 配置完全解读 ↩︎

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注

Captcha Code