Skip to content

Commit 9faa8b7

Browse files
author
huangchenbj
committed
fix: support css hmr from mini-css-extract-plugin
1 parent 74c999f commit 9faa8b7

File tree

6 files changed

+1708
-1567
lines changed

6 files changed

+1708
-1567
lines changed

src/create_app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import sourceCenter from './source/source_center'
3939

4040
// micro app instances
4141
export const appInstanceMap = new Map<string, AppInterface>()
42+
export const appCurrentScriptMap = new Map<string, HTMLScriptElement>()
4243

4344
// params of CreateApp
4445
export interface CreateAppParam {

src/sandbox/with/document.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
rawDefineProperties,
1515
} from '../../libs/utils'
1616
import {
17+
appCurrentScriptMap,
1718
appInstanceMap,
1819
} from '../../create_app'
1920
import {
@@ -232,6 +233,8 @@ function createProxyDocument (
232233
if (key === 'removeEventListener') return removeEventListener
233234
if (key === 'microAppElement') return appInstanceMap.get(appName)?.container
234235
if (key === '__MICRO_APP_NAME__') return appName
236+
// mini-css-extract-plugin in hmr mode depends on document.currentScript to map module-id to chunk file href
237+
if (key === 'currentScript') return appCurrentScriptMap.get(appName)
235238
return bindFunctionToRawTarget<Document>(Reflect.get(target, key), rawDocument, 'DOCUMENT')
236239
},
237240
set: (target: Document, key: PropertyKey, value: unknown): boolean => {

src/source/links.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,60 @@ export function fetchLinksFromHtml (
161161
})
162162
}
163163

164+
/**
165+
* Get a MutationObserver that will remove the convertStyle and disabledLink when the the other is removed
166+
* @param convertStyle converted style
167+
* @param disabledLink disabled link
168+
* @returns MutationObserver
169+
*/
170+
export function getAutoRemoveObserver (convertStyle: HTMLStyleElement, disabledLink: HTMLLinkElement): MutationObserver {
171+
const observer = new MutationObserver((mutations, obs) => {
172+
mutations.forEach((mutation) => {
173+
const removedNodes = mutation.removedNodes
174+
const removedLength = removedNodes.length
175+
let needRemoveElement: Element | null = null
176+
for (let i = 0; i < removedLength; i++) {
177+
const removedNode = removedNodes[i]
178+
if (removedNode === disabledLink) {
179+
needRemoveElement = convertStyle
180+
break
181+
}
182+
if (removedNode === convertStyle) {
183+
needRemoveElement = disabledLink
184+
break
185+
}
186+
}
187+
if (needRemoveElement) {
188+
obs.disconnect()
189+
needRemoveElement.remove()
190+
}
191+
})
192+
})
193+
194+
return observer
195+
}
196+
197+
function getDisabledLink (convertStyle: HTMLStyleElement, linkInfo: LinkSourceInfo, app: AppInterface) {
198+
const disabledLink = pureCreateElement('link')
199+
const appSpaceData = linkInfo.appSpace[app.name]
200+
appSpaceData.attrs?.forEach((value, key) => {
201+
if (key === 'href') {
202+
globalEnv.rawSetAttribute.call(disabledLink, 'data-origin-href', value)
203+
globalEnv.rawSetAttribute.call(disabledLink, 'href', CompletionPath(value, app.url))
204+
} else {
205+
globalEnv.rawSetAttribute.call(disabledLink, key, value)
206+
}
207+
})
208+
globalEnv.rawSetAttribute.call(disabledLink, 'disabled', 'true')
209+
210+
const observer = getAutoRemoveObserver(convertStyle, disabledLink)
211+
212+
return {
213+
disabledLink,
214+
observer,
215+
}
216+
}
217+
164218
/**
165219
* Fetch link succeeded, replace placeholder with style tag
166220
* NOTE:
@@ -198,6 +252,10 @@ export function fetchLinkSuccess (
198252
if (placeholder) {
199253
const convertStyle = pureCreateElement('style')
200254

255+
// mini-css-extract-plugin in hmr mode updates css by finding the <link /> element and replacing it.
256+
// so we need to create a disabled link for the hmr to work
257+
const { disabledLink, observer } = getDisabledLink(convertStyle, linkInfo, app)
258+
201259
handleConvertStyle(
202260
app,
203261
address,
@@ -207,9 +265,16 @@ export function fetchLinkSuccess (
207265
)
208266

209267
if (placeholder.parentNode) {
210-
placeholder.parentNode.replaceChild(convertStyle, placeholder)
268+
const parentNode = placeholder.parentNode
269+
globalEnv.rawWindow.__parentNode__ = parentNode
270+
parentNode.insertBefore(convertStyle, placeholder)
271+
parentNode.insertBefore(disabledLink, placeholder)
272+
parentNode.removeChild(placeholder)
273+
observer.observe(parentNode, { childList: true })
211274
} else {
212275
microAppHead.appendChild(convertStyle)
276+
microAppHead.appendChild(disabledLink)
277+
observer.observe(microAppHead, { childList: true })
213278
}
214279

215280
// clear placeholder

src/source/patch.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ import {
3232
isImageElement,
3333
isVideoElement,
3434
isAudioElement,
35+
defer,
3536
} from '../libs/utils'
3637
import scopedCSS from '../sandbox/scoped_css'
3738
import {
3839
extractLinkFromHtml,
3940
formatDynamicLink,
41+
getAutoRemoveObserver,
4042
} from './links'
4143
import {
4244
extractScriptElement,
@@ -104,6 +106,17 @@ function handleNewNode(child: Node, app: AppInterface): Node {
104106
if (address && linkInfo) {
105107
const replaceStyle = formatDynamicLink(address, app, linkInfo, child)
106108
dynamicElementInMicroAppMap.set(child, replaceStyle)
109+
110+
// mini-css-extract-plugin in hmr mode updates css by finding the <link /> element and replacing it.
111+
// so we need to insert the disabled link after the convertStyle
112+
defer(() => {
113+
globalEnv.rawSetAttribute.call(child, 'disabled', 'true')
114+
globalEnv.rawInsertAdjacentElement.call(replaceStyle, 'afterend', child)
115+
const observer = getAutoRemoveObserver(replaceStyle, child)
116+
if (replaceStyle.parentElement) {
117+
observer.observe(replaceStyle.parentElement, { childList: true })
118+
}
119+
})
107120
return replaceStyle
108121
} else if (replaceComment) {
109122
dynamicElementInMicroAppMap.set(child, replaceComment)

src/source/scripts.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import microApp from '../micro_app'
3434
import globalEnv from '../libs/global_env'
3535
import { GLOBAL_CACHED_KEY } from '../constants'
3636
import sourceCenter from './source_center'
37+
import { appCurrentScriptMap } from '../create_app'
3738

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

@@ -665,7 +666,17 @@ function runParsedFunction (app: AppInterface, scriptInfo: ScriptSourceInfo) {
665666
if (!appSpaceData.parsedFunction) {
666667
appSpaceData.parsedFunction = getParsedFunction(app, scriptInfo, appSpaceData.parsedCode!)
667668
}
668-
appSpaceData.parsedFunction.call(getEffectWindow(app))
669+
const targetWindow = getEffectWindow(app)
670+
const dummyScriptTag = document.createElement('script')
671+
dummyScriptTag.src = scriptInfo.appSpace[app.name].attrs.get('src') || ''
672+
appCurrentScriptMap.set(app.name, dummyScriptTag)
673+
try {
674+
appSpaceData.parsedFunction.call(targetWindow)
675+
} finally {
676+
if (appCurrentScriptMap.get(app.name) === dummyScriptTag) {
677+
appCurrentScriptMap.delete(app.name)
678+
}
679+
}
669680
}
670681

671682
/**

0 commit comments

Comments
 (0)