【前端工程化-组件库】从0-1构建Vue3组件库(打包发布)

前言

前面篇章我们已经完成了开源组件库编码,接下来需要做的是打包发布到 NPM 上。我们来大致梳理下实现思路:
image.png

编译打包

组件库需要做 2 种打包方式:全量打包、单组件打包,以提供给第三方调用者灵活选择使用。

打包方式

  • 全量打包:打包全部组件
  • 按需打包:单个组件打包,⽤户单独引⼊

全量组件打包

我们先来做全量组件打包,整体思路如下:

  1. 创建打包文件 build.js;
  2. 读取组件库的全局入口,设置为 entry;设置 outputDir 输出目录为 build 目录;
  3. 使用vite build配置编译参数;
  4. 使用node fs生成package.json。

具体代码实现如下:

scripts/build.js

const path = require("path")
const fs = require("fs-extra")
// 引入 vite 的 build 方法,进行编译构建
const { defineConfig, build } = require("vite")
const vue = require("@vitejs/plugin-vue")
const vueJSX = require("@vitejs/plugin-vue-jsx")
const version = require("../package.json").version
// 基础配置
const baseConfig = defineConfig({
publicDir: false,
plugins: [vue(), vueJSX()]
})
const rollupOptions = defineConfig({
// that shouldn't be bundled
external: ["vue"],
globals: {
vue: "Vue"
}
})
// 组件库全局入口
const compontsDir = path.resolve(__dirname, "../packages/components")
// 输出目录
const outputDir = path.resolve(__dirname, "../build")
// 生成 package.json
const createPackageJson = name => {
const fileStr = `{
"name": "${name ? name : "pc-vue3-ui"}",
"version": "${version}",
"description": "Vue3组件库",
"main": "${name ? "index.umd.js" : "pc-vue3-ui.umd.js"}",
"module":"${name ? "index.mjs" : "pc-vue3-ui.mjs"}",
"repository": {
"type": "git",
"url": "git+https://github.com/GGXXMM/vue3-ui.git"
},
"keywords": ["vue3", "组件库", "UI"],
"author": "guoxinming",
"license": "ISC"
}
`
// 单个组件 or 全量
const filePath = path.resolve(
outputDir,
name ? `${name}/package.json` : `package.json`
)
fs.outputFile(filePath, fileStr, "utf-8")
}
/** 全量构建 */
const buildAll = async () => {
await build(
defineConfig({
...baseConfig,
build: {
lib: {
entry: compontsDir,
name: "pc-vue3-ui",
fileName: "pc-vue3-ui",
formats: ["es", "umd"]
},
rollupOptions,
outDir: outputDir
}
})
)
createPackageJson()
}
const path = require("path")

const fs = require("fs-extra")

// 引入 vite 的 build 方法,进行编译构建


const { defineConfig, build } = require("vite")

const vue = require("@vitejs/plugin-vue")

const vueJSX = require("@vitejs/plugin-vue-jsx")

const version = require("../package.json").version




// 基础配置


const baseConfig = defineConfig({

  publicDir: false,

  plugins: [vue(), vueJSX()]


})


const rollupOptions = defineConfig({

  // that shouldn't be bundled


  external: ["vue"],

  globals: {


    vue: "Vue"


  }


})




// 组件库全局入口

const compontsDir = path.resolve(__dirname, "../packages/components")

// 输出目录

const outputDir = path.resolve(__dirname, "../build")



// 生成 package.json

const createPackageJson = name => {

  const fileStr = `{

    "name": "${name ? name : "pc-vue3-ui"}",

    "version": "${version}",

    "description": "Vue3组件库",

    "main": "${name ? "index.umd.js" : "pc-vue3-ui.umd.js"}",

    "module":"${name ? "index.mjs" : "pc-vue3-ui.mjs"}",

    "repository": {

      "type": "git",

      "url": "git+https://github.com/GGXXMM/vue3-ui.git"

    },

    "keywords": ["vue3", "组件库", "UI"],

    "author": "guoxinming",

    "license": "ISC"

  }

  `

  // 单个组件 or 全量

  const filePath = path.resolve(

    outputDir,

    name ? `${name}/package.json` : `package.json`

  )



  fs.outputFile(filePath, fileStr, "utf-8")

}



/** 全量构建 */
const buildAll = async () => {
  await build(

    defineConfig({

      ...baseConfig,

      build: {

        lib: {

          entry: compontsDir,
          name: "pc-vue3-ui",
          fileName: "pc-vue3-ui",
          formats: ["es", "umd"]

        },

        rollupOptions,

        outDir: outputDir
      }

    })

  )



  createPackageJson()
}
const path = require("path") const fs = require("fs-extra") // 引入 vite 的 build 方法,进行编译构建 const { defineConfig, build } = require("vite") const vue = require("@vitejs/plugin-vue") const vueJSX = require("@vitejs/plugin-vue-jsx") const version = require("../package.json").version // 基础配置 const baseConfig = defineConfig({ publicDir: false, plugins: [vue(), vueJSX()] }) const rollupOptions = defineConfig({ // that shouldn't be bundled external: ["vue"], globals: { vue: "Vue" } }) // 组件库全局入口 const compontsDir = path.resolve(__dirname, "../packages/components") // 输出目录 const outputDir = path.resolve(__dirname, "../build") // 生成 package.json const createPackageJson = name => { const fileStr = `{ "name": "${name ? name : "pc-vue3-ui"}", "version": "${version}", "description": "Vue3组件库", "main": "${name ? "index.umd.js" : "pc-vue3-ui.umd.js"}", "module":"${name ? "index.mjs" : "pc-vue3-ui.mjs"}", "repository": { "type": "git", "url": "git+https://github.com/GGXXMM/vue3-ui.git" }, "keywords": ["vue3", "组件库", "UI"], "author": "guoxinming", "license": "ISC" } ` // 单个组件 or 全量 const filePath = path.resolve( outputDir, name ? `${name}/package.json` : `package.json` ) fs.outputFile(filePath, fileStr, "utf-8") } /** 全量构建 */ const buildAll = async () => { await build( defineConfig({ ...baseConfig, build: { lib: { entry: compontsDir, name: "pc-vue3-ui", fileName: "pc-vue3-ui", formats: ["es", "umd"] }, rollupOptions, outDir: outputDir } }) ) createPackageJson() }

单组件打包

单组件打包,与全量打包的主要差异是获取打包入口和出口。入口entry是单组件的index.ts,输出outputDir则设置为 build 目录下的具体组件。代码实现如下所示:

const path = require("path")
const fs = require("fs-extra")
// 引入 vite 的 build 方法,进行编译构建
const { defineConfig, build } = require("vite")
const vue = require("@vitejs/plugin-vue")
const vueJSX = require("@vitejs/plugin-vue-jsx")
const version = require("../package.json").version
// 基础配置
const baseConfig = defineConfig({
publicDir: false,
plugins: [vue(), vueJSX()]
})
const rollupOptions = defineConfig({
// that shouldn't be bundled
external: ["vue"],
globals: {
vue: "Vue"
}
})
// 组件库全局入口
const compontsDir = path.resolve(__dirname, "../packages/components")
// 输出目录
const outputDir = path.resolve(__dirname, "../build")
// 生成 package.json
const createPackageJson = name => {
const fileStr = `{
"name": "${name ? name : "pc-vue3-ui"}",
"version": "${version}",
"description": "Vue3组件库",
"main": "${name ? "index.umd.js" : "pc-vue3-ui.umd.js"}",
"module":"${name ? "index.mjs" : "pc-vue3-ui.mjs"}",
"repository": {
"type": "git",
"url": "git+https://github.com/GGXXMM/vue3-ui.git"
},
"keywords": ["vue3", "组件库", "UI"],
"author": "guoxinming",
"license": "ISC"
}
`
// 单个组件 or 全量
const filePath = path.resolve(
outputDir,
name ? `${name}/package.json` : `package.json`
)
fs.outputFile(filePath, fileStr, "utf-8")
}
/** 单组件按需构建 */
const buildSingle = async name => {
await build(
defineConfig({
...baseConfig,
build: {
lib: {
entry: path.resolve(compontsDir, name),
name: "index",
fileName: "index",
formats: ["es", "umd"]
},
rollupOptions,
outDir: path.resolve(outputDir, name)
}
})
)
createPackageJson(name)
}
const path = require("path")

const fs = require("fs-extra")

// 引入 vite 的 build 方法,进行编译构建


const { defineConfig, build } = require("vite")

const vue = require("@vitejs/plugin-vue")

const vueJSX = require("@vitejs/plugin-vue-jsx")

const version = require("../package.json").version




// 基础配置


const baseConfig = defineConfig({

  publicDir: false,

  plugins: [vue(), vueJSX()]


})


const rollupOptions = defineConfig({

  // that shouldn't be bundled


  external: ["vue"],

  globals: {


    vue: "Vue"


  }


})




// 组件库全局入口

const compontsDir = path.resolve(__dirname, "../packages/components")

// 输出目录

const outputDir = path.resolve(__dirname, "../build")



// 生成 package.json

const createPackageJson = name => {

  const fileStr = `{

    "name": "${name ? name : "pc-vue3-ui"}",

    "version": "${version}",

    "description": "Vue3组件库",

    "main": "${name ? "index.umd.js" : "pc-vue3-ui.umd.js"}",

    "module":"${name ? "index.mjs" : "pc-vue3-ui.mjs"}",

    "repository": {

      "type": "git",

      "url": "git+https://github.com/GGXXMM/vue3-ui.git"

    },

    "keywords": ["vue3", "组件库", "UI"],

    "author": "guoxinming",

    "license": "ISC"

  }

  `

  // 单个组件 or 全量

  const filePath = path.resolve(

    outputDir,

    name ? `${name}/package.json` : `package.json`

  )



  fs.outputFile(filePath, fileStr, "utf-8")

}



/** 单组件按需构建 */
const buildSingle = async name => {
  await build(

    defineConfig({

      ...baseConfig,

      build: {

        lib: {

          entry: path.resolve(compontsDir, name),
          name: "index",
          fileName: "index",
          formats: ["es", "umd"]

        },

        rollupOptions,

        outDir: path.resolve(outputDir, name)
      }

    })

  )



  createPackageJson(name)
}
const path = require("path") const fs = require("fs-extra") // 引入 vite 的 build 方法,进行编译构建 const { defineConfig, build } = require("vite") const vue = require("@vitejs/plugin-vue") const vueJSX = require("@vitejs/plugin-vue-jsx") const version = require("../package.json").version // 基础配置 const baseConfig = defineConfig({ publicDir: false, plugins: [vue(), vueJSX()] }) const rollupOptions = defineConfig({ // that shouldn't be bundled external: ["vue"], globals: { vue: "Vue" } }) // 组件库全局入口 const compontsDir = path.resolve(__dirname, "../packages/components") // 输出目录 const outputDir = path.resolve(__dirname, "../build") // 生成 package.json const createPackageJson = name => { const fileStr = `{ "name": "${name ? name : "pc-vue3-ui"}", "version": "${version}", "description": "Vue3组件库", "main": "${name ? "index.umd.js" : "pc-vue3-ui.umd.js"}", "module":"${name ? "index.mjs" : "pc-vue3-ui.mjs"}", "repository": { "type": "git", "url": "git+https://github.com/GGXXMM/vue3-ui.git" }, "keywords": ["vue3", "组件库", "UI"], "author": "guoxinming", "license": "ISC" } ` // 单个组件 or 全量 const filePath = path.resolve( outputDir, name ? `${name}/package.json` : `package.json` ) fs.outputFile(filePath, fileStr, "utf-8") } /** 单组件按需构建 */ const buildSingle = async name => { await build( defineConfig({ ...baseConfig, build: { lib: { entry: path.resolve(compontsDir, name), name: "index", fileName: "index", formats: ["es", "umd"] }, rollupOptions, outDir: path.resolve(outputDir, name) } }) ) createPackageJson(name) }

完整代码

const path = require("path")
const fs = require("fs-extra")
// 引入 vite 的 build 方法,进行编译构建
const { defineConfig, build } = require("vite")
const vue = require("@vitejs/plugin-vue")
const vueJSX = require("@vitejs/plugin-vue-jsx")
const version = require("../package.json").version
// 基础配置
const baseConfig = defineConfig({
publicDir: false,
plugins: [vue(), vueJSX()]
})
const rollupOptions = defineConfig({
// that shouldn't be bundled
external: ["vue"],
globals: {
vue: "Vue"
}
})
// 组件库全局入口
const compontsDir = path.resolve(__dirname, "../packages/components")
// 输出目录
const outputDir = path.resolve(__dirname, "../build")
// 生成 package.json
const createPackageJson = name => {
const fileStr = `{
"name": "${name ? name : "pc-vue3-ui"}",
"version": "${version}",
"description": "Vue3组件库",
"main": "${name ? "index.umd.js" : "pc-vue3-ui.umd.js"}",
"module":"${name ? "index.mjs" : "pc-vue3-ui.mjs"}",
"repository": {
"type": "git",
"url": "git+https://github.com/GGXXMM/vue3-ui.git"
},
"keywords": ["vue3", "组件库", "UI"],
"author": "guoxinming",
"license": "ISC"
}
`
// 单个组件 or 全量
const filePath = path.resolve(
outputDir,
name ? `${name}/package.json` : `package.json`
)
fs.outputFile(filePath, fileStr, "utf-8")
}
/** 单组件按需构建 */
const buildSingle = async name => {
await build(
defineConfig({
...baseConfig,
build: {
lib: {
entry: path.resolve(compontsDir, name),
name: "index",
fileName: "index",
formats: ["es", "umd"]
},
rollupOptions,
outDir: path.resolve(outputDir, name)
}
})
)
createPackageJson(name)
}
/** 全量构建 */
const buildAll = async () => {
await build(
defineConfig({
...baseConfig,
build: {
lib: {
entry: compontsDir,
name: "pc-vue3-ui",
fileName: "pc-vue3-ui",
formats: ["es", "umd"]
},
rollupOptions,
outDir: outputDir
}
})
)
createPackageJson()
}
const buildLib = async () => {
await buildAll()
// 按需打包
fs.readdirSync(compontsDir)
.filter(name => {
// 获取组件的目录
const componentDir = path.resolve(compontsDir, name)
const isDir = fs.lstatSync(componentDir).isDirectory()
return isDir && fs.readdirSync(componentDir).includes("index.ts")
})
.forEach(async name => {
await buildSingle(name)
})
}
buildLib()
const path = require("path")
const fs = require("fs-extra")
// 引入 vite 的 build 方法,进行编译构建


const { defineConfig, build } = require("vite")
const vue = require("@vitejs/plugin-vue")
const vueJSX = require("@vitejs/plugin-vue-jsx")
const version = require("../package.json").version



// 基础配置


const baseConfig = defineConfig({
  publicDir: false,
  plugins: [vue(), vueJSX()]


})


const rollupOptions = defineConfig({
  // that shouldn't be bundled


  external: ["vue"],
  globals: {


    vue: "Vue"


  }


})


// 组件库全局入口
const compontsDir = path.resolve(__dirname, "../packages/components")
// 输出目录
const outputDir = path.resolve(__dirname, "../build")
// 生成 package.json
const createPackageJson = name => {
  const fileStr = `{
    "name": "${name ? name : "pc-vue3-ui"}",
    "version": "${version}",
    "description": "Vue3组件库",
    "main": "${name ? "index.umd.js" : "pc-vue3-ui.umd.js"}",
    "module":"${name ? "index.mjs" : "pc-vue3-ui.mjs"}",
    "repository": {
      "type": "git",
      "url": "git+https://github.com/GGXXMM/vue3-ui.git"
    },
    "keywords": ["vue3", "组件库", "UI"],
    "author": "guoxinming",
    "license": "ISC"
  }
  `
  // 单个组件 or 全量
  const filePath = path.resolve(
    outputDir,
    name ? `${name}/package.json` : `package.json`
  )

  fs.outputFile(filePath, fileStr, "utf-8")
}

/** 单组件按需构建 */
const buildSingle = async name => {
  await build(
    defineConfig({
      ...baseConfig,
      build: {
        lib: {
          entry: path.resolve(compontsDir, name),
          name: "index",
          fileName: "index",
          formats: ["es", "umd"]
        },
        rollupOptions,
        outDir: path.resolve(outputDir, name)
      }
    })
  )

  createPackageJson(name)
}

/** 全量构建 */
const buildAll = async () => {
  await build(
    defineConfig({
      ...baseConfig,
      build: {
        lib: {
          entry: compontsDir,
          name: "pc-vue3-ui",
          fileName: "pc-vue3-ui",
          formats: ["es", "umd"]
        },
        rollupOptions,
        outDir: outputDir
      }
    })
  )

  createPackageJson()
}

const buildLib = async () => {
  await buildAll()

  // 按需打包
  fs.readdirSync(compontsDir)
    .filter(name => {
      // 获取组件的目录
      const componentDir = path.resolve(compontsDir, name)
      const isDir = fs.lstatSync(componentDir).isDirectory()
      return isDir && fs.readdirSync(componentDir).includes("index.ts")
    })
    .forEach(async name => {
      await buildSingle(name)
    })
}

buildLib()
const path = require("path") const fs = require("fs-extra") // 引入 vite 的 build 方法,进行编译构建 const { defineConfig, build } = require("vite") const vue = require("@vitejs/plugin-vue") const vueJSX = require("@vitejs/plugin-vue-jsx") const version = require("../package.json").version // 基础配置 const baseConfig = defineConfig({ publicDir: false, plugins: [vue(), vueJSX()] }) const rollupOptions = defineConfig({ // that shouldn't be bundled external: ["vue"], globals: { vue: "Vue" } }) // 组件库全局入口 const compontsDir = path.resolve(__dirname, "../packages/components") // 输出目录 const outputDir = path.resolve(__dirname, "../build") // 生成 package.json const createPackageJson = name => { const fileStr = `{ "name": "${name ? name : "pc-vue3-ui"}", "version": "${version}", "description": "Vue3组件库", "main": "${name ? "index.umd.js" : "pc-vue3-ui.umd.js"}", "module":"${name ? "index.mjs" : "pc-vue3-ui.mjs"}", "repository": { "type": "git", "url": "git+https://github.com/GGXXMM/vue3-ui.git" }, "keywords": ["vue3", "组件库", "UI"], "author": "guoxinming", "license": "ISC" } ` // 单个组件 or 全量 const filePath = path.resolve( outputDir, name ? `${name}/package.json` : `package.json` ) fs.outputFile(filePath, fileStr, "utf-8") } /** 单组件按需构建 */ const buildSingle = async name => { await build( defineConfig({ ...baseConfig, build: { lib: { entry: path.resolve(compontsDir, name), name: "index", fileName: "index", formats: ["es", "umd"] }, rollupOptions, outDir: path.resolve(outputDir, name) } }) ) createPackageJson(name) } /** 全量构建 */ const buildAll = async () => { await build( defineConfig({ ...baseConfig, build: { lib: { entry: compontsDir, name: "pc-vue3-ui", fileName: "pc-vue3-ui", formats: ["es", "umd"] }, rollupOptions, outDir: outputDir } }) ) createPackageJson() } const buildLib = async () => { await buildAll() // 按需打包 fs.readdirSync(compontsDir) .filter(name => { // 获取组件的目录 const componentDir = path.resolve(compontsDir, name) const isDir = fs.lstatSync(componentDir).isDirectory() return isDir && fs.readdirSync(componentDir).includes("index.ts") }) .forEach(async name => { await buildSingle(name) }) } buildLib()

执行完打包脚本 scripts/build.js,build文件夹下自动编译生成如下文件:
image.png

发布 NPM

组件库源码编译打包完成后,我们准备将打包好代码发到 NPM 上,激动人心的时刻即将到来!!我们先来梳理下发布步骤:

  1. 注册npm账号,前往 www.npmjs.com/ 注册(若已有账号跳过此步骤)
    image.png

  2. 设置npm registry源地址(若当前源已是npm可跳过此步骤)

npm config set registry https://registry.npmjs.org
npm config set registry https://registry.npmjs.org
npm config set registry https://registry.npmjs.org
  1. 账号登录及核对(若已登录并确认账号可跳过此步骤)
# 登录账号
npm login
# 核对账号
who am I
# 登录账号
npm login

# 核对账号
who am I
# 登录账号 npm login # 核对账号 who am I
  1. npm publish 发布
# 此处只想发布打包编译后的文件
npm publish ./build
# 此处只想发布打包编译后的文件
npm publish ./build
# 此处只想发布打包编译后的文件 npm publish ./build

image.png
看到上图信息表示发布成功啦??,到 NPM 官网也可搜索验证:

image.png

如果发布操作频繁,我们可以封装一个发布脚本,自动化执行发布,节省时间。脚本代码如下:

publish.sh:

npm config set registry https://registry.npmjs.org
npm login # 登录,如果有 OTP, 邮箱会接收到验证码,输入即可
# 登录成功后,短时间内会保存状态,可以直接
npm publish ./build # 可能会报错提示已存在,升级个版本号再发
# 还原镜像地址
npm config set registry https://registry.npmmirror.com
npm config set registry https://registry.npmjs.org
npm login # 登录,如果有 OTP, 邮箱会接收到验证码,输入即可
# 登录成功后,短时间内会保存状态,可以直接
npm publish ./build # 可能会报错提示已存在,升级个版本号再发
# 还原镜像地址
npm config set registry https://registry.npmmirror.com
npm config set registry https://registry.npmjs.org npm login # 登录,如果有 OTP, 邮箱会接收到验证码,输入即可 # 登录成功后,短时间内会保存状态,可以直接 npm publish ./build # 可能会报错提示已存在,升级个版本号再发 # 还原镜像地址 npm config set registry https://registry.npmmirror.com

总结

行文至此,我们已实现了从0-1构建Vue3组件,从概要设计到组件开发,再到单元测试和打包发布的全流程。(Vue3组件库专题完美收官啦!!)剩下的工作就是继续完善组件库,有兴趣参与的小伙伴欢迎到 github 协同开发~~

本专栏文章:

  1. 【前端工程化-组件库】从0-1构建Vue3组件库(概要设计)
  2. 【前端工程化-组件库】从0-1构建Vue3组件库(组件开发)
  3. 【前端工程化-组件库】从0-1构建Vue3组件库(单元测试)
  4. 【前端工程化-组件库】从0-1构建Vue3组件库(打包发布)

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYQUr7Pv' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片