vite
Version:
Native-ESM powered web dev build tool
167 lines (153 loc) • 4.85 kB
text/typescript
import { promises as fs } from 'fs'
import path from 'path'
import getEtag from 'etag'
import * as convertSourceMap from 'convert-source-map'
import { SourceDescription, SourceMap } from 'rollup'
import { ViteDevServer } from '..'
import chalk from 'chalk'
import {
createDebugger,
cleanUrl,
prettifyUrl,
removeTimestampQuery,
timeFrom,
ensureWatchedFile
} from '../utils'
import { checkPublicFile } from '../plugins/asset'
import { ssrTransform } from '../ssr/ssrTransform'
import { injectSourcesContent } from './sourcemap'
import { isFileServingAllowed } from './middlewares/static'
const debugLoad = createDebugger('vite:load')
const debugTransform = createDebugger('vite:transform')
const debugCache = createDebugger('vite:cache')
const isDebug = !!process.env.DEBUG
export interface TransformResult {
code: string
map: SourceMap | null
etag?: string
deps?: string[]
}
export interface TransformOptions {
ssr?: boolean
html?: boolean
}
export async function transformRequest(
url: string,
server: ViteDevServer,
options: TransformOptions = {}
): Promise<TransformResult | null> {
const { config, pluginContainer, moduleGraph, watcher } = server
url = removeTimestampQuery(url)
const { root, logger } = config
const prettyUrl = isDebug ? prettifyUrl(url, root) : ''
const ssr = !!options.ssr
// check if we have a fresh cache
const module = await moduleGraph.getModuleByUrl(url)
const cached =
module && (ssr ? module.ssrTransformResult : module.transformResult)
if (cached) {
isDebug && debugCache(`[memory] ${prettyUrl}`)
return cached
}
// resolve
const id = (await pluginContainer.resolveId(url))?.id || url
const file = cleanUrl(id)
let code: string | null = null
let map: SourceDescription['map'] = null
// load
const loadStart = isDebug ? Date.now() : 0
const loadResult = await pluginContainer.load(id, ssr)
if (loadResult == null) {
// if this is an html request and there is no load result, skip ahead to
// SPA fallback.
if (options.html && !id.endsWith('.html')) {
return null
}
// try fallback loading it from fs as string
// if the file is a binary, there should be a plugin that already loaded it
// as string
// only try the fallback if access is allowed, skip for out of root url
// like /service-worker.js or /api/users
if (options.ssr || isFileServingAllowed(file, server)) {
try {
code = await fs.readFile(file, 'utf-8')
isDebug && debugLoad(`${timeFrom(loadStart)} [fs] ${prettyUrl}`)
} catch (e) {
if (e.code !== 'ENOENT') {
throw e
}
}
}
if (code) {
try {
map = (
convertSourceMap.fromSource(code) ||
convertSourceMap.fromMapFileSource(code, path.dirname(file))
)?.toObject()
} catch (e) {
logger.warn(`Failed to load source map for ${url}.`, {
timestamp: true
})
}
}
} else {
isDebug && debugLoad(`${timeFrom(loadStart)} [plugin] ${prettyUrl}`)
if (typeof loadResult === 'object') {
code = loadResult.code
map = loadResult.map
} else {
code = loadResult
}
}
if (code == null) {
if (checkPublicFile(url, config)) {
throw new Error(
`Failed to load url ${url} (resolved id: ${id}). ` +
`This file is in /public and will be copied as-is during build without ` +
`going through the plugin transforms, and therefore should not be ` +
`imported from source code. It can only be referenced via HTML tags.`
)
} else {
return null
}
}
// ensure module in graph after successful load
const mod = await moduleGraph.ensureEntryFromUrl(url)
ensureWatchedFile(watcher, mod.file, root)
// transform
const transformStart = isDebug ? Date.now() : 0
const transformResult = await pluginContainer.transform(code, id, map, ssr)
if (
transformResult == null ||
(typeof transformResult === 'object' && transformResult.code == null)
) {
// no transform applied, keep code as-is
isDebug &&
debugTransform(
timeFrom(transformStart) + chalk.dim(` [skipped] ${prettyUrl}`)
)
} else {
isDebug && debugTransform(`${timeFrom(transformStart)} ${prettyUrl}`)
code = transformResult.code!
map = transformResult.map
}
if (map && mod.file) {
map = (typeof map === 'string' ? JSON.parse(map) : map) as SourceMap
if (map.mappings && !map.sourcesContent) {
await injectSourcesContent(map, mod.file)
}
}
if (ssr) {
return (mod.ssrTransformResult = await ssrTransform(
code,
map as SourceMap,
url
))
} else {
return (mod.transformResult = {
code,
map,
etag: getEtag(code, { weak: true })
} as TransformResult)
}
}