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

✨ hijack the dynamic stylesheet injection during development phase #129

Merged
merged 1 commit into from
Nov 5, 2019
Merged
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
10 changes: 6 additions & 4 deletions src/hijackers/dynamicStylesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
*/
import { Freer } from '../interfaces';

export default function hijack(): Freer {
const rawHtmlAppendChild = HTMLHeadElement.prototype.appendChild;
const rawHtmlAppendChild = HTMLHeadElement.prototype.appendChild;

export default function hijack(bootstrapping = false): Freer {
let dynamicStyleSheets: HTMLLinkElement[] = [];
HTMLHeadElement.prototype.appendChild = function appendChild<T extends Node>(this: any, newChild: T) {
// hijack dynamic style injection
Expand All @@ -23,8 +23,10 @@ export default function hijack(): Freer {

return function rebuild() {
dynamicStyleSheets.forEach(stylesheet => document.head.appendChild(stylesheet));
// for gc
dynamicStyleSheets = [];
if (!bootstrapping) {
// for gc

Choose a reason for hiding this comment

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

为什么开发阶段需要 gc,其他阶段不需要?

Copy link
Member Author

Choose a reason for hiding this comment

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

因为 bootstrapping 阶段拦截到的副作用会被多次 rebuild

dynamicStyleSheets = [];
}
};
};
}
7 changes: 6 additions & 1 deletion src/hijackers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
* @since 2019-04-11
*/

import { noop } from 'lodash';
import { Freer } from '../interfaces';
import hijackDynamicStylesheet from './dynamicStylesheet';
import hijackHistoryListener from './historyListener';
import hijackTimer from './timer';
import hijackWindowListener from './windowListener';

export function hijack(): Freer[] {
export function hijackAtMounting(): Freer[] {
Ariel-Cheng marked this conversation as resolved.
Show resolved Hide resolved
return [hijackTimer(), hijackWindowListener(), hijackHistoryListener(), hijackDynamicStylesheet()];
}

export function hijackAtBootstrapping(): Freer[] {
return [process.env.NODE_ENV === 'development' ? hijackDynamicStylesheet(true) : () => () => noop];
}
5 changes: 3 additions & 2 deletions src/hijackers/timer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import { noop } from 'lodash';
import { sleep } from '../utils';

const rawWindowInterval = window.setInterval;
const rawWindowTimeout = window.setTimeout;

export default function hijack() {
const rawWindowInterval = window.setInterval;
const rawWindowTimeout = window.setTimeout;
const timerIds: number[] = [];
const intervalIds: number[] = [];

Expand Down
5 changes: 3 additions & 2 deletions src/hijackers/windowListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@

import { noop } from 'lodash';

const rawAddEventListener = window.addEventListener;
const rawRemoveEventListener = window.removeEventListener;

export default function hijack() {
const listenerMap = new Map<string, EventListenerOrEventListenerObject[]>();
const rawAddEventListener = window.addEventListener;
const rawRemoveEventListener = window.removeEventListener;

window.addEventListener = (
type: string,
Expand Down
30 changes: 22 additions & 8 deletions src/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @author Kuitos
* @since 2019-04-11
*/
import { hijack } from './hijackers';
import { hijackAtBootstrapping, hijackAtMounting } from './hijackers';
import { Freer, Rebuilder } from './interfaces';
import { isConstructable } from './utils';

Expand Down Expand Up @@ -53,7 +53,11 @@ export function genSandbox(appName: string) {
// 持续记录更新的(新增和修改的)全局变量的 map,用于在任意时刻做 snapshot
const currentUpdatedPropsValueMapForSnapshot = new Map<PropertyKey, any>();

let freers: Freer[] = [];
// some side effect could be be invoked while bootstrapping, such as dynamic stylesheet injection with style-loader, especially during the development phase
const bootstrappingFreers = hijackAtBootstrapping();
// mounting freers are one-off and should be re-init at every mounting time
let mountingFreers: Freer[] = [];

let sideEffectsRebuilders: Rebuilder[] = [];

// render 沙箱的上下文快照
Expand Down Expand Up @@ -118,6 +122,14 @@ export function genSandbox(appName: string) {
* 也可能是从 unmount 之后再次唤醒进入 mount
*/
async mount() {
const sideEffectsRebuildersAtBootstrapping = sideEffectsRebuilders.slice(0, bootstrappingFreers.length);
const sideEffectsRebuildersAtMounting = sideEffectsRebuilders.slice(bootstrappingFreers.length);

// must rebuild the side effects which added at bootstrapping firstly to recovery to nature state
if (sideEffectsRebuildersAtBootstrapping.length) {
sideEffectsRebuildersAtBootstrapping.forEach(rebuild => rebuild());
}

/* ------------------------------------------ 因为有上下文依赖(window),以下代码执行顺序不能变 ------------------------------------------ */

/* ------------------------------------------ 1. 启动/恢复 快照 ------------------------------------------ */
Expand All @@ -131,15 +143,17 @@ export function genSandbox(appName: string) {

/* ------------------------------------------ 2. 开启全局变量补丁 ------------------------------------------*/
// render 沙箱启动时开始劫持各类全局监听,这就要求应用初始化阶段不应该有 事件监听/定时器 等副作用
freers.push(...hijack());
mountingFreers = hijackAtMounting();

/* ------------------------------------------ 3. 重置一些初始化时的副作用 ------------------------------------------*/
// 存在 rebuilder 则表明有些副作用需要重建
if (sideEffectsRebuilders.length) {
sideEffectsRebuilders.forEach(rebuild => rebuild());
sideEffectsRebuilders = [];
if (sideEffectsRebuildersAtMounting.length) {
sideEffectsRebuildersAtMounting.forEach(rebuild => rebuild());
}

// clean up rebuilders
sideEffectsRebuilders = [];

inAppSandbox = true;
},

Expand All @@ -155,8 +169,8 @@ export function genSandbox(appName: string) {
}

// record the rebuilders of window side effects (event listeners or timers)
freers.forEach(free => sideEffectsRebuilders.push(free()));
freers = [];
// note that the frees of mounting phase are one-off as it will be re-init at next mounting
sideEffectsRebuilders = [...bootstrappingFreers, ...mountingFreers].map(free => free());

Choose a reason for hiding this comment

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

freers ==> freeres ?


// renderSandboxSnapshot = snapshot(currentUpdatedPropsValueMapForSnapshot);
// restore global props to initial snapshot
Expand Down