UNPKG

vite

Version:

Native-ESM powered web dev build tool

167 lines (153 loc) 4.85 kB
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) } }