@ecopages/core 0.2.0-alpha.25 → 0.2.0-alpha.27

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.
Files changed (111) hide show
  1. package/README.md +63 -7
  2. package/package.json +4 -47
  3. package/src/adapters/bun/create-app.ts +54 -2
  4. package/src/adapters/bun/hmr-manager.test.ts +0 -2
  5. package/src/adapters/bun/hmr-manager.ts +1 -24
  6. package/src/adapters/bun/server-adapter.ts +30 -4
  7. package/src/adapters/node/node-hmr-manager.test.ts +0 -2
  8. package/src/adapters/node/node-hmr-manager.ts +2 -25
  9. package/src/adapters/shared/explicit-static-render-preparation.ts +58 -0
  10. package/src/adapters/shared/explicit-static-route-matcher.test.ts +6 -6
  11. package/src/adapters/shared/explicit-static-route-matcher.ts +22 -31
  12. package/src/adapters/shared/file-route-middleware-pipeline.test.ts +5 -10
  13. package/src/adapters/shared/file-route-middleware-pipeline.ts +8 -17
  14. package/src/adapters/shared/fs-server-response-factory.test.ts +32 -43
  15. package/src/adapters/shared/fs-server-response-factory.ts +15 -37
  16. package/src/adapters/shared/fs-server-response-matcher.test.ts +65 -39
  17. package/src/adapters/shared/fs-server-response-matcher.ts +94 -43
  18. package/src/adapters/shared/hmr-manager.contract.test.ts +0 -4
  19. package/src/adapters/shared/render-context.ts +3 -3
  20. package/src/adapters/shared/server-adapter.test.ts +53 -0
  21. package/src/adapters/shared/server-adapter.ts +228 -159
  22. package/src/adapters/shared/server-route-handler.test.ts +6 -5
  23. package/src/adapters/shared/server-route-handler.ts +4 -4
  24. package/src/adapters/shared/server-static-builder.test.ts +4 -4
  25. package/src/adapters/shared/server-static-builder.ts +4 -4
  26. package/src/config/README.md +1 -1
  27. package/src/config/config-builder.test.ts +0 -1
  28. package/src/config/config-builder.ts +2 -7
  29. package/src/dev/host-runtime.ts +34 -0
  30. package/src/eco/eco.browser.test.ts +2 -2
  31. package/src/eco/eco.browser.ts +2 -2
  32. package/src/eco/eco.test.ts +6 -6
  33. package/src/eco/eco.ts +12 -12
  34. package/src/eco/eco.types.ts +3 -3
  35. package/src/errors/index.ts +1 -0
  36. package/src/hmr/client/hmr-runtime.ts +4 -2
  37. package/src/hmr/strategies/js-hmr-strategy.test.ts +0 -1
  38. package/src/hmr/strategies/js-hmr-strategy.ts +0 -6
  39. package/src/integrations/ghtml/ghtml-renderer.test.ts +7 -7
  40. package/src/integrations/ghtml/ghtml-renderer.ts +1 -11
  41. package/src/plugins/eco-component-meta-plugin.ts +0 -1
  42. package/src/plugins/integration-plugin.test.ts +9 -14
  43. package/src/plugins/integration-plugin.ts +34 -22
  44. package/src/plugins/processor.ts +17 -0
  45. package/src/route-renderer/GRAPH.md +81 -289
  46. package/src/route-renderer/README.md +67 -105
  47. package/src/route-renderer/orchestration/component-render-context.ts +45 -38
  48. package/src/route-renderer/orchestration/declared-ownership-graph.ts +62 -0
  49. package/src/route-renderer/orchestration/foreign-subtree-execution.service.ts +383 -0
  50. package/src/route-renderer/orchestration/integration-renderer.test.ts +118 -121
  51. package/src/route-renderer/orchestration/integration-renderer.ts +362 -403
  52. package/src/route-renderer/orchestration/ownership-planning.service.ts +97 -0
  53. package/src/route-renderer/orchestration/ownership-validation.service.ts +76 -0
  54. package/src/route-renderer/orchestration/processed-asset-dedupe.ts +1 -1
  55. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.test.ts → queued-foreign-subtree-resolution.service.test.ts} +76 -71
  56. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.ts → queued-foreign-subtree-resolution.service.ts} +68 -63
  57. package/src/route-renderer/orchestration/render-output.utils.ts +21 -13
  58. package/src/route-renderer/orchestration/{render-preparation.service.test.ts → route-render-orchestrator.prepare-render-options.test.ts} +160 -85
  59. package/src/route-renderer/orchestration/route-render-orchestrator.test.ts +265 -0
  60. package/src/route-renderer/orchestration/{render-preparation.service.ts → route-render-orchestrator.ts} +244 -160
  61. package/src/route-renderer/page-loading/component-dependency-collection.ts +9 -3
  62. package/src/route-renderer/page-loading/declared-asset-collection.ts +2 -5
  63. package/src/route-renderer/page-loading/dependency-resolver.test.ts +107 -11
  64. package/src/route-renderer/page-loading/dependency-resolver.ts +6 -12
  65. package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +1 -1
  66. package/src/route-renderer/page-loading/lazy-entry-collection.ts +1 -1
  67. package/src/route-renderer/page-loading/lazy-trigger-planning.ts +1 -1
  68. package/src/route-renderer/page-loading/module-declaration-aggregation.ts +1 -1
  69. package/src/route-renderer/page-loading/module-declaration-scripts.ts +1 -1
  70. package/src/route-renderer/page-loading/page-dependency-bundling.ts +105 -66
  71. package/src/route-renderer/route-renderer.ts +28 -31
  72. package/src/router/README.md +16 -19
  73. package/src/router/server/route-registry.test.ts +176 -0
  74. package/src/router/server/route-registry.ts +382 -0
  75. package/src/services/README.md +1 -2
  76. package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +1 -1
  77. package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +1 -4
  78. package/src/services/assets/asset-processing-service/asset-processing.service.ts +1 -2
  79. package/src/services/assets/asset-processing-service/assets.types.ts +3 -0
  80. package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +1 -1
  81. package/src/services/assets/asset-processing-service/index.ts +1 -0
  82. package/src/{route-renderer/orchestration/page-packaging.service.test.ts → services/assets/asset-processing-service/page-package.test.ts} +38 -14
  83. package/src/services/assets/asset-processing-service/page-package.ts +93 -0
  84. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +4 -5
  85. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +13 -10
  86. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +3 -0
  87. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +6 -0
  88. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +2 -0
  89. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +1 -0
  90. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +2 -0
  91. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +1 -1
  92. package/src/services/html/html-transformer.service.test.ts +1 -4
  93. package/src/services/module-loading/app-server-module-transpiler.service.ts +1 -3
  94. package/src/services/module-loading/node-bootstrap-plugin.ts +17 -3
  95. package/src/services/module-loading/page-module-import.service.ts +0 -1
  96. package/src/services/module-loading/source-module-support.ts +1 -1
  97. package/src/static-site-generator/static-site-generator.test.ts +124 -32
  98. package/src/static-site-generator/static-site-generator.ts +168 -185
  99. package/src/types/internal-types.ts +13 -12
  100. package/src/types/public-types.ts +55 -39
  101. package/src/watchers/project-watcher.test-helpers.ts +4 -3
  102. package/src/route-renderer/orchestration/boundary-planning.service.ts +0 -146
  103. package/src/route-renderer/orchestration/page-packaging.service.ts +0 -85
  104. package/src/route-renderer/orchestration/render-execution.service.test.ts +0 -196
  105. package/src/route-renderer/orchestration/render-execution.service.ts +0 -182
  106. package/src/route-renderer/orchestration/route-shell-composer.service.ts +0 -162
  107. package/src/router/server/fs-router-scanner.test.ts +0 -83
  108. package/src/router/server/fs-router-scanner.ts +0 -224
  109. package/src/router/server/fs-router.test.ts +0 -214
  110. package/src/router/server/fs-router.ts +0 -122
  111. package/src/services/runtime-state/runtime-specifier-registry.service.ts +0 -96
@@ -1,214 +0,0 @@
1
- import { describe, expect, test, vi } from 'vitest';
2
- import { FIXTURE_APP_PROJECT_DIR } from '../../../__fixtures__/constants.js';
3
- import { ConfigBuilder } from '../../config/config-builder.js';
4
- import type { Route, Routes } from '../../types/internal-types.js';
5
- import { FSRouter } from './fs-router.ts';
6
- import { FSRouterScanner } from './fs-router-scanner.ts';
7
-
8
- const {
9
- templatesExt,
10
- absolutePaths: { pagesDir, distDir },
11
- } = await new ConfigBuilder().setRootDir(FIXTURE_APP_PROJECT_DIR).build();
12
-
13
- const scanner = new FSRouterScanner({
14
- dir: pagesDir,
15
- appConfig: {
16
- absolutePaths: {
17
- distDir,
18
- },
19
- } as any,
20
- origin: 'http://localhost:3000',
21
- templatesExt,
22
- options: {
23
- buildMode: false,
24
- },
25
- });
26
-
27
- const router = new FSRouter({
28
- origin: 'http://localhost:3000',
29
- assetPrefix: distDir,
30
- scanner,
31
- });
32
-
33
- await router.init();
34
-
35
- describe('FSRouter', async () => {
36
- describe('init', async () => {
37
- test('should scan and return routes', async () => {
38
- expect(Object.keys(router.routes).sort()).toEqual([
39
- 'http://localhost:3000/',
40
- 'http://localhost:3000/404',
41
- 'http://localhost:3000/catch-all/[...path]',
42
- 'http://localhost:3000/dynamic/another-blog-post',
43
- 'http://localhost:3000/dynamic/blog-post',
44
- 'http://localhost:3000/postcss-hmr',
45
- ]);
46
- });
47
- });
48
-
49
- describe('getDynamicParams', async () => {
50
- test.each([
51
- ['/products/[id]', '/products/123', { id: '123' }],
52
- ['/products/[id]', '/products/123/456', { id: '123' }],
53
- ['/products/[id]', '/products/123/456/789', { id: '123' }],
54
- ])(
55
- 'dynamic route %p with URL %p should have dynamic params %p',
56
- async (dynamicPathname, pathname, expected) => {
57
- const route: Route = {
58
- filePath: '',
59
- kind: 'dynamic',
60
- pathname: dynamicPathname,
61
- };
62
- const params = router.getDynamicParams(route, pathname);
63
-
64
- expect(params).toEqual(expected);
65
- },
66
- );
67
-
68
- test.each([
69
- ['/products/[...id]', '/products/123/456/789', { id: ['123', '456', '789'] }],
70
- ['/products/[...id]', '/products/123', { id: ['123'] }],
71
- ['/products/[...id]', '/products', { id: [] }],
72
- ])(
73
- 'catch-all route %p with URL %p should have dynamic params %p',
74
- async (catchAllRoute, pathname, expected) => {
75
- const route: Route = {
76
- filePath: '',
77
- kind: 'dynamic',
78
- pathname: catchAllRoute,
79
- };
80
- const params = router.getDynamicParams(route, pathname);
81
-
82
- expect(params).toEqual(expected);
83
- },
84
- );
85
- });
86
-
87
- describe('getSearchParams', () => {
88
- test('should extract query parameters from URL', () => {
89
- const url = new URL('http://localhost:3000/page?foo=bar&baz=qux');
90
- const params = router.getSearchParams(url);
91
-
92
- expect(params).toEqual({ foo: 'bar', baz: 'qux' });
93
- });
94
-
95
- test('should return empty object for URL without query params', () => {
96
- const url = new URL('http://localhost:3000/page');
97
- const params = router.getSearchParams(url);
98
-
99
- expect(params).toEqual({});
100
- });
101
- });
102
-
103
- describe('match', () => {
104
- const createRouterWithRoutes = (routes: Routes) => {
105
- const Scanner = { scan: async () => routes } as unknown as FSRouterScanner;
106
- const testRouter = new FSRouter({
107
- origin: 'http://localhost:3000',
108
- assetPrefix: '/dist',
109
- scanner: Scanner,
110
- });
111
- testRouter.routes = routes;
112
- return testRouter;
113
- };
114
-
115
- test('should match exact route', () => {
116
- const testRouter = createRouterWithRoutes({
117
- '/about': { filePath: '/pages/about.ts', kind: 'exact', pathname: '/about' },
118
- });
119
-
120
- const result = testRouter.match('http://localhost:3000/about');
121
-
122
- expect(result).not.toBeNull();
123
- expect(result?.kind).toBe('exact');
124
- expect(result?.pathname).toBe('/about');
125
- });
126
-
127
- test('should match exact route with trailing slash', () => {
128
- const testRouter = createRouterWithRoutes({
129
- '/about': { filePath: '/pages/about.ts', kind: 'exact', pathname: '/about' },
130
- });
131
-
132
- const result = testRouter.match('http://localhost:3000/about/');
133
-
134
- expect(result).not.toBeNull();
135
- expect(result?.kind).toBe('exact');
136
- });
137
-
138
- test('should match dynamic route', () => {
139
- const testRouter = createRouterWithRoutes({
140
- '/dynamic/[id]': { filePath: '/pages/dynamic/[id].ts', kind: 'dynamic', pathname: '/dynamic/[id]' },
141
- });
142
-
143
- const result = testRouter.match('http://localhost:3000/dynamic/123');
144
-
145
- expect(result).not.toBeNull();
146
- expect(result?.kind).toBe('dynamic');
147
- expect(result?.params).toEqual({ id: '123' });
148
- });
149
-
150
- test('should match catch-all route', () => {
151
- const testRouter = createRouterWithRoutes({
152
- '/catch-all/[...slug]': {
153
- filePath: '/pages/catch-all/[...slug].ts',
154
- kind: 'catch-all',
155
- pathname: '/catch-all/[...slug]',
156
- },
157
- });
158
-
159
- const result = testRouter.match('http://localhost:3000/catch-all/a/b/c');
160
-
161
- expect(result).not.toBeNull();
162
- expect(result?.kind).toBe('catch-all');
163
- expect(result?.params).toEqual({ slug: ['a', 'b', 'c'] });
164
- });
165
-
166
- test('should include query params in match result', () => {
167
- const testRouter = createRouterWithRoutes({
168
- '/page': { filePath: '/pages/page.ts', kind: 'exact', pathname: '/page' },
169
- });
170
-
171
- const result = testRouter.match('http://localhost:3000/page?sort=asc&page=2');
172
-
173
- expect(result?.query).toEqual({ sort: 'asc', page: '2' });
174
- });
175
-
176
- test('should return null for non-matching route', () => {
177
- const testRouter = createRouterWithRoutes({
178
- '/about': { filePath: '/pages/about.ts', kind: 'exact', pathname: '/about' },
179
- });
180
-
181
- const result = testRouter.match('http://localhost:3000/contact');
182
-
183
- expect(result).toBeNull();
184
- });
185
-
186
- test('should prefer exact match over dynamic', () => {
187
- const testRouter = createRouterWithRoutes({
188
- '/blog/latest': { filePath: '/pages/blog/latest.ts', kind: 'exact', pathname: '/blog/latest' },
189
- '/blog/[slug]': { filePath: '/pages/blog/[slug].ts', kind: 'dynamic', pathname: '/blog/[slug]' },
190
- });
191
-
192
- const result = testRouter.match('http://localhost:3000/blog/latest');
193
-
194
- expect(result?.kind).toBe('exact');
195
- });
196
- });
197
-
198
- describe('setOnReload and reload', () => {
199
- test('should set and call onReload callback', async () => {
200
- const Scanner = { scan: async () => ({}) } as unknown as FSRouterScanner;
201
- const testRouter = new FSRouter({
202
- origin: 'http://localhost:3000',
203
- assetPrefix: '/dist',
204
- scanner: Scanner,
205
- });
206
-
207
- const callback = vi.fn(() => {});
208
- testRouter.setOnReload(callback);
209
- testRouter.reload();
210
-
211
- expect(callback).toHaveBeenCalledTimes(1);
212
- });
213
- });
214
- });
@@ -1,122 +0,0 @@
1
- import { appLogger } from '../../global/app-logger.ts';
2
- import type { MatchResult, Route, Routes } from '../../types/internal-types.ts';
3
- import type { FSRouterScanner } from './fs-router-scanner.ts';
4
-
5
- /**
6
- * A class that manages the routes of the file system.
7
- * It scans the file system for files with the specified extensions and creates a map of routes.
8
- * It also provides a method to match a request to a route.
9
- * It can be used to reload the routes when the file system changes.
10
- */
11
- export class FSRouter {
12
- origin: string;
13
- assetPrefix: string;
14
- routes: Routes = {};
15
- scanner: FSRouterScanner;
16
- onReload?: () => void;
17
-
18
- constructor({ origin, assetPrefix, scanner }: { origin: string; assetPrefix: string; scanner: FSRouterScanner }) {
19
- this.origin = origin;
20
- this.assetPrefix = assetPrefix;
21
- this.scanner = scanner;
22
- }
23
-
24
- async init() {
25
- this.routes = await this.scanner.scan();
26
- appLogger.debug('FSRouter initialized', this.routes);
27
- }
28
-
29
- getDynamicParams(route: Route, pathname: string): Record<string, string | string[]> {
30
- const params: Record<string, string | string[]> = {};
31
- const routeParts = route.pathname.split('/');
32
- const pathnameParts = pathname.split('/');
33
-
34
- for (let i = 0; i < routeParts.length; i++) {
35
- const part = routeParts[i];
36
- if (part.startsWith('[') && part.endsWith(']')) {
37
- if (part.startsWith('[...')) {
38
- const param = part.slice(4, -1);
39
- params[param] = pathnameParts.slice(i);
40
- break;
41
- }
42
- const param = part.slice(1, -1);
43
- params[param] = pathnameParts[i];
44
- }
45
- }
46
- return params;
47
- }
48
-
49
- getSearchParams(url: URL): Record<string, string> {
50
- const query: Record<string, string> = {};
51
- for (const [key, value] of url.searchParams) {
52
- query[key] = value;
53
- }
54
- return query;
55
- }
56
-
57
- match(requestUrl: string): MatchResult | null {
58
- const url = new URL(requestUrl);
59
- const pathname = url.pathname.replace(this.origin, '');
60
-
61
- const routeValues = Object.values(this.routes);
62
-
63
- for (const route of routeValues) {
64
- if (route.kind === 'exact' && (pathname === route.pathname || pathname === `${route.pathname}/`)) {
65
- return {
66
- filePath: route.filePath,
67
- kind: 'exact',
68
- pathname: route.pathname,
69
- query: this.getSearchParams(url),
70
- };
71
- }
72
- }
73
-
74
- for (const route of routeValues) {
75
- const cleanPathname = route.pathname.replace(/\[.*?\]/g, '');
76
- const isValidDynamicRoute = pathname.includes(cleanPathname);
77
-
78
- if (route.kind === 'dynamic' && isValidDynamicRoute) {
79
- const routeParts = route.pathname.split('/');
80
- const pathnameParts = pathname.split('/');
81
-
82
- if (routeParts.length === pathnameParts.length) {
83
- return {
84
- filePath: route.filePath,
85
- kind: 'dynamic',
86
- pathname: pathname,
87
- query: this.getSearchParams(url),
88
- params: this.getDynamicParams(route, pathname),
89
- };
90
- }
91
- }
92
- }
93
-
94
- for (const route of routeValues) {
95
- const cleanPathname = route.pathname.replace(/\[.*?\]/g, '');
96
- const isValidCatchAllRoute = pathname.includes(cleanPathname);
97
-
98
- if (route.kind === 'catch-all' && isValidCatchAllRoute) {
99
- return {
100
- filePath: route.filePath,
101
- kind: 'catch-all',
102
- pathname: pathname,
103
- query: this.getSearchParams(url),
104
- params: this.getDynamicParams(route, pathname),
105
- };
106
- }
107
- }
108
-
109
- return null;
110
- }
111
-
112
- setOnReload(cb: () => void) {
113
- this.onReload = cb;
114
- }
115
-
116
- reload() {
117
- this.init();
118
- if (this.onReload) {
119
- this.onReload();
120
- }
121
- }
122
- }
@@ -1,96 +0,0 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.ts';
2
-
3
- /**
4
- * Stores runtime-visible bare-specifier mappings for one app instance.
5
- *
6
- * @remarks
7
- * Integrations populate this registry during runtime setup when they expose
8
- * browser runtime modules through stable bare imports. Build and HMR code later
9
- * consume the collected map to create aliasing and bootstrap behavior without
10
- * forcing integrations to own global registry state.
11
- */
12
- export interface RuntimeSpecifierRegistry {
13
- /**
14
- * Merges a new batch of specifier mappings into the registry.
15
- *
16
- * @remarks
17
- * Later registrations replace earlier URLs for the same specifier. This keeps
18
- * runtime setup deterministic while still allowing an integration to refresh
19
- * its own declarations during one app session.
20
- */
21
- register(map: Record<string, string>): void;
22
-
23
- /**
24
- * Returns the current registry contents.
25
- *
26
- * @remarks
27
- * The returned map is the live backing map for the registry implementation, so
28
- * callers should treat it as read-only unless they intentionally own registry
29
- * mutation semantics.
30
- */
31
- getAll(): Map<string, string>;
32
-
33
- /**
34
- * Removes all registered specifiers for the current app/runtime instance.
35
- */
36
- clear(): void;
37
- }
38
-
39
- /**
40
- * Default in-memory runtime specifier registry used by core.
41
- *
42
- * @remarks
43
- * Runtime specifier maps are app-local bootstrap metadata, not durable build
44
- * artifacts, so the default implementation stays intentionally small.
45
- */
46
- export class InMemoryRuntimeSpecifierRegistry implements RuntimeSpecifierRegistry {
47
- private readonly specifierMap = new Map<string, string>();
48
-
49
- /**
50
- * Merges one integration-provided mapping set into the app registry.
51
- */
52
- register(map: Record<string, string>): void {
53
- for (const [specifier, url] of Object.entries(map)) {
54
- this.specifierMap.set(specifier, url);
55
- }
56
- }
57
-
58
- /**
59
- * Returns the live app-owned mapping table.
60
- */
61
- getAll(): Map<string, string> {
62
- return this.specifierMap;
63
- }
64
-
65
- /**
66
- * Clears all mappings for the current runtime session.
67
- */
68
- clear(): void {
69
- this.specifierMap.clear();
70
- }
71
- }
72
-
73
- /**
74
- * Returns the runtime specifier registry owned by one app instance.
75
- *
76
- * @remarks
77
- * Older tests and helpers may not seed runtime state explicitly yet, so this
78
- * helper still provides an in-memory fallback when the app runtime has not been
79
- * initialized.
80
- */
81
- export function getAppRuntimeSpecifierRegistry(appConfig: EcoPagesAppConfig): RuntimeSpecifierRegistry {
82
- return appConfig.runtime?.runtimeSpecifierRegistry ?? new InMemoryRuntimeSpecifierRegistry();
83
- }
84
-
85
- /**
86
- * Installs the runtime specifier registry that should back one app instance.
87
- */
88
- export function setAppRuntimeSpecifierRegistry(
89
- appConfig: EcoPagesAppConfig,
90
- runtimeSpecifierRegistry: RuntimeSpecifierRegistry,
91
- ): void {
92
- appConfig.runtime = {
93
- ...(appConfig.runtime ?? {}),
94
- runtimeSpecifierRegistry,
95
- };
96
- }