|
import { createFilter, normalizePath } from 'vite'; |
|
import * as fs from 'node:fs'; |
|
import { log } from './log.js'; |
|
|
|
const VITE_FS_PREFIX = '/@fs/'; |
|
const IS_WINDOWS = process.platform === 'win32'; |
|
|
|
const SUPPORTED_COMPILER_OPTIONS = [ |
|
'generate', |
|
'dev', |
|
'css', |
|
'hydratable', |
|
'customElement', |
|
'immutable', |
|
'enableSourcemap' |
|
]; |
|
const TYPES_WITH_COMPILER_OPTIONS = ['style', 'script', 'all']; |
|
|
|
|
|
|
|
|
|
|
|
function splitId(id) { |
|
const parts = id.split('?', 2); |
|
const filename = parts[0]; |
|
const rawQuery = parts[1]; |
|
return { filename, rawQuery }; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseToSvelteRequest(id, filename, rawQuery, root, timestamp, ssr) { |
|
const query = parseRequestQuery(rawQuery); |
|
const rawOrDirect = !!(query.raw || query.direct); |
|
if (query.url || (!query.svelte && rawOrDirect)) { |
|
|
|
return; |
|
} |
|
const raw = rawOrDirect; |
|
const normalizedFilename = normalize(filename, root); |
|
const cssId = createVirtualImportId(filename, root, 'style'); |
|
|
|
return { |
|
id, |
|
filename, |
|
normalizedFilename, |
|
cssId, |
|
query, |
|
timestamp, |
|
ssr, |
|
raw |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createVirtualImportId(filename, root, type) { |
|
const parts = ['svelte', `type=${type}`]; |
|
if (type === 'style') { |
|
parts.push('lang.css'); |
|
} |
|
if (existsInRoot(filename, root)) { |
|
filename = root + filename; |
|
} else if (filename.startsWith(VITE_FS_PREFIX)) { |
|
filename = IS_WINDOWS |
|
? filename.slice(VITE_FS_PREFIX.length) |
|
: filename.slice(VITE_FS_PREFIX.length - 1); |
|
} |
|
|
|
return `${filename}?${parts.join('&')}`; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function parseRequestQuery(rawQuery) { |
|
const query = Object.fromEntries(new URLSearchParams(rawQuery)); |
|
for (const key in query) { |
|
if (query[key] === '') { |
|
|
|
query[key] = true; |
|
} |
|
} |
|
const compilerOptions = query.compilerOptions; |
|
if (compilerOptions) { |
|
if (!((query.raw || query.direct) && TYPES_WITH_COMPILER_OPTIONS.includes(query.type))) { |
|
throw new Error( |
|
`Invalid compilerOptions in query ${rawQuery}. CompilerOptions are only supported for raw or direct queries with type in "${TYPES_WITH_COMPILER_OPTIONS.join( |
|
', ' |
|
)}" e.g. '?svelte&raw&type=script&compilerOptions={"generate":"ssr","dev":false}` |
|
); |
|
} |
|
try { |
|
const parsed = JSON.parse(compilerOptions); |
|
const invalid = Object.keys(parsed).filter( |
|
(key) => !SUPPORTED_COMPILER_OPTIONS.includes(key) |
|
); |
|
if (invalid.length) { |
|
throw new Error( |
|
`Invalid compilerOptions in query ${rawQuery}: ${invalid.join( |
|
', ' |
|
)}. Supported: ${SUPPORTED_COMPILER_OPTIONS.join(', ')}` |
|
); |
|
} |
|
query.compilerOptions = parsed; |
|
} catch (e) { |
|
log.error('failed to parse request query compilerOptions', e); |
|
throw e; |
|
} |
|
} |
|
|
|
return query; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function normalize(filename, normalizedRoot) { |
|
return stripRoot(normalizePath(filename), normalizedRoot); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function existsInRoot(filename, root) { |
|
if (filename.startsWith(VITE_FS_PREFIX)) { |
|
return false; |
|
} |
|
return fs.existsSync(root + filename); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function stripRoot(normalizedFilename, normalizedRoot) { |
|
return normalizedFilename.startsWith(normalizedRoot + '/') |
|
? normalizedFilename.slice(normalizedRoot.length) |
|
: normalizedFilename; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function buildFilter(include, exclude, extensions) { |
|
const rollupFilter = createFilter(include, exclude); |
|
return (filename) => rollupFilter(filename) && extensions.some((ext) => filename.endsWith(ext)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function buildIdParser(options) { |
|
const { include, exclude, extensions, root } = options; |
|
const normalizedRoot = normalizePath(root); |
|
const filter = buildFilter(include, exclude, extensions ?? []); |
|
return (id, ssr, timestamp = Date.now()) => { |
|
const { filename, rawQuery } = splitId(id); |
|
if (filter(filename)) { |
|
return parseToSvelteRequest(id, filename, rawQuery, normalizedRoot, timestamp, ssr); |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function buildModuleIdParser(options) { |
|
const { include, exclude, extensions } = options?.experimental?.compileModule ?? {}; |
|
const root = options.root; |
|
const normalizedRoot = normalizePath(root); |
|
const filter = buildFilter(include, exclude, extensions ?? ['.svelte.js', '.svelte.ts']); |
|
return (id, ssr, timestamp = Date.now()) => { |
|
const { filename, rawQuery } = splitId(id); |
|
if (filter(filename)) { |
|
return parseToSvelteModuleRequest(id, filename, rawQuery, normalizedRoot, timestamp, ssr); |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseToSvelteModuleRequest(id, filename, rawQuery, root, timestamp, ssr) { |
|
const query = parseRequestQuery(rawQuery); |
|
|
|
if (query.url || query.raw || query.direct) { |
|
|
|
return; |
|
} |
|
|
|
const normalizedFilename = normalize(filename, root); |
|
|
|
return { |
|
id, |
|
filename, |
|
normalizedFilename, |
|
query, |
|
timestamp, |
|
ssr |
|
}; |
|
} |
|
|