Vue3 i18n Unit test 與 CSP 問題

tags: Vue i18n CSP
category: Front-End
description: Vue3 i18n 測試與 CSP (Content Security Policy) 問題
created_at: 2023/09/01 15:30:00

cover image


前言

最近在做學校系統的 i18n,做完之後發現跑測試就掛掉了,然後修好讓測試跑過之後,嘗試 build 之後跑看看,發現了 CSP 的問題,所以就來紀錄一下碰到的問題跟解決方式,未來在碰到就回來看

使用的測試套件是 vitest,然後用 Vue3 原生的 test-utils 來測試。

然後這個專案的 vite 版本為 3.x,所以後面用到的套件會是支援 vite 3.x 的版本。


i18n 在測試碰到的問題

問題一

錯誤訊息(節錄)

SyntaxError: Need to install with `app.use` function

因為忘了在 mount 的時候加上 i18n 的 plugin,所以你需要在 mount 的時候加上 i18n 的 plugin,像是下面這樣:

註: 只是示意,不是完整的程式碼

import { mount } from '@vue/test-utils';
import { createI18n } from 'vue-i18n';
import { messages } from '@/i18n';

const i18n = createI18n({
    locale: 'zh-TW',
    messages,
});

const wrapper = mount(Component, {
    global: {
        plugins: [i18n],
    },
});

或是在全域設定 i18n 的 plugin,像是下面這樣:

import { config } from "@vue/test-utils";

config.global.plugins = [i18n];

問題二

錯誤訊息(節錄)

TypeError: _ctx.$t is not a function

在 template 當中直接使用 $t() ,但是他沒抓到 $t,所以你需要給他 mock 一下,這邊一樣簡單掛到 global 上面,像是下面這樣:

import { config } from "@vue/test-utils";

config.global.mocks = {
  $t: (msg) => msg,
};

如果你的測試資料不存在 i18n 設定的 messages 當中,會跳出 [intlify] Not found 'text' key in 'xx' locale messages. 這個訊息,而用這個方式也可以另類的解決他。 (畢竟就跳過了)

我是覺得測試的時候不太 care 這個問題,除非你有特別的目的,不然就直接 mock 掉就好了。


CSP 問題

當前面測試都通過了,這時就可以 build 看看了,然後發現了 CSP 的問題。前提是你有設定,不然就不會有問題

錯誤訊息(節錄):

EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive

這個可以看官方,他有提到這個問題,然後他也有提供解決方式,主要是這一段:

IF CSP is enabled, vue-i18n.esm-bundler.js would not work with compiler due to eval statements. These statements violate the default-src 'self' header. Instead you need to use vue-i18n.runtime.esm-bundler.js.

所以你需要改用 vue-i18n.runtime.esm-bundler.js,然後你可以在 vite.config.js 裡面加上下面這段:

import { defineConfig } from 'vite';

export default defineConfig({
    resolve: {
        alias: {
            "@": fileURLToPath(new URL("./src", import.meta.url)),
            "vue-i18n": "vue-i18n/dist/vue-i18n.runtime.esm-bundler.js", // <-- 加入這行
        },
    },
});

然後下 build 之後測試,正當我以為一切順利,後來發現他翻譯的怪怪的(雖然過了CSP)

雖然沒有錯誤訊息,不過就是我們知道同個 key 可以透過 | 來做分隔,然後使用上可以指定要用哪一個翻譯,但是他卻把全部輸出出來,例如:

{
    "foo": "bar | baz"
}

理論上我應該只需要 bar 或是 baz,但是他卻把全部輸出出來,像是下面這樣:

bar | baz

這並不是我要的,所以還需要再處理。

接著我將文件往下翻且嘗試了一下,發現根本不需要做上面的事情XD

接著要使用一個 vite 的 plugin,名字叫做 vite-plugin-vue-i18n,因為這邊 vite 版本是 3.x,所以使用這個。

在 vite.config 裡面加上下面這段:

import { defineConfig } from 'vite';

export default defineConfig({
    plugins: [
        vueI18n({
            include: resolve(
                dirname(fileURLToPath(import.meta.url)),
                "./src/assets/locales/**"  // <-- your locale directory path
            ),
        }),
    ],
});

然後我跑了 run dev,發現一切如此的美好,接著我要 build 來看看成效,卻..

[vite:load-fallback] Could not load vue-i18n/dist/vue-i18n.runtime.mjs (imported by src/main.js): ENOENT: no such file or directory, open 'xxx'
error during build:
Error: Could not load vue-i18n/dist/vue-i18n.runtime.mjs (imported by src/main.js): ENOENT: no such file or directory, open 'xxx'

這時去看看他的設定,發現這個 plugin 預設只跑在 runtime,而我希望 build 的時候也跑,所以多一條設定:

import { defineConfig } from 'vite';

export default defineConfig({
    plugins: [
        vueI18n({
            include: resolve(
                dirname(fileURLToPath(import.meta.url)),
                "./src/assets/locales/**"  // <-- your locale directory path
            ),
            runtimeOnly: false, // <-- 加入這行
        }),
    ],
});

這時在 build 就會過了。


題外話

  • Q: 我是怎麼在 Local 測試 CSP 的
  • A: 我為前端專案包了一個 Dockerfile,然後在 nginx 裡面加上 CSP 的 header 設定,這樣基本上我 local 沒問題,部署到 server 上面也應該不會有問題。

結論

其實這是開會的時候提到 i18n ,本來預估是明年中才要上線,但我說我已經做好且 push 到一個 feature 的分支上了,只是還沒發 pr 併到測試機,然後就決定要放上測試機給大家測一測,如果沒問題也能提早上,就碰到了這些問題。

因為這個專案當時沒有導入 husky 等 git hook,而我當初想說只是套套 i18n,我又沒對測試寫太多過於死的 test case,應該不會碰到什麼問題,所以其實有點偷懶沒在 local 先跑過測試,最後在 CI 階段被我發現,就立刻把他修正。

然後 CSP 也是沒想到會中,就剛好趁這機會一次把他搞定。

偷偷工商: 安裝 husky 或其他開發套件到你的前端專案,也可以透過我做的 lai-cmd,用法非常簡單。

在我合併回 main 之前,都要下 @dev 的標籤,如下:

$ npx lai-cmd@dev init



最後更新時間: 2023年09月01日.