Skip to content

Commit

Permalink
feat: remove webpack chunk cache attributes just while there are mult…
Browse files Browse the repository at this point in the history
…i instances loaded on document (#2873)
  • Loading branch information
kuitos authored Jan 4, 2024
1 parent ad64bec commit 56fef69
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/orange-boats-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"qiankun": patch
"@qiankunjs/shared": patch
---

feat: remove webpack chunk cache attributes just while there are multi instances loaded on document
77 changes: 69 additions & 8 deletions packages/qiankun/src/core/loadApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@
import type { LoaderOpts } from '@qiankunjs/loader';
import { loadEntry } from '@qiankunjs/loader';
import type { Sandbox } from '@qiankunjs/sandbox';
import { createSandboxContainer } from '@qiankunjs/sandbox';
import { moduleResolver as defaultModuleResolver, transpileAssets, warn, wrapFetchWithCache } from '@qiankunjs/shared';
import { createSandboxContainer, nativeGlobal } from '@qiankunjs/sandbox';
import {
defineProperty,
hasOwnProperty,
moduleResolver as defaultModuleResolver,
transpileAssets,
warn,
wrapFetchWithCache,
} from '@qiankunjs/shared';
import { concat, isFunction, mergeWith } from 'lodash';
import type { ParcelConfigObject } from 'single-spa';
import getAddOns from '../addons';
Expand Down Expand Up @@ -52,9 +59,11 @@ export default async function loadApp<T extends ObjectType>(
let mountSandbox: (container: HTMLElement) => Promise<void> = () => Promise.resolve();
let unmountSandbox = () => Promise.resolve();
let sandboxInstance: Sandbox | undefined;
const instanceId = genInstanceId(appName);
let mountTimes = 1;

let microAppDOMContainer: HTMLElement = container;
initContainer(microAppDOMContainer, appName, sandbox);
initContainer(microAppDOMContainer, appName, { sandboxCfg: sandbox, mountTimes, instanceId });

if (sandbox) {
const sandboxContainer = createSandboxContainer(appName, () => microAppDOMContainer, {
Expand All @@ -71,13 +80,16 @@ export default async function loadApp<T extends ObjectType>(
unmountSandbox = () => sandboxContainer.unmount();
}

if (instanceId > 1) {
removeWebpackChunkCacheWhenAppHaveMultiInstance(appName);
}

const containerOpts: LoaderOpts = {
fetch: fetchWithLruCache,
sandbox: sandboxInstance,
nodeTransformer,
...restConfiguration,
};

const lifecyclesPromise = loadEntry<MicroAppLifeCycles>(entry, microAppDOMContainer, containerOpts);

const assetPublicPath = calcPublicPath(entry);
Expand All @@ -102,8 +114,6 @@ export default async function loadApp<T extends ObjectType>(
sandboxInstance?.latestSetProp,
);

let mountTimes = 1;

return (mountContainer) => {
const parcelConfig: ParcelConfigObject = {
name: appName,
Expand All @@ -127,7 +137,7 @@ export default async function loadApp<T extends ObjectType>(

// while the micro app is remounting, we need to load the entry manually
if (mountTimes > 1) {
initContainer(mountContainer, appName, sandbox);
initContainer(mountContainer, appName, { sandboxCfg: sandbox, mountTimes, instanceId });
// html scripts should be removed to avoid repeatedly execute
const htmlString = await getPureHTMLStringWithoutScripts(entry, fetchWithLruCache);
await loadEntry(htmlString, mountContainer, containerOpts);
Expand Down Expand Up @@ -173,14 +183,26 @@ export default async function loadApp<T extends ObjectType>(
};
}

function initContainer(container: HTMLElement, appName: string, sandboxCfg: AppConfiguration['sandbox']): void {
function initContainer(
container: HTMLElement,
appName: string,
opts: { sandboxCfg: AppConfiguration['sandbox']; mountTimes: number; instanceId: number },
): void {
const { sandboxCfg, mountTimes, instanceId } = opts;
while (container.firstChild) {
container.removeChild(container.firstChild);
}

container.dataset.name = appName;
container.dataset.version = version;
container.dataset.sandboxCfg = JSON.stringify(sandboxCfg);

if (mountTimes > 1) {
container.dataset.mountTimes = String(mountTimes);
}
if (instanceId > 1) {
container.dataset.instanceId = String(instanceId);
}
}

function clearContainer(container: HTMLElement): void {
Expand Down Expand Up @@ -253,3 +275,42 @@ function calcPublicPath(entry: string): string {
return '';
}
}

/**
* To prevent webpack from skipping reload logic and causing the js not to re-execute when a micro app is loaded multiple times on the same viewport,
* the data-webpack attribute of the script must be removed.
* see https://github.com/webpack/webpack/blob/1f13ff9fe587e094df59d660b4611b1bd19aed4c/lib/runtime/LoadScriptRuntimeModule.js#L131-L136
*/
function removeWebpackChunkCacheWhenAppHaveMultiInstance(appName: string): void {
const mountedSameNameApps = document.querySelectorAll(`[data-name^="${appName}"]`);
if (mountedSameNameApps.length > 1) {
mountedSameNameApps.forEach((appContainerElement) => {
appContainerElement.querySelectorAll('script[src]').forEach((script) => {
script.removeAttribute('data-webpack');
});
});
}
}

const globalAppInstanceStoreKey = '__agii__';
declare global {
interface Window {
// app global instance id
[globalAppInstanceStoreKey]?: Record<string, number>;
}
}

function genInstanceId(appName: string): number {
if (!hasOwnProperty(nativeGlobal, globalAppInstanceStoreKey)) {
defineProperty(nativeGlobal, globalAppInstanceStoreKey, {
enumerable: false,
configurable: false,
writable: true,
value: {},
});
}
nativeGlobal[globalAppInstanceStoreKey]![appName] = nativeGlobal[globalAppInstanceStoreKey]![appName]
? nativeGlobal[globalAppInstanceStoreKey]![appName] + 1
: 1;
return nativeGlobal[globalAppInstanceStoreKey]![appName];
}
5 changes: 0 additions & 5 deletions packages/shared/src/assets-transpilers/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,6 @@ export default function transpileScript(
const srcAttribute = script.getAttribute('src');
const { sandbox, scriptTranspiledDeferred } = opts;

// To prevent webpack from skipping reload logic and causing the js not to re-execute when a micro app is loaded multiple times, the data-webpack attribute of the script must be removed.
// see https://github.com/webpack/webpack/blob/1f13ff9fe587e094df59d660b4611b1bd19aed4c/lib/runtime/LoadScriptRuntimeModule.js#L131-L136
// FIXME We should determine whether the current micro application is being loaded for the second time. If not, this removal should not be performed.
script.removeAttribute('data-webpack');

try {
const { mode, result } = preTranspile(
{
Expand Down

0 comments on commit 56fef69

Please sign in to comment.