Vite SSR & MSW build 失敗
set: 踩坑分享
tags: Vite
SSR
Svelte
category: Front-End
description: Vite SSR & MSW build 失敗
created_at: 2023/04/11 20:00:00
updated_at: 2023/04/16 14:00:00
2023.04.16 更
我今天忽然想到,還有 hooks.server
可以用,就不需要那麼麻煩的在 layout
去做(還要判斷是否在 server
環境),後來發現只要在 hooks.server
做就沒事了。
原來是我自己在耍笨,不過至少也學到了解決問題。
所以下面的內容變成了 debug
過程經驗分享,或是說 vite.config
的 resolve.alias
使用案例分享(?)
雖然我還是很想知道為什麼在 layout
做就會出事就是了..
下方內容可能已過時,有興趣再往下閱讀
前言
最近在做課程作業的時候想拿 Svelte
(準確來說是 Sveltekit
) 當練習,追求完整性的我當然上了 ,最後在 MSW(Mock Service Worker)
Build
的時候居然噴錯了。
而且 github
上也有在討論這個問題,看起來是 Vite
然後 SSR(Server Side Render)
就會碰到這個問題的樣子。
雖然上面的討論沒有得到解答,但至少我今天嘗試的時候讓他 Build
成功了,所以想說寫個一篇記錄一下。
順便廣告一下自己寫的 Svelte
系列,有興趣可以去看看。
重現錯誤
懶得看官方慢慢來的話,直接照下面步驟(如果套件沒大改的話應該能動XD)
$ npm i msw -D
建立檔案: src/mocks/handlers.js
import { rest } from 'msw'
export const handlers = [
rest.get('/user', (req, res, ctx) => {
return res(
ctx.status(200)
)
}),
]
初始化 service-worker
$ npx msw init static --save
建立檔案: src/mocks/browser.js
import { setupWorker } from 'msw'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
建立檔案: src/mocks/server.js
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
整合 browser
,建立: src/hooks.client.js
if (import.meta.env.MODE === 'development') {
const {worker} = await import('./mocks/browser')
worker.start()
}
這時你可以先 Build
看看,理論上會成功,因為問題出在 SSR
,跟 CSR
無關
$ npm run build
再來整合 server
,建立: src/routes/+layout.js
import { browser } from '$app/environment'
if(!browser && process.env.NODE_ENV === 'development') {
const { server } = await import('../mocks/server')
server.listen()
}
這時候 Build
就會出事了
"setTimeout" is not exported by "__vite-browser-external", imported by "node_modules/msw/lib/node/index.mjs".
file: /node_modules/msw/lib/node/index.mjs:28:9
26:
27: // config/polyfills-node.ts
28: import { setTimeout as nodeSetTimeout } from "timers";
^
29: var setTimeout = nodeSetTimeout;
[vite-plugin-sveltekit-compile] "setTimeout" is not exported by "__vite-browser-external", imported by "node_modules/msw/lib/node/index.mjs".
file: /node_modules/msw/lib/node/index.mjs:28:9
26:
27: // config/polyfills-node.ts
28: import { setTimeout as nodeSetTimeout } from "timers";
^
29: var setTimeout = nodeSetTimeout;
✓ built in 2.87s
error during build:
RollupError: "setTimeout" is not exported by "__vite-browser-external", imported by "node_modules/msw/lib/node/index.mjs".
at error (file:////node_modules/rollup/dist/es/shared/node-entry.js:2128:30)
at Module.error (file:////node_modules/rollup/dist/es/shared/node-entry.js:13322:16)
at Module.traceVariable (file:////node_modules/rollup/dist/es/shared/node-entry.js:13707:29)
at ModuleScope.findVariable (file:////node_modules/rollup/dist/es/shared/node-entry.js:12210:39)
at Identifier.bind (file:////node_modules/rollup/dist/es/shared/node-entry.js:8103:40)
at VariableDeclarator.bind (file:////node_modules/rollup/dist/es/shared/node-entry.js:5749:23)
at VariableDeclaration.bind (file:////node_modules/rollup/dist/es/shared/node-entry.js:5745:28)
at Program.bind (file:////node_modules/rollup/dist/es/shared/node-entry.js:5745:28)
at Module.bindReferences (file:////node_modules/rollup/dist/es/shared/node-entry.js:13318:18)
at Graph.sortModules (file:////node_modules/rollup/dist/es/shared/node-entry.js:24704:20)
解決過程
看看錯誤訊息,關鍵就是 setTimeout
找不到,然後進去看原始碼:
// config/polyfills-node.ts
import { setTimeout as nodeSetTimeout } from "timers";
var setTimeout = nodeSetTimeout;
他是死在 import { setTimeout as nodeSetTimeout } from "timers";
這一行,不太清楚為什麼他的 polyfills
沒有正常運作,我也去嘗試過一些 polyfill
相關的套件,但也是沒有用。
於是我就稍微測一下上面那段 code
真的有問題嗎?
建立檔案: test.js
import { setTimeout as nodeSetTimeout } from "timers";
var setTimeout = nodeSetTimeout;
console.log(setTimeout)
然後執行他:
$ node test.js
輸出:
[Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
}
於是我就想到那我把 vite
加入一個 alias
在 resolve
的時候是不是就可以解決?
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
resolve: {
alias: {
'timers': '/test'
}
}
});
註: 因為目前還在測試,所以路徑弄得很醜,可以自己調整把他弄漂亮一些
這時再改一下 test.js
,因為剛才只有輸出並沒有 export
import { setTimeout as nodeSetTimeout } from "timers";
var setTimeout = nodeSetTimeout;
export {
setTimeout
}
這時就成功了,超感動Der,於是再改成下面的寫法,因為總不能這樣寫死,這樣不好。
export * from "timers";
這時居然就失敗了?!!!,我先懷疑是我自己語法下錯,所以我又開了一個檔案 time.js
import { setTimeout } from './test.js'
console.log(setTimeout)
這時執行 node time.js
,會輸出:
[Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
}
WTF?!
,沒道理阿,我是不是哪裡搞錯了什麼?
這時我又嘗試下面這樣:
import { setTimeout } from "timers";
export {
setTimeout
}
這樣居然也是失敗??
不就差一個 var setTimeout = nodeSetTimeout;
而已嗎..
於是我嘗試了很久,最後採用這個方式解決(雖然還是有點寫死..,幸好這個模組提供的方法不多):
const {
setTimeout,
clearTimeout,
setInterval,
clearInterval,
setImmediate,
clearImmediate
} = await import('timers');
export {
setTimeout,
clearTimeout,
setInterval,
clearInterval,
setImmediate,
clearImmediate
};
順帶一提,下面這樣居然不行哦XD
import {
setTimeout,
clearTimeout,
setInterval,
clearInterval,
setImmediate,
clearImmediate
} from 'timers';
export {
setTimeout,
clearTimeout,
setInterval,
clearInterval,
setImmediate,
clearImmediate
};
靜態 import
不行, 動態 import
才可以,唉..嘆氣。
總之就到這邊解決了。終於 Build
得起來了。
解決懶人包(乾淨版)
- 建立檔案:
src/polyfills/timers.js
const { setTimeout, clearTimeout, setInterval, clearInterval, setImmediate, clearImmediate } =
await import('timers');
export { setTimeout, clearTimeout, setInterval, clearInterval, setImmediate, clearImmediate };
註: 也可以使用 .ts
- 修改
vite.config.[js|ts]
import { fileURLToPath, URL } from 'node:url';
export default defineConfig({
// 略
resolve: {
// 略
alias: {
// 略
timers: fileURLToPath(new URL('./src', import.meta.url) + '/polyfills/timers.js')
}
}
})
總結
解決辦法很簡單,不過沒辦法立刻找到現成解法就需要花一點時間去研究,希望可以幫到碰到一樣問題的人,或是之後官方直接解決,就不用那麼麻煩了。