Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 209 additions & 0 deletions packages/lib/__tests__/compilerRuntime.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { describe, expect, it } from 'vitest'

/**
* These functions are defined in remote-production.ts and used to patch
* compiler-runtime chunks at build time. We replicate them here to test
* in isolation without a full Rollup build.
*/

// --- Replicated from remote-production.ts ---

const findImportSharedExportName = (code: string): string | null => {
const unminifiedExport = /export\s*\{[^}]*\bas\s+importShared\b[^}]*\}/
if (unminifiedExport.test(code)) {
return 'importShared'
}

const asyncFnRe = /async\s+function\s+(\w+)\s*\(\s*(\w+)/g
let fnMatch: RegExpExecArray | null

while ((fnMatch = asyncFnRe.exec(code)) !== null) {
const window = code.substring(
fnMatch.index,
Math.min(fnMatch.index + 300, code.length)
)
if (window.includes('moduleCache') || window.includes('Promise')) {
const internalName = fnMatch[1]!

const exportRe = new RegExp(
`export\\s*\\{[^}]*\\b${internalName}\\s+as\\s+(\\w+)`
)
const exportMatch = exportRe.exec(code)
if (exportMatch) {
return exportMatch[1]!
}

const directExportRe = new RegExp(
`export\\s*\\{[^}]*\\b${internalName}\\b`
)
if (directExportRe.test(code)) {
return internalName
}
}
}

return null
}

const computeRelativePath = (from: string, to: string): string => {
const fromParts = from.split('/')
const toParts = to.split('/')

fromParts.pop()

let common = 0
while (
common < fromParts.length &&
common < toParts.length &&
fromParts[common] === toParts[common]
) {
common++
}

const ups = fromParts.length - common
const remaining = toParts.slice(common)
const prefix = ups > 0 ? '../'.repeat(ups) : './'

return prefix + remaining.join('/')
}

const patchCompilerRuntime = (
code: string,
federationImportFile: string,
runtimeFile: string,
importSharedName: string
): string => {
if (!code.includes('useMemoCache') || !code.includes('export{')) {
return code
}

const relPath = computeRelativePath(runtimeFile, federationImportFile)

return [
`import{${importSharedName} as __s}from"${relPath}";`,
`var __react=await __s("react");`,
`var __internals=__react.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;`,
`var __obj={c:function(n){return __internals.H.useMemoCache(n)}};`,
`export{__obj as c};`
].join('')
}

// --- Tests ---

describe('findImportSharedExportName', () => {
it('should detect unminified importShared export', () => {
const code = `async function importShared(name, shareScope) {
return moduleCache[name] ? new Promise((r) => r(moduleCache[name])) : null;
}
export{importShared, getSharedFromRuntime as importSharedRuntime}`

expect(findImportSharedExportName(code)).toBe('importShared')
})

it('should detect minified importShared export (renamed)', () => {
const code = `async function xe(e,t="default"){return moduleCache[e]?new Promise((r)=>r(moduleCache[e])):null}export{xe as i,ye as j}`

expect(findImportSharedExportName(code)).toBe('i')
})

it('should detect direct export (not renamed)', () => {
const code = `async function myImport(n,s="default"){return moduleCache[n]?new Promise((r)=>r(moduleCache[n])):null}export{myImport}`

expect(findImportSharedExportName(code)).toBe('myImport')
})

it('should return null when no importShared-like function found', () => {
const code = `function hello(){return "world"}export{hello}`

expect(findImportSharedExportName(code)).toBeNull()
})
})

describe('computeRelativePath', () => {
it('should compute same-directory path', () => {
expect(
computeRelativePath(
'assets/compiler-runtime-abc.js',
'assets/__federation_fn_import-xyz.js'
)
).toBe('./__federation_fn_import-xyz.js')
})

it('should compute parent-directory path', () => {
expect(
computeRelativePath(
'assets/chunks/compiler-runtime-abc.js',
'assets/__federation_fn_import-xyz.js'
)
).toBe('../__federation_fn_import-xyz.js')
})

it('should compute path for files at root level', () => {
expect(
computeRelativePath(
'compiler-runtime-abc.js',
'__federation_fn_import-xyz.js'
)
).toBe('./__federation_fn_import-xyz.js')
})
})

describe('patchCompilerRuntime', () => {
const SAMPLE_CHUNK = `import{r as R}from"./index-XYZ.js";var i={exports:{}},o={};var u;function m(){if(u)return o;u=1;var r=R().__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;return o.c=function(e){return r.H.useMemoCache(e)},o}var s=m();export{s as c};`

it('should rewrite compiler-runtime chunk to use importShared', () => {
const patched = patchCompilerRuntime(
SAMPLE_CHUNK,
'assets/__federation_fn_import-xyz.js',
'assets/compiler-runtime-abc.js',
'importShared'
)

expect(patched).toContain('import{importShared as __s}')
expect(patched).toContain('await __s("react")')
expect(patched).toContain(
'__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE'
)
expect(patched).toContain('useMemoCache')
expect(patched).toContain('export{__obj as c}')
// Should NOT contain the original direct import
expect(patched).not.toContain('./index-XYZ.js')
})

it('should use correct relative path to federation import chunk', () => {
const patched = patchCompilerRuntime(
SAMPLE_CHUNK,
'assets/__federation_fn_import-xyz.js',
'assets/compiler-runtime-abc.js',
'i'
)

expect(patched).toContain(
'import{i as __s}from"./__federation_fn_import-xyz.js"'
)
})

it('should not patch code without useMemoCache', () => {
const code = `import{r as R}from"./index.js";export{R as react};`
const result = patchCompilerRuntime(
code,
'assets/__federation_fn_import.js',
'assets/some-chunk.js',
'importShared'
)

expect(result).toBe(code)
})

it('should not patch code without export statement', () => {
const code = `var r = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; r.H.useMemoCache(1);`
const result = patchCompilerRuntime(
code,
'assets/__federation_fn_import.js',
'assets/some-chunk.js',
'importShared'
)

expect(result).toBe(code)
})
})
155 changes: 155 additions & 0 deletions packages/lib/__tests__/flattenModule.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { describe, expect, it } from 'vitest'

/**
* flattenModule is a runtime template (federation_fn_import.js) that gets
* injected into the build output. We replicate the logic here to unit-test
* the Proxy behaviour without requiring a full build.
*/

// Simulates the moduleCache used by the runtime
const moduleCache: Record<string, unknown> = {}

// This is the patched flattenModule (Proxy-based)
const flattenModule = (module: any, name: string) => {
if (typeof module.default === 'function') {
Object.keys(module).forEach((key) => {
if (key !== 'default') {
module.default[key] = module[key]
}
})
moduleCache[name] = module.default
return module.default
}
if (module.default) {
const originalModule = module
module = new Proxy(module.default, {
get(target, prop) {
if (prop !== 'default' && prop in originalModule)
return originalModule[prop]
return target[prop]
},
has(target, prop) {
return prop in originalModule || prop in target
},
ownKeys(target) {
const keys = new Set([
...Reflect.ownKeys(target),
...Reflect.ownKeys(originalModule)
])
keys.delete('default')
return [...keys]
}
})
}
moduleCache[name] = module
return module
}

describe('flattenModule — Proxy preserves live bindings', () => {
it('should preserve mutable state on default export (React hooks dispatcher pattern)', () => {
// Simulates React's module structure: __CLIENT_INTERNALS...H is null at
// load time and only set during render.
const internals = { H: null as (() => void) | null }
const reactModule = {
// eslint-disable-next-line @typescript-eslint/no-empty-function
default: { __INTERNALS: internals, createElement: () => {} },
__esModule: true
}

const result = flattenModule(reactModule, 'react-mutable')

// At load time, H is null
expect(result.__INTERNALS.H).toBeNull()

// Simulate render — React sets H at runtime
internals.H = () => 'dispatcher active'

// The Proxy should reflect the live value, NOT a snapshot
expect(result.__INTERNALS.H).not.toBeNull()
expect(result.__INTERNALS.H!()).toBe('dispatcher active')
})

it('should expose named exports alongside default export properties', () => {
const module = {
default: { defaultProp: 'from-default' },
namedExport: 'from-named',
anotherExport: 42
}

const result = flattenModule(module, 'mixed-exports')

expect(result.defaultProp).toBe('from-default')
expect(result.namedExport).toBe('from-named')
expect(result.anotherExport).toBe(42)
})

it('should prioritise named exports over default export properties', () => {
const module = {
default: { shared: 'from-default' },
shared: 'from-named'
}

const result = flattenModule(module, 'priority-test')

// Named export takes precedence (prop in originalModule check)
expect(result.shared).toBe('from-named')
})

it('should not expose "default" key in ownKeys', () => {
const module = {
default: { a: 1 },
b: 2
}

const result = flattenModule(module, 'no-default-key')
const keys = Reflect.ownKeys(result)

expect(keys).not.toContain('default')
expect(keys).toContain('a')
expect(keys).toContain('b')
})

it('should handle function default exports by copying named exports onto it', () => {
const fn = () => 'hello'
const module = {
default: fn,
helper: 'util'
}

const result = flattenModule(module, 'fn-default')

expect(typeof result).toBe('function')
expect(result()).toBe('hello')
expect(result.helper).toBe('util')
})

it('should not cause infinite recursion (regression: Proxy traps calling themselves)', () => {
const module = {
default: { value: 'test' },
extra: true
}

// This would stack overflow if `module` was used instead of
// `originalModule` in the Proxy traps
expect(() => {
const result = flattenModule(module, 'recursion-guard')
// Trigger has trap
'value' in result
'extra' in result
'nonexistent' in result
// Trigger ownKeys trap
Object.keys(result)
Reflect.ownKeys(result)
}).not.toThrow()
})

it('should return module as-is when there is no default export', () => {
const module = { a: 1, b: 2 }

const result = flattenModule(module, 'no-default')

expect(result).toBe(module)
expect(result.a).toBe(1)
expect(result.b).toBe(2)
})
})
Loading