Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/cache css #374

Merged
merged 13 commits into from
Dec 9, 2021
4 changes: 2 additions & 2 deletions src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export default class AppRouter extends React.Component<AppRouterProps, AppRouter
ErrorComponent: ({ err }: { err: string | Error}) => <div>{ typeof err === 'string' ? err : err?.message }</div>,
LoadingComponent: <div>Loading...</div>,
NotFoundComponent: <div>NotFound</div>,
shouldAssetsRemove: () => true,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

默认移除,确认下之前的 assets 移除逻辑是否已经兼容

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是兼容的,有默认的全局设置

onAppEnter: () => {},
onAppLeave: () => {},
onLoadingApp: () => {},
Expand Down Expand Up @@ -93,7 +92,6 @@ export default class AppRouter extends React.Component<AppRouterProps, AppRouter
*/
const { shouldAssetsRemove, onAppEnter, onAppLeave, fetch, basename } = this.props;
start({
shouldAssetsRemove,
onAppLeave,
onAppEnter,
onLoadingApp: this.loadingApp,
Expand All @@ -102,7 +100,9 @@ export default class AppRouter extends React.Component<AppRouterProps, AppRouter
reroute: this.handleRouteChange,
fetch,
basename,
...(shouldAssetsRemove ? { shouldAssetsRemove } : {}),
});

this.setState({ started: true });
}

Expand Down
47 changes: 31 additions & 16 deletions src/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { setCache } from './util/cache';
import { loadScriptByFetch, loadScriptByImport } from './util/loaders';
import { getLifecyleByLibrary, getLifecyleByRegister } from './util/getLifecycle';
import { mergeFrameworkBaseToPath, getAppBasename, shouldSetBasename } from './util/helpers';
import globalConfiguration from './util/globalConfiguration';
import globalConfiguration, { temporaryState } from './util/globalConfiguration';

import type { StartConfiguration } from './util/globalConfiguration';

Expand All @@ -31,6 +31,8 @@ interface LifecycleProps {
customProps?: object;
}

type LoadScriptMode = 'fetch' | 'script' | 'import';

export interface ModuleLifeCycle {
mount?: (props: LifecycleProps) => Promise<void> | void;
unmount?: (props: LifecycleProps) => Promise<void> | void;
Expand All @@ -57,7 +59,7 @@ export interface BaseConfig extends PathOption {
* @deprecated
*/
umd?: boolean;
loadScriptMode?: 'fetch' | 'script' | 'import';
loadScriptMode?: LoadScriptMode;
checkActive?: (url: string) => boolean;
appAssets?: Assets;
props?: object;
Expand Down Expand Up @@ -168,8 +170,7 @@ export async function loadAppModule(appConfig: AppConfig) {

let lifecycle: ModuleLifeCycle = {};
onLoadingApp(appConfig);
const appSandbox = createSandbox(appConfig.sandbox) as Sandbox;
const { url, container, entry, entryContent, name, scriptAttributes = [], umd } = appConfig;
const { url, container, entry, entryContent, name, scriptAttributes = [], loadScriptMode, appSandbox } = appConfig;
const appAssets = url ? getUrlAssets(url) : await getEntryAssets({
root: container,
entry,
Expand All @@ -178,30 +179,37 @@ export async function loadAppModule(appConfig: AppConfig) {
assetsCacheKey: name,
fetch,
});
updateAppConfig(appConfig.name, { appAssets, appSandbox });

/**
* LoadScriptMode has the first priority
*/
const loadScriptMode = appConfig.loadScriptMode ?? (umd ? 'fetch' : 'script');
updateAppConfig(appConfig.name, { appAssets });

const cacheCss = shouldCacheCss(loadScriptMode);

switch (loadScriptMode) {
case 'import':
await loadAndAppendCssAssets([
...appAssets.cssList,
...filterRemovedAssets(importCachedAssets[name] || [], ['LINK', 'STYLE']),
]);
], {
cacheCss,
fetch,
});
lifecycle = await loadScriptByImport(appAssets.jsList);
// Not to handle script element temporarily.
break;
case 'fetch':
await loadAndAppendCssAssets(appAssets.cssList);
lifecycle = await loadScriptByFetch(appAssets.jsList, appSandbox);
await loadAndAppendCssAssets(appAssets.cssList, {
cacheCss,
fetch,
});
lifecycle = await loadScriptByFetch(appAssets.jsList, appSandbox, fetch);
break;
default:
await Promise.all([
loadAndAppendCssAssets(appAssets.cssList),
loadAndAppendJsAssets(appAssets, { sandbox: appSandbox, fetch, scriptAttributes }),
loadAndAppendCssAssets(appAssets.cssList, {
cacheCss,
fetch,
}),
loadAndAppendJsAssets(appAssets, { scriptAttributes }),
]);
lifecycle =
getLifecyleByLibrary() ||
Expand Down Expand Up @@ -243,6 +251,10 @@ function combineLifecyle(lifecycle: ModuleLifeCycle, appConfig: AppConfig) {
return combinedLifecyle;
}

function shouldCacheCss(mode: LoadScriptMode) {
return temporaryState.shouldAssetsRemoveConfigured ? false : (mode !== 'script');
}

function registerAppBeforeLoad(app: AppConfig, options?: AppLifecylceOptions) {
const { name } = app;
const appIndex = getAppNames().indexOf(name);
Expand Down Expand Up @@ -336,7 +348,7 @@ export async function createMicroApp(
setCache('root', container);
}

const { basename: frameworkBasename } = userConfiguration;
const { basename: frameworkBasename, fetch } = userConfiguration;

if (shouldSetBasename(activePath, basename)) {
setCache('basename', getAppBasename(activePath, frameworkBasename, basename));
Expand All @@ -349,7 +361,10 @@ export async function createMicroApp(
break;
case UNMOUNTED:
if (!appConfig.cached) {
await loadAndAppendCssAssets(appConfig?.appAssets?.cssList || []);
await loadAndAppendCssAssets(appConfig?.appAssets?.cssList || [], {
cacheCss: shouldCacheCss(appConfig.loadScriptMode),
fetch,
});
}
await mountMicroApp(appConfig.name);
break;
Expand Down
10 changes: 8 additions & 2 deletions src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { AppConfig, getMicroApps, createMicroApp, unmountMicroApp, clearMicroApp
import { emptyAssets, recordAssets } from './util/handleAssets';
import { LOADING_ASSETS, MOUNTED } from './util/constant';
import { doPrefetch } from './util/prefetch';
import globalConfiguration, { RouteType, StartConfiguration } from './util/globalConfiguration';
import globalConfiguration, { temporaryState } from './util/globalConfiguration';
import type { RouteType, StartConfiguration } from './util/globalConfiguration';

if (!window?.fetch) {
throw new Error('[icestark] window.fetch not found, you need polyfill it');
Expand All @@ -22,7 +23,6 @@ interface OriginalStateFunction {
(state: any, title: string, url?: string): void;
}


let started = false;
const originalPush: OriginalStateFunction = window.history.pushState;
const originalReplace: OriginalStateFunction = window.history.replaceState;
Expand Down Expand Up @@ -148,6 +148,12 @@ const unHijackEventListener = (): void => {
};

function start(options?: StartConfiguration) {
// See https://github.com/ice-lab/icestark/issues/373#issuecomment-971366188
// todos: remove it from 3.x
if (options?.shouldAssetsRemove && !temporaryState.shouldAssetsRemoveConfigured) {
temporaryState.shouldAssetsRemoveConfigured = true;
}

if (started) {
console.log('icestark has been already started');
return;
Expand Down
5 changes: 5 additions & 0 deletions src/util/globalConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,8 @@ const globalConfiguration: StartConfiguration = {
};

export default globalConfiguration;

// todos: remove it from 3.x
export const temporaryState = {
shouldAssetsRemoveConfigured: false,
};
54 changes: 37 additions & 17 deletions src/util/handleAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ export function appendCSS(

if (type && type === AssetTypeEnum.INLINE) {
const styleElement: HTMLStyleElement = document.createElement('style');
styleElement.innerHTML = content;
styleElement.id = id;
maoxiaoke marked this conversation as resolved.
Show resolved Hide resolved
styleElement.setAttribute(PREFIX, DYNAMIC);
styleElement.innerHTML = content;
root.appendChild(styleElement);
resolve();
return;
Expand Down Expand Up @@ -660,11 +660,45 @@ export function cacheAssets(cacheKey: string): void {
* @export
* @param {Assets} assets
*/
export async function loadAndAppendCssAssets(cssList: Array<Asset | HTMLElement>) {
export async function loadAndAppendCssAssets(cssList: Array<Asset | HTMLElement>, {
cacheCss = false,
fetch = defaultFetch,
}: {
cacheCss?: boolean;
fetch?: Fetch;
}) {
const cssRoot: HTMLElement = document.getElementsByTagName('head')[0];

if (cacheCss) {
let useLinks = false;
let cssContents = null;

try {
// No need to cache css when running into `<style />`
const needCachedCss = cssList.filter((css) => !isElement(css));
cssContents = await fetchStyles(
needCachedCss as Asset[],
fetch,
);
} catch (e) {
useLinks = true;
}

// Try hard to avoid break-change if fetching links error.
// And supposed to be remove from 3.x
if (!useLinks) {
return await Promise.all([
...cssContents.map((content, index) => appendCSS(
cssRoot,
{ content, type: AssetTypeEnum.INLINE }, `${PREFIX}-css-${index}`,
)),
...cssList.filter((css) => isElement(css)).map((asset, index) => appendCSS(cssRoot, asset, `${PREFIX}-css-${index}`)),
]);
}
}

// load css content
await Promise.all(
return await Promise.all(
cssList.map((asset, index) => appendCSS(cssRoot, asset, `${PREFIX}-css-${index}`)),
);
}
Expand All @@ -680,29 +714,15 @@ export async function loadAndAppendCssAssets(cssList: Array<Asset | HTMLElement>
export async function loadAndAppendJsAssets(
assets: Assets,
{
sandbox,
fetch = defaultFetch,
scriptAttributes = [],
}: {
sandbox?: Sandbox;
fetch?: Fetch;
scriptAttributes?: ScriptAttributes;
},
) {
const jsRoot: HTMLElement = document.getElementsByTagName('head')[0];

const { jsList } = assets;

// handle scripts
if (sandbox && !sandbox.sandboxDisabled) {
const jsContents = await fetchScripts(jsList, fetch);
// excute code by order
jsContents.forEach((script) => {
sandbox.execScriptInSandbox(script);
});
return;
}

// dispose inline script
const hasInlineScript = jsList.find((asset) => asset.type === AssetTypeEnum.INLINE);
if (hasInlineScript) {
Expand Down
4 changes: 2 additions & 2 deletions src/util/loaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ function executeScripts(scripts: string[], sandbox?: Sandbox, globalwindow: Wind
/**
* load bundle
*/
export function loadScriptByFetch(jsList: Asset[], sandbox?: Sandbox) {
return fetchScripts(jsList)
export function loadScriptByFetch(jsList: Asset[], sandbox?: Sandbox, fetch = window.fetch) {
return fetchScripts(jsList, fetch)
.then((scriptTexts) => {
const globalwindow = getGobalWindow(sandbox);

Expand Down
2 changes: 0 additions & 2 deletions tests/AppRouter.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as React from 'react';
import { render } from '@testing-library/react';
import { AppRouter, AppRoute } from '../src/index';


const delay = (milliscond: number) => new Promise(resolve => setTimeout(resolve, milliscond));

describe('AppRouter', () => {
Expand Down Expand Up @@ -94,7 +93,6 @@ describe('AppRouter', () => {

await delay(1000);
expect(container.innerHTML).toContain('商家平台')

unmount();
})

Expand Down
8 changes: 4 additions & 4 deletions tests/handleAssets.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ describe('appendAssets', () => {
'http://icestark.com/css/index.css',
'http://icestark.com/js/test1.js',
]);
Promise.all([loadAndAppendCssAssets(assets.cssList), loadAndAppendJsAssets(assets, {
Promise.all([loadAndAppendCssAssets(assets.cssList, {}), loadAndAppendJsAssets(assets, {
scriptAttributes: ['crossorigin=anonymous', 'nomodule=false', 'type=module', 'src=http://xxxx.js']
})])
.then(() => {
Expand Down Expand Up @@ -519,7 +519,7 @@ describe('appendAssets', () => {
const assets = getUrlAssets([
'http://icestark.com/js/index.js'
]);
Promise.all([loadAndAppendCssAssets(assets.cssList), loadAndAppendJsAssets(assets, {
Promise.all([loadAndAppendCssAssets(assets.cssList, {}), loadAndAppendJsAssets(assets, {
scriptAttributes: (url) => {
if (url.includes('//icestark.com/js/index.js')) {
return ['crossorigin=anonymous']
Expand Down Expand Up @@ -552,7 +552,7 @@ describe('appendAssets', () => {
'http://icestark.com/css/index.css',
'http://icestark.com/js/test1.js',
]);
Promise.all([loadAndAppendCssAssets(assets.cssList), loadAndAppendJsAssets(assets, {})])
Promise.all([loadAndAppendCssAssets(assets.cssList, {}), loadAndAppendJsAssets(assets, {})])
});

test('appendAssets - duplicate', done => {
Expand All @@ -564,7 +564,7 @@ describe('appendAssets', () => {
'http://icestark.com/js/test1.js',
'http://icestark.com/js/test1.js',
]);
Promise.all([loadAndAppendCssAssets(assets.cssList), loadAndAppendJsAssets(assets, {})])
Promise.all([loadAndAppendCssAssets(assets.cssList, {}), loadAndAppendJsAssets(assets, {})])
.then(() => {
const scripts = document.getElementsByTagName('script');
const styleSheets = document.getElementsByTagName('link');
Expand Down