从零搭建pnpm + monorepo + vuepress2.x + vue3的组件库(三)之使用rollup打包组件库

3,886 阅读5分钟

我正在参加「掘金·启航计划」

背景

前面几个文章讲述了如何使用pnpm的 workspace 功能实现 monorepo 架构的设计,以及如何用vue3+typescript+pnpm实现组件库的开发以及发布。到此为止,将组件库的包发布到npm之后,就可以在其他项目中进行使用了。

由于组件库的开发使用了vite-plugin-vue-setup-extend插件为组件定义name,但是通过下载npm上的组件库依赖包使用组件时,发现使用vite-plugin-vue-setup-extend定义name属性的组件均没有办法正常使用,原因是,因为组件库本身使用了这个插件,但是发布的组件库依赖包并没有这个插件,导致使用组件的组件名称与组件本身名称不一致所以无法使用

比如:组件库定义的组件名称为baseTable,但是通过安装组件库,使用该组件时,组件的名称是组件文件名称table(如果组件内部没有定义name 属性,则会自动以文件名称作为组件名称)。

解决思路

方案一:在项目中也同样安装vite-plugin-vue-setup-extend 插件

使用这个方法对项目有要求,要求项目本身必须也以vite打包的方式。

使用该方案有些鸡肋,需要强制用户安装这个插件,无法实现开箱即用。

方案二:借助工具对组件库进行打包,把组件名称打包进去,实现开箱即用

本文主要讲述方案二的实现方法

正文:使用rollup对组件库打包

一、安装rollup

npm install --global rollup

二、 rollup安装之后,小试牛刀

组件库项目结构目录:

|-- project
    |-- README.md
    |-- docs
    |   |-- README.md (首页页面设计,参考vuepress官网)
    |   |-- .vuepress
    |   |   |-- client.js(应用级别的配置)
    |   |   |-- config.ts(配置文件)
    |   |   |-- configs(存储导航配置文件夹)
    |   |   |   |-- navbar.js(顶部导航配置)
    |   |   |   |-- sidebar.js(侧导航配置)
    |   |   |-- styles
    |   |       |-- index.scss(全局css样式文件)
    |   |-- components(组件说明文档)
    |   |   |-- comp1.md
    |   |   |-- comp2.md
    |   |   |-- ...
    |   |-- guide(使用指南文档)
    |       |-- intro.md
    |-- packages(组件库核心文件夹)
        |-- index.ts
        |-- components (自定义组件文件夹)
        |   |-- index.ts
        |   |-- package.json
        |   |-- Table
        |   |   |-- README.md
        |   |   |-- package.json
        |   |   |-- src
        |   |       |-- index.vue
        |   |       |-- main.ts
        |   |-- base-h1
        |       |-- index.vue
        |-- mcl-ui
        |   |-- component.ts
        |-- utils (自定义工具文件夹)
        |   |--  index.ts
        |   |--  package.json
        |-- package.json (组件库本身的package.json)
           

在项目根目录增加 rollup.config.js文件,配置如下:

// rollup.config.js
export default {
  input: 'packages/index.ts',
  output: {
    file: 'bundle.js',
    format: 'cjs',
  },
}

在终端执行rollup -c对组件库进行打包

三、打包过程中遇到的Q&A

总体思路: 1、通过rollup -c对组件库进行打包,缺啥补啥; 2、对于vite-plugin-vue-setup-extend插件找到了一个可以替换的插件unplugin-vue-setup-extend,这个插件有针对不用环境的打包配置,支持vite、rollup、webpack、nuxt、vue cli,这样就可以在组件库项目中使用vite的配置方式,使用rollup 打包组件库时使用rollup的方式,从而解决在其他项目中组件无法使用的问题; 3、rollup配置打包输出文件路径为packages/lib/index.js,同时配置packages/package.jsonmain属性为"lib/index.js"(因为组件库本身就是以packages目录进行发布的)

Q1:To load an ES module, set "type": "module" in the package.json or use the .mjs extension.

Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
[!] RollupError: Node tried to load your configuration file as CommonJS even though it is likely an ES module. To resolve this, change the extension of your configuration to ".mjs", set "type": "module" in your package.json file or pass the "--bundleConfigAsCjs" flag.

解决:在package.json中增加"type": "module",

Q2: [!] RollupError: Could not resolve "./xxx" from "packages/index.ts"

[!] RollupError: Could not resolve "./mcl-ui" from "packages/index.ts"

解决方案: 安装@rollup/plugin-node-resolve

pnpm add @rollup/plugin-node-resolve -Dw
// rollup.config.js
import { nodeResolve } from '@rollup/plugin-node-resolve'

export default {
  input: 'packages/index.ts',
  output: {
    file: 'bundle.js',
    format: 'cjs',
  },
  plugins: [
    nodeResolve({
      extensions: ['.mjs', '.js', '.json', '.ts'],
    }),
  ],
}

Q3: [!] RollupError: Unexpected token (Note that you need plugins to import files that are not JavaScript)

[!] RollupError: Unexpected token (Note that you need plugins to import files that are not JavaScript)
packages/mcl-ui/index.ts (2:12)
1: import Components from './component'
2: import type { App } from 'vue'
               ^
3: 
4: export const INSTALLED_KEY = Symbol('INSTALLED_KEY')
    at error (/usr/local/lib/node_modules/rollup/dist/shared/rollup.js:260:30)
    at Module.error (/usr/local/lib/node_modules/rollup/dist/shared/rollup.js:13652:16)
    at Module.tryParse (/usr/local/lib/node_modules/rollup/dist/shared/rollup.js:14326:25)
    at Module.setSource (/usr/local/lib/node_modules/rollup/dist/shared/rollup.js:13936:39)
    at ModuleLoader.addModuleSource (/usr/local/lib/node_modules/rollup/dist/shared/rollup.js:23644:20)

解决方案:

1:

pnpm add  @rollup/plugin-typescript -Dw

// rollup.config.js
import typescript from '@rollup/plugin-typescript'

 plugins: [
   //...
    typescript({ tsconfig: './tsconfig.json', declaration: false }),
  ],

2: 推荐使用 rollup-plugin-esbuild,因为这个插件集成了rollup-plugin-typescript2, @rollup/plugin-typescript and rollup-plugin-terser

pnpm add  rollup-plugin-esbuild -Dw

rollup.config.js中添加如下配置

import esbuild from 'rollup-plugin-esbuild'
export default {
  ...
  plugins: [
     esbuild({
      // All options are optional
      include: /.[jt]sx?$/, // default, inferred from `loaders` option
      // exclude: /node_modules/, // default
      sourceMap: process.env.NODE_ENV === 'production',
      minify: process.env.NODE_ENV === 'production',
      target: 'es2018', // default, or 'es20XX', 'esnext'
      // jsx: 'transform', // default, or 'preserve'
      // jsxFactory: 'React.createElement',
      // jsxFragment: 'React.Fragment',
      // Like @rollup/plugin-replace
      // define: {
      //   'process.env.NODE_ENV': JSON.stringify('production'),
      // },
      loaders: {
        '.vue': 'ts',
      },
      // tsconfig: 'tsconfig.json', // default
    }),
  ]
}

Q4: [!] (plugin esbuild) Error: Transform failed with 1 error:

/Users/frontEnd/study/vuepress-ui/packages/components/pagination/src/pagination.vue:2:7: ERROR: Expected ">" but found "v"

[!] (plugin esbuild) Error: Transform failed with 1 error:
/Users/frontEnd/study/vuepress-ui/packages/components/pagination/src/pagination.vue:2:7: ERROR: Expected ">" but found "v"

解决:

pnpm add rollup-plugin-vue -Dw

plugins: [
    ...
    vuePlugin({
      // 引用的vue插件,即上述引入的插件使用一遍,以及添加一些选项
      include: /.vue$/,
      // 把单文件组件中的样式,插入到html中的style标签
      css: false,
      // 把组件转换成 render 函数
      //compileTemplate: true
    }),
 ]

Q5: 运行pnpm i 报错:ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE

ERR_PNPM_ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE  The lockfile is broken! A full installation will be performed in an attempt to fix it.

解决:

npm i -g pnpm //更新 pnpm 至最新版本
rm -rf $(pnpm store path) //删除 pnpm store
//重新运行
pnpm i

Q6: scss语法的支持

pnpm add rollup-plugin-postcss -Dw

rollup.config.js中添加如下配置

import postcss from 'rollup-plugin-postcss'
export default {
  ...
  plugins: [
    postcss({
      // 把 css 放到和js同一目录
      extract: true,
    }),
  ]
}

Q7: 报错:<script> and <script setup> must have the same language type

[!] (plugin vue) RollupError: [@vue/compiler-sfc] <script> and <script setup> must have the same language type

报错原因:排除网上说的多个<script>设置的lang不一致的原因之后,发现是使用unplugin-vue-setup-extend插件定义组件名称时引起的bug,报错版本是0.3.2,把版本回退到0.3.1解决.

Q8:执行rollup -c提示警告:(!) Mixing named and default exports

(!) Mixing named and default exports
https://rollupjs.org/configuration-options/#output-exports
The following entry modules are using named and default exports together:
packages/index.ts

Consumers of your bundle will have to use chunk.default to access their default export, which may not be what you want. Use `output.exports: "named"` to disable this warning.

解决:

rollup.config.js中添加exports: 'named',配置

export default {
  input: 'packages/index.ts',
  output: {
    name: 'lib',
    file: 'packages/lib/index.js',
    format: 'cjs',
    exports: 'named', // 添加这一行
  },
}

Q9: 执行 rollup -c 成功打包,但是提交代码进行eslint校验时会对打包内容进行校验

解决:

添加.eslintignore文件,把打包文件的路径添加到文件里

node_modules
dist 
packages/lib 

Q10: "type":"moudle"引起所有.js后缀的文件eslint报错

.commitlintrc.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.
Instead rename .commitlintrc.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in /Users/frontEnd/study/vuepress-ui/package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).

解决:把 .js后缀的文件改为.json,不能改为.json的改为.cjs

Q11:rollup打包之后,在项目中使用组件库,提示报错:

报错原因:可能是项目中的vue和组件库使用的vue版本不一致

index.js:2174 Uncaught TypeError: Cannot read properties of null (reading 'isCE')
    at renderSlot (index.js:2174:34)
    at Proxy.<anonymous> (index.js:3438:13)
    at renderComponentRoot (runtime-core.esm-bundler.js:914:44)
    at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js:5649:57)
    at ReactiveEffect.run (reactivity.esm-bundler.js:190:25)
    at instance.update (runtime-core.esm-bundler.js:5763:56)
    at setupRenderEffect (runtime-core.esm-bundler.js:5777:9)
    at mountComponent (runtime-core.esm-bundler.js:5559:9)
    at processComponent (runtime-core.esm-bundler.js:5517:17)
    at patch (runtime-core.esm-bundler.js:5119:21)

解决方案:不要把**Vue**本身打包进组件库代码,修改组件库中的 rollup.config.js文件,添加external属性

export default{
  ...
  external: ['vue'],
}

rollup.config.js文件最终配置

import { nodeResolve } from '@rollup/plugin-node-resolve'
import esbuild from 'rollup-plugin-esbuild'
import commonjs from '@rollup/plugin-commonjs'
import vuePlugin from 'rollup-plugin-vue'
import postcss from 'rollup-plugin-postcss'
import VueSetupExtend from 'unplugin-vue-setup-extend/rollup'

export default {
  input: 'packages/index.ts',
  output: {
    name: 'lib',
    file: 'packages/lib/index.js',
    format: 'cjs',
    exports: 'named',
  },
  plugins: [
    VueSetupExtend(),
    nodeResolve({
      extensions: ['.mjs', '.js', '.json', '.ts'],
    }),
    vuePlugin({
      // 引用的vue插件,即上述引入的插件使用一遍,以及添加一些选项
      include: /\.vue$/,
      // 把单文件组件中的样式,插入到html中的style标签
      css: true,
      // 把组件转换成 render 函数
      //compileTemplate: true
    }),
    esbuild({
      // All options are optional
      include: /\.[jt]sx?$/, // default, inferred from `loaders` option
      // exclude: /node_modules/, // default
      sourceMap: process.env.NODE_ENV === 'production',
      minify: process.env.NODE_ENV === 'production',
      target: 'es2018', // default, or 'es20XX', 'esnext'
      loaders: {
        '.vue': 'ts',
      },
      // tsconfig: 'tsconfig.json', // default
    }),
    postcss({
      // 把 css 放到和js同一目录
      extract: true,
    }),
    // 需放在postcss后面,需要等postcss对scss语法编译之后再进行语法校验,否则会提示不支持scss语法
    commonjs(),
  ],
  external: ['vue'],
}

package.json文件:

{
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "license": "MIT",
  "type": "module",
  "scripts": {
    "docs:dev": "vuepress dev docs",
    "docs:build": "vuepress build docs",
    "preinstall": "npx only-allow pnpm",
    "commit": "cz",
    "prepare": "husky install",
    "build:ui": "rollup -c"
  },
  "lint-staged": {
    "*.{vue,js,ts,jsx,tsx,json}": "eslint --fix",
    "*.md": [
      "prettier --write"
    ]
  },
  "config": {
    "commitizen": {
      "path": "node_modules/cz-customizable"
    }
  },
  "devDependencies": {
    "@commitlint/config-conventional": "^17.2.0",
    "@rollup/plugin-commonjs": "^24.0.1",
    "@rollup/plugin-node-resolve": "^15.0.1",
    "@typescript-eslint/eslint-plugin": "^5.48.0",
    "@typescript-eslint/parser": "^5.48.0",
    "@vue/compiler-sfc": "^3.2.47",
    "@vuepress/bundler-vite": "2.0.0-beta.60",
    "@vuepress/client": "2.0.0-beta.51",
    "commitizen": "^4.2.5",
    "commitlint": "^17.2.0",
    "cz-conventional-changelog": "^3.3.0",
    "cz-customizable": "^7.0.0",
    "element-plus": "^2.2.20",
    "esbuild": "^0.17.8",
    "eslint": "^8.30.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-vue": "^9.8.0",
    "husky": "^8.0.2",
    "lint-staged": "^13.1.0",
    "mcl-ui": "^0.0.46",
    "postcss": "^8.4.21",
    "prettier": "^2.8.1",
    "rollup": "^3.15.0",
    "rollup-plugin-esbuild": "^5.0.0",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-vue": "^6.0.0",
    "sass": "^1.56.1",
    "typescript": "^4.8.4",
    "unplugin-vue-setup-extend": "0.3.0",
    "vite": "^4.0.4",
    "vue": "3.2.44",
    "vuepress": "2.0.0-beta.51",
    "vuepress-plugin-demoblock-plus": "^2.0.0"
  },
  "peerDependencies": {
    "vue": "^3.2.41"
  }
}