Element Plus 组件库实现:11. 小结——打包发布到npm

发布于:2024-05-08 ⋅ 阅读:(43) ⋅ 点赞:(0)

前言

前边我们已经介绍了十个组件的基本实现,包括Button基础交互组件、Collapse展示组件、Dropdown下拉菜单导航组件、Message反馈组件以及Form表单组件,后续组件将会逐步更新。那么既然实现了这些组件,怎么样才能让用户看到或者是使用呢?那么这个时候就应该考虑到组件的集成和部署,我们需要将这些组件打包成一个可供用户下载的库,或者是集成到现有的框架或者系统中。

打包类型

在打包之前,先要确定一下打包成什么样的类型,是ES Moudle、Common Js、或者是AMD、CMD等其他类型,那么就要分析下各种模块类型的优缺点吧:

  • ES Moudle:
    • 优点:
      • 支持代码分割和按需加载,减少首次加载时间
      • 原生支持,无需额外的打包工具或插件
      • 可以与Webpack、Rollup等现代打包工具无缝集成
    • 缺点:
      • 在Node.js环境中,需要较新的版本才能支持ES Modules
      • 对于不支持ES Modules的旧浏览器,需要使用构建工具进行转换
  • Common Js:
    • 优点:
      • 在Node.js环境中广泛使用,无需额外配置
      • 可以与Webpack等打包工具配合使用
    • 缺点:
      • 不支持代码分割和按需加载。
      • 对于浏览器环境,需要额外的转换步骤
  • UMD:
    • 优点:
      • 兼容性好,适用于各种环境
      • 易于通过 <script> 标签直接引入使用
    • 缺点:
      • 打包后的文件通常较大,因为需要包含所有可能的模块加载逻辑
      • 不利于代码分割和按需加载
  • AMD: 好像有点过时了?

基于以上分析,我们知道 Common Js是没法在浏览器中直接运行的,而ES Moudle和UMD是可以的,并且现在构建项目一般都会用到webpack或Vite等构建工具,对于不支持ES Moudle的旧浏览器也不用担心。那么我们的打包模块就暂定ES MouldeUMD两种格式。

Vue的插件系统

确定完打包类型,不着急去打包,先来看一下 ,那么什么是插件呢?简单来说就是:一段给Vue实例添加全局功能的代码。那么怎样写插件呢?如下:

自定义定义一个插件:

const myPlugin = {
  install(app, options) {
    // 配置此应用
    console.log("插件被注册了")
  }
}

使用插件:

import { createApp } from 'vue'

const app = createApp(App)

app.use(myPlugin)

所以插件的格式就是一个对象暴露出一个install方法或者一个function,那么可以在插件上做什么呢?一般来说可以做以下几种事情:

  1. 通过  和  注册一到多个全局组件或自定义指令
  2. 通过  使一个资源进整个应用
  3. 向  中添加一些全局实例属性或方法

很显然,我们这里需要用插件来注册我们的组件库组件。

注册Button组件

这里以Button组件为例,创建一个入口文件用于将Button组件注册到全局进行使用:

// Button/index.ts
import type { App } from 'vue'
import Button from './Button.vue'

// Button本身就是一个object,这里可以直接添加install方法
Button.install = (app: App) => {
  app.component(Button.name!, Button)
}

export default Button
// 将所有的类型导入进来
export * from './types'

这样你可以在全局单独注册Button以使用,其他组件类似,不再一一列出

全局注册所有组件

如果想全局注册所有组件,可以将组件保存到一个数组里,然后遍历注册,这里仅以两个组件为例:

// src/index.ts
import type { App } from 'vue'

import Button from '@/components/Button'
import Collapse, { CollapseItem } from '@/components/Collapse'

import './styles/index.css'



const components = [
  Button,
  Collapse,
  CollapseItem,
]
// 全局注册所组件
const install = (app: App) => {
  components.forEach((compoment) => {
    app.component(compoment.name!, compoment)
  })
}

export {
  Button,
  Collapse,
  CollapseItem
}

export default {
  install
}

其他组件也类似,不过需要注意的是,除了组件,其他用到的东西也要引入,否则打包的时候会丢失,比如样式;另外,Message组件创建的方式是调用函数,所以用到的函数也要引进来,方法如上。

打包配置

万事俱备,只欠打包,接下来就来看一下打包配置吧。这里用到的是Rollup,当然,不需要重新安装Rollup了,因为Vite本身就集成了Rollup。因为要打包成两种格式,所以需要分别配置ES ModuleUMD模式的打包配置。

ES Module

仅展示部分配置,其他配置与vite.config.js差别不大:

// vite.es.config.js
 build: {
    outDir: 'dist/es',
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'YvElement',
      fileName: 'yv-element',
      formats: ['es']
    },
    rollupOptions: {
      external: [
        'vue',
        '@fortawesome/fontawesome-svg-core',
        '@fortawesome/free-solid-svg-icons',
        '@fortawesome/vue-fontawesome',
        'async-validator',
        '@popperjs/core'
      ],
      output: {
        assetFileNames: (chunkInfo) => {
          if (chunkInfo.name === 'style.css') {
            return 'index.css'
          }
          return chunkInfo.name as string
        }
      }
    }
  }

其中,outDir为输出文件夹;name为暴露的全局变量,且在formats包含'umd''iife'时是必须的;fileName为输出包的文件名;formats即表示格式,这里就是es格式。

rollupOptions选项中,external表示在打包时不想打包进去的文件,这些将作为外部依赖来处理; assetFileNames函数用于自定义 asset 文件名生成,这里style.css在打包时生成名为index.css

UMD

仅展示部分配置,其他配置与vite.config.js差别不大:

 build: {
    outDir: 'dist/umd',
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'YvElement',
      fileName: 'yv-element',
      formats: ['umd']
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        exports: 'named',
        globals: {
          vue: 'Vue'
        },
        assetFileNames: (chunkInfo) => {
          if (chunkInfo.name === 'style.css') {
            return 'index.css'
          }
          return chunkInfo.name as string
        }
      }
    }
  }

这个配置中,exports: 'named'表示'named' 表示使用命名导出(named exports),这意味着 Rollup 会保留源代码中的命名导出,并在生成的 UMD 模块中使用它们。

vite.config.json

再来看一下vite.config.json的配置:

import dts from 'vite-plugin-dts'
export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
    dts({
      tsconfigPath: './tsconfig.build.json'
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'YvElement',
      fileName: 'yv-element'
    },
    rollupOptions: {
      external: [
        'vue',
        '@fortawesome/fontawesome-svg-core',
        '@fortawesome/free-solid-svg-icons',
        '@fortawesome/vue-fontawesome'
      ],
      output: {
        exports: 'named',
        globals: {
          vue: 'Vue'
        },
        assetFileNames: (chunkInfo) => {
          if (chunkInfo.name === 'style.css') {
            return 'index.css'
          }
          return chunkInfo.name as string
        }
      }
    }
  }
})

这里需要一个第三方的插件 ,一款用于在  中从 .ts(x) 或 .vue 源文件生成类型文件(*.d.ts)的 Vite 插件,详情见官方文档。

这里还需要配置你要处理的文件:

// tsconfig.build.json
{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "include": ["src/index.ts", "src/components/**/*", "src/hooks/**/*"],
  "exclude": ["src/components/**/__test__/*"],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "jsx": "preserve"
  },
  "references": [
    {
      "path": "./tsconfig.json"
    }
  ]
}

package.json

然后就是配置package.json中相关的打包发布信息了,这里仅展示关键部分:

  "type": "module",
  "private": false,
  "homepage": "https://yv-ui.y-y-y.online",
  "repository": {
    "type": "git",
    "url": "https://github.com/xiaoniu666888/yv-ui"
  },
  "files": [
    "dist"
  ],
  "sideEffects": [
    "dist/index.css"
  ],
  "main": "./dist/umd/yv-element.umd.cjs",
  "module": "./dist/es/yv-element.js",
  "types": "./dist/types/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/es/yv-element.js",
      "require": "./dist/umd/yv-element.umd.cjs"
    },
    "./dist/": {
      "import": "./dist/",
      "require": "./dist/"
    }
  }

比较多,不过从键名也能知道是什么意思,简单挑几个说明一下:

  • "type": "module": 指示这个文件是一个 ES 模块

  • "private": false: 表示包是公开的(npm发布私有包好像是要收费的)

  • files: 当包被发布时,应包含哪些文件,确保只发布必要的文件

  • "sideEffects": [ "dist/index.css" ]:这意味着 "dist/index.css" 文件可能有副作用,不应该被 tree shaking 掉

  • "main": "./dist/umd/yv-element.umd.cjs": 当使用 CommonJS 的 require 方法时,这是包的入口点

  • "module": "./dist/es/yv-element.js": 当使用 ES6+ 的 import 语句时,这是包的入口点

  • "types": "./dist/types/index.d.ts": TypeScript 的类型定义文件的位置\

  • "exports": 定义包的导出映射,允许更精细地控制当使用 ES6+ import 或 CommonJS require 时应解析哪个文件

    • "./dist/": 表示允许从 "./dist/" 路径直接导入或 require 文件

另外,还有其他配置:

  "peerDependencies": {
    "vue": "^3.4.21"
  },

peerDependencies 字段用于指定与当前包一起使用的其他包的版本。这些依赖项不是自动安装的,而是由使用当前包的项目来管理和安装的,也就是说在使用该组件库的时候,核心依赖库Vue必须先下载安装,组件库不能脱离Vue而被单独依赖并引用。

脚本

再来看一下打包时的脚本命令:

"build": "npm run build-only",
"build-only": "run-p build-es build-umd",
"build-umd": "vite build --config vite.umd.config.ts",
"build-es": "vite build --config vite.es.config.ts",
"prepublishOnly": "npm run build"
  • "build" :

    • 这个命令是主要的构建命令,当你运行 npm run build 时,它会执行两个子命令:build-only 和 move-style
  • "build-only" :

    •  build-es 和 build-umd 同时开始执行
  • "build-umd"  和  "build-es" :

    • build-umd 使用 vite.umd.config.ts 作为配置文件
    • build-es 使用 vite.es.config.ts 作为配置文件
  • "prepublishOnly" :

    • 这是一个 npm 生命周期钩子,当运行 npm publish 命令时,prepublishOnly 会在发布之前执行
    • 这里,设置为运行 npm run build ,也就是说在发布包之前,它会先执行构建过程,这样就不用手动执行打包命令了

打包发布

最后,完成上述配置之后,终于可以打包发布了,执行命令:

npm publish --access public

打包之前需要注册npm账号然后登录,这里就不再赘述了。

总结

本文主要介绍对组件库的打包发布,需要注意的是Vue的插件系统,利用插件全局注册所有组件,然后配置打包文件,这个过程较为繁琐,要注意最终会打包成两种格式,ES Module 和 UMD 格式,需要分别配置打包文件,最后注意在发布到npm上的时候要将包设置为公开的。后续组件将会逐渐更新,最近还在找实习,只能暂停了,呜呜呜~

最后,这里是组件库的使用文档:,这里是仓库地址:,以上仅为本菜鸟的拙见,如有失误的地方,还请大佬评论区批评指正。