Vue3+Vite SSR项目集成Vuetify3遇未知.css文件扩展名错误
Vue 3 + Vuetify 3 + Vite SSR 中 Unknown file extension ".css" 错误解决
错误信息
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".css" for /tmp/vite-plugin-vue/playground/ssr-vue/node_modules/vuetify/lib/components/VApp/VApp.css at new NodeError (node:internal/errors:387:5) at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:75:11) at defaultGetFormat (node:internal/modules/esm/get_format:117:38) at defaultLoad (node:internal/modules/esm/load:81:20) at nextLoad (node:internal/modules/esm/loader:163:28) at ESMLoader.load (node:internal/modules/esm/loader:605:26) at ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:22) at new ModuleJob (node:internal/modules/esm/module_job:63:26) at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:480:17) at ESMLoader.getModuleJob (node:internal/modules/esm/loader:434:34)
相关代码
应用初始化代码
import { createSSRApp } from 'vue' import 'vuetify/styles' import { createVuetify } from 'vuetify' import * as components from 'vuetify/components' import * as directives from 'vuetify/directives' import App from './App.vue' import { createRouter } from './router' export function createApp() { const app = createSSRApp(App) const router = createRouter() app.use(router) const vuetify = createVuetify({ ssr: true, components, directives, }) app.use(vuetify) return { app, router } }
Vite配置文件(vite.config.js)
import path from 'node:path' import { defineConfig } from 'vite' import vuePlugin from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify' const virtualFile = '@virtual-file' const virtualId = '\0' + virtualFile const nestedVirtualFile = '@nested-virtual-file' const nestedVirtualId = '\0' + nestedVirtualFile const base = '/test/' // preserve this to test loading __filename & __dirname in ESM as Vite polyfills them. // if Vite incorrectly load this file, node.js would error out. globalThis.__vite_test_filename = __filename globalThis.__vite_test_dirname = __dirname export default defineConfig(({ command, ssrBuild }) => ({ base, plugins: [ vuePlugin({ template: { transformAssetUrls } }), vuetify({ autoImport: true, styles: { configFile: 'src/styles/settings.scss', }, }), vueJsx(), { name: 'virtual', resolveId(id) { if (id === '@foo') { return id } }, load(id, options) { const ssrFromOptions = options?.ssr ?? false if (id === '@foo') { // Force a mismatch error if ssrBuild is different from ssrFromOptions return `export default { msg: '${ command === 'build' && !!ssrBuild !== ssrFromOptions ? `defineConfig ssrBuild !== ssr from load options` : 'hi' }' }` } }, }, { name: 'virtual-module', resolveId(id) { if (id === virtualFile) { return virtualId } else if (id === nestedVirtualFile) { return nestedVirtualId } }, load(id) { if (id === virtualId) { return `export { msg } from "@nested-virtual-file";` } else if (id === nestedVirtualId) { return `export const msg = "[success] from conventional virtual file"` } }, }, // Example of a plugin that injects a helper from a virtual module that can // be used in renderBuiltUrl (function () { const queryRE = /\?.*$/s const hashRE = /#.*$/s const cleanUrl = (url) => url.replace(hashRE, '').replace(queryRE, '') let config const virtualId = '\0virtual:ssr-vue-built-url' return { name: 'built-url', enforce: 'post', configResolved(_config) { config = _config }, resolveId(id) { if (id === virtualId) { return id } }, load(id) { if (id === virtualId) { return { code: `export const __ssr_vue_processAssetPath = (url) => '${base}' + url`, moduleSideEffects: 'no-treeshake', } } }, transform(code, id) { const cleanId = cleanUrl(id) if ( config.build.ssr && (cleanId.endsWith('.js') || cleanId.endsWith('.vue')) && !code.includes('__ssr_vue_processAssetPath') ) { return { code: `import { __ssr_vue_processAssetPath } from '${virtualId}';__ssr_vue_processAssetPath;` + code, sourcemap: null, // no sourcemap support to speed up CI } } }, } })(), ], experimental: { renderBuiltUrl(filename, { hostType, type, ssr }) { if (ssr && type === 'asset' && hostType === 'js') { return { runtime: `__ssr_vue_processAssetPath(${JSON.stringify(filename)})`, } } }, }, build: { minify: false, }, ssr: { noExternal: [ // this package has uncompiled .vue files '@vitejs/test-example-external-component', ], }, optimizeDeps: { exclude: ['@vitejs/test-example-external-component'], }, }))
解决方法
1. 更新SSR noExternal配置
在vite.config.js的ssr.noExternal数组中添加vuetify和vite-plugin-vuetify,让Vite将这些依赖纳入SSR打包流程,避免Node.js直接加载未处理的CSS文件:
ssr: { noExternal: [ '@vitejs/test-example-external-component', 'vuetify', 'vite-plugin-vuetify' ], },
2. 优化依赖预构建(可选)
如果遇到依赖预构建问题,可以将Vuetify相关样式和组件添加到optimizeDeps.include中,确保Vite提前处理这些依赖:
optimizeDeps: { exclude: ['@vitejs/test-example-external-component'], include: ['vuetify/styles', 'vuetify/components', 'vuetify/directives'] },
3. 确认插件加载顺序
确保vuetify插件在vuePlugin之后加载,这是因为Vuetify需要依赖Vue的模板转换配置,当前你的配置已经符合要求,无需调整。
原理说明
Node.js的ES模块加载器默认不支持解析.css文件的导入,而SSR构建时如果直接从node_modules加载Vuetify,Node会尝试处理其中的CSS导入从而报错。通过设置ssr.noExternal,Vite会将这些依赖打包到SSR产物中,在打包过程中自动处理CSS导入,避免Node直接处理这类文件。
内容的提问来源于stack exchange,提问作者Amritpal Singh
相关产品推荐
相关产品推荐

