如何將Vue的composable和component經由Vite打包至npm平台,讓其他人可以載入使用

前言

想把團隊會重複使用到的composable和component打包至npm平台後重複使用,像是在Vue.js世界常遇到的VueUse、ElementPlus是如何做到套件打包,上網查相關資料將實作筆記整理出來。

建立Vue專案

1
npm init vue@next [專案名稱]

如下圖,專案名稱自行取名即可,但可以先到npm平台看一下,我們取名的名稱是否已經被別人命名走,如果有重複,最後套件部署上去會失敗喔,要特別留意。
筆者用fred-vue-library專案名稱示範。
基本上沒有額外需求,直接Enter(選No)到底即可。

建立composable (視需求)

什麼是composable?基本上就是跟js檔案很像,把裡面的共用邏輯打包出來,如果希望這個是有響應式的物件,那就需要使用到composable
如果想做composable再實作

  1. 至src資料夾底下建立composable資料夾
  2. src/composable資料夾內新增useMouse.js檔案,以下是官網composable範例用來呈現目前滑鼠所在的x和y座標
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import { ref, onMounted, onUnmounted } from 'vue'

    export function useMouse() {

    const x = ref(0)
    const y = ref(0)

    function update(event) {
    x.value = event.pageX
    y.value = event.pageY
    }

    onMounted(() => window.addEventListener('mousemove', update))
    onUnmounted(() => window.removeEventListener('mousemove', update))

    return { x, y }
    }

    官網 Composables

建立component (視需求)

  1. 先清空src/components資料夾內所有的vue檔案(因為我們不會用到)
  2. src/components資料夾內新增FButton.vue檔案
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    <script>
    export default {
    name: "FButton",
    props: {
    size: {
    type: String,
    default: "middle",
    },
    type: {
    type: String,
    default: "default",
    },
    },
    };
    </script>
    <template>
    <button class="fred-btn" :class="[size, type]">
    <slot></slot>
    </button>
    </template>

    <style>
    .fred-btn {
    appearance: none;
    border: none;
    outline: none;
    background: #fff;
    text-align: center;
    border: 1px solid transparent;
    border-radius: 4px;
    cursor: pointer;
    }

    .large {
    width: 240px;
    height: 50px;
    font-size: 16px;
    }

    .medium {
    width: 180px;
    height: 50px;
    font-size: 16px;
    }

    .small {
    width: 100px;
    height: 32px;
    }

    .default {
    border-color: #e4e4e4;
    color: #666;
    }

    .primary {
    border-color: blue;
    background: blue;
    color: #fff;
    }

    .danger {
    border-color: red;
    color: red;
    background: lighten(red, 50%);
    }
    </style>

其他配置(重要)

  1. 在src資料夾內新增index.js
    此檔案是用來放最後套件進入的第一個入口,非常重要,所以要把我們上述步驟的composable和component整理在這個js檔裡面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import FButton from "./components/FButton.vue";
    import { useMouse } from "./composable/useMouse.js";

    export { FButton, useMouse }; //用來本地引入

    //用來全域引入
    const components = [FButton];
    export default {
    install: function (Vue, options = {}) {
    components.forEach(component => {
    Vue.component(component.name, component);
    });
    },
    };
    • 把FButton, useMouse做輸出,未來在vue檔內做區域引入使用
    • 因為composable比較少用全域引入,所以就沒有做了,範例只針對component也做全域引入(未來使用者在專案內可以在main.js用 .use()方式引入)
  2. 調整vite.config.js檔案內容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import path from 'path';

    // https://vitejs.dev/config/
    export default defineConfig({
    plugins: [vue()],
    build: {
    lib: {
    entry: path.resolve(__dirname, 'src/index.js'),
    name: 'FredVueLibrary',
    fileName: (format) => `fred-vue-library.${format}.js`
    },
    rollupOptions: {
    // 確保外部化處理那些你的庫中不需要打包的依賴
    external: ['vue'],
    output: {
    // 為全局變量提供一個名稱
    globals: {
    vue: 'Vue'
    }
    }
    }
    }
    })
    • src/index.js就是上一個步驟做的檔案,作為套件的entry(入口)
  3. 調整package.json檔案內容

  • P.S.要記得先到npm平台先進行註冊,要記得使用者名稱以及信箱,要填入到author欄位
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    {
    "name": "fred-vue-library",
    "version": "1.0.0",
    "type": "module",
    "main": "./dist/fred-vue-library.umd.js",
    "module": "./dist/fred-vue-library.es.js",
    "exports":{
    ".": {
    "import": "./dist/fred-vue-library.es.js",
    "require": "./dist/fred-vue-library.umd.js"
    },
    "./style.css": "./dist/style.css"
    },
    "files": [
    "dist"
    ],
    "author": "fredchang <spadej03727@gmail.com>",
    "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
    },
    "dependencies": {
    "less": "^4.2.0",
    "vue": "^3.3.4"
    },
    "devDependencies": {
    "@vitejs/plugin-vue": "^4.2.3",
    "vite": "^4.4.5"
    }
    }
    • 如為第一次發布,version設為1.0.0即可,但如果後續有更新,要再發布,就一定要大於最新一次的發布版本。
    • exports欄位內,import我是填入es.js方式,因為現在前端幾乎都是使用ES6方式import套件,所以就比較不會用到umd的格式了,如有興趣了解差異,再自行研究。
    • 要留意如果你的package.json裡面有private欄位記得設為false,或直接刪掉即可。否則無法發佈到npm平台。
  1. 將src/App.vue內容預設內容清空,留下以下配置即可
    1
    2
    3
    4
    5
    6
    7
    <script setup>
    </script>

    <template>
    <div>
    </div>
    </template>

準備npm發布

  • 登入npm

    1
    npm login
  • 如果不確定登入狀態,可以確認一下身分(沒其他可能就可跳過)

    1
    npm whoami
  • 先把剛剛實作的程式先build出來

    1
    npm build
  • 如果發布前想先試試看套件能否順利載入可以用以下指令

    1
    npm link [套件名]

    所以如果像示範的則填入npm link fred-vue-library
    可藉由此指令,在本地引入看正不正常。
    需特別注意這個有cache問題,所以還是npm publish發布出去引入看結果最準

  • 發布

    1
    npm publish

建立其他專案安裝套件

請依照這篇文章開頭處找建立Vue專案章節,建置其他專案

  1. 先安裝Vite所需套件

    1
    npm i
  2. 建立我們剛剛發布的專案

    1
    npm i fred-vue-library

    這邊請自行換成自己的套件名

  3. 在App.vue引入相關模組,可跟著我修改如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script setup>
    import { useMouse, FButton } from "fred-vue-library";
    import "fred-vue-library/style.css";
    const { x, y } = useMouse();
    </script>


    <template>
    <div>
    <FButton size="large" type="primary">測試按鈕</FButton>
    {{ x }},{{ y }}
    </div>
    </template>

    fred-vue-library請自行換成自己發布的套件名稱。
    此為區域引入方式,

  4. 把專案運行起來

    1
    npm run dev
  5. 呈現成果

  6. (補充)使用全域引入component
    main.js調整如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import { createApp } from 'vue'
    import App from './App.vue'

    import "fred-vue-library/style.css";
    import FredVueLibrary from "fred-vue-library"

    const app = createApp(App)

    app.use(FredVueLibrary).mount('#app')

    然後把App.vue區域引入component也拿掉,css檔也拿掉,更改如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script setup>
    import { useMouse } from "fred-vue-library";
    const { x, y } = useMouse();
    </script>


    <template>
    <div>
    <FButton size="large" type="primary">測試按鈕</FButton>
    {{ x }},{{ y }}
    </div>
    </template>

    這樣也跟上一點的呈現結果相同,要不要用全域就看個人了,我個人比較喜歡區域引入,對於目前這個頁面載入了哪些元件比較清楚。

小結

  1. 透過這個整理,更熟悉Vite打包Vue元件相關過程。
  2. 一直踩到npm link的雷,所以建議部署還是以npm publish為主,這樣檢查套件是否正常運作最準確。

全部程式碼:Github Repo

參考資料
npm Docs
Medium文章-作者:Debby Ji
SF文章-作者:一柯白菜