Skip to content

Commit

Permalink
feat(core): custom node render (#594)
Browse files Browse the repository at this point in the history
  • Loading branch information
imtaotao authored Jan 4, 2023
1 parent 4a7da5e commit a65152e
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 43 deletions.
6 changes: 5 additions & 1 deletion dev/app-main/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ let defaultConfig: RunInfo = {
// 默认值为 false. snapshot 表明是否开启快照沙箱,默认情况下关闭快照沙箱,使用 VM 沙箱(VM 沙箱支持多实例)
// 当使用 loadApp 手动挂载子应用时,请确保 snapshot 设置为 false
snapshot: false,

},

// autoRefreshApp: 主应用在已经打开子应用页面的前提下,跳转子应用的子路由触发子应用的视图更新,默认值为 true
Expand Down Expand Up @@ -146,6 +145,11 @@ let defaultConfig: RunInfo = {
// console.log('子应用路由未匹配', path);
},

// customRender(data) {
// console.log(data);
// return data;
// },

// 业务自定义fetch
// loader: {
// async fetch(url) {
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { SyncHook, AsyncHook, PluginSystem } from '@garfish/hooks';
import type { Node } from '@garfish/utils';
import {
SyncHook,
SyncWaterfallHook,
AsyncHook,
PluginSystem,
} from '@garfish/hooks';
import { interfaces } from './interface';

// prettier-ignore
Expand Down Expand Up @@ -53,5 +59,11 @@ export function appLifecycle() {
],
void
>(),
customRender: new SyncWaterfallHook<{
node: Node,
parent: Element,
app: interfaces.App,
customElement: Element | null,
}>('customRender'),
});
}
91 changes: 63 additions & 28 deletions packages/core/src/module/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ export class App {
public htmlNode: HTMLElement | ShadowRoot;
public customExports: Record<string, any> = {}; // If you don't want to use the CJS export, can use this
public sourceList: Array<{ tagName: string; url: string }> = [];
public sourceListMap: Map<string, { tagName: string; url: string }> = new Map();
public sourceListMap: Map<string, { tagName: string; url: string }> =
new Map();
public appInfo: AppInfo;
public context: Garfish;
public hooks: interfaces.AppHooks;
Expand Down Expand Up @@ -149,32 +150,43 @@ export class App {
url: entryManager.url ? transformUrl(entryManager.url, url) : url,
});
}
if (isGarfishConfigType({ type: entryManager.findAttributeValue(node, 'type') })) {
if (
isGarfishConfigType({
type: entryManager.findAttributeValue(node, 'type'),
})
) {
// garfish config script founded
// parse it
this.childGarfishConfig = JSON.parse((node.children?.[0] as Text)?.content);
this.childGarfishConfig = JSON.parse(
(node.children?.[0] as Text)?.content,
);
}
});
}
this.appInfo.entry && this.addSourceList({ tagName: 'html', url: this.appInfo.entry })
this.appInfo.entry &&
this.addSourceList({ tagName: 'html', url: this.appInfo.entry });
}

get rootElement() {
return findTarget(this.htmlNode, [`div[${__MockBody__}]`, 'body']);
}

get getSourceList () {
get getSourceList() {
return this.sourceList;
}

addSourceList(sourceInfo: Array<{ tagName: string; url: string }> | { tagName: string; url: string }){
addSourceList(
sourceInfo:
| Array<{ tagName: string; url: string }>
| { tagName: string; url: string },
) {
if (this.appInfo.disableSourceListCollect) return;
if (Array.isArray(sourceInfo)){
let nSourceList = sourceInfo.filter(item => {
if (Array.isArray(sourceInfo)) {
const nSourceList = sourceInfo.filter((item) => {
const dup = Object.assign({}, item);
dup.url = dup.url.startsWith('/') ?
`${location.origin}${dup.url}` :
dup.url;
dup.url = dup.url.startsWith('/')
? `${location.origin}${dup.url}`
: dup.url;

if (!this.sourceListMap.has(dup.url) && dup.url.startsWith('http')) {
this.sourceListMap.set(dup.url, dup);
Expand All @@ -185,11 +197,11 @@ export class App {
this.sourceList = this.sourceList.concat(nSourceList);
} else {
const dup = Object.assign({}, sourceInfo);
dup.url = dup.url.startsWith('/') ?
`${location.origin}${dup.url}` :
dup.url;
dup.url = dup.url.startsWith('/')
? `${location.origin}${dup.url}`
: dup.url;

if (!this.sourceListMap.get(dup.url) && dup.url.startsWith('http')){
if (!this.sourceListMap.get(dup.url) && dup.url.startsWith('http')) {
this.sourceList.push(dup);
this.sourceListMap.set(dup.url, dup);
}
Expand All @@ -203,7 +215,9 @@ export class App {
}

isNoEntryScript(url = '') {
return this.childGarfishConfig.sandbox?.noEntryScripts?.some(item => url.indexOf(item) > -1);
return this.childGarfishConfig.sandbox?.noEntryScripts?.some(
(item) => url.indexOf(item) > -1,
);
}

execScript(
Expand Down Expand Up @@ -241,12 +255,17 @@ export class App {
// If the node is an es module, use native esmModule
if (options && options.isModule) {
this.esmQueue.add(async (next) => {
await this.esModuleLoader.load(code, {
// rebuild full env
...this.getExecScriptEnv(),
// this 'env' may lost commonjs data
...env,
}, url, options);
await this.esModuleLoader.load(
code,
{
// rebuild full env
...this.getExecScriptEnv(),
// this 'env' may lost commonjs data
...env,
},
url,
options,
);
next();
});
} else {
Expand Down Expand Up @@ -543,6 +562,11 @@ export class App {
return DOMApis.createElement(node);
},

iframe: (node) => {
baseUrl && entryManager.toResolveUrl(node, 'src', baseUrl);
return DOMApis.createElement(node);
},

// The body and head this kind of treatment is to compatible with the old version
body: (node) => {
if (!this.strictIsolation) {
Expand Down Expand Up @@ -585,9 +609,12 @@ export class App {
if (jsManager) {
const { url, scriptCode } = jsManager;
const mockOriginScript = document.createElement('script');
node.attributes.forEach((attribute)=>{
node.attributes.forEach((attribute) => {
if (attribute.key) {
mockOriginScript.setAttribute(attribute.key, attribute.value || '');
mockOriginScript.setAttribute(
attribute.key,
attribute.value || '',
);
}
});

Expand All @@ -597,8 +624,8 @@ export class App {
async: false,
isInline: jsManager.isInlineScript(),
noEntry: toBoolean(
entryManager.findAttributeValue(node, 'no-entry')
|| this.isNoEntryScript(targetUrl),
entryManager.findAttributeValue(node, 'no-entry') ||
this.isNoEntryScript(targetUrl),
),
originScript: mockOriginScript,
});
Expand Down Expand Up @@ -654,8 +681,16 @@ export class App {
},
};

// Render dom tree and append to document.
entryManager.createElements(customRenderer, htmlNode);
// Render dom tree and append to document
entryManager.createElements(customRenderer, htmlNode, (node, parent) => {
// Trigger a custom render hook
return this.hooks.lifecycle.customRender.emit({
node,
parent,
app: this,
customElement: null,
});
});
}

private async checkAndGetProvider() {
Expand Down
4 changes: 1 addition & 3 deletions packages/hooks/src/asyncWaterfallHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ export class AsyncWaterfallHook<T extends Record<string, any>> extends SyncHook<
}
}
} else {
this.onerror(
`The "${this.type}" type has a plugin return value error.`,
);
this.onerror(`The return value of hook "${this.type}" is incorrect.`);
}
return data;
};
Expand Down
4 changes: 1 addition & 3 deletions packages/hooks/src/syncWaterfallHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ export class SyncWaterfallHook<T extends Record<string, any>> extends SyncHook<
if (checkReturnData(data, tempData)) {
data = tempData;
} else {
this.onerror(
`The "${this.type}" type has a plugin return value error.`,
);
this.onerror(`The return value of hook "${this.type}" is incorrect.`);
break;
}
} catch (e) {
Expand Down
31 changes: 24 additions & 7 deletions packages/loader/src/managers/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {
} from '@garfish/utils';

type Renderer = Record<string, (node: Node) => null | Element | Comment>;
type CommonRender = (
node: Node,
parent: Element,
) => { customElement?: Element | null };

export class TemplateManager {
public url: string | undefined;
Expand Down Expand Up @@ -60,7 +64,11 @@ export class TemplateManager {
}

// Render dom tree
createElements(renderer: Renderer, parent: Element) {
createElements(
renderer: Renderer,
parent: Element,
commonRender?: CommonRender,
) {
const elements: Array<Element> = [];
const traverse = (node: Node | Text, parentEl?: Element) => {
let el: any;
Expand All @@ -70,13 +78,22 @@ export class TemplateManager {
el = this.DOMApis.createTextNode(node);
parentEl && parentEl.appendChild(el);
} else if (this.DOMApis.isNode(node)) {
const { tagName, children } = node as Node;
if (renderer[tagName]) {
el = renderer[tagName](node as Node);
} else {
el = this.DOMApis.createElement(node as Node);
const { tagName, children } = node;
if (typeof commonRender === 'function') {
el = commonRender(node, parent)?.customElement;
}
// If the general renderer does not return a result, need to use the internal renderer
if (!el) {
if (renderer[tagName]) {
el = renderer[tagName](node);
} else {
el = this.DOMApis.createElement(node);
}
}

if (parentEl && el) {
parentEl.appendChild(el);
}
if (parentEl && el) parentEl.appendChild(el);

if (el) {
const { nodeType, _ignoreChildNodes } = el;
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/sentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export const sourceListTags = [
'img',
'video',
'audio',
'iframe',
];
export const sourceNode = makeMap(sourceListTags);

Expand Down

1 comment on commit a65152e

@vercel
Copy link

@vercel vercel bot commented on a65152e Jan 4, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.