mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
Turbopack: extract core hmr client logic into shared runtime (#88912)
This PR mostly moves existing code. Most of the diff churn is from re-recording shapshots. This PR is nowhere as scary as the diffstat would have you believe.
This extracts the core HMR client logic, abstracting away hooks for platform-specific features like the websocket transport and page reload.
In a future PR this will be implemented as an in-process backend for nodejs server hmr.
Test Plan: CI, existing hmr tests
This commit is contained in:
+4
-4
@@ -1,7 +1,7 @@
|
||||
/// <reference path="../../../shared/runtime-types.d.ts" />
|
||||
/// <reference path="../../runtime/base/dev-globals.d.ts" />
|
||||
/// <reference path="../../runtime/base/dev-protocol.d.ts" />
|
||||
/// <reference path="../../runtime/base/dev-extensions.ts" />
|
||||
/// <reference path="../../../shared/runtime/runtime-types.d.ts" />
|
||||
/// <reference path="../../../shared/runtime/dev-globals.d.ts" />
|
||||
/// <reference path="../../../shared/runtime/dev-protocol.d.ts" />
|
||||
/// <reference path="../../../shared/runtime/dev-extensions.ts" />
|
||||
|
||||
type SendMessage = (msg: any) => void
|
||||
export type WebSocketMessage =
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './hmr-client'
|
||||
+43
-636
@@ -14,11 +14,12 @@ const devContextPrototype = Context.prototype as TurbopackDevContext
|
||||
* It will be appended to the runtime code of each runtime right after the
|
||||
* shared runtime utils.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
const devModuleCache: ModuleCache<HotModule> = Object.create(null)
|
||||
// Assign browser's module cache and runtime modules to shared HMR state
|
||||
devModuleCache = Object.create(null)
|
||||
devContextPrototype.c = devModuleCache
|
||||
runtimeModules = new Set()
|
||||
|
||||
// This file must not use `import` and `export` statements. Otherwise, it
|
||||
// becomes impossible to augment interfaces declared in `<reference>`d files
|
||||
@@ -50,22 +51,6 @@ interface DevRuntimeBackend {
|
||||
restart: () => void
|
||||
}
|
||||
|
||||
class UpdateApplyError extends Error {
|
||||
name = 'UpdateApplyError'
|
||||
|
||||
dependencyChain: ModuleId[]
|
||||
|
||||
constructor(message: string, dependencyChain: ModuleId[]) {
|
||||
super(message)
|
||||
this.dependencyChain = dependencyChain
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Module IDs that are instantiated as part of the runtime of a chunk.
|
||||
*/
|
||||
const runtimeModules: Set<ModuleId> = new Set()
|
||||
|
||||
/**
|
||||
* Map from module ID to the chunks that contain this module.
|
||||
*
|
||||
@@ -93,20 +78,6 @@ const chunkListChunksMap: Map<ChunkListPath, Set<ChunkPath>> = new Map()
|
||||
*/
|
||||
const chunkChunkListsMap: Map<ChunkPath, Set<ChunkListPath>> = new Map()
|
||||
|
||||
/**
|
||||
* Maps module IDs to persisted data between executions of their hot module
|
||||
* implementation (`hot.data`).
|
||||
*/
|
||||
const moduleHotData: Map<ModuleId, HotData> = new Map()
|
||||
/**
|
||||
* Maps module instances to their hot module state.
|
||||
*/
|
||||
const moduleHotState: Map<Module, HotState> = new Map()
|
||||
/**
|
||||
* Modules that call `module.hot.invalidate()` (while being updated).
|
||||
*/
|
||||
const queuedInvalidatedModules: Set<ModuleId> = new Set()
|
||||
|
||||
/**
|
||||
* Gets or instantiates a runtime module.
|
||||
*/
|
||||
@@ -185,74 +156,36 @@ function instantiateModule(
|
||||
sourceType: SourceType,
|
||||
sourceData: SourceData
|
||||
): Module {
|
||||
// We are in development, this is always a string.
|
||||
let id = moduleId as string
|
||||
// Browser: creates base HotModule object (hot API added by shared code)
|
||||
const createModuleObjectFn = (id: ModuleId) => {
|
||||
return createModuleObject(id) as HotModule
|
||||
}
|
||||
|
||||
const moduleFactory = moduleFactories.get(id)
|
||||
if (typeof moduleFactory !== 'function') {
|
||||
// This can happen if modules incorrectly handle HMR disposes/updates,
|
||||
// e.g. when they keep a `setTimeout` around which still executes old code
|
||||
// and contains e.g. a `require("something")` call.
|
||||
throw new Error(
|
||||
factoryNotAvailableMessage(id, sourceType, sourceData) +
|
||||
' It might have been deleted in an HMR update.'
|
||||
// Browser: creates DevContext with refresh
|
||||
const createContext = (
|
||||
module: HotModule,
|
||||
exports: Exports,
|
||||
refresh: RefreshContext
|
||||
) => {
|
||||
return new (DevContext as any as DevContextConstructor)(
|
||||
module,
|
||||
exports,
|
||||
refresh
|
||||
)
|
||||
}
|
||||
|
||||
const hotData = moduleHotData.get(id)!
|
||||
const { hot, hotState } = createModuleHot(id, hotData)
|
||||
|
||||
let parents: ModuleId[]
|
||||
switch (sourceType) {
|
||||
case SourceType.Runtime:
|
||||
runtimeModules.add(id)
|
||||
parents = []
|
||||
break
|
||||
case SourceType.Parent:
|
||||
// No need to add this module as a child of the parent module here, this
|
||||
// has already been taken care of in `getOrInstantiateModuleFromParent`.
|
||||
parents = [sourceData as ModuleId]
|
||||
break
|
||||
case SourceType.Update:
|
||||
parents = (sourceData as ModuleId[]) || []
|
||||
break
|
||||
default:
|
||||
invariant(
|
||||
sourceType,
|
||||
(sourceType) => `Unknown source type: ${sourceType}`
|
||||
)
|
||||
}
|
||||
|
||||
const module: HotModule = createModuleObject(id) as HotModule
|
||||
const exports = module.exports
|
||||
module.parents = parents
|
||||
module.children = []
|
||||
module.hot = hot
|
||||
|
||||
devModuleCache[id] = module
|
||||
moduleHotState.set(module, hotState)
|
||||
|
||||
// NOTE(alexkirsz) This can fail when the module encounters a runtime error.
|
||||
try {
|
||||
runModuleExecutionHooks(module, (refresh) => {
|
||||
const context = new (DevContext as any as DevContextConstructor)(
|
||||
module,
|
||||
exports,
|
||||
refresh
|
||||
)
|
||||
moduleFactory(context, module, exports)
|
||||
})
|
||||
} catch (error) {
|
||||
module.error = error as any
|
||||
throw error
|
||||
}
|
||||
|
||||
if (module.namespaceObject && module.exports !== module.namespaceObject) {
|
||||
// in case of a circular dependency: cjs1 -> esm2 -> cjs1
|
||||
interopEsm(module.exports, module.namespaceObject)
|
||||
}
|
||||
|
||||
return module
|
||||
// Use shared instantiation logic (includes hot API setup)
|
||||
return instantiateModuleShared(
|
||||
moduleId,
|
||||
sourceType,
|
||||
sourceData,
|
||||
moduleFactories,
|
||||
devModuleCache,
|
||||
runtimeModules,
|
||||
createModuleObjectFn,
|
||||
createContext,
|
||||
runModuleExecutionHooks
|
||||
)
|
||||
}
|
||||
|
||||
const DUMMY_REFRESH_CONTEXT = {
|
||||
@@ -349,91 +282,6 @@ function registerExportsAndSetupBoundaryForReactRefresh(
|
||||
}
|
||||
}
|
||||
|
||||
function formatDependencyChain(dependencyChain: ModuleId[]): string {
|
||||
return `Dependency chain: ${dependencyChain.join(' -> ')}`
|
||||
}
|
||||
|
||||
function computeOutdatedModules(
|
||||
added: Map<ModuleId, EcmascriptModuleEntry | undefined>,
|
||||
modified: Map<ModuleId, EcmascriptModuleEntry>
|
||||
): {
|
||||
outdatedModules: Set<ModuleId>
|
||||
newModuleFactories: Map<ModuleId, ModuleFactory>
|
||||
} {
|
||||
const newModuleFactories = new Map<ModuleId, ModuleFactory>()
|
||||
|
||||
for (const [moduleId, entry] of added) {
|
||||
if (entry != null) {
|
||||
newModuleFactories.set(moduleId, _eval(entry))
|
||||
}
|
||||
}
|
||||
|
||||
const outdatedModules = computedInvalidatedModules(modified.keys())
|
||||
|
||||
for (const [moduleId, entry] of modified) {
|
||||
newModuleFactories.set(moduleId, _eval(entry))
|
||||
}
|
||||
|
||||
return { outdatedModules, newModuleFactories }
|
||||
}
|
||||
|
||||
function computedInvalidatedModules(
|
||||
invalidated: Iterable<ModuleId>
|
||||
): Set<ModuleId> {
|
||||
const outdatedModules = new Set<ModuleId>()
|
||||
|
||||
for (const moduleId of invalidated) {
|
||||
const effect = getAffectedModuleEffects(moduleId)
|
||||
|
||||
switch (effect.type) {
|
||||
case 'unaccepted':
|
||||
throw new UpdateApplyError(
|
||||
`cannot apply update: unaccepted module. ${formatDependencyChain(
|
||||
effect.dependencyChain
|
||||
)}.`,
|
||||
effect.dependencyChain
|
||||
)
|
||||
case 'self-declined':
|
||||
throw new UpdateApplyError(
|
||||
`cannot apply update: self-declined module. ${formatDependencyChain(
|
||||
effect.dependencyChain
|
||||
)}.`,
|
||||
effect.dependencyChain
|
||||
)
|
||||
case 'accepted':
|
||||
for (const outdatedModuleId of effect.outdatedModules) {
|
||||
outdatedModules.add(outdatedModuleId)
|
||||
}
|
||||
break
|
||||
// TODO(alexkirsz) Dependencies: handle dependencies effects.
|
||||
default:
|
||||
invariant(effect, (effect) => `Unknown effect type: ${effect?.type}`)
|
||||
}
|
||||
}
|
||||
|
||||
return outdatedModules
|
||||
}
|
||||
|
||||
function computeOutdatedSelfAcceptedModules(
|
||||
outdatedModules: Iterable<ModuleId>
|
||||
): { moduleId: ModuleId; errorHandler: true | Function }[] {
|
||||
const outdatedSelfAcceptedModules: {
|
||||
moduleId: ModuleId
|
||||
errorHandler: true | Function
|
||||
}[] = []
|
||||
for (const moduleId of outdatedModules) {
|
||||
const module = devModuleCache[moduleId]
|
||||
const hotState = moduleHotState.get(module)!
|
||||
if (module && hotState.selfAccepted && !hotState.selfInvalidated) {
|
||||
outdatedSelfAcceptedModules.push({
|
||||
moduleId,
|
||||
errorHandler: hotState.selfAccepted,
|
||||
})
|
||||
}
|
||||
}
|
||||
return outdatedSelfAcceptedModules
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds, deletes, and moves modules between chunks. This must happen before the
|
||||
* dispose phase as it needs to know which modules were removed from all chunks,
|
||||
@@ -461,139 +309,6 @@ function updateChunksPhase(
|
||||
return { disposedModules }
|
||||
}
|
||||
|
||||
function disposePhase(
|
||||
outdatedModules: Iterable<ModuleId>,
|
||||
disposedModules: Iterable<ModuleId>
|
||||
): { outdatedModuleParents: Map<ModuleId, Array<ModuleId>> } {
|
||||
for (const moduleId of outdatedModules) {
|
||||
disposeModule(moduleId, 'replace')
|
||||
}
|
||||
|
||||
for (const moduleId of disposedModules) {
|
||||
disposeModule(moduleId, 'clear')
|
||||
}
|
||||
|
||||
// Removing modules from the module cache is a separate step.
|
||||
// We also want to keep track of previous parents of the outdated modules.
|
||||
const outdatedModuleParents = new Map()
|
||||
for (const moduleId of outdatedModules) {
|
||||
const oldModule = devModuleCache[moduleId]
|
||||
outdatedModuleParents.set(moduleId, oldModule?.parents)
|
||||
delete devModuleCache[moduleId]
|
||||
}
|
||||
|
||||
// TODO(alexkirsz) Dependencies: remove outdated dependency from module
|
||||
// children.
|
||||
|
||||
return { outdatedModuleParents }
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of an instance of a module.
|
||||
*
|
||||
* Returns the persistent hot data that should be kept for the next module
|
||||
* instance.
|
||||
*
|
||||
* NOTE: mode = "replace" will not remove modules from the devModuleCache
|
||||
* This must be done in a separate step afterwards.
|
||||
* This is important because all modules need to be disposed to update the
|
||||
* parent/child relationships before they are actually removed from the devModuleCache.
|
||||
* If this was done in this method, the following disposeModule calls won't find
|
||||
* the module from the module id in the cache.
|
||||
*/
|
||||
function disposeModule(moduleId: ModuleId, mode: 'clear' | 'replace') {
|
||||
const module = devModuleCache[moduleId]
|
||||
if (!module) {
|
||||
return
|
||||
}
|
||||
|
||||
const hotState = moduleHotState.get(module)!
|
||||
const data = {}
|
||||
|
||||
// Run the `hot.dispose` handler, if any, passing in the persistent
|
||||
// `hot.data` object.
|
||||
for (const disposeHandler of hotState.disposeHandlers) {
|
||||
disposeHandler(data)
|
||||
}
|
||||
|
||||
// This used to warn in `getOrInstantiateModuleFromParent` when a disposed
|
||||
// module is still importing other modules.
|
||||
module.hot.active = false
|
||||
|
||||
moduleHotState.delete(module)
|
||||
|
||||
// TODO(alexkirsz) Dependencies: delete the module from outdated deps.
|
||||
|
||||
// Remove the disposed module from its children's parent list.
|
||||
// It will be added back once the module re-instantiates and imports its
|
||||
// children again.
|
||||
for (const childId of module.children) {
|
||||
const child = devModuleCache[childId]
|
||||
if (!child) {
|
||||
continue
|
||||
}
|
||||
|
||||
const idx = child.parents.indexOf(module.id)
|
||||
if (idx >= 0) {
|
||||
child.parents.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 'clear':
|
||||
delete devModuleCache[module.id]
|
||||
moduleHotData.delete(module.id)
|
||||
break
|
||||
case 'replace':
|
||||
moduleHotData.set(module.id, data)
|
||||
break
|
||||
default:
|
||||
invariant(mode, (mode) => `invalid mode: ${mode}`)
|
||||
}
|
||||
}
|
||||
|
||||
function applyPhase(
|
||||
outdatedSelfAcceptedModules: {
|
||||
moduleId: ModuleId
|
||||
errorHandler: true | Function
|
||||
}[],
|
||||
newModuleFactories: Map<ModuleId, ModuleFactory>,
|
||||
outdatedModuleParents: Map<ModuleId, Array<ModuleId>>,
|
||||
reportError: (err: any) => void
|
||||
) {
|
||||
// Update module factories.
|
||||
for (const [moduleId, factory] of newModuleFactories.entries()) {
|
||||
applyModuleFactoryName(factory)
|
||||
moduleFactories.set(moduleId, factory)
|
||||
}
|
||||
|
||||
// TODO(alexkirsz) Run new runtime entries here.
|
||||
|
||||
// TODO(alexkirsz) Dependencies: call accept handlers for outdated deps.
|
||||
|
||||
// Re-instantiate all outdated self-accepted modules.
|
||||
for (const { moduleId, errorHandler } of outdatedSelfAcceptedModules) {
|
||||
try {
|
||||
instantiateModule(
|
||||
moduleId,
|
||||
SourceType.Update,
|
||||
outdatedModuleParents.get(moduleId)
|
||||
)
|
||||
} catch (err) {
|
||||
if (typeof errorHandler === 'function') {
|
||||
try {
|
||||
errorHandler(err, { moduleId, module: devModuleCache[moduleId] })
|
||||
} catch (err2) {
|
||||
reportError(err2)
|
||||
reportError(err)
|
||||
}
|
||||
} else {
|
||||
reportError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyUpdate(update: PartialUpdate) {
|
||||
switch (update.type) {
|
||||
case 'ChunkListUpdate':
|
||||
@@ -651,254 +366,26 @@ function applyChunkListUpdate(update: ChunkListUpdate) {
|
||||
}
|
||||
|
||||
function applyEcmascriptMergedUpdate(update: EcmascriptMergedUpdate) {
|
||||
// Browser-specific chunk management phase
|
||||
const { entries = {}, chunks = {} } = update
|
||||
const { added, modified, chunksAdded, chunksDeleted } = computeChangedModules(
|
||||
entries,
|
||||
chunks
|
||||
)
|
||||
const { outdatedModules, newModuleFactories } = computeOutdatedModules(
|
||||
added,
|
||||
modified
|
||||
chunks,
|
||||
chunkModulesMap
|
||||
)
|
||||
const { disposedModules } = updateChunksPhase(chunksAdded, chunksDeleted)
|
||||
|
||||
applyInternal(outdatedModules, disposedModules, newModuleFactories)
|
||||
}
|
||||
|
||||
function applyInvalidatedModules(outdatedModules: Set<ModuleId>) {
|
||||
if (queuedInvalidatedModules.size > 0) {
|
||||
computedInvalidatedModules(queuedInvalidatedModules).forEach((moduleId) => {
|
||||
outdatedModules.add(moduleId)
|
||||
})
|
||||
|
||||
queuedInvalidatedModules.clear()
|
||||
}
|
||||
|
||||
return outdatedModules
|
||||
}
|
||||
|
||||
function applyInternal(
|
||||
outdatedModules: Set<ModuleId>,
|
||||
disposedModules: Iterable<ModuleId>,
|
||||
newModuleFactories: Map<ModuleId, ModuleFactory>
|
||||
) {
|
||||
outdatedModules = applyInvalidatedModules(outdatedModules)
|
||||
|
||||
const outdatedSelfAcceptedModules =
|
||||
computeOutdatedSelfAcceptedModules(outdatedModules)
|
||||
|
||||
const { outdatedModuleParents } = disposePhase(
|
||||
outdatedModules,
|
||||
disposedModules
|
||||
)
|
||||
|
||||
// we want to continue on error and only throw the error after we tried applying all updates
|
||||
let error: any
|
||||
|
||||
function reportError(err: any) {
|
||||
if (!error) error = err
|
||||
}
|
||||
|
||||
applyPhase(
|
||||
outdatedSelfAcceptedModules,
|
||||
newModuleFactories,
|
||||
outdatedModuleParents,
|
||||
reportError
|
||||
)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (queuedInvalidatedModules.size > 0) {
|
||||
applyInternal(new Set(), [], new Map())
|
||||
}
|
||||
}
|
||||
|
||||
function computeChangedModules(
|
||||
entries: Record<ModuleId, EcmascriptModuleEntry>,
|
||||
updates: Record<ChunkPath, EcmascriptMergedChunkUpdate>
|
||||
): {
|
||||
added: Map<ModuleId, EcmascriptModuleEntry | undefined>
|
||||
modified: Map<ModuleId, EcmascriptModuleEntry>
|
||||
deleted: Set<ModuleId>
|
||||
chunksAdded: Map<ChunkPath, Set<ModuleId>>
|
||||
chunksDeleted: Map<ChunkPath, Set<ModuleId>>
|
||||
} {
|
||||
const chunksAdded = new Map()
|
||||
const chunksDeleted = new Map()
|
||||
const added: Map<ModuleId, EcmascriptModuleEntry> = new Map()
|
||||
const modified = new Map()
|
||||
const deleted: Set<ModuleId> = new Set()
|
||||
|
||||
for (const [chunkPath, mergedChunkUpdate] of Object.entries(updates) as Array<
|
||||
[ChunkPath, EcmascriptMergedChunkUpdate]
|
||||
>) {
|
||||
switch (mergedChunkUpdate.type) {
|
||||
case 'added': {
|
||||
const updateAdded = new Set(mergedChunkUpdate.modules)
|
||||
for (const moduleId of updateAdded) {
|
||||
added.set(moduleId, entries[moduleId])
|
||||
}
|
||||
chunksAdded.set(chunkPath, updateAdded)
|
||||
break
|
||||
}
|
||||
case 'deleted': {
|
||||
// We could also use `mergedChunkUpdate.modules` here.
|
||||
const updateDeleted = new Set(chunkModulesMap.get(chunkPath))
|
||||
for (const moduleId of updateDeleted) {
|
||||
deleted.add(moduleId)
|
||||
}
|
||||
chunksDeleted.set(chunkPath, updateDeleted)
|
||||
break
|
||||
}
|
||||
case 'partial': {
|
||||
const updateAdded = new Set(mergedChunkUpdate.added)
|
||||
const updateDeleted = new Set(mergedChunkUpdate.deleted)
|
||||
for (const moduleId of updateAdded) {
|
||||
added.set(moduleId, entries[moduleId])
|
||||
}
|
||||
for (const moduleId of updateDeleted) {
|
||||
deleted.add(moduleId)
|
||||
}
|
||||
chunksAdded.set(chunkPath, updateAdded)
|
||||
chunksDeleted.set(chunkPath, updateDeleted)
|
||||
break
|
||||
}
|
||||
default:
|
||||
invariant(
|
||||
mergedChunkUpdate,
|
||||
(mergedChunkUpdate) =>
|
||||
`Unknown merged chunk update type: ${mergedChunkUpdate.type}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// If a module was added from one chunk and deleted from another in the same update,
|
||||
// consider it to be modified, as it means the module was moved from one chunk to another
|
||||
// AND has new code in a single update.
|
||||
for (const moduleId of added.keys()) {
|
||||
if (deleted.has(moduleId)) {
|
||||
added.delete(moduleId)
|
||||
deleted.delete(moduleId)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [moduleId, entry] of Object.entries(entries)) {
|
||||
// Modules that haven't been added to any chunk but have new code are considered
|
||||
// to be modified.
|
||||
// This needs to be under the previous loop, as we need it to get rid of modules
|
||||
// that were added and deleted in the same update.
|
||||
if (!added.has(moduleId)) {
|
||||
modified.set(moduleId, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return { added, deleted, modified, chunksAdded, chunksDeleted }
|
||||
}
|
||||
|
||||
type ModuleEffect =
|
||||
| {
|
||||
type: 'unaccepted'
|
||||
dependencyChain: ModuleId[]
|
||||
}
|
||||
| {
|
||||
type: 'self-declined'
|
||||
dependencyChain: ModuleId[]
|
||||
moduleId: ModuleId
|
||||
}
|
||||
| {
|
||||
type: 'accepted'
|
||||
moduleId: ModuleId
|
||||
outdatedModules: Set<ModuleId>
|
||||
}
|
||||
|
||||
function getAffectedModuleEffects(moduleId: ModuleId): ModuleEffect {
|
||||
const outdatedModules: Set<ModuleId> = new Set()
|
||||
|
||||
type QueueItem = { moduleId?: ModuleId; dependencyChain: ModuleId[] }
|
||||
|
||||
const queue: QueueItem[] = [
|
||||
{
|
||||
moduleId,
|
||||
dependencyChain: [],
|
||||
},
|
||||
]
|
||||
|
||||
let nextItem
|
||||
while ((nextItem = queue.shift())) {
|
||||
const { moduleId, dependencyChain } = nextItem
|
||||
|
||||
if (moduleId != null) {
|
||||
if (outdatedModules.has(moduleId)) {
|
||||
// Avoid infinite loops caused by cycles between modules in the dependency chain.
|
||||
continue
|
||||
}
|
||||
|
||||
outdatedModules.add(moduleId)
|
||||
}
|
||||
|
||||
// We've arrived at the runtime of the chunk, which means that nothing
|
||||
// else above can accept this update.
|
||||
if (moduleId === undefined) {
|
||||
return {
|
||||
type: 'unaccepted',
|
||||
dependencyChain,
|
||||
}
|
||||
}
|
||||
|
||||
const module = devModuleCache[moduleId]
|
||||
const hotState = moduleHotState.get(module)!
|
||||
|
||||
if (
|
||||
// The module is not in the cache. Since this is a "modified" update,
|
||||
// it means that the module was never instantiated before.
|
||||
!module || // The module accepted itself without invalidating globalThis.
|
||||
// TODO is that right?
|
||||
(hotState.selfAccepted && !hotState.selfInvalidated)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (hotState.selfDeclined) {
|
||||
return {
|
||||
type: 'self-declined',
|
||||
dependencyChain,
|
||||
moduleId,
|
||||
}
|
||||
}
|
||||
|
||||
if (runtimeModules.has(moduleId)) {
|
||||
queue.push({
|
||||
moduleId: undefined,
|
||||
dependencyChain: [...dependencyChain, moduleId],
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
for (const parentId of module.parents) {
|
||||
const parent = devModuleCache[parentId]
|
||||
|
||||
if (!parent) {
|
||||
// TODO(alexkirsz) Is this even possible?
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO(alexkirsz) Dependencies: check accepted and declined
|
||||
// dependencies here.
|
||||
|
||||
queue.push({
|
||||
moduleId: parentId,
|
||||
dependencyChain: [...dependencyChain, moduleId],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'accepted',
|
||||
moduleId,
|
||||
outdatedModules,
|
||||
}
|
||||
// Use shared HMR update implementation
|
||||
applyEcmascriptMergedUpdateShared({
|
||||
added,
|
||||
modified,
|
||||
disposedModules,
|
||||
evalModuleEntry: _eval, // browser's eval with source maps
|
||||
instantiateModule,
|
||||
applyModuleFactoryName,
|
||||
moduleFactories,
|
||||
devModuleCache,
|
||||
})
|
||||
}
|
||||
|
||||
function handleApply(chunkListPath: ChunkListPath, update: ServerMessage) {
|
||||
@@ -932,86 +419,6 @@ function handleApply(chunkListPath: ChunkListPath, update: ServerMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
function createModuleHot(
|
||||
moduleId: ModuleId,
|
||||
hotData: HotData
|
||||
): { hot: Hot; hotState: HotState } {
|
||||
const hotState: HotState = {
|
||||
selfAccepted: false,
|
||||
selfDeclined: false,
|
||||
selfInvalidated: false,
|
||||
disposeHandlers: [],
|
||||
}
|
||||
|
||||
const hot: Hot = {
|
||||
// TODO(alexkirsz) This is not defined in the HMR API. It was used to
|
||||
// decide whether to warn whenever an HMR-disposed module required other
|
||||
// modules. We might want to remove it.
|
||||
active: true,
|
||||
|
||||
data: hotData ?? {},
|
||||
|
||||
// TODO(alexkirsz) Support full (dep, callback, errorHandler) form.
|
||||
accept: (
|
||||
modules?: string | string[] | AcceptErrorHandler,
|
||||
_callback?: AcceptCallback,
|
||||
_errorHandler?: AcceptErrorHandler
|
||||
) => {
|
||||
if (modules === undefined) {
|
||||
hotState.selfAccepted = true
|
||||
} else if (typeof modules === 'function') {
|
||||
hotState.selfAccepted = modules
|
||||
} else {
|
||||
throw new Error('unsupported `accept` signature')
|
||||
}
|
||||
},
|
||||
|
||||
decline: (dep) => {
|
||||
if (dep === undefined) {
|
||||
hotState.selfDeclined = true
|
||||
} else {
|
||||
throw new Error('unsupported `decline` signature')
|
||||
}
|
||||
},
|
||||
|
||||
dispose: (callback) => {
|
||||
hotState.disposeHandlers.push(callback)
|
||||
},
|
||||
|
||||
addDisposeHandler: (callback) => {
|
||||
hotState.disposeHandlers.push(callback)
|
||||
},
|
||||
|
||||
removeDisposeHandler: (callback) => {
|
||||
const idx = hotState.disposeHandlers.indexOf(callback)
|
||||
if (idx >= 0) {
|
||||
hotState.disposeHandlers.splice(idx, 1)
|
||||
}
|
||||
},
|
||||
|
||||
invalidate: () => {
|
||||
hotState.selfInvalidated = true
|
||||
queuedInvalidatedModules.add(moduleId)
|
||||
},
|
||||
|
||||
// NOTE(alexkirsz) This is part of the management API, which we don't
|
||||
// implement, but the Next.js React Refresh runtime uses this to decide
|
||||
// whether to schedule an update.
|
||||
status: () => 'idle',
|
||||
|
||||
// NOTE(alexkirsz) Since we always return "idle" for now, these are no-ops.
|
||||
addStatusHandler: (_handler) => {},
|
||||
removeStatusHandler: (_handler) => {},
|
||||
|
||||
// NOTE(jridgewell) Check returns the list of updated modules, but we don't
|
||||
// want the webpack code paths to ever update (the turbopack paths handle
|
||||
// this already).
|
||||
check: () => Promise.resolve(null),
|
||||
}
|
||||
|
||||
return { hot, hotState }
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a module from a chunk.
|
||||
* Returns `true` if there are no remaining chunks including this module.
|
||||
|
||||
-21
@@ -58,27 +58,6 @@ type ChunkList = {
|
||||
source: 'entry' | 'dynamic'
|
||||
}
|
||||
|
||||
enum SourceType {
|
||||
/**
|
||||
* The module was instantiated because it was included in an evaluated chunk's
|
||||
* runtime.
|
||||
* SourceData is a ChunkPath.
|
||||
*/
|
||||
Runtime = 0,
|
||||
/**
|
||||
* The module was instantiated because a parent module imported it.
|
||||
* SourceData is a ModuleId.
|
||||
*/
|
||||
Parent = 1,
|
||||
/**
|
||||
* The module was instantiated because it was included in a chunk's hot module
|
||||
* update.
|
||||
* SourceData is an array of ModuleIds or undefined.
|
||||
*/
|
||||
Update = 2,
|
||||
}
|
||||
|
||||
type SourceData = ChunkPath | ModuleId | ModuleId[] | undefined
|
||||
interface RuntimeBackend {
|
||||
registerChunk: (
|
||||
chunkPath: ChunkPath | ChunkScript,
|
||||
|
||||
@@ -0,0 +1,827 @@
|
||||
/// <reference path="./runtime-utils.ts" />
|
||||
/// <reference path="./runtime-types.d.ts" />
|
||||
/// <reference path="./dev-extensions.ts" />
|
||||
/// <reference path="./dev-protocol.d.ts" />
|
||||
|
||||
type HotModuleFactoryFunction = ModuleFactoryFunction<
|
||||
HotModule,
|
||||
TurbopackBaseContext<HotModule>
|
||||
>
|
||||
|
||||
/**
|
||||
* Shared HMR (Hot Module Replacement) implementation.
|
||||
*
|
||||
* This file contains the complete HMR implementation that's shared between
|
||||
* browser and Node.js runtimes. It manages module hot state, dependency
|
||||
* tracking, the module.hot API, and the full HMR update flow.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The development module cache shared across the runtime.
|
||||
* Browser runtime declares this directly.
|
||||
* Node.js runtime assigns globalThis.__turbopack_module_cache__ to this.
|
||||
*/
|
||||
let devModuleCache: Record<ModuleId, any>
|
||||
|
||||
/**
|
||||
* Module IDs that are instantiated as part of the runtime of a chunk.
|
||||
*/
|
||||
let runtimeModules: Set<ModuleId>
|
||||
|
||||
/**
|
||||
* Maps module IDs to persisted data between executions of their hot module
|
||||
* implementation (`hot.data`).
|
||||
*/
|
||||
const moduleHotData: Map<ModuleId, HotData> = new Map()
|
||||
|
||||
/**
|
||||
* Maps module instances to their hot module state.
|
||||
* Uses WeakMap so it works with both HotModule and ModuleWithDirection.
|
||||
*/
|
||||
const moduleHotState: WeakMap<any, HotState> = new WeakMap()
|
||||
|
||||
/**
|
||||
* Modules that call `module.hot.invalidate()` (while being updated).
|
||||
*/
|
||||
const queuedInvalidatedModules: Set<ModuleId> = new Set()
|
||||
|
||||
class UpdateApplyError extends Error {
|
||||
name = 'UpdateApplyError'
|
||||
|
||||
dependencyChain: ModuleId[]
|
||||
|
||||
constructor(message: string, dependencyChain: ModuleId[]) {
|
||||
super(message)
|
||||
this.dependencyChain = dependencyChain
|
||||
}
|
||||
}
|
||||
|
||||
type ModuleEffect =
|
||||
| {
|
||||
type: 'unaccepted'
|
||||
dependencyChain: ModuleId[]
|
||||
}
|
||||
| {
|
||||
type: 'self-declined'
|
||||
dependencyChain: ModuleId[]
|
||||
moduleId: ModuleId
|
||||
}
|
||||
| {
|
||||
type: 'accepted'
|
||||
moduleId: ModuleId
|
||||
outdatedModules: Set<ModuleId>
|
||||
}
|
||||
|
||||
/**
|
||||
* Records parent-child relationship when a module imports another.
|
||||
* Should be called during module instantiation.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function trackModuleImport(
|
||||
parentModule: ModuleWithDirection,
|
||||
childModuleId: ModuleId,
|
||||
childModule: ModuleWithDirection | undefined
|
||||
): void {
|
||||
// Record that parent imports child
|
||||
if (parentModule.children.indexOf(childModuleId) === -1) {
|
||||
parentModule.children.push(childModuleId)
|
||||
}
|
||||
|
||||
// Record that child is imported by parent
|
||||
if (childModule && childModule.parents.indexOf(parentModule.id) === -1) {
|
||||
childModule.parents.push(parentModule.id)
|
||||
}
|
||||
}
|
||||
|
||||
function formatDependencyChain(dependencyChain: ModuleId[]): string {
|
||||
return `Dependency chain: ${dependencyChain.join(' -> ')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks the dependency tree to find all modules affected by a change.
|
||||
* Returns information about whether the update can be accepted and which
|
||||
* modules need to be invalidated.
|
||||
*
|
||||
* Copied directly from browser implementation.
|
||||
*
|
||||
* @param moduleId - The module to check for effects
|
||||
*/
|
||||
function getAffectedModuleEffects(moduleId: ModuleId): ModuleEffect {
|
||||
const outdatedModules: Set<ModuleId> = new Set()
|
||||
|
||||
type QueueItem = { moduleId?: ModuleId; dependencyChain: ModuleId[] }
|
||||
|
||||
const queue: QueueItem[] = [
|
||||
{
|
||||
moduleId,
|
||||
dependencyChain: [],
|
||||
},
|
||||
]
|
||||
|
||||
let nextItem
|
||||
while ((nextItem = queue.shift())) {
|
||||
const { moduleId: currentModuleId, dependencyChain } = nextItem
|
||||
|
||||
// We've arrived at the runtime of the chunk, which means that nothing
|
||||
// else above can accept this update.
|
||||
if (currentModuleId === undefined) {
|
||||
return {
|
||||
type: 'unaccepted',
|
||||
dependencyChain,
|
||||
}
|
||||
}
|
||||
|
||||
if (outdatedModules.has(currentModuleId)) {
|
||||
// Avoid infinite loops caused by cycles between modules in the dependency chain.
|
||||
continue
|
||||
}
|
||||
|
||||
outdatedModules.add(currentModuleId)
|
||||
|
||||
const module = devModuleCache[currentModuleId]
|
||||
const hotState = moduleHotState.get(module)!
|
||||
|
||||
if (
|
||||
// The module is not in the cache. Since this is a "modified" update,
|
||||
// it means that the module was never instantiated before.
|
||||
!module || // The module accepted itself without invalidating globalThis.
|
||||
// TODO is that right?
|
||||
(hotState.selfAccepted && !hotState.selfInvalidated)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (hotState.selfDeclined) {
|
||||
return {
|
||||
type: 'self-declined',
|
||||
dependencyChain,
|
||||
moduleId: currentModuleId,
|
||||
}
|
||||
}
|
||||
|
||||
if (runtimeModules.has(currentModuleId)) {
|
||||
queue.push({
|
||||
moduleId: undefined,
|
||||
dependencyChain: [...dependencyChain, currentModuleId],
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
for (const parentId of module.parents) {
|
||||
const parent = devModuleCache[parentId]
|
||||
|
||||
if (!parent) {
|
||||
// TODO(alexkirsz) Is this even possible?
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO(alexkirsz) Dependencies: check accepted and declined
|
||||
// dependencies here.
|
||||
|
||||
queue.push({
|
||||
moduleId: parentId,
|
||||
dependencyChain: [...dependencyChain, currentModuleId],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'accepted',
|
||||
moduleId,
|
||||
outdatedModules,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes all modules that need to be invalidated based on which modules changed.
|
||||
*
|
||||
* Copied directly from browser implementation.
|
||||
*
|
||||
* @param invalidated - The modules to check for invalidation
|
||||
*/
|
||||
function computedInvalidatedModules(
|
||||
invalidated: Iterable<ModuleId>
|
||||
): Set<ModuleId> {
|
||||
const outdatedModules = new Set<ModuleId>()
|
||||
|
||||
for (const moduleId of invalidated) {
|
||||
const effect = getAffectedModuleEffects(moduleId)
|
||||
|
||||
switch (effect.type) {
|
||||
case 'unaccepted':
|
||||
throw new UpdateApplyError(
|
||||
`cannot apply update: unaccepted module. ${formatDependencyChain(
|
||||
effect.dependencyChain
|
||||
)}.`,
|
||||
effect.dependencyChain
|
||||
)
|
||||
case 'self-declined':
|
||||
throw new UpdateApplyError(
|
||||
`cannot apply update: self-declined module. ${formatDependencyChain(
|
||||
effect.dependencyChain
|
||||
)}.`,
|
||||
effect.dependencyChain
|
||||
)
|
||||
case 'accepted':
|
||||
for (const outdatedModuleId of effect.outdatedModules) {
|
||||
outdatedModules.add(outdatedModuleId)
|
||||
}
|
||||
break
|
||||
// TODO(alexkirsz) Dependencies: handle dependencies effects.
|
||||
default:
|
||||
invariant(effect, (effect) => `Unknown effect type: ${effect?.type}`)
|
||||
}
|
||||
}
|
||||
|
||||
return outdatedModules
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the module.hot API object and its internal state.
|
||||
* This provides the HMR API that user code calls (module.hot.accept(), etc.)
|
||||
*/
|
||||
|
||||
function createModuleHot(
|
||||
moduleId: ModuleId,
|
||||
hotData: HotData
|
||||
): { hot: Hot; hotState: HotState } {
|
||||
const hotState: HotState = {
|
||||
selfAccepted: false,
|
||||
selfDeclined: false,
|
||||
selfInvalidated: false,
|
||||
disposeHandlers: [],
|
||||
}
|
||||
|
||||
const hot: Hot = {
|
||||
// TODO(alexkirsz) This is not defined in the HMR API. It was used to
|
||||
// decide whether to warn whenever an HMR-disposed module required other
|
||||
// modules. We might want to remove it.
|
||||
active: true,
|
||||
|
||||
data: hotData ?? {},
|
||||
|
||||
// TODO(alexkirsz) Support full (dep, callback, errorHandler) form.
|
||||
accept: (
|
||||
modules?: string | string[] | AcceptErrorHandler,
|
||||
_callback?: AcceptCallback,
|
||||
_errorHandler?: AcceptErrorHandler
|
||||
) => {
|
||||
if (modules === undefined) {
|
||||
hotState.selfAccepted = true
|
||||
} else if (typeof modules === 'function') {
|
||||
hotState.selfAccepted = modules
|
||||
} else {
|
||||
throw new Error('unsupported `accept` signature')
|
||||
}
|
||||
},
|
||||
|
||||
decline: (dep) => {
|
||||
if (dep === undefined) {
|
||||
hotState.selfDeclined = true
|
||||
} else {
|
||||
throw new Error('unsupported `decline` signature')
|
||||
}
|
||||
},
|
||||
|
||||
dispose: (callback) => {
|
||||
hotState.disposeHandlers.push(callback)
|
||||
},
|
||||
|
||||
addDisposeHandler: (callback) => {
|
||||
hotState.disposeHandlers.push(callback)
|
||||
},
|
||||
|
||||
removeDisposeHandler: (callback) => {
|
||||
const idx = hotState.disposeHandlers.indexOf(callback)
|
||||
if (idx >= 0) {
|
||||
hotState.disposeHandlers.splice(idx, 1)
|
||||
}
|
||||
},
|
||||
|
||||
invalidate: () => {
|
||||
hotState.selfInvalidated = true
|
||||
queuedInvalidatedModules.add(moduleId)
|
||||
},
|
||||
|
||||
// NOTE(alexkirsz) This is part of the management API, which we don't
|
||||
// implement, but the Next.js React Refresh runtime uses this to decide
|
||||
// whether to schedule an update.
|
||||
status: () => 'idle',
|
||||
|
||||
// NOTE(alexkirsz) Since we always return "idle" for now, these are no-ops.
|
||||
addStatusHandler: (_handler) => {},
|
||||
removeStatusHandler: (_handler) => {},
|
||||
|
||||
// NOTE(jridgewell) Check returns the list of updated modules, but we don't
|
||||
// want the webpack code paths to ever update (the turbopack paths handle
|
||||
// this already).
|
||||
check: () => Promise.resolve(null),
|
||||
}
|
||||
|
||||
return { hot, hotState }
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes queued invalidated modules and adds them to the outdated modules set.
|
||||
* Modules that call module.hot.invalidate() are queued and processed here.
|
||||
*
|
||||
* @param outdatedModules - The set of already outdated modules
|
||||
*/
|
||||
|
||||
function applyInvalidatedModules(
|
||||
outdatedModules: Set<ModuleId>
|
||||
): Set<ModuleId> {
|
||||
if (queuedInvalidatedModules.size > 0) {
|
||||
computedInvalidatedModules(queuedInvalidatedModules).forEach((moduleId) => {
|
||||
outdatedModules.add(moduleId)
|
||||
})
|
||||
|
||||
queuedInvalidatedModules.clear()
|
||||
}
|
||||
|
||||
return outdatedModules
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes which outdated modules have self-accepted and can be hot reloaded.
|
||||
*/
|
||||
|
||||
function computeOutdatedSelfAcceptedModules(
|
||||
outdatedModules: Iterable<ModuleId>
|
||||
): { moduleId: ModuleId; errorHandler: true | Function }[] {
|
||||
const outdatedSelfAcceptedModules: {
|
||||
moduleId: ModuleId
|
||||
errorHandler: true | Function
|
||||
}[] = []
|
||||
for (const moduleId of outdatedModules) {
|
||||
const module = devModuleCache[moduleId]
|
||||
const hotState = moduleHotState.get(module)
|
||||
if (module && hotState?.selfAccepted && !hotState.selfInvalidated) {
|
||||
outdatedSelfAcceptedModules.push({
|
||||
moduleId,
|
||||
errorHandler: hotState.selfAccepted,
|
||||
})
|
||||
}
|
||||
}
|
||||
return outdatedSelfAcceptedModules
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of an instance of a module.
|
||||
* Runs hot.dispose handlers and manages persistent hot data.
|
||||
*
|
||||
* NOTE: mode = "replace" will not remove modules from devModuleCache.
|
||||
* This must be done in a separate step afterwards.
|
||||
*/
|
||||
function disposeModule(moduleId: ModuleId, mode: 'clear' | 'replace') {
|
||||
const module = devModuleCache[moduleId]
|
||||
if (!module) {
|
||||
return
|
||||
}
|
||||
|
||||
const hotState = moduleHotState.get(module)
|
||||
if (!hotState) {
|
||||
return
|
||||
}
|
||||
|
||||
const data: HotData = {}
|
||||
|
||||
// Run the `hot.dispose` handler, if any, passing in the persistent
|
||||
// `hot.data` object.
|
||||
for (const disposeHandler of hotState.disposeHandlers) {
|
||||
disposeHandler(data)
|
||||
}
|
||||
|
||||
// This used to warn in `getOrInstantiateModuleFromParent` when a disposed
|
||||
// module is still importing other modules.
|
||||
if (module.hot) {
|
||||
module.hot.active = false
|
||||
}
|
||||
|
||||
moduleHotState.delete(module)
|
||||
|
||||
// TODO(alexkirsz) Dependencies: delete the module from outdated deps.
|
||||
|
||||
// Remove the disposed module from its children's parent list.
|
||||
// It will be added back once the module re-instantiates and imports its
|
||||
// children again.
|
||||
for (const childId of module.children) {
|
||||
const child = devModuleCache[childId]
|
||||
if (!child) {
|
||||
continue
|
||||
}
|
||||
|
||||
const idx = child.parents.indexOf(module.id)
|
||||
if (idx >= 0) {
|
||||
child.parents.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 'clear':
|
||||
delete devModuleCache[module.id]
|
||||
moduleHotData.delete(module.id)
|
||||
break
|
||||
case 'replace':
|
||||
moduleHotData.set(module.id, data)
|
||||
break
|
||||
default:
|
||||
invariant(mode, (mode) => `invalid mode: ${mode}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose phase: runs dispose handlers and cleans up outdated/disposed modules.
|
||||
* Returns the parent modules of outdated modules for use in the apply phase.
|
||||
*/
|
||||
|
||||
function disposePhase(
|
||||
outdatedModules: Iterable<ModuleId>,
|
||||
disposedModules: Iterable<ModuleId>
|
||||
): { outdatedModuleParents: Map<ModuleId, Array<ModuleId>> } {
|
||||
for (const moduleId of outdatedModules) {
|
||||
disposeModule(moduleId, 'replace')
|
||||
}
|
||||
|
||||
for (const moduleId of disposedModules) {
|
||||
disposeModule(moduleId, 'clear')
|
||||
}
|
||||
|
||||
// Removing modules from the module cache is a separate step.
|
||||
// We also want to keep track of previous parents of the outdated modules.
|
||||
const outdatedModuleParents = new Map<ModuleId, Array<ModuleId>>()
|
||||
for (const moduleId of outdatedModules) {
|
||||
const oldModule = devModuleCache[moduleId]
|
||||
outdatedModuleParents.set(moduleId, oldModule?.parents)
|
||||
delete devModuleCache[moduleId]
|
||||
}
|
||||
|
||||
// TODO(alexkirsz) Dependencies: remove outdated dependency from module
|
||||
// children.
|
||||
|
||||
return { outdatedModuleParents }
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
/**
|
||||
* Shared module instantiation logic.
|
||||
* This handles the full module instantiation flow for both browser and Node.js.
|
||||
* Only React Refresh hooks differ between platforms (passed as callback).
|
||||
*/
|
||||
function instantiateModuleShared(
|
||||
moduleId: ModuleId,
|
||||
sourceType: SourceType,
|
||||
sourceData: SourceData,
|
||||
moduleFactories: ModuleFactories,
|
||||
devModuleCache: ModuleCache<HotModule>,
|
||||
runtimeModules: Set<ModuleId>,
|
||||
createModuleObjectFn: (id: ModuleId) => HotModule,
|
||||
createContextFn: (module: HotModule, exports: Exports, refresh?: any) => any,
|
||||
runModuleExecutionHooksFn: (
|
||||
module: HotModule,
|
||||
exec: (refresh: any) => void
|
||||
) => void
|
||||
): HotModule {
|
||||
// 1. Factory validation (same in both browser and Node.js)
|
||||
const id = moduleId as string
|
||||
const moduleFactory = moduleFactories.get(id)
|
||||
if (typeof moduleFactory !== 'function') {
|
||||
throw new Error(
|
||||
`Module ${id} was instantiated, but the module factory is not available.`
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Hot API setup (same in both - works for browser, included for Node.js)
|
||||
const hotData = moduleHotData.get(id)!
|
||||
const { hot, hotState } = createModuleHot(id, hotData)
|
||||
|
||||
// 3. Parent assignment logic (same in both)
|
||||
let parents: ModuleId[]
|
||||
switch (sourceType) {
|
||||
case SourceType.Runtime:
|
||||
runtimeModules.add(id)
|
||||
parents = []
|
||||
break
|
||||
case SourceType.Parent:
|
||||
parents = [sourceData as ModuleId]
|
||||
break
|
||||
case SourceType.Update:
|
||||
parents = (sourceData as ModuleId[]) || []
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown source type: ${sourceType}`)
|
||||
}
|
||||
|
||||
// 4. Module creation (platform creates base module object)
|
||||
const module = createModuleObjectFn(id)
|
||||
const exports = module.exports
|
||||
module.parents = parents
|
||||
module.children = []
|
||||
module.hot = hot
|
||||
|
||||
devModuleCache[id] = module
|
||||
moduleHotState.set(module, hotState)
|
||||
|
||||
// 5. Module execution (React Refresh hooks are platform-specific)
|
||||
try {
|
||||
runModuleExecutionHooksFn(module, (refresh) => {
|
||||
const context = createContextFn(module, exports, refresh)
|
||||
moduleFactory.call(exports, context, module, exports)
|
||||
})
|
||||
} catch (error) {
|
||||
module.error = error as any
|
||||
throw error
|
||||
}
|
||||
|
||||
// 6. ESM interop (same in both)
|
||||
if (module.namespaceObject && module.exports !== module.namespaceObject) {
|
||||
// in case of a circular dependency: cjs1 -> esm2 -> cjs1
|
||||
interopEsm(module.exports, module.namespaceObject)
|
||||
}
|
||||
|
||||
return module
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes update entries and chunks to determine which modules were added, modified, or deleted.
|
||||
* This is pure logic that doesn't depend on the runtime environment.
|
||||
*/
|
||||
function computeChangedModules(
|
||||
entries: Record<ModuleId, EcmascriptModuleEntry>,
|
||||
updates: Record<ChunkPath, EcmascriptMergedChunkUpdate>,
|
||||
chunkModulesMap?: Map<ChunkPath, Set<ModuleId>>
|
||||
): {
|
||||
added: Map<ModuleId, EcmascriptModuleEntry | undefined>
|
||||
modified: Map<ModuleId, EcmascriptModuleEntry>
|
||||
deleted: Set<ModuleId>
|
||||
chunksAdded: Map<ChunkPath, Set<ModuleId>>
|
||||
chunksDeleted: Map<ChunkPath, Set<ModuleId>>
|
||||
} {
|
||||
const chunksAdded = new Map()
|
||||
const chunksDeleted = new Map()
|
||||
const added: Map<ModuleId, EcmascriptModuleEntry> = new Map()
|
||||
const modified = new Map()
|
||||
const deleted: Set<ModuleId> = new Set()
|
||||
|
||||
for (const [chunkPath, mergedChunkUpdate] of Object.entries(updates) as Array<
|
||||
[ChunkPath, EcmascriptMergedChunkUpdate]
|
||||
>) {
|
||||
switch (mergedChunkUpdate.type) {
|
||||
case 'added': {
|
||||
const updateAdded = new Set(mergedChunkUpdate.modules)
|
||||
for (const moduleId of updateAdded) {
|
||||
added.set(moduleId, entries[moduleId])
|
||||
}
|
||||
chunksAdded.set(chunkPath, updateAdded)
|
||||
break
|
||||
}
|
||||
case 'deleted': {
|
||||
const updateDeleted = chunkModulesMap
|
||||
? new Set(chunkModulesMap.get(chunkPath))
|
||||
: new Set<ModuleId>()
|
||||
for (const moduleId of updateDeleted) {
|
||||
deleted.add(moduleId)
|
||||
}
|
||||
chunksDeleted.set(chunkPath, updateDeleted)
|
||||
break
|
||||
}
|
||||
case 'partial': {
|
||||
const updateAdded = new Set(mergedChunkUpdate.added)
|
||||
const updateDeleted = new Set(mergedChunkUpdate.deleted)
|
||||
for (const moduleId of updateAdded) {
|
||||
added.set(moduleId, entries[moduleId])
|
||||
}
|
||||
for (const moduleId of updateDeleted) {
|
||||
deleted.add(moduleId)
|
||||
}
|
||||
chunksAdded.set(chunkPath, updateAdded)
|
||||
chunksDeleted.set(chunkPath, updateDeleted)
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown merged chunk update`)
|
||||
}
|
||||
}
|
||||
|
||||
// If a module was added from one chunk and deleted from another in the same update,
|
||||
// consider it to be modified, as it means the module was moved from one chunk to another
|
||||
// AND has new code in a single update.
|
||||
for (const moduleId of added.keys()) {
|
||||
if (deleted.has(moduleId)) {
|
||||
added.delete(moduleId)
|
||||
deleted.delete(moduleId)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [moduleId, entry] of Object.entries(entries)) {
|
||||
// Modules that haven't been added to any chunk but have new code are considered
|
||||
// to be modified.
|
||||
// This needs to be under the previous loop, as we need it to get rid of modules
|
||||
// that were added and deleted in the same update.
|
||||
if (!added.has(moduleId)) {
|
||||
modified.set(moduleId, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return { added, deleted, modified, chunksAdded, chunksDeleted }
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles new module code and walks the dependency tree to find all outdated modules.
|
||||
* Uses the evalModuleEntry function to compile code (platform-specific).
|
||||
*
|
||||
* @param added - Map of added modules
|
||||
* @param modified - Map of modified modules
|
||||
* @param evalModuleEntry - Function to compile module code
|
||||
*/
|
||||
function computeOutdatedModules(
|
||||
added: Map<ModuleId, EcmascriptModuleEntry | undefined>,
|
||||
modified: Map<ModuleId, EcmascriptModuleEntry>,
|
||||
evalModuleEntry: (entry: EcmascriptModuleEntry) => HotModuleFactoryFunction
|
||||
): {
|
||||
outdatedModules: Set<ModuleId>
|
||||
newModuleFactories: Map<ModuleId, HotModuleFactoryFunction>
|
||||
} {
|
||||
const newModuleFactories = new Map<ModuleId, HotModuleFactoryFunction>()
|
||||
|
||||
// Compile added modules
|
||||
for (const [moduleId, entry] of added) {
|
||||
if (entry != null) {
|
||||
newModuleFactories.set(moduleId, evalModuleEntry(entry))
|
||||
}
|
||||
}
|
||||
|
||||
// Walk dependency tree to find all modules affected by modifications
|
||||
const outdatedModules = computedInvalidatedModules(modified.keys())
|
||||
|
||||
for (const [moduleId, entry] of modified) {
|
||||
newModuleFactories.set(moduleId, evalModuleEntry(entry))
|
||||
}
|
||||
|
||||
return { outdatedModules, newModuleFactories }
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates module factories and re-instantiates self-accepted modules.
|
||||
* Uses the instantiateModule function (platform-specific via callback).
|
||||
*/
|
||||
function applyPhase(
|
||||
outdatedSelfAcceptedModules: {
|
||||
moduleId: ModuleId
|
||||
errorHandler: true | Function
|
||||
}[],
|
||||
newModuleFactories: Map<ModuleId, HotModuleFactoryFunction>,
|
||||
outdatedModuleParents: Map<ModuleId, Array<ModuleId>>,
|
||||
moduleFactories: ModuleFactories,
|
||||
devModuleCache: ModuleCache<HotModule>,
|
||||
instantiateModuleFn: (
|
||||
moduleId: ModuleId,
|
||||
sourceType: SourceType,
|
||||
sourceData: SourceData
|
||||
) => HotModule,
|
||||
applyModuleFactoryNameFn: (factory: HotModuleFactoryFunction) => void,
|
||||
reportError: (err: any) => void
|
||||
) {
|
||||
// Update module factories
|
||||
for (const [moduleId, factory] of newModuleFactories.entries()) {
|
||||
applyModuleFactoryNameFn(factory)
|
||||
moduleFactories.set(moduleId, factory)
|
||||
}
|
||||
|
||||
// TODO(alexkirsz) Run new runtime entries here.
|
||||
|
||||
// TODO(alexkirsz) Dependencies: call accept handlers for outdated deps.
|
||||
|
||||
// Re-instantiate all outdated self-accepted modules
|
||||
for (const { moduleId, errorHandler } of outdatedSelfAcceptedModules) {
|
||||
try {
|
||||
instantiateModuleFn(
|
||||
moduleId,
|
||||
SourceType.Update,
|
||||
outdatedModuleParents.get(moduleId)
|
||||
)
|
||||
} catch (err) {
|
||||
if (typeof errorHandler === 'function') {
|
||||
try {
|
||||
errorHandler(err, { moduleId, module: devModuleCache[moduleId] })
|
||||
} catch (err2) {
|
||||
reportError(err2)
|
||||
reportError(err)
|
||||
}
|
||||
} else {
|
||||
reportError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation that orchestrates the full HMR update flow:
|
||||
* invalidation, disposal, and application of new modules.
|
||||
*/
|
||||
function applyInternal(
|
||||
outdatedModules: Set<ModuleId>,
|
||||
disposedModules: Iterable<ModuleId>,
|
||||
newModuleFactories: Map<ModuleId, HotModuleFactoryFunction>,
|
||||
moduleFactories: ModuleFactories,
|
||||
devModuleCache: ModuleCache<HotModule>,
|
||||
instantiateModuleFn: (
|
||||
moduleId: ModuleId,
|
||||
sourceType: SourceType,
|
||||
sourceData: SourceData
|
||||
) => HotModule,
|
||||
applyModuleFactoryNameFn: (factory: HotModuleFactoryFunction) => void
|
||||
) {
|
||||
outdatedModules = applyInvalidatedModules(outdatedModules)
|
||||
|
||||
// Find self-accepted modules to re-instantiate
|
||||
const outdatedSelfAcceptedModules =
|
||||
computeOutdatedSelfAcceptedModules(outdatedModules)
|
||||
|
||||
// Run dispose handlers, save hot.data, clear caches
|
||||
const { outdatedModuleParents } = disposePhase(
|
||||
outdatedModules,
|
||||
disposedModules
|
||||
)
|
||||
|
||||
let error: any
|
||||
|
||||
function reportError(err: any) {
|
||||
if (!error) error = err // Keep first error
|
||||
}
|
||||
|
||||
applyPhase(
|
||||
outdatedSelfAcceptedModules,
|
||||
newModuleFactories,
|
||||
outdatedModuleParents,
|
||||
moduleFactories,
|
||||
devModuleCache,
|
||||
instantiateModuleFn,
|
||||
applyModuleFactoryNameFn,
|
||||
reportError
|
||||
)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
// Recursively apply any queued invalidations from new module execution
|
||||
if (queuedInvalidatedModules.size > 0) {
|
||||
applyInternal(
|
||||
new Set(),
|
||||
[],
|
||||
new Map(),
|
||||
moduleFactories,
|
||||
devModuleCache,
|
||||
instantiateModuleFn,
|
||||
applyModuleFactoryNameFn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point for applying an ECMAScript merged update.
|
||||
* This is called by both browser and Node.js runtimes with platform-specific callbacks.
|
||||
*/
|
||||
function applyEcmascriptMergedUpdateShared(options: {
|
||||
added: Map<ModuleId, EcmascriptModuleEntry | undefined>
|
||||
modified: Map<ModuleId, EcmascriptModuleEntry>
|
||||
disposedModules: Iterable<ModuleId>
|
||||
evalModuleEntry: (entry: EcmascriptModuleEntry) => HotModuleFactoryFunction
|
||||
instantiateModule: (
|
||||
moduleId: ModuleId,
|
||||
sourceType: SourceType,
|
||||
sourceData: SourceData
|
||||
) => HotModule
|
||||
applyModuleFactoryName: (factory: HotModuleFactoryFunction) => void
|
||||
moduleFactories: ModuleFactories
|
||||
devModuleCache: ModuleCache<HotModule>
|
||||
}) {
|
||||
const {
|
||||
added,
|
||||
modified,
|
||||
disposedModules,
|
||||
evalModuleEntry,
|
||||
instantiateModule,
|
||||
applyModuleFactoryName,
|
||||
moduleFactories,
|
||||
devModuleCache,
|
||||
} = options
|
||||
|
||||
const { outdatedModules, newModuleFactories } = computeOutdatedModules(
|
||||
added,
|
||||
modified,
|
||||
evalModuleEntry
|
||||
)
|
||||
|
||||
applyInternal(
|
||||
outdatedModules,
|
||||
disposedModules,
|
||||
newModuleFactories,
|
||||
moduleFactories,
|
||||
devModuleCache,
|
||||
instantiateModule,
|
||||
applyModuleFactoryName
|
||||
)
|
||||
}
|
||||
+8
@@ -76,6 +76,14 @@ type LoadWebAssemblyModule = (
|
||||
type ModuleCache<M> = Record<ModuleId, M>
|
||||
// TODO properly type values here
|
||||
type ModuleFactories = Map<ModuleId, Function>
|
||||
/**
|
||||
* Constraint for module factory functions used in HMR.
|
||||
* Platform-specific runtimes define their own ModuleFactory types that must satisfy this constraint.
|
||||
*/
|
||||
type ModuleFactoryFunction<
|
||||
TModule extends Module,
|
||||
TContext extends TurbopackBaseContext<TModule>,
|
||||
> = (this: TContext, ...args: unknown[]) => unknown
|
||||
/**
|
||||
* This is an alternating, non-empty arrow of module factory functions and module ids
|
||||
* `[id1, id2..., factory1, id3, factory2, id4, id5, factory3]`
|
||||
|
||||
+52
-3
@@ -11,6 +11,32 @@
|
||||
|
||||
type EsmNamespaceObject = Record<string, any>
|
||||
|
||||
/**
|
||||
* Describes why a module was instantiated.
|
||||
* Shared between browser and Node.js runtimes.
|
||||
*/
|
||||
enum SourceType {
|
||||
/**
|
||||
* The module was instantiated because it was included in an evaluated chunk's
|
||||
* runtime.
|
||||
* SourceData is a ChunkPath.
|
||||
*/
|
||||
Runtime = 0,
|
||||
/**
|
||||
* The module was instantiated because a parent module imported it.
|
||||
* SourceData is a ModuleId.
|
||||
*/
|
||||
Parent = 1,
|
||||
/**
|
||||
* The module was instantiated because it was included in a chunk's hot module
|
||||
* update.
|
||||
* SourceData is an array of ModuleIds or undefined.
|
||||
*/
|
||||
Update = 2,
|
||||
}
|
||||
|
||||
type SourceData = ChunkPath | ModuleId | ModuleId[] | undefined
|
||||
|
||||
// @ts-ignore Defined in `dev-base.ts`
|
||||
declare function getOrInstantiateModuleFromParent<M>(
|
||||
id: ModuleId,
|
||||
@@ -105,6 +131,17 @@ function createModuleObject(id: ModuleId): Module {
|
||||
}
|
||||
}
|
||||
|
||||
function createModuleWithDirection(id: ModuleId): ModuleWithDirection {
|
||||
return {
|
||||
exports: {},
|
||||
error: undefined,
|
||||
id,
|
||||
namespaceObject: undefined,
|
||||
parents: [],
|
||||
children: [],
|
||||
}
|
||||
}
|
||||
|
||||
type BindingTag = 0
|
||||
const BindingTag_Value = 0 as BindingTag
|
||||
|
||||
@@ -512,9 +549,21 @@ function installCompressedModuleFactories(
|
||||
if (end === chunkModules.length) {
|
||||
throw new Error('malformed chunk format, expected a factory function')
|
||||
}
|
||||
// Each chunk item has a 'primary id' and optional additional ids. If the primary id is already
|
||||
// present we know all the additional ids are also present, so we don't need to check.
|
||||
if (!moduleFactories.has(moduleId)) {
|
||||
|
||||
// Check if ANY of the module IDs in this group already have factories (e.g., from HMR updates).
|
||||
// If so, skip installing the old factory from disk to preserve the HMR-updated code.
|
||||
let hasExistingFactory = false
|
||||
const groupIds: ModuleId[] = []
|
||||
for (let j = i; j < end; j++) {
|
||||
const id = chunkModules[j] as ModuleId
|
||||
groupIds.push(id)
|
||||
if (moduleFactories.has(id)) {
|
||||
hasExistingFactory = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasExistingFactory) {
|
||||
const moduleFactoryFn = chunkModules[end] as Function
|
||||
applyModuleFactoryName(moduleFactoryFn)
|
||||
newModuleId?.(moduleId)
|
||||
|
||||
@@ -38,6 +38,7 @@ pub async fn get_browser_runtime_code(
|
||||
match runtime_type {
|
||||
RuntimeType::Production => runtime_base_code.push("browser/runtime/base/build-base.ts"),
|
||||
RuntimeType::Development => {
|
||||
runtime_base_code.push("shared/runtime/hmr-runtime.ts");
|
||||
runtime_base_code.push("browser/runtime/base/dev-base.ts");
|
||||
}
|
||||
#[cfg(feature = "test")]
|
||||
|
||||
+7
-6
File diff suppressed because one or more lines are too long
+628
-497
File diff suppressed because it is too large
Load Diff
+43
-3
@@ -8,6 +8,26 @@ const WORKER_FORWARDED_GLOBALS = [];
|
||||
*
|
||||
* It will be prepended to the runtime code of each runtime.
|
||||
*/ /* eslint-disable @typescript-eslint/no-unused-vars */ /// <reference path="./runtime-types.d.ts" />
|
||||
/**
|
||||
* Describes why a module was instantiated.
|
||||
* Shared between browser and Node.js runtimes.
|
||||
*/ var SourceType = /*#__PURE__*/ function(SourceType) {
|
||||
/**
|
||||
* The module was instantiated because it was included in an evaluated chunk's
|
||||
* runtime.
|
||||
* SourceData is a ChunkPath.
|
||||
*/ SourceType[SourceType["Runtime"] = 0] = "Runtime";
|
||||
/**
|
||||
* The module was instantiated because a parent module imported it.
|
||||
* SourceData is a ModuleId.
|
||||
*/ SourceType[SourceType["Parent"] = 1] = "Parent";
|
||||
/**
|
||||
* The module was instantiated because it was included in a chunk's hot module
|
||||
* update.
|
||||
* SourceData is an array of ModuleIds or undefined.
|
||||
*/ SourceType[SourceType["Update"] = 2] = "Update";
|
||||
return SourceType;
|
||||
}(SourceType || {});
|
||||
const REEXPORTED_OBJECTS = new WeakMap();
|
||||
/**
|
||||
* Constructs the `__turbopack_context__` object for a module.
|
||||
@@ -48,6 +68,16 @@ function getOverwrittenModule(moduleCache, id) {
|
||||
namespaceObject: undefined
|
||||
};
|
||||
}
|
||||
function createModuleWithDirection(id) {
|
||||
return {
|
||||
exports: {},
|
||||
error: undefined,
|
||||
id,
|
||||
namespaceObject: undefined,
|
||||
parents: [],
|
||||
children: []
|
||||
};
|
||||
}
|
||||
const BindingTag_Value = 0;
|
||||
/**
|
||||
* Adds the getters to the exports object.
|
||||
@@ -341,9 +371,19 @@ function installCompressedModuleFactories(chunkModules, offset, moduleFactories,
|
||||
if (end === chunkModules.length) {
|
||||
throw new Error('malformed chunk format, expected a factory function');
|
||||
}
|
||||
// Each chunk item has a 'primary id' and optional additional ids. If the primary id is already
|
||||
// present we know all the additional ids are also present, so we don't need to check.
|
||||
if (!moduleFactories.has(moduleId)) {
|
||||
// Check if ANY of the module IDs in this group already have factories (e.g., from HMR updates).
|
||||
// If so, skip installing the old factory from disk to preserve the HMR-updated code.
|
||||
let hasExistingFactory = false;
|
||||
const groupIds = [];
|
||||
for(let j = i; j < end; j++){
|
||||
const id = chunkModules[j];
|
||||
groupIds.push(id);
|
||||
if (moduleFactories.has(id)) {
|
||||
hasExistingFactory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasExistingFactory) {
|
||||
const moduleFactoryFn = chunkModules[end];
|
||||
applyModuleFactoryName(moduleFactoryFn);
|
||||
newModuleId?.(moduleId);
|
||||
|
||||
+5
-5
File diff suppressed because one or more lines are too long
+43
-3
@@ -8,6 +8,26 @@ const WORKER_FORWARDED_GLOBALS = [];
|
||||
*
|
||||
* It will be prepended to the runtime code of each runtime.
|
||||
*/ /* eslint-disable @typescript-eslint/no-unused-vars */ /// <reference path="./runtime-types.d.ts" />
|
||||
/**
|
||||
* Describes why a module was instantiated.
|
||||
* Shared between browser and Node.js runtimes.
|
||||
*/ var SourceType = /*#__PURE__*/ function(SourceType) {
|
||||
/**
|
||||
* The module was instantiated because it was included in an evaluated chunk's
|
||||
* runtime.
|
||||
* SourceData is a ChunkPath.
|
||||
*/ SourceType[SourceType["Runtime"] = 0] = "Runtime";
|
||||
/**
|
||||
* The module was instantiated because a parent module imported it.
|
||||
* SourceData is a ModuleId.
|
||||
*/ SourceType[SourceType["Parent"] = 1] = "Parent";
|
||||
/**
|
||||
* The module was instantiated because it was included in a chunk's hot module
|
||||
* update.
|
||||
* SourceData is an array of ModuleIds or undefined.
|
||||
*/ SourceType[SourceType["Update"] = 2] = "Update";
|
||||
return SourceType;
|
||||
}(SourceType || {});
|
||||
const REEXPORTED_OBJECTS = new WeakMap();
|
||||
/**
|
||||
* Constructs the `__turbopack_context__` object for a module.
|
||||
@@ -48,6 +68,16 @@ function getOverwrittenModule(moduleCache, id) {
|
||||
namespaceObject: undefined
|
||||
};
|
||||
}
|
||||
function createModuleWithDirection(id) {
|
||||
return {
|
||||
exports: {},
|
||||
error: undefined,
|
||||
id,
|
||||
namespaceObject: undefined,
|
||||
parents: [],
|
||||
children: []
|
||||
};
|
||||
}
|
||||
const BindingTag_Value = 0;
|
||||
/**
|
||||
* Adds the getters to the exports object.
|
||||
@@ -341,9 +371,19 @@ function installCompressedModuleFactories(chunkModules, offset, moduleFactories,
|
||||
if (end === chunkModules.length) {
|
||||
throw new Error('malformed chunk format, expected a factory function');
|
||||
}
|
||||
// Each chunk item has a 'primary id' and optional additional ids. If the primary id is already
|
||||
// present we know all the additional ids are also present, so we don't need to check.
|
||||
if (!moduleFactories.has(moduleId)) {
|
||||
// Check if ANY of the module IDs in this group already have factories (e.g., from HMR updates).
|
||||
// If so, skip installing the old factory from disk to preserve the HMR-updated code.
|
||||
let hasExistingFactory = false;
|
||||
const groupIds = [];
|
||||
for(let j = i; j < end; j++){
|
||||
const id = chunkModules[j];
|
||||
groupIds.push(id);
|
||||
if (moduleFactories.has(id)) {
|
||||
hasExistingFactory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasExistingFactory) {
|
||||
const moduleFactoryFn = chunkModules[end];
|
||||
applyModuleFactoryName(moduleFactoryFn);
|
||||
newModuleId?.(moduleId);
|
||||
|
||||
+5
-5
File diff suppressed because one or more lines are too long
+626
-495
File diff suppressed because it is too large
Load Diff
+6
-5
File diff suppressed because one or more lines are too long
+55
-32
@@ -163,6 +163,26 @@ function _ts_generator(thisArg, body) {
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Describes why a module was instantiated.
|
||||
* Shared between browser and Node.js runtimes.
|
||||
*/ var SourceType = /*#__PURE__*/ function(SourceType) {
|
||||
/**
|
||||
* The module was instantiated because it was included in an evaluated chunk's
|
||||
* runtime.
|
||||
* SourceData is a ChunkPath.
|
||||
*/ SourceType[SourceType["Runtime"] = 0] = "Runtime";
|
||||
/**
|
||||
* The module was instantiated because a parent module imported it.
|
||||
* SourceData is a ModuleId.
|
||||
*/ SourceType[SourceType["Parent"] = 1] = "Parent";
|
||||
/**
|
||||
* The module was instantiated because it was included in a chunk's hot module
|
||||
* update.
|
||||
* SourceData is an array of ModuleIds or undefined.
|
||||
*/ SourceType[SourceType["Update"] = 2] = "Update";
|
||||
return SourceType;
|
||||
}(SourceType || {});
|
||||
var REEXPORTED_OBJECTS = new WeakMap();
|
||||
/**
|
||||
* Constructs the `__turbopack_context__` object for a module.
|
||||
@@ -203,6 +223,16 @@ function getOverwrittenModule(moduleCache, id) {
|
||||
namespaceObject: undefined
|
||||
};
|
||||
}
|
||||
function createModuleWithDirection(id) {
|
||||
return {
|
||||
exports: {},
|
||||
error: undefined,
|
||||
id: id,
|
||||
namespaceObject: undefined,
|
||||
parents: [],
|
||||
children: []
|
||||
};
|
||||
}
|
||||
var BindingTag_Value = 0;
|
||||
/**
|
||||
* Adds the getters to the exports object.
|
||||
@@ -588,9 +618,19 @@ function installCompressedModuleFactories(chunkModules, offset, moduleFactories,
|
||||
if (end === chunkModules.length) {
|
||||
throw new Error('malformed chunk format, expected a factory function');
|
||||
}
|
||||
// Each chunk item has a 'primary id' and optional additional ids. If the primary id is already
|
||||
// present we know all the additional ids are also present, so we don't need to check.
|
||||
if (!moduleFactories.has(moduleId)) {
|
||||
// Check if ANY of the module IDs in this group already have factories (e.g., from HMR updates).
|
||||
// If so, skip installing the old factory from disk to preserve the HMR-updated code.
|
||||
var hasExistingFactory = false;
|
||||
var groupIds = [];
|
||||
for(var j = i; j < end; j++){
|
||||
var id = chunkModules[j];
|
||||
groupIds.push(id);
|
||||
if (moduleFactories.has(id)) {
|
||||
hasExistingFactory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasExistingFactory) {
|
||||
var moduleFactoryFn = chunkModules[end];
|
||||
applyModuleFactoryName(moduleFactoryFn);
|
||||
newModuleId === null || newModuleId === void 0 ? void 0 : newModuleId(moduleId);
|
||||
@@ -951,23 +991,6 @@ function _ts_generator(thisArg, body) {
|
||||
}
|
||||
}
|
||||
var browserContextPrototype = Context.prototype;
|
||||
var SourceType = /*#__PURE__*/ function(SourceType) {
|
||||
/**
|
||||
* The module was instantiated because it was included in an evaluated chunk's
|
||||
* runtime.
|
||||
* SourceData is a ChunkPath.
|
||||
*/ SourceType[SourceType["Runtime"] = 0] = "Runtime";
|
||||
/**
|
||||
* The module was instantiated because a parent module imported it.
|
||||
* SourceData is a ModuleId.
|
||||
*/ SourceType[SourceType["Parent"] = 1] = "Parent";
|
||||
/**
|
||||
* The module was instantiated because it was included in a chunk's hot module
|
||||
* update.
|
||||
* SourceData is an array of ModuleIds or undefined.
|
||||
*/ SourceType[SourceType["Update"] = 2] = "Update";
|
||||
return SourceType;
|
||||
}(SourceType || {});
|
||||
var moduleFactories = new Map();
|
||||
contextPrototype.M = moduleFactories;
|
||||
var availableModules = new Map();
|
||||
@@ -975,13 +998,13 @@ var availableModuleChunks = new Map();
|
||||
function factoryNotAvailableMessage(moduleId, sourceType, sourceData) {
|
||||
var instantiationReason;
|
||||
switch(sourceType){
|
||||
case 0:
|
||||
case SourceType.Runtime:
|
||||
instantiationReason = `as a runtime entry of chunk ${sourceData}`;
|
||||
break;
|
||||
case 1:
|
||||
case SourceType.Parent:
|
||||
instantiationReason = `because it was required from module ${sourceData}`;
|
||||
break;
|
||||
case 2:
|
||||
case SourceType.Update:
|
||||
instantiationReason = 'because of an HMR update';
|
||||
break;
|
||||
default:
|
||||
@@ -992,11 +1015,11 @@ function factoryNotAvailableMessage(moduleId, sourceType, sourceData) {
|
||||
return `Module ${moduleId} was instantiated ${instantiationReason}, but the module factory is not available.`;
|
||||
}
|
||||
function loadChunk(chunkData) {
|
||||
return loadChunkInternal(1, this.m.id, chunkData);
|
||||
return loadChunkInternal(SourceType.Parent, this.m.id, chunkData);
|
||||
}
|
||||
browserContextPrototype.l = loadChunk;
|
||||
function loadInitialChunk(chunkPath, chunkData) {
|
||||
return loadChunkInternal(0, chunkPath, chunkData);
|
||||
return loadChunkInternal(SourceType.Runtime, chunkPath, chunkData);
|
||||
}
|
||||
function loadChunkInternal(sourceType, sourceData, chunkData) {
|
||||
return _async_to_generator(function() {
|
||||
@@ -1177,7 +1200,7 @@ var loadedChunk = Promise.resolve(undefined);
|
||||
var instrumentedBackendLoadChunks = new WeakMap();
|
||||
// Do not make this async. React relies on referential equality of the returned Promise.
|
||||
function loadChunkByUrl(chunkUrl) {
|
||||
return loadChunkByUrlInternal(1, this.m.id, chunkUrl);
|
||||
return loadChunkByUrlInternal(SourceType.Parent, this.m.id, chunkUrl);
|
||||
}
|
||||
browserContextPrototype.L = loadChunkByUrl;
|
||||
// Do not make this async. React relies on referential equality of the returned Promise.
|
||||
@@ -1189,13 +1212,13 @@ function loadChunkByUrlInternal(sourceType, sourceData, chunkUrl) {
|
||||
entry = thenable.then(resolve).catch(function(cause) {
|
||||
var loadReason;
|
||||
switch(sourceType){
|
||||
case 0:
|
||||
case SourceType.Runtime:
|
||||
loadReason = `as a runtime dependency of chunk ${sourceData}`;
|
||||
break;
|
||||
case 1:
|
||||
case SourceType.Parent:
|
||||
loadReason = `from module ${sourceData}`;
|
||||
break;
|
||||
case 2:
|
||||
case SourceType.Update:
|
||||
loadReason = 'from an HMR update';
|
||||
break;
|
||||
default:
|
||||
@@ -1299,7 +1322,7 @@ browserContextPrototype.b = createWorker;
|
||||
/**
|
||||
* Instantiates a runtime module.
|
||||
*/ function instantiateRuntimeModule(moduleId, chunkPath) {
|
||||
return instantiateModule(moduleId, 0, chunkPath);
|
||||
return instantiateModule(moduleId, SourceType.Runtime, chunkPath);
|
||||
}
|
||||
/**
|
||||
* Returns the URL relative to the origin where a chunk can be fetched from.
|
||||
@@ -1359,11 +1382,11 @@ var regexCssUrl = /\.css(?:\?[^#]*)?(?:#.*)?$/;
|
||||
return regexCssUrl.test(chunkUrl);
|
||||
}
|
||||
function loadWebAssembly(chunkPath, edgeModule, importsObj) {
|
||||
return BACKEND.loadWebAssembly(1, this.m.id, chunkPath, edgeModule, importsObj);
|
||||
return BACKEND.loadWebAssembly(SourceType.Parent, this.m.id, chunkPath, edgeModule, importsObj);
|
||||
}
|
||||
contextPrototype.w = loadWebAssembly;
|
||||
function loadWebAssemblyModule(chunkPath, edgeModule) {
|
||||
return BACKEND.loadWebAssemblyModule(1, this.m.id, chunkPath, edgeModule);
|
||||
return BACKEND.loadWebAssemblyModule(SourceType.Parent, this.m.id, chunkPath, edgeModule);
|
||||
}
|
||||
contextPrototype.u = loadWebAssemblyModule;
|
||||
/// <reference path="./runtime-base.ts" />
|
||||
|
||||
+4
-4
File diff suppressed because one or more lines are too long
+6
-5
File diff suppressed because one or more lines are too long
+6
-5
File diff suppressed because one or more lines are too long
+626
-495
File diff suppressed because it is too large
Load Diff
+626
-495
File diff suppressed because it is too large
Load Diff
+6
-5
File diff suppressed because one or more lines are too long
+6
-5
File diff suppressed because one or more lines are too long
+626
-495
File diff suppressed because it is too large
Load Diff
+626
-495
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user