@analogjs/astro-angular 3.0.0-alpha.92 → 3.0.0-alpha.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/client-ngh.js +10 -9
- package/src/client-ngh.js.map +1 -1
- package/src/client.d.ts +1 -1
- package/src/client.js +8 -5
- package/src/client.js.map +1 -1
- package/src/create-component.d.ts +8 -1
- package/src/create-component.js +14 -2
- package/src/create-component.js.map +1 -1
- package/src/projection.d.ts +28 -0
- package/src/projection.js +98 -0
- package/src/projection.js.map +1 -0
- package/src/selector.d.ts +16 -0
- package/src/selector.js +63 -0
- package/src/selector.js.map +1 -0
- package/src/server-ngh.js +31 -20
- package/src/server-ngh.js.map +1 -1
- package/src/server.d.ts +1 -1
- package/src/server.js +29 -43
- package/src/server.js.map +1 -1
- package/src/server-providers.d.ts +0 -8
- package/src/server-providers.js +0 -23
- package/src/server-providers.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@analogjs/astro-angular",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.93",
|
|
4
4
|
"description": "Use Angular components within Astro",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Brandon Roberts <robertsbt@gmail.com>",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"url": "https://github.com/sponsors/brandonroberts"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@analogjs/vite-plugin-angular": "3.0.0-alpha.
|
|
59
|
+
"@analogjs/vite-plugin-angular": "3.0.0-alpha.93",
|
|
60
60
|
"rehype": "^13.0.2"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
package/src/client-ngh.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { buildProjectableNodes, collectProjectedNodes } from "./projection.js";
|
|
1
2
|
import { ID_PROP_NAME } from "./id.js";
|
|
2
|
-
import { createComponentBindings, getComponentElementTag } from "./create-component.js";
|
|
3
|
+
import { createComponentBindings, getComponentElementTag, registerRootComponent } from "./create-component.js";
|
|
3
4
|
import { ensureSsrIntegrityMarker } from "./ssr-integrity.js";
|
|
4
|
-
import {
|
|
5
|
+
import { APP_ID, createComponent, provideZonelessChangeDetection, reflectComponentType } from "@angular/core";
|
|
5
6
|
import { createApplication, provideClientHydration } from "@angular/platform-browser";
|
|
6
7
|
//#region packages/astro-angular/src/client-ngh.ts
|
|
7
8
|
var client_ngh_default = (element) => {
|
|
8
|
-
return (Component, props) => {
|
|
9
|
+
return (Component, props, slots) => {
|
|
9
10
|
const mirror = reflectComponentType(Component);
|
|
10
11
|
if (!mirror) return;
|
|
11
12
|
ensureSsrIntegrityMarker();
|
|
@@ -17,7 +18,8 @@ var client_ngh_default = (element) => {
|
|
|
17
18
|
reuseDom = false;
|
|
18
19
|
}
|
|
19
20
|
const ngAppId = hostElement?.getAttribute(ID_PROP_NAME);
|
|
20
|
-
|
|
21
|
+
const projectableNodes = (reuseDom && ngAppId ? collectProjectedNodes(hostElement, mirror, ngAppId) : void 0) ?? buildProjectableNodes(mirror, slots, document);
|
|
22
|
+
return createApplication({ providers: [
|
|
21
23
|
provideZonelessChangeDetection(),
|
|
22
24
|
reuseDom ? provideClientHydration(...Component.hydrationFeatures?.() || []) : [],
|
|
23
25
|
{
|
|
@@ -26,14 +28,13 @@ var client_ngh_default = (element) => {
|
|
|
26
28
|
},
|
|
27
29
|
...Component.clientProviders || []
|
|
28
30
|
] }).then((appRef) => {
|
|
29
|
-
|
|
31
|
+
registerRootComponent(appRef, createComponent(Component, {
|
|
30
32
|
environmentInjector: appRef.injector,
|
|
31
33
|
hostElement,
|
|
34
|
+
projectableNodes,
|
|
32
35
|
bindings: createComponentBindings(mirror, props, hostElement)
|
|
33
|
-
});
|
|
34
|
-
appRef
|
|
35
|
-
appRef.components.push(componentRef);
|
|
36
|
-
appRef.injector.get(APP_BOOTSTRAP_LISTENER, []).forEach((cb) => cb(componentRef));
|
|
36
|
+
}));
|
|
37
|
+
return appRef;
|
|
37
38
|
}).catch((error) => {
|
|
38
39
|
console.error("Failed to hydrate Angular component:", error);
|
|
39
40
|
});
|
package/src/client-ngh.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-ngh.js","names":[],"sources":["../../src/client-ngh.ts"],"sourcesContent":["import {\n type EnvironmentProviders,\n type Provider,\n reflectComponentType,\n provideZonelessChangeDetection,\n Type,\n APP_ID,\n createComponent,\n
|
|
1
|
+
{"version":3,"file":"client-ngh.js","names":[],"sources":["../../src/client-ngh.ts"],"sourcesContent":["import {\n type EnvironmentProviders,\n type Provider,\n reflectComponentType,\n provideZonelessChangeDetection,\n Type,\n APP_ID,\n createComponent,\n} from '@angular/core';\nimport {\n createApplication,\n type HydrationFeature,\n type HydrationFeatureKind,\n provideClientHydration,\n} from '@angular/platform-browser';\nimport {\n createComponentBindings,\n getComponentElementTag,\n registerRootComponent,\n} from './create-component.ts';\nimport { ID_PROP_NAME } from './id.ts';\nimport { ensureSsrIntegrityMarker } from './ssr-integrity.ts';\nimport { buildProjectableNodes, collectProjectedNodes } from './projection.ts';\n\nexport default (element: HTMLElement) => {\n return (\n Component: Type<unknown> & {\n clientProviders?: (Provider | EnvironmentProviders)[];\n hydrationFeatures?: () => HydrationFeature<HydrationFeatureKind>[];\n },\n props?: Record<string, unknown>,\n slots?: unknown,\n ) => {\n const mirror = reflectComponentType(Component);\n\n if (!mirror) {\n // Not an Angular component\n return;\n }\n\n ensureSsrIntegrityMarker();\n\n let hostElement = element.querySelector(mirror.selector);\n let reuseDom = true;\n\n if (!hostElement) {\n // This is a client-only component\n hostElement = document.createElement(getComponentElementTag(mirror));\n element.appendChild(hostElement);\n reuseDom = false;\n }\n\n const ngAppId = hostElement?.getAttribute(ID_PROP_NAME);\n\n // Hydration reuses the server-rendered DOM, so hand Angular the projected\n // nodes it already rendered instead of freshly parsed copies.\n const projectableNodes =\n (reuseDom && ngAppId\n ? collectProjectedNodes(hostElement, mirror, ngAppId)\n : undefined) ?? buildProjectableNodes(mirror, slots, document);\n\n return createApplication({\n providers: [\n provideZonelessChangeDetection(),\n reuseDom\n ? provideClientHydration(...(Component.hydrationFeatures?.() || []))\n : [],\n {\n provide: APP_ID,\n useValue: ngAppId || 'ng',\n },\n ...(Component.clientProviders || []),\n ],\n })\n .then((appRef) => {\n const componentRef = createComponent(Component, {\n environmentInjector: appRef.injector,\n hostElement,\n projectableNodes,\n bindings: createComponentBindings(mirror, props, hostElement),\n });\n\n registerRootComponent(appRef, componentRef);\n\n return appRef;\n })\n .catch((error) => {\n console.error('Failed to hydrate Angular component:', error);\n });\n };\n};\n"],"mappings":";;;;;;;AAwBA,IAAA,sBAAgB,YAAyB;AACvC,SACE,WAIA,OACA,UACG;EACH,MAAM,SAAS,qBAAqB,UAAU;AAE9C,MAAI,CAAC,OAEH;AAGF,4BAA0B;EAE1B,IAAI,cAAc,QAAQ,cAAc,OAAO,SAAS;EACxD,IAAI,WAAW;AAEf,MAAI,CAAC,aAAa;AAEhB,iBAAc,SAAS,cAAc,uBAAuB,OAAO,CAAC;AACpE,WAAQ,YAAY,YAAY;AAChC,cAAW;;EAGb,MAAM,UAAU,aAAa,aAAa,aAAa;EAIvD,MAAM,oBACH,YAAY,UACT,sBAAsB,aAAa,QAAQ,QAAQ,GACnD,KAAA,MAAc,sBAAsB,QAAQ,OAAO,SAAS;AAElE,SAAO,kBAAkB,EACvB,WAAW;GACT,gCAAgC;GAChC,WACI,uBAAuB,GAAI,UAAU,qBAAqB,IAAI,EAAE,CAAE,GAClE,EAAE;GACN;IACE,SAAS;IACT,UAAU,WAAW;IACtB;GACD,GAAI,UAAU,mBAAmB,EAAE;GACpC,EACF,CAAC,CACC,MAAM,WAAW;AAQhB,yBAAsB,QAPD,gBAAgB,WAAW;IAC9C,qBAAqB,OAAO;IAC5B;IACA;IACA,UAAU,wBAAwB,QAAQ,OAAO,YAAY;IAC9D,CAAC,CAEyC;AAE3C,UAAO;IACP,CACD,OAAO,UAAU;AAChB,WAAQ,MAAM,wCAAwC,MAAM;IAC5D"}
|
package/src/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EnvironmentProviders, Provider, ɵComponentType as ComponentType } from "@angular/core";
|
|
2
2
|
declare const _default: (element: HTMLElement) => ((Component: ComponentType<unknown> & {
|
|
3
3
|
clientProviders?: (Provider | EnvironmentProviders)[];
|
|
4
|
-
}, props?: Record<string, unknown>,
|
|
4
|
+
}, props?: Record<string, unknown>, slots?: unknown) => void);
|
|
5
5
|
export default _default;
|
package/src/client.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
+
import { buildProjectableNodes } from "./projection.js";
|
|
1
2
|
import { createComponent, provideZonelessChangeDetection, reflectComponentType } from "@angular/core";
|
|
2
3
|
import { createApplication } from "@angular/platform-browser";
|
|
3
4
|
import { Subject, takeUntil } from "rxjs";
|
|
4
5
|
//#region packages/astro-angular/src/client.ts
|
|
5
6
|
var client_default = (element) => {
|
|
6
|
-
return (Component, props,
|
|
7
|
+
return (Component, props, slots) => {
|
|
8
|
+
const mirror = reflectComponentType(Component);
|
|
9
|
+
if (!mirror) return;
|
|
7
10
|
createApplication({ providers: [provideZonelessChangeDetection(), ...Component.clientProviders || []] }).then((appRef) => {
|
|
8
11
|
const componentRef = createComponent(Component, {
|
|
9
12
|
environmentInjector: appRef.injector,
|
|
10
|
-
hostElement: element
|
|
13
|
+
hostElement: element,
|
|
14
|
+
projectableNodes: buildProjectableNodes(mirror, slots, document)
|
|
11
15
|
});
|
|
12
|
-
|
|
13
|
-
if (props && mirror) {
|
|
16
|
+
if (props) {
|
|
14
17
|
for (const [key, value] of Object.entries(props)) if (mirror.inputs.some(({ templateName, propName }) => templateName === key || propName === key)) componentRef.setInput(key, value);
|
|
15
18
|
}
|
|
16
|
-
if (mirror
|
|
19
|
+
if (mirror.outputs.length && props?.["data-analog-id"]) {
|
|
17
20
|
const destroySubject = new Subject();
|
|
18
21
|
element.setAttribute("data-analog-id", props["data-analog-id"]);
|
|
19
22
|
mirror.outputs.forEach(({ templateName, propName }) => {
|
package/src/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../src/client.ts"],"sourcesContent":["import {\n EnvironmentProviders,\n Provider,\n reflectComponentType,\n ɵComponentType as ComponentType,\n provideZonelessChangeDetection,\n} from '@angular/core';\nimport { ApplicationRef, createComponent } from '@angular/core';\nimport { createApplication } from '@angular/platform-browser';\nimport { Observable, Subject, takeUntil } from 'rxjs';\n\nexport default (\n element: HTMLElement,\n): ((\n Component: ComponentType<unknown> & {\n clientProviders?: (Provider | EnvironmentProviders)[];\n },\n props?: Record<string, unknown>,\n
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../src/client.ts"],"sourcesContent":["import {\n EnvironmentProviders,\n Provider,\n reflectComponentType,\n ɵComponentType as ComponentType,\n provideZonelessChangeDetection,\n} from '@angular/core';\nimport { ApplicationRef, createComponent } from '@angular/core';\nimport { createApplication } from '@angular/platform-browser';\nimport { Observable, Subject, takeUntil } from 'rxjs';\nimport { buildProjectableNodes } from './projection.ts';\n\nexport default (\n element: HTMLElement,\n): ((\n Component: ComponentType<unknown> & {\n clientProviders?: (Provider | EnvironmentProviders)[];\n },\n props?: Record<string, unknown>,\n slots?: unknown,\n) => void) => {\n return (\n Component: ComponentType<unknown> & {\n clientProviders?: (Provider | EnvironmentProviders)[];\n },\n props?: Record<string, unknown>,\n slots?: unknown,\n ): void => {\n const mirror = reflectComponentType(Component);\n\n if (!mirror) {\n // Not an Angular component\n return;\n }\n\n createApplication({\n providers: [\n provideZonelessChangeDetection(),\n ...(Component.clientProviders || []),\n ],\n }).then((appRef: ApplicationRef) => {\n const componentRef = createComponent(Component, {\n environmentInjector: appRef.injector,\n hostElement: element,\n projectableNodes: buildProjectableNodes(mirror, slots, document),\n });\n\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (\n mirror.inputs.some(\n ({ templateName, propName }) =>\n templateName === key || propName === key,\n )\n ) {\n componentRef.setInput(key, value);\n }\n }\n }\n\n if (mirror.outputs.length && props?.['data-analog-id']) {\n const destroySubject = new Subject<void>();\n element.setAttribute(\n 'data-analog-id',\n props['data-analog-id'] as string,\n );\n\n mirror.outputs.forEach(({ templateName, propName }) => {\n const outputName = templateName || propName;\n const component = componentRef.instance as Record<\n string,\n Observable<unknown>\n >;\n component[outputName]\n .pipe(takeUntil(destroySubject))\n .subscribe((detail) => {\n const event = new CustomEvent(outputName, {\n bubbles: true,\n cancelable: true,\n composed: true,\n detail,\n });\n element.dispatchEvent(event);\n });\n });\n\n appRef.onDestroy(() => {\n destroySubject.next();\n destroySubject.complete();\n });\n }\n\n appRef.attachView(componentRef.hostView);\n });\n };\n};\n"],"mappings":";;;;;AAYA,IAAA,kBACE,YAOY;AACZ,SACE,WAGA,OACA,UACS;EACT,MAAM,SAAS,qBAAqB,UAAU;AAE9C,MAAI,CAAC,OAEH;AAGF,oBAAkB,EAChB,WAAW,CACT,gCAAgC,EAChC,GAAI,UAAU,mBAAmB,EAAE,CACpC,EACF,CAAC,CAAC,MAAM,WAA2B;GAClC,MAAM,eAAe,gBAAgB,WAAW;IAC9C,qBAAqB,OAAO;IAC5B,aAAa;IACb,kBAAkB,sBAAsB,QAAQ,OAAO,SAAS;IACjE,CAAC;AAEF,OAAI;SACG,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KACE,OAAO,OAAO,MACX,EAAE,cAAc,eACf,iBAAiB,OAAO,aAAa,IACxC,CAED,cAAa,SAAS,KAAK,MAAM;;AAKvC,OAAI,OAAO,QAAQ,UAAU,QAAQ,mBAAmB;IACtD,MAAM,iBAAiB,IAAI,SAAe;AAC1C,YAAQ,aACN,kBACA,MAAM,kBACP;AAED,WAAO,QAAQ,SAAS,EAAE,cAAc,eAAe;KACrD,MAAM,aAAa,gBAAgB;AACjB,kBAAa,SAIrB,YACP,KAAK,UAAU,eAAe,CAAC,CAC/B,WAAW,WAAW;MACrB,MAAM,QAAQ,IAAI,YAAY,YAAY;OACxC,SAAS;OACT,YAAY;OACZ,UAAU;OACV;OACD,CAAC;AACF,cAAQ,cAAc,MAAM;OAC5B;MACJ;AAEF,WAAO,gBAAgB;AACrB,oBAAe,MAAM;AACrB,oBAAe,UAAU;MACzB;;AAGJ,UAAO,WAAW,aAAa,SAAS;IACxC"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import { type Binding, type ComponentMirror } from "@angular/core";
|
|
1
|
+
import { type ApplicationRef, type Binding, type ComponentMirror, type ComponentRef } from "@angular/core";
|
|
2
2
|
export declare function getComponentElementTag(mirror: ComponentMirror<unknown>): string;
|
|
3
3
|
export declare function createInputBindings(mirror: ComponentMirror<unknown>, props?: Record<string, unknown>): Binding[];
|
|
4
4
|
export declare function createOutputBindings(hostElement: Element, mirror: ComponentMirror<unknown>): Binding[];
|
|
5
5
|
export declare function createComponentBindings(mirror: ComponentMirror<unknown>, props?: Record<string, unknown>, hostElement?: Element): Binding[];
|
|
6
|
+
/**
|
|
7
|
+
* Registers a component created with `createComponent` as a root component,
|
|
8
|
+
* the way `bootstrapApplication` does: attach the view, track it on the
|
|
9
|
+
* application, run the initial change detection and notify
|
|
10
|
+
* `APP_BOOTSTRAP_LISTENER`s such as the router's.
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerRootComponent(appRef: ApplicationRef, componentRef: ComponentRef<unknown>): void;
|
package/src/create-component.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./id.js";
|
|
2
|
-
import { inputBinding, outputBinding } from "@angular/core";
|
|
2
|
+
import { APP_BOOTSTRAP_LISTENER, inputBinding, outputBinding } from "@angular/core";
|
|
3
3
|
//#region packages/astro-angular/src/create-component.ts
|
|
4
4
|
function getComponentElementTag(mirror) {
|
|
5
5
|
return mirror.selector.split(",")[0] || "ng-component";
|
|
@@ -25,7 +25,19 @@ function createComponentBindings(mirror, props, hostElement) {
|
|
|
25
25
|
const outputBindings = createOutputBindings(hostElement, mirror);
|
|
26
26
|
return [...inputBindings, ...outputBindings];
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Registers a component created with `createComponent` as a root component,
|
|
30
|
+
* the way `bootstrapApplication` does: attach the view, track it on the
|
|
31
|
+
* application, run the initial change detection and notify
|
|
32
|
+
* `APP_BOOTSTRAP_LISTENER`s such as the router's.
|
|
33
|
+
*/
|
|
34
|
+
function registerRootComponent(appRef, componentRef) {
|
|
35
|
+
appRef.attachView(componentRef.hostView);
|
|
36
|
+
appRef.tick();
|
|
37
|
+
appRef.components.push(componentRef);
|
|
38
|
+
appRef.injector.get(APP_BOOTSTRAP_LISTENER, []).forEach((listener) => listener(componentRef));
|
|
39
|
+
}
|
|
28
40
|
//#endregion
|
|
29
|
-
export { createComponentBindings, getComponentElementTag };
|
|
41
|
+
export { createComponentBindings, createInputBindings, getComponentElementTag, registerRootComponent };
|
|
30
42
|
|
|
31
43
|
//# sourceMappingURL=create-component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-component.js","names":[],"sources":["../../src/create-component.ts"],"sourcesContent":["import {\n type Binding,\n type ComponentMirror,\n inputBinding,\n outputBinding,\n} from '@angular/core';\nimport { ID_PROP_NAME } from './id.ts';\n\nexport function getComponentElementTag(\n mirror: ComponentMirror<unknown>,\n): string {\n return mirror.selector.split(',')[0] || 'ng-component';\n}\n\nexport function createInputBindings(\n mirror: ComponentMirror<unknown>,\n props?: Record<string, unknown>,\n): Binding[] {\n if (!props) {\n return [];\n }\n\n const inputBindings = Object.entries(props)\n .filter(([key]) =>\n mirror.inputs.some(({ templateName }) => templateName === key),\n )\n .map(([key, value]) => inputBinding(key, () => value));\n\n return inputBindings;\n}\n\nexport function createOutputBindings(\n hostElement: Element,\n mirror: ComponentMirror<unknown>,\n): Binding[] {\n const outputBindings = mirror.outputs.map(({ templateName }) =>\n outputBinding(templateName, (detail) => {\n const event = new CustomEvent(templateName, {\n bubbles: true,\n cancelable: true,\n composed: true,\n detail,\n });\n hostElement.dispatchEvent(event);\n }),\n );\n\n return outputBindings;\n}\n\nexport function createComponentBindings(\n mirror: ComponentMirror<unknown>,\n props?: Record<string, unknown>,\n hostElement?: Element,\n): Binding[] {\n const inputBindings = createInputBindings(mirror, props);\n\n if (!mirror.outputs.length || !props?.[ID_PROP_NAME] || !hostElement) {\n return inputBindings;\n }\n\n const outputBindings = createOutputBindings(hostElement, mirror);\n\n return [...inputBindings, ...outputBindings];\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"create-component.js","names":[],"sources":["../../src/create-component.ts"],"sourcesContent":["import {\n APP_BOOTSTRAP_LISTENER,\n type ApplicationRef,\n type Binding,\n type ComponentMirror,\n type ComponentRef,\n inputBinding,\n outputBinding,\n} from '@angular/core';\nimport { ID_PROP_NAME } from './id.ts';\n\nexport function getComponentElementTag(\n mirror: ComponentMirror<unknown>,\n): string {\n return mirror.selector.split(',')[0] || 'ng-component';\n}\n\nexport function createInputBindings(\n mirror: ComponentMirror<unknown>,\n props?: Record<string, unknown>,\n): Binding[] {\n if (!props) {\n return [];\n }\n\n const inputBindings = Object.entries(props)\n .filter(([key]) =>\n mirror.inputs.some(({ templateName }) => templateName === key),\n )\n .map(([key, value]) => inputBinding(key, () => value));\n\n return inputBindings;\n}\n\nexport function createOutputBindings(\n hostElement: Element,\n mirror: ComponentMirror<unknown>,\n): Binding[] {\n const outputBindings = mirror.outputs.map(({ templateName }) =>\n outputBinding(templateName, (detail) => {\n const event = new CustomEvent(templateName, {\n bubbles: true,\n cancelable: true,\n composed: true,\n detail,\n });\n hostElement.dispatchEvent(event);\n }),\n );\n\n return outputBindings;\n}\n\nexport function createComponentBindings(\n mirror: ComponentMirror<unknown>,\n props?: Record<string, unknown>,\n hostElement?: Element,\n): Binding[] {\n const inputBindings = createInputBindings(mirror, props);\n\n if (!mirror.outputs.length || !props?.[ID_PROP_NAME] || !hostElement) {\n return inputBindings;\n }\n\n const outputBindings = createOutputBindings(hostElement, mirror);\n\n return [...inputBindings, ...outputBindings];\n}\n\n/**\n * Registers a component created with `createComponent` as a root component,\n * the way `bootstrapApplication` does: attach the view, track it on the\n * application, run the initial change detection and notify\n * `APP_BOOTSTRAP_LISTENER`s such as the router's.\n */\nexport function registerRootComponent(\n appRef: ApplicationRef,\n componentRef: ComponentRef<unknown>,\n): void {\n appRef.attachView(componentRef.hostView);\n appRef.tick();\n appRef.components.push(componentRef);\n appRef.injector\n .get(APP_BOOTSTRAP_LISTENER, [])\n .forEach((listener) => listener(componentRef));\n}\n"],"mappings":";;;AAWA,SAAgB,uBACd,QACQ;AACR,QAAO,OAAO,SAAS,MAAM,IAAI,CAAC,MAAM;;AAG1C,SAAgB,oBACd,QACA,OACW;AACX,KAAI,CAAC,MACH,QAAO,EAAE;AASX,QANsB,OAAO,QAAQ,MAAM,CACxC,QAAQ,CAAC,SACR,OAAO,OAAO,MAAM,EAAE,mBAAmB,iBAAiB,IAAI,CAC/D,CACA,KAAK,CAAC,KAAK,WAAW,aAAa,WAAW,MAAM,CAAC;;AAK1D,SAAgB,qBACd,aACA,QACW;AAaX,QAZuB,OAAO,QAAQ,KAAK,EAAE,mBAC3C,cAAc,eAAe,WAAW;EACtC,MAAM,QAAQ,IAAI,YAAY,cAAc;GAC1C,SAAS;GACT,YAAY;GACZ,UAAU;GACV;GACD,CAAC;AACF,cAAY,cAAc,MAAM;GAChC,CACH;;AAKH,SAAgB,wBACd,QACA,OACA,aACW;CACX,MAAM,gBAAgB,oBAAoB,QAAQ,MAAM;AAExD,KAAI,CAAC,OAAO,QAAQ,UAAU,CAAC,QAAA,qBAAyB,CAAC,YACvD,QAAO;CAGT,MAAM,iBAAiB,qBAAqB,aAAa,OAAO;AAEhE,QAAO,CAAC,GAAG,eAAe,GAAG,eAAe;;;;;;;;AAS9C,SAAgB,sBACd,QACA,cACM;AACN,QAAO,WAAW,aAAa,SAAS;AACxC,QAAO,MAAM;AACb,QAAO,WAAW,KAAK,aAAa;AACpC,QAAO,SACJ,IAAI,wBAAwB,EAAE,CAAC,CAC/B,SAAS,aAAa,SAAS,aAAa,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComponentMirror } from "@angular/core";
|
|
2
|
+
/**
|
|
3
|
+
* Distributes the HTML Astro rendered for the component's slots across the
|
|
4
|
+
* component's `<ng-content>` slots, using Angular's own selector semantics.
|
|
5
|
+
*
|
|
6
|
+
* The result is indexed to match `mirror.ngContentSelectors`. Angular does not
|
|
7
|
+
* apply `select` matching to `projectableNodes`, so the matching happens here.
|
|
8
|
+
* @param mirror The component mirror, to read the `ng-content` selectors
|
|
9
|
+
* @param slots The slot HTML passed by Astro, keyed by slot name
|
|
10
|
+
* @param document The document the component is rendered into
|
|
11
|
+
* @returns The nodes to project, or `undefined` when the component has no `ng-content`
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildProjectableNodes(mirror: ComponentMirror<unknown>, slots: unknown, document: Document): Node[][] | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Wraps each non-empty slot in comment markers, so the client can find the
|
|
16
|
+
* server-rendered nodes again when hydrating. Angular reuses the DOM during
|
|
17
|
+
* hydration, so it must be handed those nodes rather than freshly parsed ones.
|
|
18
|
+
* @param projectableNodes The distributed nodes, see `buildProjectableNodes`
|
|
19
|
+
* @param appId The island's APP_ID, to keep markers apart from nested islands
|
|
20
|
+
* @param document The document the component is rendered into
|
|
21
|
+
*/
|
|
22
|
+
export declare function markProjectableNodes(projectableNodes: Node[][], appId: string, document: Document): Node[][];
|
|
23
|
+
/**
|
|
24
|
+
* Collects the server-rendered projected nodes from the host element using the
|
|
25
|
+
* markers added by `markProjectableNodes`.
|
|
26
|
+
* @returns The nodes per slot, or `undefined` when the host carries no markers
|
|
27
|
+
*/
|
|
28
|
+
export declare function collectProjectedNodes(hostElement: Element, mirror: ComponentMirror<unknown>, appId: string): Node[][] | undefined;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { isSameSelector, parseSelectorList } from "./selector.js";
|
|
2
|
+
//#region packages/astro-angular/src/projection.ts
|
|
3
|
+
var ELEMENT_NODE = 1;
|
|
4
|
+
var TEXT_NODE = 3;
|
|
5
|
+
var COMMENT_NODE = 8;
|
|
6
|
+
var SHOW_COMMENT = 128;
|
|
7
|
+
var MARKER_PREFIX = "analog-slot:";
|
|
8
|
+
/**
|
|
9
|
+
* Distributes the HTML Astro rendered for the component's slots across the
|
|
10
|
+
* component's `<ng-content>` slots, using Angular's own selector semantics.
|
|
11
|
+
*
|
|
12
|
+
* The result is indexed to match `mirror.ngContentSelectors`. Angular does not
|
|
13
|
+
* apply `select` matching to `projectableNodes`, so the matching happens here.
|
|
14
|
+
* @param mirror The component mirror, to read the `ng-content` selectors
|
|
15
|
+
* @param slots The slot HTML passed by Astro, keyed by slot name
|
|
16
|
+
* @param document The document the component is rendered into
|
|
17
|
+
* @returns The nodes to project, or `undefined` when the component has no `ng-content`
|
|
18
|
+
*/
|
|
19
|
+
function buildProjectableNodes(mirror, slots, document) {
|
|
20
|
+
const selectors = mirror.ngContentSelectors;
|
|
21
|
+
if (!selectors.length) return;
|
|
22
|
+
const projectableNodes = selectors.map(() => []);
|
|
23
|
+
const wildcardIndex = selectors.lastIndexOf("*");
|
|
24
|
+
for (const node of parseSlots(slots, document)) {
|
|
25
|
+
if (node.nodeType === TEXT_NODE && !node.textContent?.trim()) continue;
|
|
26
|
+
const matched = selectors.findIndex((selector) => selector !== "*" && matches(node, selector));
|
|
27
|
+
const target = matched > -1 ? matched : wildcardIndex;
|
|
28
|
+
if (target > -1) projectableNodes[target].push(node);
|
|
29
|
+
}
|
|
30
|
+
return projectableNodes;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Wraps each non-empty slot in comment markers, so the client can find the
|
|
34
|
+
* server-rendered nodes again when hydrating. Angular reuses the DOM during
|
|
35
|
+
* hydration, so it must be handed those nodes rather than freshly parsed ones.
|
|
36
|
+
* @param projectableNodes The distributed nodes, see `buildProjectableNodes`
|
|
37
|
+
* @param appId The island's APP_ID, to keep markers apart from nested islands
|
|
38
|
+
* @param document The document the component is rendered into
|
|
39
|
+
*/
|
|
40
|
+
function markProjectableNodes(projectableNodes, appId, document) {
|
|
41
|
+
return projectableNodes.map((nodes, index) => nodes.length ? [
|
|
42
|
+
document.createComment(`${MARKER_PREFIX}${appId}:${index}`),
|
|
43
|
+
...nodes,
|
|
44
|
+
document.createComment(`/${MARKER_PREFIX}${appId}:${index}`)
|
|
45
|
+
] : nodes);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Collects the server-rendered projected nodes from the host element using the
|
|
49
|
+
* markers added by `markProjectableNodes`.
|
|
50
|
+
* @returns The nodes per slot, or `undefined` when the host carries no markers
|
|
51
|
+
*/
|
|
52
|
+
function collectProjectedNodes(hostElement, mirror, appId) {
|
|
53
|
+
const selectors = mirror.ngContentSelectors;
|
|
54
|
+
if (!selectors.length) return;
|
|
55
|
+
const projectableNodes = selectors.map(() => []);
|
|
56
|
+
const walker = hostElement.ownerDocument.createTreeWalker(hostElement, SHOW_COMMENT);
|
|
57
|
+
let found = false;
|
|
58
|
+
for (let start = walker.nextNode(); start; start = walker.nextNode()) {
|
|
59
|
+
const match = start.textContent?.startsWith(`${MARKER_PREFIX}${appId}:`) ? Number(start.textContent.slice(`${MARKER_PREFIX}${appId}:`.length)) : NaN;
|
|
60
|
+
if (Number.isNaN(match) || match >= selectors.length) continue;
|
|
61
|
+
const end = `/${MARKER_PREFIX}${appId}:${match}`;
|
|
62
|
+
const nodes = [start];
|
|
63
|
+
let node = start.nextSibling;
|
|
64
|
+
while (node && !(node.nodeType === COMMENT_NODE && node.textContent === end)) {
|
|
65
|
+
nodes.push(node);
|
|
66
|
+
node = node.nextSibling;
|
|
67
|
+
}
|
|
68
|
+
if (node) nodes.push(node);
|
|
69
|
+
projectableNodes[match] = nodes;
|
|
70
|
+
found = true;
|
|
71
|
+
}
|
|
72
|
+
return found ? projectableNodes : void 0;
|
|
73
|
+
}
|
|
74
|
+
function parseSlots(slots, document) {
|
|
75
|
+
const html = slots && typeof slots === "object" ? Object.values(slots).join("") : String(slots ?? "");
|
|
76
|
+
if (!html) return [];
|
|
77
|
+
const template = document.createElement("template");
|
|
78
|
+
template.innerHTML = html;
|
|
79
|
+
return Array.from(template.content.childNodes);
|
|
80
|
+
}
|
|
81
|
+
function matches(node, selector) {
|
|
82
|
+
if (node.nodeType !== ELEMENT_NODE) return false;
|
|
83
|
+
const element = node;
|
|
84
|
+
const projectAs = element.getAttribute("ngProjectAs");
|
|
85
|
+
if (projectAs !== null) {
|
|
86
|
+
const [alias] = parseSelectorList(projectAs);
|
|
87
|
+
return parseSelectorList(selector).some((candidate) => isSameSelector(alias, candidate));
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return element.matches(selector);
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { buildProjectableNodes, collectProjectedNodes, markProjectableNodes };
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=projection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection.js","names":[],"sources":["../../src/projection.ts"],"sourcesContent":["import type { ComponentMirror } from '@angular/core';\nimport { isSameSelector, parseSelectorList } from './selector.ts';\n\nconst ELEMENT_NODE = 1;\nconst TEXT_NODE = 3;\nconst COMMENT_NODE = 8;\nconst SHOW_COMMENT = 128;\n\nconst MARKER_PREFIX = 'analog-slot:';\n\n/**\n * Distributes the HTML Astro rendered for the component's slots across the\n * component's `<ng-content>` slots, using Angular's own selector semantics.\n *\n * The result is indexed to match `mirror.ngContentSelectors`. Angular does not\n * apply `select` matching to `projectableNodes`, so the matching happens here.\n * @param mirror The component mirror, to read the `ng-content` selectors\n * @param slots The slot HTML passed by Astro, keyed by slot name\n * @param document The document the component is rendered into\n * @returns The nodes to project, or `undefined` when the component has no `ng-content`\n */\nexport function buildProjectableNodes(\n mirror: ComponentMirror<unknown>,\n slots: unknown,\n document: Document,\n): Node[][] | undefined {\n const selectors = mirror.ngContentSelectors;\n\n if (!selectors.length) {\n return undefined;\n }\n\n const projectableNodes: Node[][] = selectors.map(() => []);\n // As in Angular, unmatched content goes to the last default slot.\n const wildcardIndex = selectors.lastIndexOf('*');\n\n for (const node of parseSlots(slots, document)) {\n // Angular drops whitespace-only text nodes between elements at compile\n // time, so projecting them would only suppress `ng-content` fallbacks.\n if (node.nodeType === TEXT_NODE && !node.textContent?.trim()) {\n continue;\n }\n\n const matched = selectors.findIndex(\n (selector) => selector !== '*' && matches(node, selector),\n );\n const target = matched > -1 ? matched : wildcardIndex;\n\n if (target > -1) {\n projectableNodes[target].push(node);\n }\n }\n\n return projectableNodes;\n}\n\n/**\n * Wraps each non-empty slot in comment markers, so the client can find the\n * server-rendered nodes again when hydrating. Angular reuses the DOM during\n * hydration, so it must be handed those nodes rather than freshly parsed ones.\n * @param projectableNodes The distributed nodes, see `buildProjectableNodes`\n * @param appId The island's APP_ID, to keep markers apart from nested islands\n * @param document The document the component is rendered into\n */\nexport function markProjectableNodes(\n projectableNodes: Node[][],\n appId: string,\n document: Document,\n): Node[][] {\n return projectableNodes.map((nodes, index) =>\n nodes.length\n ? [\n document.createComment(`${MARKER_PREFIX}${appId}:${index}`),\n ...nodes,\n document.createComment(`/${MARKER_PREFIX}${appId}:${index}`),\n ]\n : nodes,\n );\n}\n\n/**\n * Collects the server-rendered projected nodes from the host element using the\n * markers added by `markProjectableNodes`.\n * @returns The nodes per slot, or `undefined` when the host carries no markers\n */\nexport function collectProjectedNodes(\n hostElement: Element,\n mirror: ComponentMirror<unknown>,\n appId: string,\n): Node[][] | undefined {\n const selectors = mirror.ngContentSelectors;\n\n if (!selectors.length) {\n return undefined;\n }\n\n const projectableNodes: Node[][] = selectors.map(() => []);\n const walker = hostElement.ownerDocument.createTreeWalker(\n hostElement,\n SHOW_COMMENT,\n );\n let found = false;\n\n for (let start = walker.nextNode(); start; start = walker.nextNode()) {\n const match = start.textContent?.startsWith(`${MARKER_PREFIX}${appId}:`)\n ? Number(start.textContent.slice(`${MARKER_PREFIX}${appId}:`.length))\n : NaN;\n\n if (Number.isNaN(match) || match >= selectors.length) {\n continue;\n }\n\n const end = `/${MARKER_PREFIX}${appId}:${match}`;\n const nodes: Node[] = [start];\n let node = start.nextSibling;\n\n while (\n node &&\n !(node.nodeType === COMMENT_NODE && node.textContent === end)\n ) {\n nodes.push(node);\n node = node.nextSibling;\n }\n\n if (node) {\n nodes.push(node);\n }\n\n projectableNodes[match] = nodes;\n found = true;\n }\n\n return found ? projectableNodes : undefined;\n}\n\nfunction parseSlots(slots: unknown, document: Document): Node[] {\n const html =\n slots && typeof slots === 'object'\n ? Object.values(slots).join('')\n : String(slots ?? '');\n\n if (!html) {\n return [];\n }\n\n const template = document.createElement('template');\n template.innerHTML = html;\n\n return Array.from(template.content.childNodes);\n}\n\nfunction matches(node: Node, selector: string): boolean {\n if (node.nodeType !== ELEMENT_NODE) {\n return false;\n }\n\n const element = node as Element;\n const projectAs = element.getAttribute('ngProjectAs');\n\n // As in Angular, `ngProjectAs` replaces the element's own selector and is\n // compared structurally against the `select` list.\n if (projectAs !== null) {\n const [alias] = parseSelectorList(projectAs);\n return parseSelectorList(selector).some((candidate) =>\n isSameSelector(alias, candidate),\n );\n }\n\n try {\n return element.matches(selector);\n } catch {\n // `ngContentSelectors` may contain selectors that `matches()` rejects.\n return false;\n }\n}\n"],"mappings":";;AAGA,IAAM,eAAe;AACrB,IAAM,YAAY;AAClB,IAAM,eAAe;AACrB,IAAM,eAAe;AAErB,IAAM,gBAAgB;;;;;;;;;;;;AAatB,SAAgB,sBACd,QACA,OACA,UACsB;CACtB,MAAM,YAAY,OAAO;AAEzB,KAAI,CAAC,UAAU,OACb;CAGF,MAAM,mBAA6B,UAAU,UAAU,EAAE,CAAC;CAE1D,MAAM,gBAAgB,UAAU,YAAY,IAAI;AAEhD,MAAK,MAAM,QAAQ,WAAW,OAAO,SAAS,EAAE;AAG9C,MAAI,KAAK,aAAa,aAAa,CAAC,KAAK,aAAa,MAAM,CAC1D;EAGF,MAAM,UAAU,UAAU,WACvB,aAAa,aAAa,OAAO,QAAQ,MAAM,SAAS,CAC1D;EACD,MAAM,SAAS,UAAU,KAAK,UAAU;AAExC,MAAI,SAAS,GACX,kBAAiB,QAAQ,KAAK,KAAK;;AAIvC,QAAO;;;;;;;;;;AAWT,SAAgB,qBACd,kBACA,OACA,UACU;AACV,QAAO,iBAAiB,KAAK,OAAO,UAClC,MAAM,SACF;EACE,SAAS,cAAc,GAAG,gBAAgB,MAAM,GAAG,QAAQ;EAC3D,GAAG;EACH,SAAS,cAAc,IAAI,gBAAgB,MAAM,GAAG,QAAQ;EAC7D,GACD,MACL;;;;;;;AAQH,SAAgB,sBACd,aACA,QACA,OACsB;CACtB,MAAM,YAAY,OAAO;AAEzB,KAAI,CAAC,UAAU,OACb;CAGF,MAAM,mBAA6B,UAAU,UAAU,EAAE,CAAC;CAC1D,MAAM,SAAS,YAAY,cAAc,iBACvC,aACA,aACD;CACD,IAAI,QAAQ;AAEZ,MAAK,IAAI,QAAQ,OAAO,UAAU,EAAE,OAAO,QAAQ,OAAO,UAAU,EAAE;EACpE,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG,gBAAgB,MAAM,GAAG,GACpE,OAAO,MAAM,YAAY,MAAM,GAAG,gBAAgB,MAAM,GAAG,OAAO,CAAC,GACnE;AAEJ,MAAI,OAAO,MAAM,MAAM,IAAI,SAAS,UAAU,OAC5C;EAGF,MAAM,MAAM,IAAI,gBAAgB,MAAM,GAAG;EACzC,MAAM,QAAgB,CAAC,MAAM;EAC7B,IAAI,OAAO,MAAM;AAEjB,SACE,QACA,EAAE,KAAK,aAAa,gBAAgB,KAAK,gBAAgB,MACzD;AACA,SAAM,KAAK,KAAK;AAChB,UAAO,KAAK;;AAGd,MAAI,KACF,OAAM,KAAK,KAAK;AAGlB,mBAAiB,SAAS;AAC1B,UAAQ;;AAGV,QAAO,QAAQ,mBAAmB,KAAA;;AAGpC,SAAS,WAAW,OAAgB,UAA4B;CAC9D,MAAM,OACJ,SAAS,OAAO,UAAU,WACtB,OAAO,OAAO,MAAM,CAAC,KAAK,GAAG,GAC7B,OAAO,SAAS,GAAG;AAEzB,KAAI,CAAC,KACH,QAAO,EAAE;CAGX,MAAM,WAAW,SAAS,cAAc,WAAW;AACnD,UAAS,YAAY;AAErB,QAAO,MAAM,KAAK,SAAS,QAAQ,WAAW;;AAGhD,SAAS,QAAQ,MAAY,UAA2B;AACtD,KAAI,KAAK,aAAa,aACpB,QAAO;CAGT,MAAM,UAAU;CAChB,MAAM,YAAY,QAAQ,aAAa,cAAc;AAIrD,KAAI,cAAc,MAAM;EACtB,MAAM,CAAC,SAAS,kBAAkB,UAAU;AAC5C,SAAO,kBAAkB,SAAS,CAAC,MAAM,cACvC,eAAe,OAAO,UAAU,CACjC;;AAGH,KAAI;AACF,SAAO,QAAQ,QAAQ,SAAS;SAC1B;AAEN,SAAO"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CSS selector in the shape Angular's compiler uses for `ng-content` and
|
|
3
|
+
* `ngProjectAs` matching. Ported from `CssSelector` in `@angular/compiler`.
|
|
4
|
+
*/
|
|
5
|
+
export interface ParsedSelector {
|
|
6
|
+
element: string;
|
|
7
|
+
attrs: [string, string][];
|
|
8
|
+
classNames: string[];
|
|
9
|
+
notSelectors: ParsedSelector[];
|
|
10
|
+
}
|
|
11
|
+
export declare function parseSelectorList(selector: string): ParsedSelector[];
|
|
12
|
+
/**
|
|
13
|
+
* Structural equality, matching how Angular compares an `ngProjectAs` selector
|
|
14
|
+
* against the selectors of an `ng-content`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isSameSelector(a: ParsedSelector, b: ParsedSelector): boolean;
|
package/src/selector.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region packages/astro-angular/src/selector.ts
|
|
2
|
+
var SELECTOR_REGEXP = /* @__PURE__ */ new RegExp("(\\:not\\()|(([\\.\\#]?)[-\\w]+)|(?:\\[([-.\\w*\\\\$]+)(?:=([\"']?)([^\\]\"']*)\\5)?\\])|(\\))|(\\s*,\\s*)", "g");
|
|
3
|
+
var NOT = 1;
|
|
4
|
+
var TAG = 2;
|
|
5
|
+
var PREFIX = 3;
|
|
6
|
+
var ATTRIBUTE = 4;
|
|
7
|
+
var ATTRIBUTE_VALUE = 6;
|
|
8
|
+
var NOT_END = 7;
|
|
9
|
+
var SEPARATOR = 8;
|
|
10
|
+
function createSelector() {
|
|
11
|
+
return {
|
|
12
|
+
element: "",
|
|
13
|
+
attrs: [],
|
|
14
|
+
classNames: [],
|
|
15
|
+
notSelectors: []
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function parseSelectorList(selector) {
|
|
19
|
+
const results = [];
|
|
20
|
+
let cssSelector = createSelector();
|
|
21
|
+
let current = cssSelector;
|
|
22
|
+
let match;
|
|
23
|
+
const regexp = new RegExp(SELECTOR_REGEXP.source, "g");
|
|
24
|
+
while (match = regexp.exec(selector)) {
|
|
25
|
+
if (match[NOT]) {
|
|
26
|
+
current = createSelector();
|
|
27
|
+
cssSelector.notSelectors.push(current);
|
|
28
|
+
}
|
|
29
|
+
const tag = match[TAG];
|
|
30
|
+
if (tag) {
|
|
31
|
+
const prefix = match[PREFIX];
|
|
32
|
+
if (prefix === "#") current.attrs.push(["id", tag.slice(1)]);
|
|
33
|
+
else if (prefix === ".") current.classNames.push(tag.slice(1).toLowerCase());
|
|
34
|
+
else current.element = tag;
|
|
35
|
+
}
|
|
36
|
+
const attribute = match[ATTRIBUTE];
|
|
37
|
+
if (attribute) current.attrs.push([attribute, match[ATTRIBUTE_VALUE] ?? ""]);
|
|
38
|
+
if (match[NOT_END]) current = cssSelector;
|
|
39
|
+
if (match[SEPARATOR]) {
|
|
40
|
+
results.push(cssSelector);
|
|
41
|
+
cssSelector = current = createSelector();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
results.push(cssSelector);
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Structural equality, matching how Angular compares an `ngProjectAs` selector
|
|
49
|
+
* against the selectors of an `ng-content`.
|
|
50
|
+
*/
|
|
51
|
+
function isSameSelector(a, b) {
|
|
52
|
+
return normalizeElement(a.element) === normalizeElement(b.element) && a.attrs.length === b.attrs.length && a.attrs.every(([name, value], i) => {
|
|
53
|
+
const [otherName, otherValue] = b.attrs[i];
|
|
54
|
+
return name === otherName && value === otherValue;
|
|
55
|
+
}) && a.classNames.length === b.classNames.length && a.classNames.every((name, i) => name === b.classNames[i]) && a.notSelectors.length === b.notSelectors.length && a.notSelectors.every((not, i) => isSameSelector(not, b.notSelectors[i]));
|
|
56
|
+
}
|
|
57
|
+
function normalizeElement(element) {
|
|
58
|
+
return element === "*" ? "" : element;
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { isSameSelector, parseSelectorList };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.js","names":[],"sources":["../../src/selector.ts"],"sourcesContent":["/**\n * A CSS selector in the shape Angular's compiler uses for `ng-content` and\n * `ngProjectAs` matching. Ported from `CssSelector` in `@angular/compiler`.\n */\nexport interface ParsedSelector {\n element: string;\n attrs: [string, string][];\n classNames: string[];\n notSelectors: ParsedSelector[];\n}\n\nconst SELECTOR_REGEXP = new RegExp(\n '(\\\\:not\\\\()|' + // 1: \":not(\"\n '(([\\\\.\\\\#]?)[-\\\\w]+)|' + // 2: \"tag\"; 3: \".\"/\"#\"\n '(?:\\\\[([-.\\\\w*\\\\\\\\$]+)(?:=([\"\\']?)([^\\\\]\"\\']*)\\\\5)?\\\\])|' + // 4: attribute; 5: quote; 6: value\n '(\\\\))|' + // 7: \")\"\n '(\\\\s*,\\\\s*)', // 8: \",\"\n 'g',\n);\n\nconst NOT = 1;\nconst TAG = 2;\nconst PREFIX = 3;\nconst ATTRIBUTE = 4;\nconst ATTRIBUTE_VALUE = 6;\nconst NOT_END = 7;\nconst SEPARATOR = 8;\n\nfunction createSelector(): ParsedSelector {\n return { element: '', attrs: [], classNames: [], notSelectors: [] };\n}\n\nexport function parseSelectorList(selector: string): ParsedSelector[] {\n const results: ParsedSelector[] = [];\n let cssSelector = createSelector();\n let current = cssSelector;\n let inNot = false;\n let match: RegExpExecArray | null;\n\n const regexp = new RegExp(SELECTOR_REGEXP.source, 'g');\n\n while ((match = regexp.exec(selector))) {\n if (match[NOT]) {\n inNot = true;\n current = createSelector();\n cssSelector.notSelectors.push(current);\n }\n\n const tag = match[TAG];\n if (tag) {\n const prefix = match[PREFIX];\n if (prefix === '#') {\n current.attrs.push(['id', tag.slice(1)]);\n } else if (prefix === '.') {\n current.classNames.push(tag.slice(1).toLowerCase());\n } else {\n current.element = tag;\n }\n }\n\n const attribute = match[ATTRIBUTE];\n if (attribute) {\n current.attrs.push([attribute, match[ATTRIBUTE_VALUE] ?? '']);\n }\n\n if (match[NOT_END]) {\n inNot = false;\n current = cssSelector;\n }\n\n if (match[SEPARATOR]) {\n results.push(cssSelector);\n cssSelector = current = createSelector();\n }\n }\n\n results.push(cssSelector);\n\n return results;\n}\n\n/**\n * Structural equality, matching how Angular compares an `ngProjectAs` selector\n * against the selectors of an `ng-content`.\n */\nexport function isSameSelector(a: ParsedSelector, b: ParsedSelector): boolean {\n return (\n normalizeElement(a.element) === normalizeElement(b.element) &&\n a.attrs.length === b.attrs.length &&\n a.attrs.every(([name, value], i) => {\n const [otherName, otherValue] = b.attrs[i];\n return name === otherName && value === otherValue;\n }) &&\n a.classNames.length === b.classNames.length &&\n a.classNames.every((name, i) => name === b.classNames[i]) &&\n a.notSelectors.length === b.notSelectors.length &&\n a.notSelectors.every((not, i) => isSameSelector(not, b.notSelectors[i]))\n );\n}\n\nfunction normalizeElement(element: string): string {\n return element === '*' ? '' : element;\n}\n"],"mappings":";AAWA,IAAM,kCAAkB,IAAI,OAC1B,8GAKA,IACD;AAED,IAAM,MAAM;AACZ,IAAM,MAAM;AACZ,IAAM,SAAS;AACf,IAAM,YAAY;AAClB,IAAM,kBAAkB;AACxB,IAAM,UAAU;AAChB,IAAM,YAAY;AAElB,SAAS,iBAAiC;AACxC,QAAO;EAAE,SAAS;EAAI,OAAO,EAAE;EAAE,YAAY,EAAE;EAAE,cAAc,EAAE;EAAE;;AAGrE,SAAgB,kBAAkB,UAAoC;CACpE,MAAM,UAA4B,EAAE;CACpC,IAAI,cAAc,gBAAgB;CAClC,IAAI,UAAU;CAEd,IAAI;CAEJ,MAAM,SAAS,IAAI,OAAO,gBAAgB,QAAQ,IAAI;AAEtD,QAAQ,QAAQ,OAAO,KAAK,SAAS,EAAG;AACtC,MAAI,MAAM,MAAM;AAEd,aAAU,gBAAgB;AAC1B,eAAY,aAAa,KAAK,QAAQ;;EAGxC,MAAM,MAAM,MAAM;AAClB,MAAI,KAAK;GACP,MAAM,SAAS,MAAM;AACrB,OAAI,WAAW,IACb,SAAQ,MAAM,KAAK,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;YAC/B,WAAW,IACpB,SAAQ,WAAW,KAAK,IAAI,MAAM,EAAE,CAAC,aAAa,CAAC;OAEnD,SAAQ,UAAU;;EAItB,MAAM,YAAY,MAAM;AACxB,MAAI,UACF,SAAQ,MAAM,KAAK,CAAC,WAAW,MAAM,oBAAoB,GAAG,CAAC;AAG/D,MAAI,MAAM,SAER,WAAU;AAGZ,MAAI,MAAM,YAAY;AACpB,WAAQ,KAAK,YAAY;AACzB,iBAAc,UAAU,gBAAgB;;;AAI5C,SAAQ,KAAK,YAAY;AAEzB,QAAO;;;;;;AAOT,SAAgB,eAAe,GAAmB,GAA4B;AAC5E,QACE,iBAAiB,EAAE,QAAQ,KAAK,iBAAiB,EAAE,QAAQ,IAC3D,EAAE,MAAM,WAAW,EAAE,MAAM,UAC3B,EAAE,MAAM,OAAO,CAAC,MAAM,QAAQ,MAAM;EAClC,MAAM,CAAC,WAAW,cAAc,EAAE,MAAM;AACxC,SAAO,SAAS,aAAa,UAAU;GACvC,IACF,EAAE,WAAW,WAAW,EAAE,WAAW,UACrC,EAAE,WAAW,OAAO,MAAM,MAAM,SAAS,EAAE,WAAW,GAAG,IACzD,EAAE,aAAa,WAAW,EAAE,aAAa,UACzC,EAAE,aAAa,OAAO,KAAK,MAAM,eAAe,KAAK,EAAE,aAAa,GAAG,CAAC;;AAI5E,SAAS,iBAAiB,SAAyB;AACjD,QAAO,YAAY,MAAM,KAAK"}
|
package/src/server-ngh.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { buildProjectableNodes, markProjectableNodes } from "./projection.js";
|
|
1
2
|
import { ID_PROP_NAME } from "./id.js";
|
|
2
|
-
import { getComponentElementTag } from "./create-component.js";
|
|
3
|
+
import { createInputBindings, getComponentElementTag, registerRootComponent } from "./create-component.js";
|
|
3
4
|
import { getContext, incrementId } from "./context.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { bootstrapApplication, provideClientHydration } from "@angular/platform-browser";
|
|
5
|
+
import { APP_ID, DOCUMENT, createComponent, provideZonelessChangeDetection, reflectComponentType } from "@angular/core";
|
|
6
|
+
import { createApplication, provideClientHydration } from "@angular/platform-browser";
|
|
7
7
|
import { platformServer, provideServerRendering, renderApplication, ɵSERVER_CONTEXT } from "@angular/platform-server";
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
@@ -18,7 +18,7 @@ function getHydrationScript() {
|
|
|
18
18
|
async function check(Component) {
|
|
19
19
|
return !!reflectComponentType(Component);
|
|
20
20
|
}
|
|
21
|
-
async function renderToStaticMarkup(Component, props,
|
|
21
|
+
async function renderToStaticMarkup(Component, props, children, metadata) {
|
|
22
22
|
const mirror = reflectComponentType(Component);
|
|
23
23
|
if (!mirror) throw new Error((metadata?.displayName || "<unknown component>") + " is not an Angular component");
|
|
24
24
|
const elementTag = getComponentElementTag(mirror);
|
|
@@ -27,21 +27,32 @@ async function renderToStaticMarkup(Component, props, _children, metadata) {
|
|
|
27
27
|
const platformRef = platformServer();
|
|
28
28
|
const document = platformRef.injector.get(DOCUMENT);
|
|
29
29
|
document.body.innerHTML = `${getHydrationScript()}<${elementTag} ${ID_PROP_NAME}="${ngAppId}"></${elementTag}>`;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
const hostElement = document.querySelector(elementTag);
|
|
31
|
+
let projectableNodes = buildProjectableNodes(mirror, children, document);
|
|
32
|
+
if (metadata?.hydrate && projectableNodes) projectableNodes = markProjectableNodes(projectableNodes, String(ngAppId), document);
|
|
33
|
+
const bootstrap = async (context) => {
|
|
34
|
+
const appRef = await createApplication({ providers: [
|
|
35
|
+
provideServerRendering(),
|
|
36
|
+
{
|
|
37
|
+
provide: ɵSERVER_CONTEXT,
|
|
38
|
+
useValue: "analog"
|
|
39
|
+
},
|
|
40
|
+
provideZonelessChangeDetection(),
|
|
41
|
+
metadata?.hydrate ? provideClientHydration(...Component.hydrationFeatures?.() || []) : [],
|
|
42
|
+
{
|
|
43
|
+
provide: APP_ID,
|
|
44
|
+
useValue: ngAppId
|
|
45
|
+
},
|
|
46
|
+
...Component.renderProviders || []
|
|
47
|
+
] }, context);
|
|
48
|
+
registerRootComponent(appRef, createComponent(Component, {
|
|
49
|
+
environmentInjector: appRef.injector,
|
|
50
|
+
hostElement,
|
|
51
|
+
projectableNodes,
|
|
52
|
+
bindings: createInputBindings(mirror, props)
|
|
53
|
+
}));
|
|
54
|
+
return appRef;
|
|
55
|
+
};
|
|
45
56
|
const html = await renderApplication(bootstrap, { document });
|
|
46
57
|
document.documentElement.innerHTML = html;
|
|
47
58
|
let styleTags = "";
|
package/src/server-ngh.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-ngh.js","names":[],"sources":["../../src/server-ngh.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\n\nimport type { EnvironmentProviders, Provider, Type } from '@angular/core';\nimport {\n reflectComponentType,\n provideZonelessChangeDetection,\n DOCUMENT,\n APP_ID,\n} from '@angular/core';\nimport {\n provideServerRendering,\n renderApplication,\n ɵSERVER_CONTEXT,\n platformServer,\n} from '@angular/platform-server';\nimport {\n
|
|
1
|
+
{"version":3,"file":"server-ngh.js","names":[],"sources":["../../src/server-ngh.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\n\nimport type { EnvironmentProviders, Provider, Type } from '@angular/core';\nimport {\n createComponent,\n reflectComponentType,\n provideZonelessChangeDetection,\n DOCUMENT,\n APP_ID,\n} from '@angular/core';\nimport {\n provideServerRendering,\n renderApplication,\n ɵSERVER_CONTEXT,\n platformServer,\n} from '@angular/platform-server';\nimport {\n createApplication,\n type HydrationFeature,\n type HydrationFeatureKind,\n provideClientHydration,\n type BootstrapContext,\n} from '@angular/platform-browser';\nimport type { AstroComponentMetadata, SSRLoadedRendererValue } from 'astro';\nimport { getContext, incrementId, type RendererContext } from './context.ts';\nimport { ID_PROP_NAME } from './id.ts';\nimport {\n createInputBindings,\n getComponentElementTag,\n registerRootComponent,\n} from './create-component.ts';\nimport { buildProjectableNodes, markProjectableNodes } from './projection.ts';\n\nconst require = createRequire(import.meta.url);\nlet jsActionContractScript: string | undefined = undefined;\n\nconst HYDRATION_SCRIPT_ID = 'ng-event-dispatch-contract';\n\nfunction getHydrationScript(): string {\n jsActionContractScript ??=\n `<script type=\"text/javascript\" id=\"${HYDRATION_SCRIPT_ID}\">` +\n readFileSync(\n require.resolve('@angular/core/event-dispatch-contract.min.js'),\n 'utf-8',\n ) +\n '</script>';\n return jsActionContractScript;\n}\n\nasync function check(Component: Type<unknown>) {\n return !!reflectComponentType(Component);\n}\n\nasync function renderToStaticMarkup(\n this: RendererContext,\n Component: Type<unknown> & {\n renderProviders?: (Provider | EnvironmentProviders)[];\n hydrationFeatures?: () => HydrationFeature<HydrationFeatureKind>[];\n },\n props: Record<string, unknown>,\n children: unknown,\n metadata?: AstroComponentMetadata,\n) {\n const mirror = reflectComponentType(Component);\n\n if (!mirror) {\n // This should be unreachable: the `check` function verifies that Component is an Angular component.\n throw new Error(\n (metadata?.displayName || '<unknown component>') +\n ' is not an Angular component',\n );\n }\n\n const elementTag = getComponentElementTag(mirror);\n const ngAppId = props?.[ID_PROP_NAME] || incrementId(getContext(this.result));\n\n // When the platform ref is destroyed, it will reset ngServerMode back to `undefined` if it was not defined when it is\n // created. See https://github.com/angular/angular/blob/2ce0e98f79a02ddc550d00580e8e232cfed3bfb2/packages/platform-server/src/server.ts#L138\n globalThis.ngServerMode = true;\n\n const platformRef = platformServer();\n const document = platformRef.injector.get(DOCUMENT);\n\n // Incremental hydration requires the event dispatch script to be present.\n document.body.innerHTML = `${getHydrationScript()}<${elementTag} ${ID_PROP_NAME}=\"${ngAppId}\"></${elementTag}>`;\n\n const hostElement = document.querySelector(elementTag) as Element;\n let projectableNodes = buildProjectableNodes(mirror, children, document);\n\n if (metadata?.hydrate && projectableNodes) {\n projectableNodes = markProjectableNodes(\n projectableNodes,\n String(ngAppId),\n document,\n );\n }\n\n const bootstrap = async (context?: BootstrapContext) => {\n const appRef = await createApplication(\n {\n providers: [\n provideServerRendering(),\n { provide: ɵSERVER_CONTEXT, useValue: 'analog' },\n provideZonelessChangeDetection(),\n metadata?.hydrate\n ? provideClientHydration(...(Component.hydrationFeatures?.() || []))\n : [],\n {\n provide: APP_ID,\n useValue: ngAppId,\n },\n ...(Component.renderProviders || []),\n ],\n },\n context,\n );\n\n const componentRef = createComponent(Component, {\n environmentInjector: appRef.injector,\n hostElement,\n projectableNodes,\n bindings: createInputBindings(mirror, props),\n });\n\n registerRootComponent(appRef, componentRef);\n\n return appRef;\n };\n\n const html = await renderApplication(bootstrap, {\n document,\n });\n\n document.documentElement.innerHTML = html;\n let styleTags = '';\n\n document.head.childNodes.forEach((node) => {\n if (node.nodeName === 'STYLE') {\n styleTags += (node as HTMLElement).outerHTML;\n }\n });\n\n // Remove the hydration script, so only one is present on the page.\n document.getElementById(HYDRATION_SCRIPT_ID)?.remove();\n\n const correctedHtml = styleTags + document.body.innerHTML;\n\n platformRef.destroy();\n\n return { html: correctedHtml };\n}\n\nexport default {\n check,\n renderToStaticMarkup,\n renderHydrationScript: () => getHydrationScript(),\n} satisfies SSRLoadedRendererValue;\n"],"mappings":";;;;;;;;;;AAkCA,IAAM,UAAU,cAAc,OAAO,KAAK,IAAI;AAC9C,IAAI,yBAA6C,KAAA;AAEjD,IAAM,sBAAsB;AAE5B,SAAS,qBAA6B;AACpC,4BACE,sCAAsC,oBAAoB,MAC1D,aACE,QAAQ,QAAQ,+CAA+C,EAC/D,QACD,GACD;AACF,QAAO;;AAGT,eAAe,MAAM,WAA0B;AAC7C,QAAO,CAAC,CAAC,qBAAqB,UAAU;;AAG1C,eAAe,qBAEb,WAIA,OACA,UACA,UACA;CACA,MAAM,SAAS,qBAAqB,UAAU;AAE9C,KAAI,CAAC,OAEH,OAAM,IAAI,OACP,UAAU,eAAe,yBACxB,+BACH;CAGH,MAAM,aAAa,uBAAuB,OAAO;CACjD,MAAM,UAAU,QAAA,qBAAyB,YAAY,WAAW,KAAK,OAAO,CAAC;AAI7E,YAAW,eAAe;CAE1B,MAAM,cAAc,gBAAgB;CACpC,MAAM,WAAW,YAAY,SAAS,IAAI,SAAS;AAGnD,UAAS,KAAK,YAAY,GAAG,oBAAoB,CAAC,GAAG,WAAW,GAAG,aAAa,IAAI,QAAQ,MAAM,WAAW;CAE7G,MAAM,cAAc,SAAS,cAAc,WAAW;CACtD,IAAI,mBAAmB,sBAAsB,QAAQ,UAAU,SAAS;AAExE,KAAI,UAAU,WAAW,iBACvB,oBAAmB,qBACjB,kBACA,OAAO,QAAQ,EACf,SACD;CAGH,MAAM,YAAY,OAAO,YAA+B;EACtD,MAAM,SAAS,MAAM,kBACnB,EACE,WAAW;GACT,wBAAwB;GACxB;IAAE,SAAS;IAAiB,UAAU;IAAU;GAChD,gCAAgC;GAChC,UAAU,UACN,uBAAuB,GAAI,UAAU,qBAAqB,IAAI,EAAE,CAAE,GAClE,EAAE;GACN;IACE,SAAS;IACT,UAAU;IACX;GACD,GAAI,UAAU,mBAAmB,EAAE;GACpC,EACF,EACD,QACD;AASD,wBAAsB,QAPD,gBAAgB,WAAW;GAC9C,qBAAqB,OAAO;GAC5B;GACA;GACA,UAAU,oBAAoB,QAAQ,MAAM;GAC7C,CAAC,CAEyC;AAE3C,SAAO;;CAGT,MAAM,OAAO,MAAM,kBAAkB,WAAW,EAC9C,UACD,CAAC;AAEF,UAAS,gBAAgB,YAAY;CACrC,IAAI,YAAY;AAEhB,UAAS,KAAK,WAAW,SAAS,SAAS;AACzC,MAAI,KAAK,aAAa,QACpB,cAAc,KAAqB;GAErC;AAGF,UAAS,eAAe,oBAAoB,EAAE,QAAQ;CAEtD,MAAM,gBAAgB,YAAY,SAAS,KAAK;AAEhD,aAAY,SAAS;AAErB,QAAO,EAAE,MAAM,eAAe;;AAGhC,IAAA,qBAAe;CACb;CACA;CACA,6BAA6B,oBAAoB;CAClD"}
|
package/src/server.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { EnvironmentProviders, Provider, ɵComponentType as ComponentType }
|
|
|
2
2
|
declare function check(Component: ComponentType<unknown>, _props: Record<string, unknown>, _children: unknown): boolean;
|
|
3
3
|
declare function renderToStaticMarkup(Component: ComponentType<unknown> & {
|
|
4
4
|
renderProviders: (Provider | EnvironmentProviders)[];
|
|
5
|
-
}, props: Record<string, unknown>,
|
|
5
|
+
}, props: Record<string, unknown>, children: unknown): Promise<{
|
|
6
6
|
html: string;
|
|
7
7
|
}>;
|
|
8
8
|
declare const renderer: {
|
package/src/server.js
CHANGED
|
@@ -1,53 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { buildProjectableNodes } from "./projection.js";
|
|
2
|
+
import { createInputBindings, getComponentElementTag, registerRootComponent } from "./create-component.js";
|
|
3
|
+
import { DOCUMENT, createComponent, provideZonelessChangeDetection, reflectComponentType } from "@angular/core";
|
|
4
|
+
import { createApplication } from "@angular/platform-browser";
|
|
5
|
+
import { platformServer, provideServerRendering, renderApplication, ɵSERVER_CONTEXT } from "@angular/platform-server";
|
|
4
6
|
//#region packages/astro-angular/src/server.ts
|
|
5
|
-
var ANALOG_ASTRO_STATIC_PROPS = new InjectionToken("@analogjs/astro-angular: Static Props w/ Mirror Provider", { factory() {
|
|
6
|
-
return {
|
|
7
|
-
props: {},
|
|
8
|
-
mirror: {}
|
|
9
|
-
};
|
|
10
|
-
} });
|
|
11
7
|
function check(Component, _props, _children) {
|
|
12
8
|
return !!reflectComponentType(Component);
|
|
13
9
|
}
|
|
14
|
-
|
|
15
|
-
provide: BEFORE_APP_SERIALIZED,
|
|
16
|
-
useFactory: (appRef, { props, mirror }) => {
|
|
17
|
-
return () => {
|
|
18
|
-
const compRef = appRef.components[0];
|
|
19
|
-
if (compRef && props && mirror) {
|
|
20
|
-
for (const [key, value] of Object.entries(props)) if (mirror.inputs.some(({ templateName, propName }) => templateName === key || propName === key)) compRef.setInput(key, value);
|
|
21
|
-
compRef.changeDetectorRef.detectChanges();
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
},
|
|
25
|
-
deps: [ApplicationRef, ANALOG_ASTRO_STATIC_PROPS],
|
|
26
|
-
multi: true
|
|
27
|
-
};
|
|
28
|
-
async function renderToStaticMarkup(Component, props, _children) {
|
|
10
|
+
async function renderToStaticMarkup(Component, props, children) {
|
|
29
11
|
const mirror = reflectComponentType(Component);
|
|
30
|
-
|
|
12
|
+
if (!mirror) throw new Error(Component.name + " is not an Angular component");
|
|
13
|
+
const elementTag = getComponentElementTag(mirror);
|
|
31
14
|
const platformRef = platformServer();
|
|
32
15
|
const document = platformRef.injector.get(DOCUMENT);
|
|
33
|
-
document.body.innerHTML = `<${
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
16
|
+
document.body.innerHTML = `<${elementTag}></${elementTag}>`;
|
|
17
|
+
const hostElement = document.querySelector(elementTag);
|
|
18
|
+
const projectableNodes = buildProjectableNodes(mirror, children, document);
|
|
19
|
+
const bootstrap = async (context) => {
|
|
20
|
+
const appRef = await createApplication({ providers: [
|
|
21
|
+
provideServerRendering(),
|
|
22
|
+
{
|
|
23
|
+
provide: ɵSERVER_CONTEXT,
|
|
24
|
+
useValue: "analog"
|
|
25
|
+
},
|
|
26
|
+
provideZonelessChangeDetection(),
|
|
27
|
+
...Component.renderProviders || []
|
|
28
|
+
] }, context);
|
|
29
|
+
registerRootComponent(appRef, createComponent(Component, {
|
|
30
|
+
environmentInjector: appRef.injector,
|
|
31
|
+
hostElement,
|
|
32
|
+
projectableNodes,
|
|
33
|
+
bindings: createInputBindings(mirror, props)
|
|
34
|
+
}));
|
|
35
|
+
return appRef;
|
|
36
|
+
};
|
|
51
37
|
const html = await renderApplication(bootstrap, { document });
|
|
52
38
|
document.documentElement.innerHTML = html;
|
|
53
39
|
let styleTags = "";
|
package/src/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../../src/server.ts"],"sourcesContent":["import type {\n
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../../src/server.ts"],"sourcesContent":["import type {\n EnvironmentProviders,\n Provider,\n ɵComponentType as ComponentType,\n} from '@angular/core';\nimport {\n createComponent,\n reflectComponentType,\n provideZonelessChangeDetection,\n DOCUMENT,\n} from '@angular/core';\nimport {\n provideServerRendering,\n renderApplication,\n ɵSERVER_CONTEXT,\n platformServer,\n} from '@angular/platform-server';\nimport {\n createApplication,\n type BootstrapContext,\n} from '@angular/platform-browser';\nimport {\n createInputBindings,\n getComponentElementTag,\n registerRootComponent,\n} from './create-component.ts';\nimport { buildProjectableNodes } from './projection.ts';\n\nfunction check(\n Component: ComponentType<unknown>,\n _props: Record<string, unknown>,\n _children: unknown,\n): boolean {\n return !!reflectComponentType(Component);\n}\n\nasync function renderToStaticMarkup(\n Component: ComponentType<unknown> & {\n renderProviders: (Provider | EnvironmentProviders)[];\n },\n props: Record<string, unknown>,\n children: unknown,\n): Promise<{ html: string }> {\n const mirror = reflectComponentType(Component);\n\n if (!mirror) {\n // This should be unreachable: the `check` function verifies that Component is an Angular component.\n throw new Error(Component.name + ' is not an Angular component');\n }\n\n const elementTag = getComponentElementTag(mirror);\n\n const platformRef = platformServer();\n const document = platformRef.injector.get(DOCUMENT);\n document.body.innerHTML = `<${elementTag}></${elementTag}>`;\n\n const hostElement = document.querySelector(elementTag) as Element;\n const projectableNodes = buildProjectableNodes(mirror, children, document);\n\n const bootstrap = async (context?: BootstrapContext) => {\n const appRef = await createApplication(\n {\n providers: [\n provideServerRendering(),\n { provide: ɵSERVER_CONTEXT, useValue: 'analog' },\n provideZonelessChangeDetection(),\n ...(Component.renderProviders || []),\n ],\n },\n context,\n );\n\n const componentRef = createComponent(Component, {\n environmentInjector: appRef.injector,\n hostElement,\n projectableNodes,\n bindings: createInputBindings(mirror, props),\n });\n\n registerRootComponent(appRef, componentRef);\n\n return appRef;\n };\n\n const html = await renderApplication(bootstrap, {\n document,\n });\n\n document.documentElement.innerHTML = html;\n let styleTags = '';\n\n document.head.childNodes.forEach((node) => {\n if (node.nodeName === 'STYLE') {\n styleTags += (node as HTMLElement).outerHTML;\n }\n });\n\n const correctedHtml = styleTags + document.body.innerHTML;\n\n platformRef.destroy();\n\n return { html: correctedHtml };\n}\n\nconst renderer: {\n check: typeof check;\n renderToStaticMarkup: typeof renderToStaticMarkup;\n} = {\n check: check,\n renderToStaticMarkup: renderToStaticMarkup,\n};\nexport default renderer;\n"],"mappings":";;;;;;AA4BA,SAAS,MACP,WACA,QACA,WACS;AACT,QAAO,CAAC,CAAC,qBAAqB,UAAU;;AAG1C,eAAe,qBACb,WAGA,OACA,UAC2B;CAC3B,MAAM,SAAS,qBAAqB,UAAU;AAE9C,KAAI,CAAC,OAEH,OAAM,IAAI,MAAM,UAAU,OAAO,+BAA+B;CAGlE,MAAM,aAAa,uBAAuB,OAAO;CAEjD,MAAM,cAAc,gBAAgB;CACpC,MAAM,WAAW,YAAY,SAAS,IAAI,SAAS;AACnD,UAAS,KAAK,YAAY,IAAI,WAAW,KAAK,WAAW;CAEzD,MAAM,cAAc,SAAS,cAAc,WAAW;CACtD,MAAM,mBAAmB,sBAAsB,QAAQ,UAAU,SAAS;CAE1E,MAAM,YAAY,OAAO,YAA+B;EACtD,MAAM,SAAS,MAAM,kBACnB,EACE,WAAW;GACT,wBAAwB;GACxB;IAAE,SAAS;IAAiB,UAAU;IAAU;GAChD,gCAAgC;GAChC,GAAI,UAAU,mBAAmB,EAAE;GACpC,EACF,EACD,QACD;AASD,wBAAsB,QAPD,gBAAgB,WAAW;GAC9C,qBAAqB,OAAO;GAC5B;GACA;GACA,UAAU,oBAAoB,QAAQ,MAAM;GAC7C,CAAC,CAEyC;AAE3C,SAAO;;CAGT,MAAM,OAAO,MAAM,kBAAkB,WAAW,EAC9C,UACD,CAAC;AAEF,UAAS,gBAAgB,YAAY;CACrC,IAAI,YAAY;AAEhB,UAAS,KAAK,WAAW,SAAS,SAAS;AACzC,MAAI,KAAK,aAAa,QACpB,cAAc,KAAqB;GAErC;CAEF,MAAM,gBAAgB,YAAY,SAAS,KAAK;AAEhD,aAAY,SAAS;AAErB,QAAO,EAAE,MAAM,eAAe;;AAGhC,IAAM,WAGF;CACK;CACe;CACvB"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type ComponentMirror, type Provider } from "@angular/core";
|
|
2
|
-
/**
|
|
3
|
-
* Provide bootstrap listeners to ensure the rendered state matches the settled component state after applying component inputs.
|
|
4
|
-
* @param mirror The component mirror, to detect component inputs
|
|
5
|
-
* @param props Properties applied to the component in the Astro file
|
|
6
|
-
* @returns A providers array for the server renderer
|
|
7
|
-
*/
|
|
8
|
-
export declare function provideBootstrapListener(mirror: ComponentMirror<unknown>, props: Record<string, unknown>): Provider[];
|
package/src/server-providers.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { APP_BOOTSTRAP_LISTENER } from "@angular/core";
|
|
2
|
-
//#region packages/astro-angular/src/server-providers.ts
|
|
3
|
-
/**
|
|
4
|
-
* Provide bootstrap listeners to ensure the rendered state matches the settled component state after applying component inputs.
|
|
5
|
-
* @param mirror The component mirror, to detect component inputs
|
|
6
|
-
* @param props Properties applied to the component in the Astro file
|
|
7
|
-
* @returns A providers array for the server renderer
|
|
8
|
-
*/
|
|
9
|
-
function provideBootstrapListener(mirror, props) {
|
|
10
|
-
return [{
|
|
11
|
-
provide: APP_BOOTSTRAP_LISTENER,
|
|
12
|
-
useValue: (compRef) => {
|
|
13
|
-
if (props) {
|
|
14
|
-
for (const [key, value] of Object.entries(props)) if (mirror.inputs.some(({ templateName }) => templateName === key)) compRef.setInput(key, value);
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
multi: true
|
|
18
|
-
}];
|
|
19
|
-
}
|
|
20
|
-
//#endregion
|
|
21
|
-
export { provideBootstrapListener };
|
|
22
|
-
|
|
23
|
-
//# sourceMappingURL=server-providers.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server-providers.js","names":[],"sources":["../../src/server-providers.ts"],"sourcesContent":["import {\n APP_BOOTSTRAP_LISTENER,\n type ComponentMirror,\n type ComponentRef,\n type Provider,\n} from '@angular/core';\n\n/**\n * Provide bootstrap listeners to ensure the rendered state matches the settled component state after applying component inputs.\n * @param mirror The component mirror, to detect component inputs\n * @param props Properties applied to the component in the Astro file\n * @returns A providers array for the server renderer\n */\nexport function provideBootstrapListener(\n mirror: ComponentMirror<unknown>,\n props: Record<string, unknown>,\n): Provider[] {\n return [\n {\n provide: APP_BOOTSTRAP_LISTENER,\n useValue: (compRef: ComponentRef<unknown>) => {\n if (props) {\n for (const [key, value] of Object.entries(props)) {\n if (\n mirror.inputs.some(({ templateName }) => templateName === key)\n ) {\n compRef.setInput(key, value);\n }\n }\n }\n },\n multi: true,\n },\n ];\n}\n"],"mappings":";;;;;;;;AAaA,SAAgB,yBACd,QACA,OACY;AACZ,QAAO,CACL;EACE,SAAS;EACT,WAAW,YAAmC;AAC5C,OAAI;SACG,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KACE,OAAO,OAAO,MAAM,EAAE,mBAAmB,iBAAiB,IAAI,CAE9D,SAAQ,SAAS,KAAK,MAAM;;;EAKpC,OAAO;EACR,CACF"}
|