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
前言
最近在做學校系統的 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