2025/10/27

Varnish

varnish 是一種反向代理伺服器軟體,以記憶體方式存取 cache。透過 VCL (Varnish Configuration Language) 讓使用者設定 varnish。

安裝

在 Rocky Linux 8 安裝

dnf -y install varnish

設定

修改原本的設定檔

mv /etc/varnish/default.vcl /etc/varnish/default.vcl.bak
vi /etc/varnish/default.vcl

此設定檔只會 cache png 圖片檔案 7 天

backend default {
    .host = "127.0.0.1";  # Your app server
    .port = "8081";       # Your app server port
}

sub vcl_recv {
    # 僅對 GET 和 HEAD 快取
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }

    # 僅快取 .png 圖檔,其餘請求不快取
    # 比對 URL 結尾為 .png
    if (req.url ~ "\.png$") {
        # cache
        return (hash);
    } else {
        # pass
        return (pass);
    }
}

sub vcl_backend_response {
    if (beresp.status == 200) {
        # Cache for 7 day
        set beresp.ttl = 7d;
    } else {
        set beresp.ttl = 0s;
    }
}

sub vcl_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
}

啟動

systemctl start varnish
systemctl enable varnish

修改 disk cache

Varnish 無法永久將 cache 儲存到 disk (decprecated),但可透過 memory mapping,將記憶體的資料存放到 file

sudo systemctl edit varnish

修改 service

[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd \
  -a :6081 \
  -f /etc/varnish/default.vcl \
  -s file,/var/lib/varnish/varnish_storage.bin,2G

確認 file folder 存在

sudo mkdir -p /var/lib/varnish
sudo chown varnish: /var/lib/varnish

restart Varnish

# restart varnish
sudo systemctl daemon-reexec
sudo systemctl restart varnish

2025/10/13

如何使用 vue3-draggable-next esm module

vue3-draggable-next 是 draggable 套件的 vue3 版本,預設在 dist 裡面,只提供 umd 及 commonjs module,如果要在 browser 裡面,透過 esm 的方式 import module,必須要先自己製作一個簡單的 esm js

vuedraggable.umd.js 檔案,就跟 vuedraggable.umd.min.js 放在同一個目錄

// 確保 UMD 先載入(它會掛在 window.vuedraggable)
import "./vuedraggable.umd.min.js";
// 把全域變數 export 出去,提供 ESM 的 default
export default window.vuedraggable;

然後在 html 裡面,先以 importmap 方式列出 import list

因為 draggable 有使用到 sortablejs,故這邊要先放到 import list

另外在下面的 module 裡面,要先將 Vue 及 Sortable 掛載到全域變數裡面,因為 draggable 是這樣直接呼叫 Vue 跟 Sortable,所以必須這樣掛載

    <script type="importmap">
    {
        "imports": {
            "vue": "../js/lib/vue-3.5.13/vue.esm-browser.prod.min.js",
            "vuedraggable": "../js/lib/vue3-draggable-next-4.1.4/vuedraggable.esm.js",
            "sortablejs": "../js/lib/sortable-1.15.6/sortable.esm.js"
        }
    }
    </script>

    <script type="module">
        import * as Vue from 'vue';
        window.Vue = Vue; // 讓 draggable UMD 找得到 Vue.defineComponent

        import Sortable from 'sortablejs';
        window.Sortable = Sortable;
    </script>

最後可製作 App

  <script type="module">
    import { createApp, reactive } from "vue";
    import Draggable from "vuedraggable";

    const state = reactive({
      rows: [
        { id: 1, name: "Item A" },
        { id: 2, name: "Item B" },
        { id: 3, name: "Item C" }
      ]
    });

    createApp({
      components: { Draggable },
      setup() {
        return { state };
      }
    }).mount("#app");
  </script>

html 的部分,可放在 tbody 裡面。

這邊注意 #item="{ element, index } #item 裡面,只能用 element, index,不能改成其他變數名稱。否則會一直遇到 undefined 物件的問題。

<div id="app">
    <table border="1">
      <thead>
        <tr>
          <th>Drag</th>
          <th>Name</th>
        </tr>
      </thead>
      <!-- draggable tbody -->
      <draggable
        tag="tbody"
        v-model="state.rows"
        handle=".drag-handle"
      >
        <template #item="{ element, index }">
          <tr :key="element.id">
            <td class="drag-handle" style="cursor: grab;">☰</td>
            <td>{{ element.name }}</td>
          </tr>
        </template>
      </draggable>
    </table>
    <pre>{{ state.rows }}</pre>
  </div>

vuedraggable 是 SortableJS 的包裝,所以大部分事件都對應到 SortableJS events,最常用的是:

  • @start 拖曳開始

  • @end 拖曳結束

  • @add 新元素被加入(跨清單)

  • @remove 元素被移除

  • @update 同一清單內順序改變

  • @change 綜合事件(新增、刪除、移動都會觸發)

實際上操作時

  • 拖曳開始到結束:會觸發 @start 與 @end

  • 順序有變動:會觸發 @update 和 @change

  • 跨清單拖曳:會觸發 @add 與 @remove

2025/09/22

vue3 runtime

如果要使用 vue3 runtime,也就是 vue.runtime.esm-browser.js,而不使用 vue.esm-browser.js。這兩個版本的差異是使用後者,有支援 template,可直接將 template 字串寫在 component 裡面,但如果使用 runtime library,因為這個檔案大小比較小,缺少了動態編譯 template 的功能,必須改寫為使用 render function。

實例

以下是可以直接在 browser 執行的範例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title></title>
</head>
<body>
<div id="app"></div>

<script type="importmap">
    {
        "imports": {
            "vue": "https://unpkg.com/vue@3.5.13/dist/vue.runtime.esm-browser.js",
            "vue-i18n": "https://unpkg.com/vue-i18n@11.1.1/dist/vue-i18n.runtime.esm-browser.js"
        }
    }
</script>

<script type="module">
    import { createApp, h } from 'vue';
    import { createI18n, useI18n } from 'vue-i18n';

    const messages = {
        en: { greeting: 'Hello!' },
        zh: { greeting: '你好!' }
    };

    const i18n = createI18n({
        legacy: false,
        locale: 'en',
        messages
    });

    const App = {
        setup() {
            const { t, locale } = useI18n(); // 正確取得 t() 函數

            const toggleLang = () => {
                locale.value = locale.value === 'en' ? 'zh' : 'en';
            };

            return () =>
                h('div', [
                    h('h1', t('greeting')),
                    h('button', { onClick: toggleLang }, '🌐 Switch Language')
                ]);
        }
    };

    createApp(App).use(i18n).mount('#app');
</script>
</body>
</html>

compiler-dom

vue3 官方提供了一個 compiler-dom,可以將 template 字串轉換為 render function。

安裝首先要安裝 compiler-dom

npm i @vue/compiler-dom

撰寫一個 convert.js

// https://www.npmjs.com/package/@vue/compiler-dom
// npm i @vue/compiler-dom

const fs = require('fs');
const path = require('path');
const { compile } = require('@vue/compiler-dom');

const file = process.argv[2];

if (!file || !file.endsWith('.template.html')) {
  console.error('請提供一個 .template html 檔案,例如:node convert.js MyComponent.html');
  process.exit(1);
}

const filePath = path.resolve(process.cwd(), file);

const template = fs.readFileSync(filePath, 'utf-8');

// 編譯 template 成 render 函數
const { code } = compile(template, {
  mode: 'module',
  prefixIdentifiers: true, // 避免 with()
  filename: file
});

// 產出 JS 檔案名稱
const baseName = path.basename(file, '.template.html');
const outputFile = `${baseName}.template.render.js`;

// // 包裝為可匯入的模組
const outputContent = `
${code}
`;
// 寫入檔案
fs.writeFileSync(outputFile, outputContent.trim());

console.log(`Render function 已輸出為:${outputFile}`);

使用方法

把 template 的部分,改為獨立的 test.template.html 檔案

<div>
    <h1> {{ t("greeting") }} </h1>
    <button @click="toggleLang()">🌐 Switch Language</button>
</div>

透過 nodejs 將 template 轉換為 render function

import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache) {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("h1", null, _toDisplayString(_ctx.$t("greeting")), 1 /* TEXT */),
    _createElementVNode("button", {
      onClick: $event => (_ctx.toggleLang())
    }, "🌐 Switch Language", 8 /* PROPS */, ["onClick"])
  ]))
}

改寫原本的測試網頁

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title></title>
</head>
<body>
<div id="app"></div>

<script type="importmap">
    {
        "imports": {
            "vue": "https://unpkg.com/vue@3.5.13/dist/vue.runtime.esm-browser.js",
            "vue-i18n": "https://unpkg.com/vue-i18n@11.1.1/dist/vue-i18n.runtime.esm-browser.js",

            "render": "./test.template.render.js"
        }
    }
</script>

<script type="module">
    import { createApp, h } from 'vue';
    import { createI18n, useI18n } from 'vue-i18n';
    import {render} from 'render';

    const renderFn = render;

    const i18n = createI18n({
        legacy: false,
        locale: 'en',
        fallbackLocale: "en",
        messageCompiler: null,
        messages: {
            "en": {
                "greeting": 'Hello!'
            },
            "zh": {
                "greeting": '你好!'
            },
        },
    });

    const App = {
        setup() {
            const { t, locale } = useI18n();

            const toggleLang = () => {
                locale.value = locale.value === 'en' ? 'zh' : 'en';
            };

            return {
                t, locale, toggleLang
            };
        },
        render: renderFn,
        methods: {
        }
    };

    createApp(App).use(i18n).mount('#app');
</script>
</body>
</html>

note

點擊切換語言時,網頁 console 會出現這樣的警告訊息

[intlify] the message that is resolve with key 'greeting' is not supported for jit compilation

不影響網頁操作,但還不知道為什麼會出現這個 warning