vite的專案,使用 rollup 打包的方法

金色海洋(jyk)發表於2021-11-30

官網資料

構建生產版本——庫模式
https://cn.vitejs.dev/guide/build.html#library-mode

詳細設定
https://cn.vitejs.dev/config/#build-lib

技術棧

  • vite
  • rollup

打包方式

vue-cli 自帶的是 webpack 的打包方式,打出的包體積有點大,而 vite 自帶的是 rollup 的打包方式,這種方式打包的體積就非常小,官網也有一些使用說明,所以學會之後還是比較很方便的。

vite 的庫專案可以分為兩類:(我自己分的)

  • 一個是純js的專案,不帶HTML;
  • 一個是可以帶上HTML(模板)的專案,比如UI庫。

下面分別介紹一下編寫和打包方式,其實大同小異。

純js的庫專案

使用 vite 建立專案,這裡舉一個簡單的例子:

// main.js

 
  const toTypeString = (val) => { 
    return Object.prototype.toString.call(val)
  }
  
  const typeName = (val) => {
    return Object.prototype.toString.call(val).replace(/^\[object (\S+)\]$/,'$1').toLowerCase()
  }

  const hasOwnProperty = Object.prototype.hasOwnProperty
  const hasOwn = (val, key) => hasOwnProperty.call(val, key)

  const isFunction = (val) => toTypeString(val) === '[object Function]'
  const isAsync = (val) => toTypeString(val) === '[object AsyncFunction]'
  const isObject = (val) => val !== null && typeof val === 'object'
  const isArray = Array.isArray
  const isString = (val) => typeof val === 'string'
  const isNumber = (val) => typeof val === 'number'
  const isBigInt = (val) => typeof val === 'bigint'
  const isBoolean = (val) => typeof val === 'boolean'
  const isRegExp = (val) => toTypeString(val) === '[object RegExp]'
  const isDate = (val) => val instanceof Date
  const isMap = (val) => toTypeString(val) === '[object Map]'
  const isSet = (val) => toTypeString(val) === '[object Set]'
  const isPromise = (val) => toTypeString(val) === '[object Promise]'
  const isSymbol = (val) => typeof val === 'symbol'
  const isNullOrUndefined = (val) => {
    if (val === null) return true
    if (typeof val === 'undefined') return true
    return false
  }

  function log(){
    if (window.__showlog) console.log(...arguments)
  }
  const logTime = (msg, auto = true) => {
    const start = () => {
      if (window.__showlog) console.time(msg)
    }
    const end = () => {
      if (window.__showlog) console.timeEnd(msg)
    }
    if (auto) start() // 自動開始計時
    return { start, end }
  }

export {
  log, // 列印除錯資訊
  logTime, // 計時
  toTypeString, // Object.prototype.toString.call(val)
  typeName, // 獲取可以識別的名稱

  hasOwnProperty,
  hasOwn,

  isFunction, // 驗證普通函式
  isAsync, // 驗證 async 的函式
  isPromise, // 驗證 Promise
  isObject, // 驗證 Object
  isArray, // 驗證陣列
  isString, // 驗證字串
  isNumber, // 驗證 number
  isBigInt, // 驗證 BigInt
  isBoolean, // 驗證 布林
  isRegExp, // 驗證正則型別
  isDate, // 驗證日期
  isMap, // 驗證 map
  isSet, // 驗證 set
  isSymbol, // 驗證 Symbol
 
  isNullOrUndefined // null 或者 undefined 返回 true
}

程式碼比較簡單,僅僅只是演示。

想要打包的話,只能有一個出口檔案,所以內部的程式碼結構要設定好。

帶HTML的庫專案

純js的好辦了,export 輸出就好,那麼帶模板的怎麼辦呢?其實也是一樣的。

用 vite 建立一個專案,建立一個測試檔案:

// t-text.vue

  • 模板部分:
<!--單行文字-->
<template>
  <el-input
    v-model="value"
    :id="'c' + columnId"
    :name="'c' + columnId"
    :size="size"
    :clearable="clearable"
    :validate-event="validate_event"
    :show-word-limit="show_word_limit"
    @blur="run"
    @change="run"
    @clear="run"
    @input="myinput"
    @keydown="clear"
  >
  </el-input>
  • 程式碼部分
  import { defineComponent } from 'vue'
  // 按需索取的方式引入UI庫(非必須)
  import { ElInput } from 'element-plus'
  // 引入元件需要的屬性、表單子控制元件的管理類
  import { itemProps, itemController } from 'nf-ui-controller'

  export default defineComponent({
    name: 'el-form-item-text',
    props: {
      'el-input': ElInput,
      modelValue: [String, Number],
      ...itemProps // 基礎屬性
    },
    emits: ['update:modelValue'],
    setup (props, context) {
      const {
        value,
        run,
        clear,
        myinput
      } = itemController(props, context.emit)

      return {
        value,
        run,
        clear,
        myinput
      }
    }
  })
 

這是基於UI庫做的二次封裝的庫,使用了兩個第三方外掛:

  • 一個是 element-plus,採用按需載入的方式;
  • 一個是自己做的 nf-ui-controller 庫。

然後我們設定一個入口檔案 main.js

import nfText from './t-text.vue'

export {
  nfText
}

如果有很多元件的話,就要考慮好結構,這裡只是舉個例子。

設定vite.config.js

程式碼寫好之後,需要設定一下 vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path' // 主要用於alias檔案路徑別名
 
export default defineConfig({
  plugins: [vue()],
  // 打包配置
  build: {
    lib: {
      entry: resolve(__dirname, 'lib/main.js'), // 設定入口檔案
      name: 'nf-tool', // 起個名字,安裝、引入用
      fileName: (format) => `nf-tool.${format}.js` // 打包後的檔名
    },
    sourcemap: true, // 輸出.map檔案
    rollupOptions: {
      // 確保外部化處理那些你不想打包進庫的依賴
      external: ['vue'],
      output: {
        // 在 UMD 構建模式下為這些外部化的依賴提供一個全域性變數
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
})

  • entry:指定要打包的入口檔案。
  • name:包的名稱
  • fileName:包檔案的名稱,預設是umd和es兩個檔案。
  • sourcemap:是否生成 .map 檔案,預設是不會生成的,如果需要的話需要設定為 true。
  • rollupOptions:如果專案引用了第三方外掛,那麼需要在這裡設定排除,如果不設定的話,第三方外掛的原始碼也會被打包進來,這樣打包檔案就變大了。排除之後第三方的外掛會單獨存在。

vite 會按照這裡的設定進行打包,打包分為兩種模式:

  • umd:打包後程式碼很緊湊、體積小,但是不易讀;
  • es:打包後的程式碼和我們寫的程式碼很像,易讀,但是體積大。

第三方外掛的處理方式

如果專案使用了第三方的外掛,那麼需要在 external 裡面做設定:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path' // 主要用於alias檔案路徑別名

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  // 打包配置
  build: {
    lib: {
      entry: resolve(__dirname, 'lib/main.js'),
      name: 'nf-ui-element-plus',
      fileName: (format) => `nf-ui-element-plus.${format}.js`
    },
    sourcemap: true,
    rollupOptions: {
      // 確保外部化處理那些你不想打包進庫的依賴
      external: ['vue','nf-ui-controller','element-plus'], // 注意看這裡
      output: {
        // 在 UMD 構建模式下為這些外部化的依賴提供一個全域性變數
        globals: {
          vue: 'Vue',
          'nf-ui-controller': 'nfUIController',
          'element-plus': 'elementPlus'
        }
      }
    }
  }
})

設定之後,第三方外掛的程式碼,會以 import 的方式被引用。
如果不做設定的話,就會把第三方外掛裡面使用到的程式碼,拿出來作為專案內部程式碼一起被打包,這樣包的體積就變大了。

設定package.json

設定 package.json 主要是釋出的時候做資源包的說明,需要按照 npm 的要求設定屬性:

{
  "name": "nf-tool",
  "version": "0.0.4",
  "description": "JavaScript 的小工具,驗證 JavaScript 的資料型別,輸出除錯資訊等。",
  "keyword": "JavaScript typeof log",
  "files": ["dist"],
  "main": "./dist/nf-tool.umd.js",
  "module": "./dist/nf-tool.es.js",
  "exports": {
    ".": {
      "import": "./dist/nf-tool.es.js",
      "require": "./dist/nf-tool.umd.js"
    }
  },
  "private": false,
  "license": "MIT",
  "auther": "jin yang (jyk). Email: jyk0013@163.com",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview"
  },
  "dependencies": {
    "@element-plus/icons": "^0.0.11",
    "element-plus": "^1.2.0-beta.3",
    "nf-tool": "^0.0.6",
    "vue": "^3.2.16"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.9.3",
    "vite": "^2.6.4"
  }
}

如果在安裝資源包的時候,不想自動安裝第三方外掛的話,需要去掉 dependencies 和 devDependencies,否則會自動安裝裡面涉及到的第三方外掛。

打包

設定好之後我們可以用 yarn build 進行打包。

打包後的檔案是這種風格:

  • nf-tool-es.js

*.es.js的效果

  • nf-tool-umd.js

*.umd.js 的效果

釋出資源包到 npmjs.com

簡單介紹一下步驟:

  • 先到 npmjs.com 網站註冊賬號;
  • 到註冊郵箱啟用賬號;
  • 開啟終端,使用 npm login登入;
  • 使用 npm publish 釋出;

篇幅有限,細節就不介紹了。

安裝資源包

我喜歡使用 yarn 安裝資源包,因為速度更快一些。

yarn add nf-tool

package.json 裡面設定的名稱就是安裝用的名稱,所以要起個好的名稱,另外不能和現有的名稱重複。

如果一個專案即是庫專案,又需要釋出到網站怎麼辦?

為啥會有這樣的需求?
庫專案在編寫的時候,需要一個開發環境,一邊寫程式碼,一邊執行看效果。
寫好之後需要按庫專案的方式打包。
然後是不是需要一個測試環境,或者演示環境?

如果再建立一個專案寫測試和演示,那麼就有點麻煩了,如果可以在一個專案搞定就方便多了。
這個時候就需要設定不同的 vite.config.js 。

之前使用註釋的方式,改來改去的比較麻煩。現在發現 vite 提供了“模式”的方式,允許我們在 vite.config.js 裡面做不同的設定。

設定.env檔案

按照官網的說明,我們可以建立多個 .env.* 檔案,來存放不同的“模式”:

.env : 開發環境(預設)

VITE_BASEURL=./

.env.project : 測試、演示環境

VITE_BASEURL=nf-rollup-tool

.env.lib : 庫打包

VITE_BASEURL=lib

模式設定好之後我們來修改 vite.config.js

修改 vite.config.js

在 vite.config.js 裡面,首先定義不同的 defineConfig 備用,然後使用 loadEnv 讀取模式值,根據模式返回對應的 defineConfig。

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path' // 主要用於alias檔案路徑別名
const pathResolve = (dir) => resolve(__dirname, '.', dir)

// 釋出庫的設定
const lib = defineConfig({
  plugins: [vue()],
  // 打包配置
  build: {
    lib: {
      entry: resolve(__dirname, 'lib/main.js'),
      name: 'nf-tool',
      fileName: (format) => `nf-tool.${format}.js`
    },
    sourcemap: true,
    rollupOptions: {
      // 確保外部化處理那些你不想打包進庫的依賴
      external: ['vue'],
      output: {
        // 在 UMD 構建模式下為這些外部化的依賴提供一個全域性變數
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
})

// 開發模式、生產模式
const project = (url) => {
  return defineConfig({
    plugins: [vue()],
    devtools: true,
    resolve: {
      alias: {
        '/@': resolve(__dirname, '.', 'src'),
        '/nf-tool': pathResolve('lib/main.js') //
      }
    },
    base: url,
    // 打包配置
    build: {
      sourcemap: true,
      outDir: 'distp', // 指定輸出路徑,要和庫的包區分開
      assetsDir: 'static/img/', // 指定生成靜態資源的存放路徑
      rollupOptions: {
        output: {
          chunkFileNames: 'static/js1/[name]-[hash].js',
          entryFileNames: 'static/js2/[name]-[hash].js',
          assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
        },
        brotliSize: false, // 不統計
        target: 'esnext', 
        minify: 'esbuild' // 混淆器,terser構建後檔案體積更小
      }
    }
  })
}

// 用  loadEnv 讀取模式,然後返回對應的 defineConfig 
export default ({ mode }) => {
  const url = loadEnv(mode, process.cwd()).VITE_BASEURL
  if (url === 'lib') {
    // 打包庫檔案
    return lib
  } else {
    // 開發模式、生產模式
    return project(url)
  }
}

注意:需要設定不同的輸出的資料夾,否則會互相覆蓋。

修改 package.js

設定執行的命令,後面加上需要的模式。

  "scripts": {
    "dev": "vite",
    "build": "vite build --mode project",
    "lib": "vite build --mode lib",
    "serve": "vite preview"
  }

這樣就可以把不同的環境完全分開了,如果有其他的需求,還可以增加更多的模式。

更改後的打包命令

yarn dev  // 開發環境
yarn build // 打包釋出到網站,演示
yarn lib // 打包庫專案,做成資源包

這樣各司其職,互不干擾,也不用各種寫註釋了。

相關文章