diff --git a/packages/vite-node/src/externalize.ts b/packages/vite-node/src/externalize.ts index c6f5007bfaed..b388593a5719 100644 --- a/packages/vite-node/src/externalize.ts +++ b/packages/vite-node/src/externalize.ts @@ -3,12 +3,51 @@ import { isNodeBuiltin, isValidNodeImport } from 'mlly' import type { DepsHandlingOptions } from './types' import { slash } from './utils' +const KNOWN_ASSET_TYPES = [ + // images + 'png', + 'jpe?g', + 'jfif', + 'pjpeg', + 'pjp', + 'gif', + 'svg', + 'ico', + 'webp', + 'avif', + + // media + 'mp4', + 'webm', + 'ogg', + 'mp3', + 'wav', + 'flac', + 'aac', + + // fonts + 'woff2?', + 'eot', + 'ttf', + 'otf', + + // other + 'webmanifest', + 'pdf', + 'txt', +] + const ESM_EXT_RE = /\.(es|esm|esm-browser|esm-bundler|es6|module)\.js$/ const ESM_FOLDER_RE = /\/(es|esm)\/(.*\.js)$/ const defaultInline = [ /virtual:/, /\.[mc]?ts$/, + + // special Vite query strings + /[?&](init|raw|url|inline)\b/, + // Vite returns a string for assets imports, even if it's inside "node_modules" + new RegExp(`\\.(${KNOWN_ASSET_TYPES.join('|')})$`), ] const depsExternal = [ diff --git a/packages/vite-node/src/utils.ts b/packages/vite-node/src/utils.ts index c98ef475c24d..e11f22f6ffaf 100644 --- a/packages/vite-node/src/utils.ts +++ b/packages/vite-node/src/utils.ts @@ -9,10 +9,6 @@ export function slash(str: string) { return str.replace(/\\/g, '/') } -export function mergeSlashes(str: string) { - return str.replace(/\/\//g, '/') -} - export const VALID_ID_PREFIX = '/@id/' export function normalizeRequestId(id: string, base?: string): string { diff --git a/packages/vitest/src/node/plugins/cssEnabler.ts b/packages/vitest/src/node/plugins/cssEnabler.ts index fda273615eb6..43d151a11bce 100644 --- a/packages/vitest/src/node/plugins/cssEnabler.ts +++ b/packages/vitest/src/node/plugins/cssEnabler.ts @@ -70,7 +70,7 @@ export function CSSEnablerPlugin(ctx: Vitest): VitePlugin[] { return { code } } - return { code: '' } + return { code: 'export default ""' } }, }, ] diff --git a/test/core/test/imports.test.ts b/test/core/test/imports.test.ts index 9ec6e9a34f8f..eed2418e2640 100644 --- a/test/core/test/imports.test.ts +++ b/test/core/test/imports.test.ts @@ -1,4 +1,6 @@ -import { expect, test } from 'vitest' +import { mkdir, writeFile } from 'node:fs/promises' +import { resolve } from 'pathe' +import { describe, expect, test } from 'vitest' import { dynamicRelativeImport } from '../src/relative-import' test('dynamic relative import works', async () => { @@ -67,3 +69,42 @@ test('can import @vite/client', async () => { await expect(import(name)).resolves.not.toThrow() await expect(import(`/${name}`)).resolves.not.toThrow() }) + +describe('importing special files from node_modules', async () => { + const dir = resolve(__dirname, '../src/node_modules') + const wasm = resolve(dir, 'file.wasm') + const css = resolve(dir, 'file.css') + const mp3 = resolve(dir, 'file.mp3') + await mkdir(dir, { recursive: true }) + await Promise.all([ + writeFile(wasm, '(module)'), + writeFile(css, '.foo { color: red; }'), + writeFile(mp3, ''), + ]) + const importModule = (path: string) => import(path) + + test('importing wasm with ?url query', async () => { + const mod = await importModule('../src/node_modules/file.wasm?url') + expect(mod.default).toBe('/src/node_modules/file.wasm') + }) + + test('importing wasm with ?raw query', async () => { + const mod = await importModule('../src/node_modules/file.wasm?raw') + expect(mod.default).toBe('(module)') + }) + + test('importing wasm with ?init query', async () => { + const mod = await importModule('../src/node_modules/file.wasm?init') + expect(mod.default).toBeTypeOf('function') + }) + + test('importing css with ?inline query', async () => { + const mod = await importModule('../src/node_modules/file.css?inline') + expect(mod.default).toBeTypeOf('string') + }) + + test('importing asset returns a string', async () => { + const mod = await importModule('../src/node_modules/file.mp3') + expect(mod.default).toBe('/src/node_modules/file.mp3') + }) +})