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
1 change: 1 addition & 0 deletions src/create_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import sourceCenter from './source/source_center'

// micro app instances
export const appInstanceMap = new Map<string, AppInterface>()
export const appCurrentScriptMap = new Map<string, HTMLScriptElement>()

// params of CreateApp
export interface CreateAppParam {
Expand Down
3 changes: 3 additions & 0 deletions src/sandbox/with/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
rawDefineProperties,
} from '../../libs/utils'
import {
appCurrentScriptMap,
appInstanceMap,
} from '../../create_app'
import {
Expand Down Expand Up @@ -232,6 +233,8 @@ function createProxyDocument (
if (key === 'removeEventListener') return removeEventListener
if (key === 'microAppElement') return appInstanceMap.get(appName)?.container
if (key === '__MICRO_APP_NAME__') return appName
// mini-css-extract-plugin in hmr mode depends on document.currentScript to map module-id to chunk file href
if (key === 'currentScript') return appCurrentScriptMap.get(appName)
return bindFunctionToRawTarget<Document>(Reflect.get(target, key), rawDocument, 'DOCUMENT')
},
set: (target: Document, key: PropertyKey, value: unknown): boolean => {
Expand Down
66 changes: 65 additions & 1 deletion src/source/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,60 @@ export function fetchLinksFromHtml (
})
}

/**
* Get a MutationObserver that will remove the convertStyle and disabledLink when the the other is removed
* @param convertStyle converted style
* @param disabledLink disabled link
* @returns MutationObserver
*/
export function getAutoRemoveObserver (convertStyle: HTMLStyleElement, disabledLink: HTMLLinkElement): MutationObserver {
const observer = new MutationObserver((mutations, obs) => {
mutations.forEach((mutation) => {
const removedNodes = mutation.removedNodes
const removedLength = removedNodes.length
let needRemoveElement: Element | null = null
for (let i = 0; i < removedLength; i++) {
const removedNode = removedNodes[i]
if (removedNode === disabledLink) {
needRemoveElement = convertStyle
break
}
if (removedNode === convertStyle) {
needRemoveElement = disabledLink
break
}
}
if (needRemoveElement) {
obs.disconnect()
needRemoveElement.remove()
}
})
})

return observer
}

function getDisabledLink (convertStyle: HTMLStyleElement, linkInfo: LinkSourceInfo, app: AppInterface) {
const disabledLink = pureCreateElement('link')
const appSpaceData = linkInfo.appSpace[app.name]
appSpaceData.attrs?.forEach((value, key) => {
if (key === 'href') {
globalEnv.rawSetAttribute.call(disabledLink, 'data-origin-href', value)
globalEnv.rawSetAttribute.call(disabledLink, 'href', CompletionPath(value, app.url))
} else {
globalEnv.rawSetAttribute.call(disabledLink, key, value)
}
})
globalEnv.rawSetAttribute.call(disabledLink, 'disabled', 'true')

const observer = getAutoRemoveObserver(convertStyle, disabledLink)

return {
disabledLink,
observer,
}
}

/**
* Fetch link succeeded, replace placeholder with style tag
* NOTE:
Expand Down Expand Up @@ -198,6 +252,10 @@ export function fetchLinkSuccess (
if (placeholder) {
const convertStyle = pureCreateElement('style')

// mini-css-extract-plugin in hmr mode updates css by finding the <link /> element and replacing it.
// so we need to create a disabled link for the hmr to work
const { disabledLink, observer } = getDisabledLink(convertStyle, linkInfo, app)

handleConvertStyle(
app,
address,
Expand All @@ -207,9 +265,15 @@ export function fetchLinkSuccess (
)

if (placeholder.parentNode) {
placeholder.parentNode.replaceChild(convertStyle, placeholder)
const parentNode = placeholder.parentNode
parentNode.insertBefore(convertStyle, placeholder)
parentNode.insertBefore(disabledLink, placeholder)
parentNode.removeChild(placeholder)
observer.observe(parentNode, { childList: true })
} else {
microAppHead.appendChild(convertStyle)
microAppHead.appendChild(disabledLink)
observer.observe(microAppHead, { childList: true })
}

// clear placeholder
Expand Down
13 changes: 13 additions & 0 deletions src/source/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import {
isImageElement,
isVideoElement,
isAudioElement,
defer,
} from '../libs/utils'
import scopedCSS from '../sandbox/scoped_css'
import {
extractLinkFromHtml,
formatDynamicLink,
getAutoRemoveObserver,
} from './links'
import {
extractScriptElement,
Expand Down Expand Up @@ -104,6 +106,17 @@ function handleNewNode(child: Node, app: AppInterface): Node {
if (address && linkInfo) {
const replaceStyle = formatDynamicLink(address, app, linkInfo, child)
dynamicElementInMicroAppMap.set(child, replaceStyle)

// mini-css-extract-plugin in hmr mode updates css by finding the <link /> element and replacing it.
// so we need to insert the disabled link after the convertStyle
defer(() => {
globalEnv.rawSetAttribute.call(child, 'disabled', 'true')
globalEnv.rawInsertAdjacentElement.call(replaceStyle, 'afterend', child)
const observer = getAutoRemoveObserver(replaceStyle, child)
if (replaceStyle.parentElement) {
observer.observe(replaceStyle.parentElement, { childList: true })
}
})
return replaceStyle
} else if (replaceComment) {
dynamicElementInMicroAppMap.set(child, replaceComment)
Expand Down
13 changes: 12 additions & 1 deletion src/source/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import microApp from '../micro_app'
import globalEnv from '../libs/global_env'
import { GLOBAL_CACHED_KEY } from '../constants'
import sourceCenter from './source_center'
import { appCurrentScriptMap } from '../create_app'

export type moduleCallBack = Func & { moduleCount?: number, errorCount?: number }

Expand Down Expand Up @@ -665,7 +666,17 @@ function runParsedFunction (app: AppInterface, scriptInfo: ScriptSourceInfo) {
if (!appSpaceData.parsedFunction) {
appSpaceData.parsedFunction = getParsedFunction(app, scriptInfo, appSpaceData.parsedCode!)
}
appSpaceData.parsedFunction.call(getEffectWindow(app))
const targetWindow = getEffectWindow(app)
const dummyScriptTag = document.createElement('script')
dummyScriptTag.src = scriptInfo.appSpace[app.name].attrs.get('src') || ''
appCurrentScriptMap.set(app.name, dummyScriptTag)
try {
appSpaceData.parsedFunction.call(targetWindow)
} finally {
if (appCurrentScriptMap.get(app.name) === dummyScriptTag) {
appCurrentScriptMap.delete(app.name)
}
}
}

/**
Expand Down