From f0f8f44e2aad870187ea981fe79afe68989159cd Mon Sep 17 00:00:00 2001 From: Tal Hadad Date: Wed, 24 Aug 2022 00:51:55 +0300 Subject: [PATCH] fix: enforce generating `module` params when needed in relative url resolving (fixes #9807) --- packages/vite/src/node/build.ts | 57 ++++++++++++++++++++---- packages/vite/src/node/plugins/asset.ts | 40 +++++++++++------ packages/vite/src/node/plugins/css.ts | 24 ++++++---- packages/vite/src/node/plugins/worker.ts | 21 ++++++--- 4 files changed, 107 insertions(+), 35 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 34d1bb98ea60fc..a8031868e330f1 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -21,6 +21,7 @@ import commonjsPlugin from '@rollup/plugin-commonjs' import type { RollupCommonJSOptions } from 'types/commonjs' import type { RollupDynamicImportVarsOptions } from 'types/dynamicImportVars' import type { TransformOptions } from 'esbuild' +import type MagicString from 'magic-string' import type { InlineConfig, ResolvedConfig } from './config' import { isDepsOptimizerEnabled, resolveConfig } from './config' import { buildReporterPlugin } from './plugins/reporter' @@ -902,7 +903,10 @@ export type RenderBuiltAssetUrl = ( hostType: 'js' | 'css' | 'html' ssr: boolean } -) => string | { relative?: boolean; runtime?: string } | undefined +) => + | string + | { relative?: boolean; runtime?: string; needModuleParam?: boolean } + | undefined export function toOutputFilePathInString( filename: string, @@ -914,10 +918,13 @@ export function toOutputFilePathInString( toRelative: ( filename: string, hostType: string - ) => string | { runtime: string } = getToImportMetaURLBasedRelativePath( - format - ) -): string | { runtime: string } { + ) => + | string + | { + runtime: string + needModuleParam: boolean + } = getToImportMetaURLBasedRelativePath(format) +): string | { runtime: string; needModuleParam: boolean } { const { renderBuiltUrl } = config.experimental let relative = config.base === '' || config.base === './' if (renderBuiltUrl) { @@ -929,7 +936,10 @@ export function toOutputFilePathInString( }) if (typeof result === 'object') { if (result.runtime) { - return { runtime: result.runtime } + return { + runtime: result.runtime, + needModuleParam: !!result.needModuleParam + } } if (typeof result.relative === 'boolean') { relative = result.relative @@ -944,14 +954,45 @@ export function toOutputFilePathInString( return config.base + filename } +export function ensureHavingSystemJSModuleParam( + s: MagicString, + code: string +): void { + const wrapIdx = code.indexOf('System.register') + if (wrapIdx < 0) return + const functionStr = 'function (' + const functionIdx = code.indexOf(functionStr, wrapIdx) + if (functionIdx < 0) return + const functionParameterStartIdx = functionIdx + functionStr.length + const functionParameterEndIdx = code.indexOf(')', functionParameterStartIdx) + const functionParameterModuleIdx = code.indexOf( + 'module', + functionParameterStartIdx + ) + const hasModuleParameter = + functionParameterModuleIdx >= 0 && + functionParameterModuleIdx < functionParameterEndIdx + if (!hasModuleParameter) { + s.overwrite( + functionParameterStartIdx - 1, + functionParameterEndIdx + 1, + '(exports, module)' + ) + } +} + function getToImportMetaURLBasedRelativePath( format: InternalModuleFormat -): (filename: string, importer: string) => { runtime: string } { +): ( + filename: string, + importer: string +) => { runtime: string; needModuleParam: boolean } { const toRelativePath = relativeUrlMechanisms[format] return (filename, importer) => ({ runtime: toRelativePath( path.posix.relative(path.dirname(importer), filename) - ) + ), + needModuleParam: format === 'system' }) } diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index c018ba56427fce..7a95873f9a63f3 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -10,7 +10,10 @@ import type { RenderedChunk } from 'rollup' import MagicString from 'magic-string' -import { toOutputFilePathInString } from '../build' +import { + ensureHavingSystemJSModuleParam, + toOutputFilePathInString +} from '../build' import type { Plugin } from '../plugin' import type { ResolvedConfig } from '../config' import { cleanUrl, getHash, normalizePath } from '../utils' @@ -48,9 +51,10 @@ export function renderAssetUrlInJS( chunk: RenderedChunk, opts: NormalizedOutputOptions, code: string -): MagicString | undefined { +): { s: MagicString; needModuleParam: boolean } | undefined { let match: RegExpExecArray | null let s: MagicString | undefined + let needModuleParam = false // Urls added with JS using e.g. // imgElement.src = "__VITE_ASSET__5aa0ddc0__" are using quotes @@ -76,10 +80,13 @@ export function renderAssetUrlInJS( config, opts.format ) - const replacementString = - typeof replacement === 'string' - ? JSON.stringify(replacement).slice(1, -1) - : `"+${replacement.runtime}+"` + let replacementString: string + if (typeof replacement === 'string') { + replacementString = JSON.stringify(replacement).slice(1, -1) + } else { + replacementString = `"+${replacement.runtime}+"` + needModuleParam ||= replacement.needModuleParam + } s.overwrite(match.index, match.index + full.length, replacementString, { contentOnly: true }) @@ -100,16 +107,19 @@ export function renderAssetUrlInJS( config, opts.format ) - const replacementString = - typeof replacement === 'string' - ? JSON.stringify(replacement).slice(1, -1) - : `"+${replacement.runtime}+"` + let replacementString: string + if (typeof replacement === 'string') { + replacementString = JSON.stringify(replacement).slice(1, -1) + } else { + replacementString = `"+${replacement.runtime}+"` + needModuleParam ||= replacement.needModuleParam + } s.overwrite(match.index, match.index + full.length, replacementString, { contentOnly: true }) } - return s + return s && { s, needModuleParam } } /** @@ -167,9 +177,13 @@ export function assetPlugin(config: ResolvedConfig): Plugin { }, renderChunk(code, chunk, opts) { - const s = renderAssetUrlInJS(this, config, chunk, opts, code) + const result = renderAssetUrlInJS(this, config, chunk, opts, code) - if (s) { + if (result) { + const { s, needModuleParam } = result + if (needModuleParam) { + ensureHavingSystemJSModuleParam(s, code) + } return { code: s.toString(), map: config.build.sourcemap ? s.generateMap({ hires: true }) : null diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 4dc3aa7f158587..6c42d9a85b54f9 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -25,7 +25,10 @@ import type { RawSourceMap } from '@ampproject/remapping' import { getCodeWithSourcemap, injectSourcesContent } from '../server/sourcemap' import type { ModuleNode } from '../server/moduleGraph' import type { ResolveFn, ViteDevServer } from '../' -import { toOutputFilePathInCss } from '../build' +import { + ensureHavingSystemJSModuleParam, + toOutputFilePathInCss +} from '../build' import { CLIENT_PUBLIC_PATH, SPECIAL_QUERY_RE } from '../constants' import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' @@ -567,14 +570,14 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { } chunkCSS = await finalizeCss(chunkCSS, true, config) let cssString = JSON.stringify(chunkCSS) - cssString = - renderAssetUrlInJS( - this, - config, - chunk, - opts, - cssString - )?.toString() || cssString + const cssRendered = renderAssetUrlInJS( + this, + config, + chunk, + opts, + cssString + ) + cssString = cssRendered?.s.toString() || cssString const style = `__vite_style__` const injectCode = `var ${style} = document.createElement('style');` + @@ -585,6 +588,9 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { const insertIdx = code.indexOf(insertMark, wrapIdx) const s = new MagicString(code) s.appendLeft(insertIdx + insertMark.length, injectCode) + if (cssRendered?.needModuleParam) { + ensureHavingSystemJSModuleParam(s, code) + } if (config.build.sourcemap) { // resolve public URL from CSS paths, we need to use absolute paths return { diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 336562d4326dd2..24513bff8fc6eb 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -6,7 +6,11 @@ import type { Plugin } from '../plugin' import type { ViteDevServer } from '../server' import { ENV_ENTRY, ENV_PUBLIC_PATH } from '../constants' import { cleanUrl, getHash, injectQuery, parseRequest } from '../utils' -import { onRollupWarning, toOutputFilePathInString } from '../build' +import { + ensureHavingSystemJSModuleParam, + onRollupWarning, + toOutputFilePathInString +} from '../build' import { getDepsOptimizer } from '../optimizer' import { fileToUrl } from './asset' @@ -320,6 +324,7 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { if (code.match(workerAssetUrlRE) || code.includes('import.meta.url')) { let match: RegExpExecArray | null s = new MagicString(code) + let needModuleParam = false // Replace "__VITE_WORKER_ASSET__5aa0ddc0__" using relative paths const workerMap = workerCache.get(config.mainConfig || config)! @@ -336,10 +341,13 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { config, outputOptions.format ) - const replacementString = - typeof replacement === 'string' - ? JSON.stringify(replacement).slice(1, -1) - : `"+${replacement.runtime}+"` + let replacementString: string + if (typeof replacement === 'string') { + replacementString = JSON.stringify(replacement).slice(1, -1) + } else { + replacementString = `"+${replacement.runtime}+"` + needModuleParam ||= replacement.needModuleParam + } s.overwrite( match.index, match.index + full.length, @@ -349,6 +357,9 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { } ) } + if (needModuleParam) { + ensureHavingSystemJSModuleParam(s, code) + } } return result() },