Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: needs es interop check for newly discovered deps #7243

Merged
merged 2 commits into from Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 37 additions & 4 deletions packages/vite/src/node/optimizer/index.ts
Expand Up @@ -300,10 +300,7 @@ export async function createOptimizeDepsRun(
// server is running
deps = depsFromOptimizedDepInfo(newDeps)

// Clone optimized info objects, fileHash, browserHash may be changed for them
for (const o of Object.keys(newDeps)) {
metadata.optimized[o] = { ...newDeps[o] }
}
metadata.optimized = newDeps

// update global browser hash, but keep newDeps individual hashs until we know
// if files are stable so we can avoid a full page reload
Expand Down Expand Up @@ -738,3 +735,39 @@ function getDepHash(root: string, config: ResolvedConfig): string {
)
return createHash('sha256').update(content).digest('hex').substring(0, 8)
}

export function optimizeDepInfoFromFile(
metadata: DepOptimizationMetadata,
file: string
): OptimizedDepInfo | undefined {
return (
findFileInfo(metadata.optimized, file) ||
findFileInfo(metadata.discovered, file)
)
}

function findFileInfo(
dependenciesInfo: Record<string, OptimizedDepInfo>,
file: string
): OptimizedDepInfo | undefined {
for (const o of Object.keys(dependenciesInfo)) {
const info = dependenciesInfo[o]
if (info.file === file) {
return info
}
}
}

export async function optimizedDepNeedsInterop(
metadata: DepOptimizationMetadata,
file: string
): Promise<boolean | undefined> {
const depInfo = optimizeDepInfoFromFile(metadata, file)

if (!depInfo) return undefined

// Wait until the dependency has been pre-bundled
await depInfo.processing

return depInfo?.needsInterop
}
12 changes: 9 additions & 3 deletions packages/vite/src/node/optimizer/registerMissing.ts
Expand Up @@ -79,7 +79,14 @@ export function createMissingImporterRegisterFn(

// All deps, previous known and newly discovered are rebundled,
// respect insertion order to keep the metadata file stable
const newDeps = { ...metadata.optimized, ...metadata.discovered }

// Clone optimized info objects, fileHash, browserHash may be changed for them
const clonedOptimizedDeps: Record<string, OptimizedDepInfo> = {}
for (const o of Object.keys(metadata.optimized)) {
clonedOptimizedDeps[o] = { ...metadata.optimized[o] }
}

const newDeps = { ...clonedOptimizedDeps, ...metadata.discovered }
const thisDepOptimizationProcessing = depOptimizationProcessing

// Other rerun will await until this run is finished
Expand Down Expand Up @@ -119,9 +126,8 @@ export function createMissingImporterRegisterFn(
// While optimizeDeps is running, new missing deps may be discovered,
// in which case they will keep being added to metadata.discovered
for (const o of Object.keys(metadata.discovered)) {
if (!newData.optimized[o] && !newData.discovered[o]) {
if (!newData.optimized[o]) {
newData.discovered[o] = metadata.discovered[o]
delete metadata.discovered[o]
}
}
newData.processing = thisDepOptimizationProcessing.promise
Expand Down
33 changes: 20 additions & 13 deletions packages/vite/src/node/plugins/importAnalysis.ts
Expand Up @@ -49,7 +49,8 @@ import { transformRequest } from '../server/transformRequest'
import {
isOptimizedDepFile,
createIsOptimizedDepUrl,
getDepsCacheDir
getDepsCacheDir,
optimizedDepNeedsInterop
} from '../optimizer'

const isDebug = !!process.env.DEBUG
Expand Down Expand Up @@ -433,24 +434,30 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
// rewrite
if (url !== specifier) {
let rewriteDone = false
if (isOptimizedDepFile(resolvedId, config)) {
// for optimized cjs deps, support named imports by rewriting named
// imports to const assignments.
const optimizeDepsMetadata = server._optimizeDepsMetadata!
const { optimized } = optimizeDepsMetadata
if (
isOptimizedDepFile(resolvedId, config) &&
!resolvedId.match(optimizedDepChunkRE)
) {
// for optimized cjs deps, support named imports by rewriting named imports to const assignments.
// internal optimized chunks don't need es interop and are excluded

// The browserHash in resolvedId could be stale in which case there will be a full
// page reload. We could return a 404 in that case but it is safe to return the request
const file = cleanUrl(resolvedId) // Remove ?v={hash}
const dep = Object.keys(optimized).find(
(k) => optimized[k].file === file
)

// Wait until the dependency has been pre-bundled
dep && (await optimized[dep].processing)
const needsInterop = await optimizedDepNeedsInterop(
server._optimizeDepsMetadata!,
file
)

if (dep && optimized[dep].needsInterop) {
debug(`${dep} needs interop`)
if (needsInterop === undefined) {
config.logger.error(
colors.red(
`Vite Error, ${url} optimized info should be defined`
)
)
} else if (needsInterop) {
debug(`${url} needs interop`)
if (isDynamicImport) {
// rewrite `import('package')` to expose the default directly
str().overwrite(
Expand Down
27 changes: 3 additions & 24 deletions packages/vite/src/node/plugins/optimizedDeps.ts
Expand Up @@ -3,8 +3,7 @@ import type { Plugin } from '../plugin'
import colors from 'picocolors'
import { DEP_VERSION_RE } from '../constants'
import { cleanUrl, createDebugger } from '../utils'
import { isOptimizedDepFile } from '../optimizer'
import type { DepOptimizationMetadata, OptimizedDepInfo } from '../optimizer'
import { isOptimizedDepFile, optimizeDepInfoFromFile } from '../optimizer'
import type { ViteDevServer } from '..'

export const ERR_OPTIMIZE_DEPS_PROCESSING_ERROR =
Expand Down Expand Up @@ -33,6 +32,8 @@ export function optimizedDepsPlugin(): Plugin {
const browserHash = versionMatch
? versionMatch[1].split('=')[1]
: undefined

// Search in both the currently optimized and newly discovered deps
const info = optimizeDepInfoFromFile(metadata, file)
if (info) {
if (browserHash && info.browserHash !== browserHash) {
Expand Down Expand Up @@ -93,25 +94,3 @@ function throwOutdatedRequest(id: string) {
// send a 504 status code request timeout
throw err
}

function optimizeDepInfoFromFile(
metadata: DepOptimizationMetadata,
file: string
): OptimizedDepInfo | undefined {
return (
findFileInfo(metadata.optimized, file) ||
findFileInfo(metadata.discovered, file)
)
}

function findFileInfo(
dependenciesInfo: Record<string, OptimizedDepInfo>,
file: string
): OptimizedDepInfo | undefined {
for (const o of Object.keys(dependenciesInfo)) {
const info = dependenciesInfo[o]
if (info.file === file) {
return info
}
}
}
16 changes: 13 additions & 3 deletions packages/vite/src/node/server/moduleGraph.ts
Expand Up @@ -32,6 +32,7 @@ export class ModuleNode {
ssrTransformResult: TransformResult | null = null
ssrModule: Record<string, any> | null = null
lastHMRTimestamp = 0
lastInvalidationTimestamp = 0

constructor(url: string) {
this.url = url
Expand Down Expand Up @@ -94,17 +95,26 @@ export class ModuleGraph {
}
}

invalidateModule(mod: ModuleNode, seen: Set<ModuleNode> = new Set()): void {
mod.info = undefined
invalidateModule(
mod: ModuleNode,
seen: Set<ModuleNode> = new Set(),
timestamp: number = Date.now()
): void {
// Save the timestamp for this invalidation, so we can avoid caching the result of possible already started
// processing being done for this module
mod.lastInvalidationTimestamp = timestamp
// Don't invalidate mod.info and mod.meta, as they are part of the processing pipeline
// Invalidating the transform result is enough to ensure this module is re-processed next time it is requested
mod.transformResult = null
mod.ssrTransformResult = null
invalidateSSRModule(mod, seen)
}

invalidateAll(): void {
const timestamp = Date.now()
const seen = new Set<ModuleNode>()
this.idToModuleMap.forEach((mod) => {
this.invalidateModule(mod, seen)
this.invalidateModule(mod, seen, timestamp)
})
}

Expand Down
46 changes: 34 additions & 12 deletions packages/vite/src/node/server/transformRequest.ts
Expand Up @@ -81,6 +81,25 @@ async function doTransform(
return cached
}

// This module may get invalidated while we are processing it. For example
// when a full page reload is needed after the re-processing of pre-bundled
// dependencies when a missing dep is discovered. We save the current time
// to compare it to the last invalidation performed to know if we should
// cache the result of the transformation or we should discard it as stale.
//
// A module can be invalidated due to:
// 1. A full reload because of pre-bundling newly discovered deps
// 2. A full reload after a config change
// 3. The file that generated the module changed
// 4. Invalidation for a virtual module
//
// For 1 and 2, a new request for this module will be issued after
// the invalidation as part of the browser reloading the page. For 3 and 4
// there may not be a new request right away because of HMR handling.
// In all cases, the next time this module is requested, it should be
// re-processed.
const timestamp = Date.now()

// resolve
const id =
(await pluginContainer.resolveId(url, undefined, { ssr }))?.id || url
Expand Down Expand Up @@ -179,17 +198,20 @@ async function doTransform(
}
}

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)
const result = ssr
? await ssrTransform(code, map as SourceMap, url)
: ({
code,
map,
etag: getEtag(code, { weak: true })
} as TransformResult)

// Only cache the result if the module wasn't invalidated while it was
// being processed, so it is re-processed next time if it is stale
if (timestamp > mod.lastInvalidationTimestamp) {
if (ssr) mod.ssrTransformResult = result
else mod.transformResult = result
}
bluwy marked this conversation as resolved.
Show resolved Hide resolved

return result
}