@ecopages/core 0.2.0-rc.5 → 0.2.0-rc.7
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/README.md +6 -1
- package/package.json +3 -3
- package/src/declarations.d.ts +10 -1
- package/src/islands/README.md +5 -1
- package/src/plugins/README.md +1 -0
- package/src/plugins/processor.d.ts +4 -0
- package/src/plugins/processor.js +4 -0
- package/src/route-renderer/README.md +2 -2
- package/src/route-renderer/orchestration/document-shell/document-shell-render.service.d.ts +7 -2
- package/src/route-renderer/orchestration/document-shell/document-shell-render.service.js +2 -0
- package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.d.ts +3 -2
- package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.js +1 -1
- package/src/route-renderer/orchestration/integration-renderer.d.ts +4 -3
- package/src/route-renderer/orchestration/integration-renderer.js +3 -3
- package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.d.ts +1 -1
- package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.js +2 -2
- package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +3 -2
- package/src/route-renderer/orchestration/string-markup-renderer.d.ts +1 -1
- package/src/route-renderer/orchestration/string-markup-renderer.js +2 -1
- package/src/router/README.md +5 -1
- package/src/router/server/route-registry.d.ts +13 -0
- package/src/router/server/route-registry.js +46 -32
- package/src/services/README.md +2 -0
- package/src/services/assets/asset-processing-service/browser-runtime-entry-resolution.d.ts +6 -0
- package/src/services/assets/asset-processing-service/browser-runtime-entry-resolution.js +24 -5
- package/src/services/invalidation/development-invalidation.service.js +10 -3
- package/src/services/module-loading/README.md +2 -0
- package/src/services/module-loading/collection-server-module-build.service.d.ts +1 -0
- package/src/services/module-loading/collection-server-module-build.service.js +5 -2
- package/src/types/internal-types.d.ts +5 -1
- package/src/watchers/project-watcher.js +5 -1
package/README.md
CHANGED
|
@@ -53,7 +53,10 @@ flowchart TD
|
|
|
53
53
|
B --> C[DevelopmentInvalidationService]
|
|
54
54
|
C --> D{Change kind}
|
|
55
55
|
D -->|Route or server source| E[Invalidate server modules]
|
|
56
|
-
D -->|
|
|
56
|
+
D -->|Additional watch| E
|
|
57
|
+
E --> N[Notify processors]
|
|
58
|
+
N --> F[Reload browser]
|
|
59
|
+
D -->|Public asset| F
|
|
57
60
|
D -->|Processor-owned asset| G[Notify processor only]
|
|
58
61
|
D -->|HMR-eligible source| H[Core HMR manager]
|
|
59
62
|
H --> I[Strategy selection]
|
|
@@ -74,6 +77,8 @@ The manager/orchestration layer is core-owned, but framework-specific strategies
|
|
|
74
77
|
- `ConfigBuilder` seeds one app-owned build ownership path, adapter, manifest, executor, dev graph, and runtime registry.
|
|
75
78
|
- `BrowserBundleService` is the shared browser build seam used by HMR and asset-oriented browser output paths.
|
|
76
79
|
- `ServerModuleTranspiler` is the shared server-side source loading seam used by runtime bootstrap and HMR metadata loading.
|
|
80
|
+
- `RouteRegistry` gives exact and dynamic Pages priority over catch-alls, then selects the most specific matching catch-all prefix.
|
|
81
|
+
- Browser runtime package entries resolve from the application root with ESM import conditions, so framework-local packages cannot shadow app dependencies.
|
|
77
82
|
- `createApp()` stays the universal runtime entrypoint, while Vite and Nitro hosts own their advanced dev and build workflows.
|
|
78
83
|
- One bundled adapter is the default bundler. Vite-based apps route through the `ViteHostBuildAdapter` boundary marker instead.
|
|
79
84
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.7",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"directory": "packages/core"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@ecopages/file-system": "0.2.0-rc.
|
|
23
|
+
"@ecopages/file-system": "0.2.0-rc.7",
|
|
24
24
|
"@ecopages/logger": "^0.2.3",
|
|
25
25
|
"@ecopages/scripts-injector": "^0.1.5",
|
|
26
26
|
"@oxc-project/runtime": "0.141.0",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@standard-schema/utils": "^0.3.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@ecopages/dev-toolbar": "0.2.0-rc.
|
|
37
|
+
"@ecopages/dev-toolbar": "0.2.0-rc.7"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@ecopages/dev-toolbar": {
|
package/src/declarations.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import './css-imports.d.js';
|
|
2
2
|
|
|
3
3
|
import type { EcoPagesAppConfig } from './types/internal-types';
|
|
4
4
|
import type { EcoNavigationRuntime } from './router/client/navigation-coordinator';
|
|
@@ -6,6 +6,12 @@ import type { EcoNavigationRuntime } from './router/client/navigation-coordinato
|
|
|
6
6
|
type HMRHandler = (url: string) => Promise<void>;
|
|
7
7
|
type CleanupPageRootFunction = () => void;
|
|
8
8
|
type EcoPageRoot = { render: (node: unknown) => void; unmount: () => void };
|
|
9
|
+
type EcoIslandComponent = (props: Record<string, unknown>) => unknown;
|
|
10
|
+
/** Shared React island diagnostics and root lookup state exposed to devtools. */
|
|
11
|
+
type EcoIslandRuntime = {
|
|
12
|
+
islandRoots: Record<string, EcoPageRoot>;
|
|
13
|
+
islandComponents: Record<string, EcoIslandComponent>;
|
|
14
|
+
};
|
|
9
15
|
type EcoPageData = {
|
|
10
16
|
module: string;
|
|
11
17
|
props: Record<string, unknown>;
|
|
@@ -17,7 +23,10 @@ type EcoPagesWindowRuntime = {
|
|
|
17
23
|
cleanupPageRoot?: CleanupPageRootFunction;
|
|
18
24
|
pageRoot?: EcoPageRoot | null;
|
|
19
25
|
};
|
|
26
|
+
/** Roots keyed by SSR instance ID; component keys are never used for lookup. */
|
|
20
27
|
islandRoots?: Record<string, EcoPageRoot>;
|
|
28
|
+
islandComponents?: Record<string, EcoIslandComponent>;
|
|
29
|
+
__ecoIslandRuntime?: EcoIslandRuntime;
|
|
21
30
|
page?: EcoPageData;
|
|
22
31
|
};
|
|
23
32
|
|
package/src/islands/README.md
CHANGED
|
@@ -12,7 +12,11 @@ Hydratable component instances stamp a small, integration-agnostic attribute set
|
|
|
12
12
|
| `data-eco-component-key` | Optional module key used by client hydration (React) |
|
|
13
13
|
| `data-eco-props` | Optional base64 JSON props snapshot for hydration |
|
|
14
14
|
|
|
15
|
-
React may
|
|
15
|
+
React integrations may emit `<eco-island>` as the SSR host. The host uses
|
|
16
|
+
`display: contents` so it does not introduce a layout box, while the host and
|
|
17
|
+
its children remain in place as the client calls `hydrateRoot()` on that host.
|
|
18
|
+
React island integrations may add `data-eco-hydrated` after the initial client
|
|
19
|
+
commit for development diagnostics.
|
|
16
20
|
|
|
17
21
|
## Where stamping happens
|
|
18
22
|
|
package/src/plugins/README.md
CHANGED
|
@@ -37,6 +37,7 @@ These contracts are responsible for:
|
|
|
37
37
|
2. Core seals the app-owned build manifest.
|
|
38
38
|
3. Runtime startup calls runtime-only setup hooks.
|
|
39
39
|
4. Request-time rendering and development invalidation reuse those finalized contracts.
|
|
40
|
+
5. `DevelopmentInvalidationService.invalidateServerModules()` calls `Processor.invalidateServerArtifacts()` so processors can discard compiled server artifacts that are not part of the route-module graph.
|
|
40
41
|
|
|
41
42
|
## Discovered dependency metadata
|
|
42
43
|
|
|
@@ -147,6 +147,10 @@ export declare abstract class Processor<TOptions = Record<string, unknown>> {
|
|
|
147
147
|
didChange(): boolean;
|
|
148
148
|
abstract setup(): Promise<void>;
|
|
149
149
|
abstract process(input: unknown, filePath?: string): Promise<unknown>;
|
|
150
|
+
/**
|
|
151
|
+
* Discards cached server build artifacts when server modules are invalidated.
|
|
152
|
+
*/
|
|
153
|
+
invalidateServerArtifacts(): void;
|
|
150
154
|
/**
|
|
151
155
|
* Releases runtime resources owned by the processor.
|
|
152
156
|
*
|
package/src/plugins/processor.js
CHANGED
|
@@ -108,6 +108,10 @@ export class Processor {
|
|
|
108
108
|
didChange() {
|
|
109
109
|
return false;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Discards cached server build artifacts when server modules are invalidated.
|
|
113
|
+
*/
|
|
114
|
+
invalidateServerArtifacts() { }
|
|
111
115
|
/**
|
|
112
116
|
* Releases runtime resources owned by the processor.
|
|
113
117
|
*
|
|
@@ -95,12 +95,12 @@ Important:
|
|
|
95
95
|
|
|
96
96
|
- route-level fallback resolution is gone; unresolved artifacts are now a hard failure
|
|
97
97
|
- ownership is declared from canonical component identity, not inferred from final HTML
|
|
98
|
-
- declared page dependencies are resolved from final render inputs and carried to the owning integration;
|
|
98
|
+
- declared page dependencies are resolved from final render inputs and carried as dependency roots to the owning integration; those roots participate in asset, integration, ownership, and renderer-specific lifecycle collection
|
|
99
99
|
- same-integration children stay renderer-local and do not need to pass through a universal transport
|
|
100
100
|
|
|
101
101
|
## Declared Foreign Child Contract
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
Static mixed-integration component configs must declare every possible foreign child in `config.dependencies.components`. Page dependency resolvers can additionally provide request-time component roots when a route loads a component dynamically.
|
|
104
104
|
|
|
105
105
|
`OwnershipValidationService` surfaces missing metadata or unknown integrations during `prepareRenderOptions()` and throws before render execution starts.
|
|
106
106
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { BaseIntegrationContext,
|
|
1
|
+
import type { BaseIntegrationContext, ComponentRenderResult, EcoComponent, PageMetadataProps } from '../../../types/public-types.js';
|
|
2
|
+
import type { InternalComponentRenderInput } from '../../../types/internal-types.js';
|
|
2
3
|
import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
|
|
3
4
|
import { HtmlTransformerService, type HtmlDocumentContribution } from '../../../services/html/html-transformer.service.js';
|
|
4
5
|
export type DocumentShellLayoutInput = {
|
|
@@ -36,6 +37,8 @@ export type DocumentShellComposeChildrenHook = (context: DocumentShellComposeChi
|
|
|
36
37
|
export type DocumentShellComposeInput = {
|
|
37
38
|
primaryComponent: EcoComponent;
|
|
38
39
|
primaryProps: Record<string, unknown>;
|
|
40
|
+
/** Components whose ownership graphs must join the foreign-child decision. */
|
|
41
|
+
foreignChildRoots?: ReadonlyArray<EcoComponent | Partial<EcoComponent>>;
|
|
39
42
|
layout?: DocumentShellLayoutInput;
|
|
40
43
|
layouts?: DocumentShellLayoutInput[];
|
|
41
44
|
composeChildren?: DocumentShellComposeChildrenHook;
|
|
@@ -47,6 +50,8 @@ export type DocumentShellPageRenderInput = {
|
|
|
47
50
|
component: EcoComponent;
|
|
48
51
|
props: Record<string, unknown>;
|
|
49
52
|
};
|
|
53
|
+
/** Components whose ownership graphs must join the foreign-child decision. */
|
|
54
|
+
foreignChildRoots?: ReadonlyArray<EcoComponent | Partial<EcoComponent>>;
|
|
50
55
|
layout?: DocumentShellLayoutInput;
|
|
51
56
|
layouts?: DocumentShellLayoutInput[];
|
|
52
57
|
composeChildren?: DocumentShellComposeChildrenHook;
|
|
@@ -57,7 +62,7 @@ export type DocumentShellPageRenderInput = {
|
|
|
57
62
|
transformDocumentHtml?: (html: string) => string;
|
|
58
63
|
};
|
|
59
64
|
export type DocumentShellRenderDependencies = {
|
|
60
|
-
renderComponentWithForeignChildren(input:
|
|
65
|
+
renderComponentWithForeignChildren(input: InternalComponentRenderInput): Promise<ComponentRenderResult>;
|
|
61
66
|
appendProcessedDependencies(...assetGroups: Array<readonly ProcessedAsset[] | undefined>): ProcessedAsset[];
|
|
62
67
|
};
|
|
63
68
|
export type DocumentShellAttributeStamping = {
|
|
@@ -68,6 +68,7 @@ export async function composeDocumentShell(dependencies, input) {
|
|
|
68
68
|
primaryRender = await dependencies.renderComponentWithForeignChildren({
|
|
69
69
|
component: input.primaryComponent,
|
|
70
70
|
props: input.primaryProps,
|
|
71
|
+
foreignChildRoots: input.foreignChildRoots,
|
|
71
72
|
integrationContext: { rendererCache },
|
|
72
73
|
});
|
|
73
74
|
const composed = await composeChildren({
|
|
@@ -113,6 +114,7 @@ export async function renderPageDocumentShell(dependencies, input, docType) {
|
|
|
113
114
|
const { documentHtml: composedDocumentHtml } = await composeDocumentShell(dependencies, {
|
|
114
115
|
primaryComponent: input.page.component,
|
|
115
116
|
primaryProps: input.page.props,
|
|
117
|
+
foreignChildRoots: input.foreignChildRoots,
|
|
116
118
|
layout: input.layout,
|
|
117
119
|
layouts: input.layouts,
|
|
118
120
|
composeChildren: input.composeChildren,
|
package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
|
|
2
2
|
import type { BaseIntegrationContext, ComponentRenderInput, ComponentRenderResult, EcoComponent, ForeignSubtreeRenderPayload } from '../../../types/public-types.js';
|
|
3
|
+
import type { InternalComponentRenderInput } from '../../../types/internal-types.js';
|
|
3
4
|
import { type ForeignChildRuntime } from './component-render-context.js';
|
|
4
5
|
export type QueuedForeignChildDecisionInput = {
|
|
5
6
|
currentIntegration: string;
|
|
@@ -38,10 +39,10 @@ export interface ForeignSubtreeExecutionDecisionInput {
|
|
|
38
39
|
}
|
|
39
40
|
export interface ForeignSubtreeExecutionRenderOptions {
|
|
40
41
|
currentIntegrationName: string;
|
|
41
|
-
input:
|
|
42
|
+
input: InternalComponentRenderInput;
|
|
42
43
|
renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
|
|
43
44
|
normalizeComponentRenderOutput(result: ComponentRenderResult): ComponentRenderResult;
|
|
44
|
-
hasForeignChildDescendants(component: EcoComponent): boolean;
|
|
45
|
+
hasForeignChildDescendants(component: EcoComponent, foreignChildRoots?: ReadonlyArray<EcoComponent | Partial<EcoComponent>>): boolean;
|
|
45
46
|
createForeignChildRuntime(options: {
|
|
46
47
|
renderInput: ComponentRenderInput;
|
|
47
48
|
rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>;
|
|
@@ -241,7 +241,7 @@ export class ForeignSubtreeExecutionService {
|
|
|
241
241
|
if (delegatedForeignChildRender) {
|
|
242
242
|
return delegatedForeignChildRender;
|
|
243
243
|
}
|
|
244
|
-
const hasForeignChildren = options.hasForeignChildDescendants(options.input.component) ||
|
|
244
|
+
const hasForeignChildren = options.hasForeignChildDescendants(options.input.component, options.input.foreignChildRoots) ||
|
|
245
245
|
this.requiresForeignChildRuntime(options.input);
|
|
246
246
|
const activeRenderContext = getComponentRenderContext();
|
|
247
247
|
if (!hasForeignChildren) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Every integration renderer should extend this class
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import type { EcoPagesAppConfig, IHmrManager } from '../../types/internal-types.js';
|
|
6
|
+
import type { EcoPagesAppConfig, IHmrManager, InternalComponentRenderInput } from '../../types/internal-types.js';
|
|
7
7
|
import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPageFile, EcoPagesElement, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
|
|
8
8
|
import { type AssetProcessingService, type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
|
|
9
9
|
import { HtmlTransformerService } from '../../services/html/html-transformer.service.js';
|
|
@@ -217,6 +217,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
|
|
|
217
217
|
pageProps: Record<string, unknown>;
|
|
218
218
|
documentProps?: Record<string, unknown>;
|
|
219
219
|
transformDocumentHtml?: (html: string) => string;
|
|
220
|
+
foreignChildRoots?: ReadonlyArray<EcoComponent | Partial<EcoComponent>>;
|
|
220
221
|
}): Promise<string>;
|
|
221
222
|
protected renderStringComponentWithSerializedChildren(input: ComponentRenderInput, component: (props: Record<string, unknown>) => Promise<EcoPagesElement> | EcoPagesElement): Promise<ComponentRenderResult>;
|
|
222
223
|
/**
|
|
@@ -349,7 +350,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
|
|
|
349
350
|
* render them with no active foreign-child runtime, which bypasses the owning
|
|
350
351
|
* renderer's nested foreign-child handoff.
|
|
351
352
|
*/
|
|
352
|
-
renderComponentWithForeignChildren(input:
|
|
353
|
+
renderComponentWithForeignChildren(input: InternalComponentRenderInput): Promise<ComponentRenderResult>;
|
|
353
354
|
protected finalizeIslandComponentRender(input: ComponentRenderInput, result: ComponentRenderResult): ComponentRenderResult;
|
|
354
355
|
private normalizeComponentRenderOutput;
|
|
355
356
|
protected normalizeUnresolvedMarkerArtifactHtml(html: string): string;
|
|
@@ -360,7 +361,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
|
|
|
360
361
|
* This keeps foreign-child runtime setup narrow: same-integration trees can render
|
|
361
362
|
* directly without paying the queue orchestration cost.
|
|
362
363
|
*/
|
|
363
|
-
protected hasForeignChildDescendants(component: EcoComponent): boolean;
|
|
364
|
+
protected hasForeignChildDescendants(component: EcoComponent, foreignChildRoots?: ReadonlyArray<EcoComponent | Partial<EcoComponent>>): boolean;
|
|
364
365
|
/**
|
|
365
366
|
* Render a view directly to a Response object.
|
|
366
367
|
* Used for explicit routing where views are rendered from route handlers.
|
|
@@ -688,7 +688,7 @@ export class IntegrationRenderer {
|
|
|
688
688
|
input,
|
|
689
689
|
renderComponent: (renderInput) => this.renderComponent(renderInput),
|
|
690
690
|
normalizeComponentRenderOutput: (result) => this.normalizeComponentRenderOutput(result),
|
|
691
|
-
hasForeignChildDescendants: (component) => this.hasForeignChildDescendants(component),
|
|
691
|
+
hasForeignChildDescendants: (component, foreignChildRoots) => this.hasForeignChildDescendants(component, foreignChildRoots),
|
|
692
692
|
createForeignChildRuntime: ({ renderInput, rendererCache }) => this.createForeignChildRuntime({
|
|
693
693
|
renderInput,
|
|
694
694
|
rendererCache: rendererCache,
|
|
@@ -725,8 +725,8 @@ export class IntegrationRenderer {
|
|
|
725
725
|
* This keeps foreign-child runtime setup narrow: same-integration trees can render
|
|
726
726
|
* directly without paying the queue orchestration cost.
|
|
727
727
|
*/
|
|
728
|
-
hasForeignChildDescendants(component) {
|
|
729
|
-
return hasForeignChildDescendantsInGraph(component, this.name);
|
|
728
|
+
hasForeignChildDescendants(component, foreignChildRoots) {
|
|
729
|
+
return hasForeignChildDescendantsInGraph(component, this.name, foreignChildRoots);
|
|
730
730
|
}
|
|
731
731
|
/**
|
|
732
732
|
* Render a single component and return structured output for orchestration paths.
|
|
@@ -3,5 +3,5 @@ import type { EcoComponent, ResolvedLazyTrigger } from '../../../types/public-ty
|
|
|
3
3
|
import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
|
|
4
4
|
export declare function collectIntegrationNamesFromGraph(components: (EcoComponent | Partial<EcoComponent>)[], currentIntegrationName: string): Set<string>;
|
|
5
5
|
export declare function collectResolvedLazyTriggersFromGraph(components: (EcoComponent | Partial<EcoComponent>)[], currentIntegrationName: string): ResolvedLazyTrigger[];
|
|
6
|
-
export declare function hasForeignChildDescendantsInGraph(component: EcoComponent, currentIntegrationName: string): boolean;
|
|
6
|
+
export declare function hasForeignChildDescendantsInGraph(component: EcoComponent, currentIntegrationName: string, foreignChildRoots?: ReadonlyArray<EcoComponent | Partial<EcoComponent>>): boolean;
|
|
7
7
|
export declare function collectUsedIntegrationDependenciesFromGraph(appConfig: EcoPagesAppConfig, components: (EcoComponent | Partial<EcoComponent>)[], currentIntegrationName: string): ProcessedAsset[];
|
|
@@ -31,10 +31,10 @@ export function collectResolvedLazyTriggersFromGraph(components, currentIntegrat
|
|
|
31
31
|
});
|
|
32
32
|
return triggers;
|
|
33
33
|
}
|
|
34
|
-
export function hasForeignChildDescendantsInGraph(component, currentIntegrationName) {
|
|
34
|
+
export function hasForeignChildDescendantsInGraph(component, currentIntegrationName, foreignChildRoots) {
|
|
35
35
|
let foundForeign = false;
|
|
36
36
|
walkComponentGraph({
|
|
37
|
-
roots: [
|
|
37
|
+
roots: toGraphRoots([component, ...(foreignChildRoots ?? [])]),
|
|
38
38
|
currentIntegrationName,
|
|
39
39
|
onComponent: ({ component: currentComponent }) => {
|
|
40
40
|
if (foundForeign) {
|
|
@@ -88,11 +88,12 @@ export class RouteRenderOrchestrator {
|
|
|
88
88
|
dependencyInstanceKey,
|
|
89
89
|
});
|
|
90
90
|
const resolvedPageDependencyComponents = resolvedPageDependencies?.components ?? [];
|
|
91
|
+
const dependencyRoots = [...componentsToResolve, ...resolvedPageDependencyComponents];
|
|
91
92
|
const allDependencies = [
|
|
92
93
|
...resolvedDependencies,
|
|
93
|
-
...collectUsedIntegrationDependenciesFromGraph(this.appConfig,
|
|
94
|
+
...collectUsedIntegrationDependenciesFromGraph(this.appConfig, dependencyRoots, adapter.name),
|
|
94
95
|
];
|
|
95
|
-
const triggers = collectResolvedLazyTriggersFromGraph(
|
|
96
|
+
const triggers = collectResolvedLazyTriggersFromGraph(dependencyRoots, adapter.name);
|
|
96
97
|
const globalAssets = triggers.length > 0
|
|
97
98
|
? await buildGlobalInjectorAssets(this.appConfig, this.assetProcessingService, triggers, adapter.name)
|
|
98
99
|
: [];
|
|
@@ -9,6 +9,6 @@ import { IntegrationRenderer, type RenderToResponseContext } from './integration
|
|
|
9
9
|
*/
|
|
10
10
|
export declare abstract class StringMarkupRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
11
11
|
renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
|
|
12
|
-
render({ params, query, props, locals, pageLocals, metadata, Page, Layout, layoutEntries, HtmlTemplate, pageProps, }: IntegrationRendererRenderOptions): Promise<RouteRendererBody>;
|
|
12
|
+
render({ params, query, props, locals, pageLocals, metadata, Page, Layout, layoutEntries, HtmlTemplate, pageProps, resolvedPageDependencyComponents, }: IntegrationRendererRenderOptions): Promise<RouteRendererBody>;
|
|
13
13
|
renderToResponse<P = Record<string, unknown>>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
|
|
14
14
|
}
|
|
@@ -14,7 +14,7 @@ export class StringMarkupRenderer extends IntegrationRenderer {
|
|
|
14
14
|
}
|
|
15
15
|
return this.renderStringComponentWithQueuedForeignSubtrees(input, input.component);
|
|
16
16
|
}
|
|
17
|
-
async render({ params, query, props, locals, pageLocals, metadata, Page, Layout, layoutEntries, HtmlTemplate, pageProps, }) {
|
|
17
|
+
async render({ params, query, props, locals, pageLocals, metadata, Page, Layout, layoutEntries, HtmlTemplate, pageProps, resolvedPageDependencyComponents, }) {
|
|
18
18
|
try {
|
|
19
19
|
return await this.renderPageWithDocumentShell({
|
|
20
20
|
page: {
|
|
@@ -31,6 +31,7 @@ export class StringMarkupRenderer extends IntegrationRenderer {
|
|
|
31
31
|
htmlTemplate: HtmlTemplate,
|
|
32
32
|
metadata,
|
|
33
33
|
pageProps: pageProps ?? props ?? {},
|
|
34
|
+
foreignChildRoots: resolvedPageDependencyComponents,
|
|
34
35
|
});
|
|
35
36
|
}
|
|
36
37
|
catch (error) {
|
package/src/router/README.md
CHANGED
|
@@ -56,7 +56,11 @@ Match priority:
|
|
|
56
56
|
|
|
57
57
|
1. `exact` — the pathname must equal the route pathname exactly.
|
|
58
58
|
2. `dynamic` — the clean (bracket-stripped) prefix must appear in the pathname, and the segment counts must match.
|
|
59
|
-
3. `catch-all` — the
|
|
59
|
+
3. `catch-all` — the prefix segments must match the pathname. When multiple
|
|
60
|
+
catch-all routes match, shared prefix segments are compared from left to right
|
|
61
|
+
so an earlier static segment wins over a dynamic one; when those patterns tie,
|
|
62
|
+
the route with the longer prefix wins, then pathname order provides a
|
|
63
|
+
deterministic tie-breaker.
|
|
60
64
|
|
|
61
65
|
## `client/`
|
|
62
66
|
|
|
@@ -62,6 +62,19 @@ export declare class RouteRegistry {
|
|
|
62
62
|
reload(): Promise<void>;
|
|
63
63
|
onReload(listener: () => void): () => void;
|
|
64
64
|
matchRequest(requestUrl: string): RouteMatch | null;
|
|
65
|
+
/**
|
|
66
|
+
* Orders overlapping catch-all routes by the specificity of their prefix.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* Prefix segments are compared from left to right because an earlier static
|
|
70
|
+
* segment constrains a request more strongly than a later one. A longer
|
|
71
|
+
* prefix wins only after the shared segment pattern ties. The pathname tie
|
|
72
|
+
* breaker keeps equal patterns deterministic without depending on discovery
|
|
73
|
+
* order.
|
|
74
|
+
*/
|
|
75
|
+
private compareCatchAllSpecificity;
|
|
76
|
+
private getCatchAllPrefix;
|
|
77
|
+
private isDynamicRouteSegment;
|
|
65
78
|
listStaticPathExpansions(input: {
|
|
66
79
|
runtimeOrigin: string;
|
|
67
80
|
}): Promise<readonly StaticPathExpansion[]>;
|
|
@@ -49,49 +49,60 @@ export class RouteRegistry {
|
|
|
49
49
|
const requestedPathname = normalizePathname(url.pathname);
|
|
50
50
|
const query = this.getSearchParams(url);
|
|
51
51
|
for (const route of this.templateRouteList) {
|
|
52
|
-
if (route.kind
|
|
52
|
+
if (route.kind === 'exact') {
|
|
53
|
+
if (requestedPathname === route.pathname || requestedPathname === `${route.pathname}/`) {
|
|
54
|
+
return {
|
|
55
|
+
requestedPathname,
|
|
56
|
+
templateRoute: route,
|
|
57
|
+
params: {},
|
|
58
|
+
query,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
53
61
|
continue;
|
|
54
62
|
}
|
|
55
|
-
|
|
63
|
+
const params = this.tryExtractParams(route, requestedPathname);
|
|
64
|
+
if (params) {
|
|
56
65
|
return {
|
|
57
66
|
requestedPathname,
|
|
58
67
|
templateRoute: route,
|
|
59
|
-
params
|
|
68
|
+
params,
|
|
60
69
|
query,
|
|
61
70
|
};
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
continue;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Orders overlapping catch-all routes by the specificity of their prefix.
|
|
77
|
+
*
|
|
78
|
+
* @remarks
|
|
79
|
+
* Prefix segments are compared from left to right because an earlier static
|
|
80
|
+
* segment constrains a request more strongly than a later one. A longer
|
|
81
|
+
* prefix wins only after the shared segment pattern ties. The pathname tie
|
|
82
|
+
* breaker keeps equal patterns deterministic without depending on discovery
|
|
83
|
+
* order.
|
|
84
|
+
*/
|
|
85
|
+
compareCatchAllSpecificity(left, right) {
|
|
86
|
+
const leftPrefix = this.getCatchAllPrefix(left.pathname);
|
|
87
|
+
const rightPrefix = this.getCatchAllPrefix(right.pathname);
|
|
88
|
+
const sharedLength = Math.min(leftPrefix.length, rightPrefix.length);
|
|
89
|
+
for (let index = 0; index < sharedLength; index += 1) {
|
|
90
|
+
const leftIsDynamic = this.isDynamicRouteSegment(leftPrefix[index]);
|
|
91
|
+
const rightIsDynamic = this.isDynamicRouteSegment(rightPrefix[index]);
|
|
92
|
+
if (leftIsDynamic !== rightIsDynamic) {
|
|
93
|
+
return leftIsDynamic ? 1 : -1;
|
|
86
94
|
}
|
|
87
|
-
return {
|
|
88
|
-
requestedPathname,
|
|
89
|
-
templateRoute: route,
|
|
90
|
-
params,
|
|
91
|
-
query,
|
|
92
|
-
};
|
|
93
95
|
}
|
|
94
|
-
|
|
96
|
+
const prefixLengthDifference = rightPrefix.length - leftPrefix.length;
|
|
97
|
+
return prefixLengthDifference !== 0 ? prefixLengthDifference : left.pathname.localeCompare(right.pathname);
|
|
98
|
+
}
|
|
99
|
+
getCatchAllPrefix(pathname) {
|
|
100
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
101
|
+
const catchAllIndex = segments.findIndex((segment) => segment.startsWith('[...') && segment.endsWith(']'));
|
|
102
|
+
return catchAllIndex === -1 ? segments : segments.slice(0, catchAllIndex);
|
|
103
|
+
}
|
|
104
|
+
isDynamicRouteSegment(segment) {
|
|
105
|
+
return segment.startsWith('[') && segment.endsWith(']');
|
|
95
106
|
}
|
|
96
107
|
async listStaticPathExpansions(input) {
|
|
97
108
|
const expansions = [];
|
|
@@ -168,6 +179,9 @@ export class RouteRegistry {
|
|
|
168
179
|
if (priorityDifference !== 0) {
|
|
169
180
|
return priorityDifference;
|
|
170
181
|
}
|
|
182
|
+
if (left.kind === 'catch-all' && right.kind === 'catch-all') {
|
|
183
|
+
return this.compareCatchAllSpecificity(left, right);
|
|
184
|
+
}
|
|
171
185
|
if (left.pathname === '/') {
|
|
172
186
|
return -1;
|
|
173
187
|
}
|
package/src/services/README.md
CHANGED
|
@@ -30,6 +30,8 @@ so shared layout styles do not need to be rebuilt for every navigated page while
|
|
|
30
30
|
|
|
31
31
|
Browser runtime module assets resolve bare package roots through their ESM import target. For legacy packages without an `exports` map, they prefer `package.json#module` over CJS `main`; CJS resolution is only a compatibility fallback. Generated entries use `export *` for ESM files and explicit named re-exports for CJS files (so bindings such as React `jsx` exist on the vendor). A default binding is added only when the selected entry exposes one. Runtime vendors are package-root contracts: subpath imports need their own vendor declaration or remain in the consuming bundle.
|
|
32
32
|
|
|
33
|
+
Package entry lookup is rooted at the application directory, so a framework-local dependency with the same name cannot shadow the application's installed package. ESM export conditions are evaluated in the same order as the generated vendor import (`node`, `import`, then `default`).
|
|
34
|
+
|
|
33
35
|
## Design Rule
|
|
34
36
|
|
|
35
37
|
If a concern affects more than one integration or more than one runtime adapter, it usually belongs here instead of in a package-specific implementation.
|
|
@@ -3,6 +3,12 @@ export type BrowserRuntimeDefaultExportPolicy = 'emit-default' | 'skip-default';
|
|
|
3
3
|
type RequireFromRoot = ReturnType<typeof createRequire>;
|
|
4
4
|
/**
|
|
5
5
|
* Resolves a package specifier through Node's ESM import conditions from the app root.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* The resolver is rooted at the application directory so package lookup cannot
|
|
9
|
+
* accidentally use a dependency with the same name from the framework's own
|
|
10
|
+
* installation. The condition order matches the generated vendor import: Node
|
|
11
|
+
* compatibility first, then ESM import, then the package default.
|
|
6
12
|
*/
|
|
7
13
|
export declare function resolvePackageEsmEntryPath(specifier: string, rootDir: string): string | undefined;
|
|
8
14
|
/**
|
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { ResolverFactory } from 'oxc-resolver';
|
|
4
5
|
import { isBarePackageImportSpecifier } from '../../../plugins/tsconfig-import-resolver.js';
|
|
5
6
|
import { toPackageRootSpecifier } from '../../../plugins/package-specifier.js';
|
|
7
|
+
const esmEntryResolvers = new Map();
|
|
8
|
+
function getEsmEntryResolver(rootDir) {
|
|
9
|
+
const normalizedRoot = path.resolve(rootDir);
|
|
10
|
+
const cached = esmEntryResolvers.get(normalizedRoot);
|
|
11
|
+
if (cached) {
|
|
12
|
+
return cached;
|
|
13
|
+
}
|
|
14
|
+
const resolver = new ResolverFactory({ conditionNames: ['node', 'import', 'default'] });
|
|
15
|
+
esmEntryResolvers.set(normalizedRoot, resolver);
|
|
16
|
+
return resolver;
|
|
17
|
+
}
|
|
6
18
|
/**
|
|
7
19
|
* Resolves a package specifier through Node's ESM import conditions from the app root.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* The resolver is rooted at the application directory so package lookup cannot
|
|
23
|
+
* accidentally use a dependency with the same name from the framework's own
|
|
24
|
+
* installation. The condition order matches the generated vendor import: Node
|
|
25
|
+
* compatibility first, then ESM import, then the package default.
|
|
8
26
|
*/
|
|
9
27
|
export function resolvePackageEsmEntryPath(specifier, rootDir) {
|
|
10
28
|
try {
|
|
11
|
-
|
|
29
|
+
const result = getEsmEntryResolver(rootDir).sync(path.resolve(rootDir), specifier);
|
|
30
|
+
return result.path;
|
|
12
31
|
}
|
|
13
32
|
catch {
|
|
14
33
|
return undefined;
|
|
@@ -56,13 +75,13 @@ export function resolveBrowserRuntimeEntryPath(options) {
|
|
|
56
75
|
if (specifier.startsWith('.')) {
|
|
57
76
|
return requireFromRoot.resolve(specifier);
|
|
58
77
|
}
|
|
59
|
-
const esmResolvedPath = isBarePackageImportSpecifier(specifier, rootDir)
|
|
60
|
-
? resolvePackageEsmEntryPath(specifier, rootDir)
|
|
61
|
-
: undefined;
|
|
62
78
|
const legacyModulePath = resolveLegacyPackageModuleEntry({ specifier, requireFromRoot, rootDir });
|
|
63
79
|
if (legacyModulePath) {
|
|
64
80
|
return legacyModulePath;
|
|
65
81
|
}
|
|
82
|
+
const esmResolvedPath = isBarePackageImportSpecifier(specifier, rootDir)
|
|
83
|
+
? resolvePackageEsmEntryPath(specifier, rootDir)
|
|
84
|
+
: undefined;
|
|
66
85
|
if (esmResolvedPath) {
|
|
67
86
|
return esmResolvedPath;
|
|
68
87
|
}
|
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { getAppServerInvalidationState } from '../runtime-state/server-invalidation-state.service.js';
|
|
3
3
|
import { appLogger } from '../../global/app-logger.js';
|
|
4
4
|
import { clearAppDevelopmentRouteModuleBuildCaches } from '../module-loading/route-module-build-cache-registry.js';
|
|
5
|
+
import { clearCollectionServerBuildArtifacts } from '../module-loading/collection-server-module-build.service.js';
|
|
5
6
|
/**
|
|
6
7
|
* Framework-owned development invalidation service.
|
|
7
8
|
*
|
|
@@ -31,6 +32,10 @@ export class DevelopmentInvalidationService {
|
|
|
31
32
|
getAppServerInvalidationState(this.appConfig).invalidateServerModules(changedFiles);
|
|
32
33
|
this.appConfig.runtime?.appModuleLoader?.invalidateDevelopmentGraph();
|
|
33
34
|
clearAppDevelopmentRouteModuleBuildCaches(this.appConfig);
|
|
35
|
+
clearCollectionServerBuildArtifacts(this.appConfig);
|
|
36
|
+
for (const processor of this.appConfig.processors.values()) {
|
|
37
|
+
processor.invalidateServerArtifacts?.();
|
|
38
|
+
}
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* Registers an integration-owned handler for registered script entrypoint edits.
|
|
@@ -80,7 +85,7 @@ export class DevelopmentInvalidationService {
|
|
|
80
85
|
if (this.matchesAdditionalWatchPaths(filePath)) {
|
|
81
86
|
return {
|
|
82
87
|
category: 'additional-watch',
|
|
83
|
-
invalidateServerModules:
|
|
88
|
+
invalidateServerModules: true,
|
|
84
89
|
refreshRoutes: false,
|
|
85
90
|
reloadBrowser: true,
|
|
86
91
|
delegateToHmr: false,
|
|
@@ -162,11 +167,13 @@ export class DevelopmentInvalidationService {
|
|
|
162
167
|
return false;
|
|
163
168
|
for (const pattern of patterns) {
|
|
164
169
|
if (pattern.includes('*')) {
|
|
165
|
-
const ext = pattern.replace(
|
|
170
|
+
const ext = pattern.replace(/\*\*?\/\*|\*+/g, '');
|
|
166
171
|
if (normalizedPath.endsWith(ext))
|
|
167
172
|
return true;
|
|
173
|
+
continue;
|
|
168
174
|
}
|
|
169
|
-
|
|
175
|
+
const resolvedPattern = path.isAbsolute(pattern) ? pattern : path.resolve(this.appConfig.rootDir, pattern);
|
|
176
|
+
if (normalizedPath === resolvedPattern || normalizedPath.startsWith(`${resolvedPattern}${path.sep}`)) {
|
|
170
177
|
return true;
|
|
171
178
|
}
|
|
172
179
|
}
|
|
@@ -35,6 +35,8 @@ Call site (route scan, renderer, SSG, API)
|
|
|
35
35
|
|
|
36
36
|
Development import URLs use `sourceHash` plus a per-service import generation counter. Node uses that value in its `?update=` query. Bun also receives a generation-specific compiled output filename because it retains a previously imported file when only its query changes. Both paths advance after `invalidateDevelopmentGraph()` without a process-wide invalidation version in reuse keys.
|
|
37
37
|
|
|
38
|
+
Compiled collection server modules also include the app-owned server invalidation version in their output filename (`<collection>-<buildIdentity>-<invalidationVersion>.mjs`) so Node's ESM loader treats recompiled bundles as new modules after co-located helper files change.
|
|
39
|
+
|
|
38
40
|
## Files
|
|
39
41
|
|
|
40
42
|
| File | Role |
|
|
@@ -5,6 +5,7 @@ export type CollectionServerBuildArtifact = {
|
|
|
5
5
|
outputUrl: string;
|
|
6
6
|
sourcePaths: string[];
|
|
7
7
|
buildIdentity: string;
|
|
8
|
+
invalidationVersion: number;
|
|
8
9
|
};
|
|
9
10
|
export declare function getCollectionServerBuildArtifact(appConfig: EcoPagesAppConfig, collectionName: string): CollectionServerBuildArtifact | undefined;
|
|
10
11
|
export declare function clearCollectionServerBuildArtifacts(appConfig: EcoPagesAppConfig): void;
|
|
@@ -7,6 +7,7 @@ import { createServerBuildRequest } from '../../build/runtime/build-request-poli
|
|
|
7
7
|
import { recordCollectionBuild } from '../../diagnostics/request-pipeline-metrics.js';
|
|
8
8
|
import { resolveInternalExecutionDir } from '../../utils/resolve-work-dir.js';
|
|
9
9
|
import { fileSystem } from '@ecopages/file-system';
|
|
10
|
+
import { getAppServerInvalidationState } from '../runtime-state/server-invalidation-state.service.js';
|
|
10
11
|
const artifactsByCollection = new WeakMap();
|
|
11
12
|
function getArtifactMap(appConfig) {
|
|
12
13
|
let artifacts = artifactsByCollection.get(appConfig);
|
|
@@ -51,13 +52,14 @@ export async function buildCollectionServerModule(input) {
|
|
|
51
52
|
splitting: false,
|
|
52
53
|
externalPackages: true,
|
|
53
54
|
});
|
|
55
|
+
const invalidationVersion = getAppServerInvalidationState(appConfig).getServerInvalidationVersion();
|
|
54
56
|
const buildIdentity = createCollectionBuildIdentity(createBuildRequestIdentity(buildRequest), sourceFilePath, ownedSourcePaths);
|
|
55
57
|
const artifacts = getArtifactMap(appConfig);
|
|
56
58
|
const existing = artifacts.get(collectionName);
|
|
57
|
-
if (existing?.buildIdentity === buildIdentity) {
|
|
59
|
+
if (existing?.buildIdentity === buildIdentity && existing?.invalidationVersion === invalidationVersion) {
|
|
58
60
|
return existing;
|
|
59
61
|
}
|
|
60
|
-
const outputFileName = `${collectionName}-${buildIdentity}.mjs`;
|
|
62
|
+
const outputFileName = `${collectionName}-${buildIdentity}-${invalidationVersion}.mjs`;
|
|
61
63
|
const buildOptions = { ...buildRequest, naming: outputFileName.replace(/\.mjs$/u, '.[ext]') };
|
|
62
64
|
recordCollectionBuild();
|
|
63
65
|
const buildResult = await build(buildOptions);
|
|
@@ -77,6 +79,7 @@ export async function buildCollectionServerModule(input) {
|
|
|
77
79
|
outputUrl: pathToFileURL(compiledOutput).href,
|
|
78
80
|
sourcePaths: [...ownedSourcePaths, sourceFilePath],
|
|
79
81
|
buildIdentity,
|
|
82
|
+
invalidationVersion,
|
|
80
83
|
};
|
|
81
84
|
artifacts.set(collectionName, artifact);
|
|
82
85
|
return artifact;
|
|
@@ -5,7 +5,11 @@ import type { BuildRuntime } from '../build/runtime/build-runtime.js';
|
|
|
5
5
|
import type { AnyIntegrationPlugin } from '../plugins/integration-plugin.js';
|
|
6
6
|
import type { Processor } from '../plugins/processor.js';
|
|
7
7
|
import type { EcoSourceTransform } from '../plugins/source-transform.js';
|
|
8
|
-
import type { PageMetadataProps, SitemapConfig } from './public-types.js';
|
|
8
|
+
import type { ComponentRenderInput, EcoComponent, PageMetadataProps, SitemapConfig } from './public-types.js';
|
|
9
|
+
/** Internal render input carrying route-resolved dependency roots. */
|
|
10
|
+
export type InternalComponentRenderInput = ComponentRenderInput & {
|
|
11
|
+
foreignChildRoots?: ReadonlyArray<EcoComponent | Partial<EcoComponent>>;
|
|
12
|
+
};
|
|
9
13
|
import type { RouteRegistry } from '../router/server/route-registry.js';
|
|
10
14
|
import type { CacheConfig } from '../services/cache/cache.types.js';
|
|
11
15
|
import type { DevGraphService } from '../services/runtime-state/dev-graph.service.js';
|
|
@@ -184,6 +184,7 @@ export class ProjectWatcher {
|
|
|
184
184
|
await this.refreshRouterRoutesCallback();
|
|
185
185
|
}
|
|
186
186
|
if (plan.reloadBrowser) {
|
|
187
|
+
await this.notifyProcessors(filePath, event);
|
|
187
188
|
this.requestBrowserReload();
|
|
188
189
|
return;
|
|
189
190
|
}
|
|
@@ -364,7 +365,10 @@ export class ProjectWatcher {
|
|
|
364
365
|
processorPaths.add(this.appConfig.absolutePaths.publicDir);
|
|
365
366
|
}
|
|
366
367
|
for (const watchPath of this.appConfig.additionalWatchPaths) {
|
|
367
|
-
|
|
368
|
+
const resolvedWatchPath = path.isAbsolute(watchPath) || watchPath.includes('*')
|
|
369
|
+
? watchPath
|
|
370
|
+
: path.resolve(this.appConfig.rootDir, watchPath);
|
|
371
|
+
processorPaths.add(resolvedWatchPath);
|
|
368
372
|
}
|
|
369
373
|
const ignored = createProjectWatcherIgnorePredicate(this.appConfig.absolutePaths);
|
|
370
374
|
this.watcher = chokidar.watch(Array.from(processorPaths), {
|