Skip to content

Commit 6b4f62f

Browse files
authored
Merge pull request #4785 from easyops-cn/steve/v3-isolated-templates
feat(): support isolated templates
2 parents 9fd61b9 + f06f20d commit 6b4f62f

File tree

11 files changed

+125
-19
lines changed

11 files changed

+125
-19
lines changed

packages/runtime/src/CustomTemplates.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ interface LegacyTplPropProxy extends CustomTemplateProxyBasicProperty {
1818
refTransform?: unknown;
1919
}
2020

21-
class CustomTemplateRegistry {
21+
export class CustomTemplateRegistry {
2222
readonly #registry = new Map<string, CustomTemplate>();
23+
readonly #isolated: boolean | undefined;
24+
25+
constructor(isolated?: boolean) {
26+
this.#isolated = isolated;
27+
}
2328

2429
define(tagName: string, constructor: CustomTemplateConstructor): void {
2530
let registered = this.#registry.has(tagName);
@@ -179,7 +184,6 @@ class CustomTemplateRegistry {
179184
}
180185
Object.defineProperty(TplElement.prototype, propName, {
181186
get(this: RuntimeBrickElement) {
182-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
183187
return this.$$tplStateStore!.getValue(propName);
184188
},
185189
set(this: RuntimeBrickElement, value: unknown) {
@@ -194,7 +198,6 @@ class CustomTemplateRegistry {
194198
for (const [from, to] of validProxyProps) {
195199
Object.defineProperty(TplElement.prototype, from, {
196200
get(this: TplElement) {
197-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
198201
const element = this.$$getElementByRef!(to.ref) as unknown as Record<
199202
string,
200203
unknown
@@ -218,7 +221,6 @@ class CustomTemplateRegistry {
218221
for (const [from, to] of proxyMethods) {
219222
Object.defineProperty(TplElement.prototype, from, {
220223
value(this: TplElement, ...args: unknown[]) {
221-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
222224
const element = this.$$getElementByRef!(to.ref) as unknown as Record<
223225
string,
224226
Function
@@ -235,6 +237,12 @@ class CustomTemplateRegistry {
235237
get(tagName: string) {
236238
return this.#registry.get(tagName);
237239
}
240+
241+
clearIsolatedRegistry() {
242+
if (this.#isolated) {
243+
this.#registry.clear();
244+
}
245+
}
238246
}
239247

240248
function getExposedStates(state: ContextConf[] | undefined): string[] {

packages/runtime/src/StoryboardFunctionRegistry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export function StoryboardFunctionRegistryFactory({
114114
let currentApp: PartialMicroApp | undefined;
115115

116116
function registerStoryboardFunctions(
117-
functions: StoryboardFunction[],
117+
functions: StoryboardFunction[] | undefined,
118118
app?: PartialMicroApp
119119
): void {
120120
if (app) {

packages/runtime/src/createRoot.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,4 +349,53 @@ describe("preview", () => {
349349

350350
root.unmount();
351351
});
352+
353+
test("templates in scope fragment", async () => {
354+
const root = unstable_createRoot(container);
355+
356+
await root.render(
357+
[
358+
{
359+
brick: "isolated-tpl-a",
360+
},
361+
],
362+
{
363+
templates: [
364+
{
365+
name: "isolated-tpl-a",
366+
bricks: [
367+
{
368+
brick: "p",
369+
properties: {
370+
textContent: "Template A",
371+
},
372+
},
373+
],
374+
},
375+
],
376+
}
377+
);
378+
379+
const tpl = container.firstElementChild;
380+
expect(tpl?.tagName.toLowerCase()).toBe("isolated-tpl-a");
381+
expect(tpl?.innerHTML).toBe("<p>Template A</p>");
382+
383+
root.unmount();
384+
});
385+
386+
test("using unknown templates in scope fragment", async () => {
387+
const root = unstable_createRoot(container);
388+
389+
await root.render([
390+
{
391+
brick: "isolated-tpl-b",
392+
},
393+
]);
394+
395+
const tpl = container.firstElementChild;
396+
expect(tpl?.tagName.toLowerCase()).toBe("isolated-tpl-b");
397+
expect(tpl?.innerHTML).toBe("");
398+
399+
root.unmount();
400+
});
352401
});

packages/runtime/src/createRoot.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ import {
3232
isolatedFunctionRegistry,
3333
registerIsolatedFunctions,
3434
} from "./internal/compute/IsolatedFunctions.js";
35+
import {
36+
isolatedTemplateRegistryMap,
37+
registerIsolatedTemplates,
38+
} from "./internal/IsolatedTemplates.js";
3539

3640
export interface CreateRootOptions {
3741
portal?: HTMLElement;
@@ -197,7 +201,8 @@ export function unstable_createRoot(
197201
// Register functions.
198202
registerStoryboardFunctions(functions, app);
199203
} else {
200-
registerIsolatedFunctions(isolatedRoot!, functions ?? []);
204+
registerIsolatedTemplates(isolatedRoot!, templates);
205+
registerIsolatedFunctions(isolatedRoot!, functions);
201206
}
202207

203208
runtimeContext.ctxStore.define(context, runtimeContext);
@@ -272,6 +277,7 @@ export function unstable_createRoot(
272277
unmounted = true;
273278
if (isolatedRoot) {
274279
isolatedFunctionRegistry.delete(isolatedRoot);
280+
isolatedTemplateRegistryMap.delete(isolatedRoot);
275281
}
276282
unmountTree(container);
277283
if (portal) {

packages/runtime/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export * from "./auth.js";
22
export * from "./CustomEditors.js";
33
export * from "./CustomProcessors.js";
4-
export * from "./CustomTemplates.js";
4+
export { customTemplates } from "./CustomTemplates.js";
55
export * from "./fetchByProvider.js";
66
export * from "./getBasePath.js";
77
export * from "./getPageInfo.js";

packages/runtime/src/internal/CustomTemplates/expandCustomTemplate.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { childrenToSlots } from "../Renderer.js";
2121
import { hooks } from "../Runtime.js";
2222
import type { RendererContext } from "../RendererContext.js";
2323
import { replaceSlotWithSlottedBricks } from "./replaceSlotWithSlottedBricks.js";
24+
import { isolatedTemplateRegistryMap } from "../IsolatedTemplates.js";
2425

2526
export function expandCustomTemplate<T extends BrickConf | UseSingleBrickConf>(
2627
tplTagName: string,
@@ -52,7 +53,12 @@ export function expandCustomTemplate<T extends BrickConf | UseSingleBrickConf>(
5253
runtimeContext.tplStateStoreScope.push(tplStateStore);
5354
}
5455

55-
const { bricks, proxy, state, contracts } = customTemplates.get(tplTagName)!;
56+
const { bricks, proxy, state, contracts } = hostBrick.runtimeContext
57+
.isolatedRoot
58+
? isolatedTemplateRegistryMap
59+
.get(hostBrick.runtimeContext.isolatedRoot)!
60+
.get(tplTagName)!
61+
: customTemplates.get(tplTagName)!;
5662
hooks?.flowApi?.collectWidgetContract(contracts);
5763
tplStateStore.define(state, runtimeContext, asyncHostPropertyEntries);
5864

packages/runtime/src/internal/CustomTemplates/utils.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from "./utils.js";
77
import { DataStore } from "../data/DataStore.js";
88
import { customTemplates } from "../../CustomTemplates.js";
9+
import type { RuntimeContext } from "../interfaces.js";
910

1011
const tplStateStoreMap = new Map([
1112
["tpl-state-1", new DataStore("STATE", {} as any)],
@@ -72,7 +73,11 @@ describe("getTagNameOfCustomTemplate", () => {
7273
])(
7374
"getTagNameOfCustomTemplate(%j, %j) should return %j",
7475
(brick, appId, tagName) => {
75-
expect(getTagNameOfCustomTemplate(brick, appId)).toEqual(tagName);
76+
expect(
77+
getTagNameOfCustomTemplate(brick, {
78+
app: { id: appId },
79+
} as RuntimeContext)
80+
).toEqual(tagName);
7681
}
7782
);
7883
});

packages/runtime/src/internal/CustomTemplates/utils.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { DataStore } from "../data/DataStore.js";
22
import type { RuntimeBrickElement, RuntimeContext } from "../interfaces.js";
33
import { customTemplates } from "../../CustomTemplates.js";
4+
import { isolatedTemplateRegistryMap } from "../IsolatedTemplates.js";
45

56
type MinimalTplStateStoreContext = Pick<
67
RuntimeContext,
@@ -47,11 +48,21 @@ export function getTplHostElement(
4748

4849
export function getTagNameOfCustomTemplate(
4950
brick: string,
50-
appId?: string
51+
runtimeContext: Pick<RuntimeContext, "app" | "isolatedRoot">
5152
): false | string {
53+
if (runtimeContext.isolatedRoot) {
54+
const registry = isolatedTemplateRegistryMap.get(
55+
runtimeContext.isolatedRoot
56+
);
57+
if (registry?.get(brick)) {
58+
return brick;
59+
}
60+
return false;
61+
}
62+
5263
// When a template is registered by an app, it's namespace maybe missed.
53-
if (!brick.includes(".") && brick.startsWith("tpl-") && appId) {
54-
const tagName = `${appId}.${brick}`;
64+
if (!brick.includes(".") && brick.startsWith("tpl-") && runtimeContext.app) {
65+
const tagName = `${runtimeContext.app.id}.${brick}`;
5566
if (customTemplates.get(tagName)) {
5667
return tagName;
5768
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { CustomTemplate } from "@next-core/types";
2+
import { CustomTemplateRegistry } from "../CustomTemplates.js";
3+
4+
export const isolatedTemplateRegistryMap = new Map<
5+
symbol,
6+
CustomTemplateRegistry
7+
>();
8+
9+
export function registerIsolatedTemplates(
10+
isolatedRoot: symbol,
11+
templates: CustomTemplate[] | undefined
12+
) {
13+
let registry = isolatedTemplateRegistryMap.get(isolatedRoot);
14+
if (!registry) {
15+
registry = new CustomTemplateRegistry(true);
16+
isolatedTemplateRegistryMap.set(isolatedRoot, registry);
17+
}
18+
registry.clearIsolatedRegistry();
19+
if (Array.isArray(templates)) {
20+
for (const tpl of templates) {
21+
registry.define(tpl.name, tpl);
22+
}
23+
}
24+
}

packages/runtime/src/internal/Renderer.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -703,10 +703,7 @@ async function legacyRenderBrick(
703703
);
704704
}
705705

706-
const tplTagName = getTagNameOfCustomTemplate(
707-
brickName,
708-
runtimeContext.app?.id
709-
);
706+
const tplTagName = getTagNameOfCustomTemplate(brickName, runtimeContext);
710707

711708
if (tplTagName) {
712709
const tplCount = tplStack.get(tplTagName) ?? 0;

0 commit comments

Comments
 (0)