@ecopages/core 0.2.0-beta.7 → 0.2.0-beta.9
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 +27 -3
- package/src/adapters/abstract/application-adapter.d.ts +12 -1
- package/src/adapters/abstract/application-adapter.js +13 -0
- package/src/adapters/abstract/server-adapter.d.ts +3 -0
- package/src/adapters/bun/client-bridge.d.ts +1 -0
- package/src/adapters/bun/client-bridge.js +3 -0
- package/src/adapters/bun/create-app.d.ts +2 -0
- package/src/adapters/bun/create-app.js +28 -9
- package/src/adapters/bun/server-adapter.d.ts +13 -0
- package/src/adapters/bun/server-adapter.js +29 -4
- package/src/adapters/create-app.d.ts +13 -0
- package/src/adapters/create-app.js +10 -1
- package/src/adapters/node/create-app.d.ts +1 -0
- package/src/adapters/node/create-app.js +17 -7
- package/src/adapters/node/node-client-bridge.d.ts +1 -0
- package/src/adapters/node/node-client-bridge.js +3 -0
- package/src/adapters/node/server-adapter.d.ts +10 -13
- package/src/adapters/node/server-adapter.js +37 -82
- package/src/adapters/node/static-content-server.d.ts +1 -0
- package/src/adapters/node/static-content-server.js +3 -0
- package/src/adapters/shared/copy-runtime-public-dir.d.ts +6 -0
- package/src/adapters/shared/copy-runtime-public-dir.js +24 -0
- package/src/adapters/shared/runtime-app-bootstrap.d.ts +0 -1
- package/src/adapters/shared/runtime-app-bootstrap.js +1 -6
- package/src/adapters/shared/server-static-builder.d.ts +4 -6
- package/src/adapters/shared/server-static-builder.js +90 -34
- package/src/adapters/shared/shared-hmr-manager.d.ts +1 -0
- package/src/adapters/shared/shared-hmr-manager.js +15 -2
- package/src/build/README.md +44 -4
- package/src/build/build-adapter.d.ts +27 -2
- package/src/build/build-adapter.js +75 -32
- package/src/build/build-input-fingerprint.d.ts +28 -0
- package/src/build/build-input-fingerprint.js +31 -0
- package/src/build/jsx-ownership-plugins.d.ts +10 -0
- package/src/build/jsx-ownership-plugins.js +17 -0
- package/src/build/pages-unified-graph-build.d.ts +31 -0
- package/src/build/pages-unified-graph-build.js +208 -0
- package/src/build/parallel-build-executor.d.ts +21 -0
- package/src/build/parallel-build-executor.js +52 -0
- package/src/build/rolldown-adapter-helpers.js +7 -1
- package/src/build/rolldown-build-adapter.js +2 -0
- package/src/build/rolldown-build-invocation-metrics.d.ts +12 -0
- package/src/build/rolldown-build-invocation-metrics.js +43 -0
- package/src/build/rolldown-dev-build-adapter.d.ts +2 -2
- package/src/build/rolldown-dev-build-adapter.js +30 -14
- package/src/build/rolldown-plugin-bridge.d.ts +10 -1
- package/src/build/rolldown-plugin-bridge.js +17 -5
- package/src/build/rolldown-source-transform-pass.d.ts +15 -0
- package/src/build/rolldown-source-transform-pass.js +58 -0
- package/src/build/runtime-build-executor.d.ts +7 -13
- package/src/build/runtime-build-executor.js +31 -7
- package/src/build/server-entry-build-cache.d.ts +44 -0
- package/src/build/server-entry-build-cache.js +169 -0
- package/src/config/config-builder.d.ts +11 -1
- package/src/config/config-builder.js +11 -1
- package/src/hmr/hmr.test.e2e.js +15 -4
- package/src/plugins/integration-plugin.d.ts +19 -8
- package/src/plugins/integration-plugin.js +7 -2
- package/src/plugins/processor.d.ts +5 -0
- package/src/plugins/processor.js +7 -0
- package/src/plugins/source-transform.d.ts +31 -0
- package/src/plugins/source-transform.js +23 -1
- package/src/route-renderer/GRAPH.md +4 -4
- package/src/route-renderer/README.md +6 -5
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +1 -3
- package/src/route-renderer/orchestration/integration-renderer.d.ts +10 -2
- package/src/route-renderer/orchestration/integration-renderer.js +32 -12
- package/src/route-renderer/orchestration/render-output.utils.d.ts +2 -0
- package/src/route-renderer/orchestration/render-output.utils.js +4 -0
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +2 -2
- package/src/route-renderer/orchestration/route-render-orchestrator.js +17 -16
- package/src/router/client/navigation-coordinator.js +10 -0
- package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -0
- package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +1 -0
- package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +4 -2
- package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +27 -7
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +17 -3
- package/src/services/assets/browser-bundle.service.d.ts +3 -1
- package/src/services/assets/browser-bundle.service.js +14 -5
- package/src/services/html/html-transformer.service.d.ts +1 -1
- package/src/services/html/html-transformer.service.js +4 -4
- package/src/services/invalidation/development-invalidation.service.d.ts +10 -1
- package/src/services/invalidation/development-invalidation.service.js +29 -0
- package/src/services/module-loading/README.md +49 -0
- package/src/services/module-loading/app-server-module-transpiler.service.js +4 -17
- package/src/services/module-loading/page-module-import.service.d.ts +4 -1
- package/src/services/module-loading/page-module-import.service.js +52 -29
- package/src/services/module-loading/route-module-build-cache-registry.d.ts +24 -0
- package/src/services/module-loading/route-module-build-cache-registry.js +37 -0
- package/src/services/module-loading/route-module-build-cache.d.ts +4 -0
- package/src/services/module-loading/route-module-build-cache.js +38 -0
- package/src/services/module-loading/route-module-build-cache.store.d.ts +57 -0
- package/src/services/module-loading/route-module-build-cache.store.js +203 -0
- package/src/services/module-loading/route-module-build-manifest.d.ts +61 -0
- package/src/services/module-loading/route-module-build-manifest.js +124 -0
- package/src/services/module-loading/route-module-dependency-hasher.d.ts +81 -0
- package/src/services/module-loading/route-module-dependency-hasher.js +121 -0
- package/src/services/module-loading/server-module-transpiler.service.js +1 -1
- package/src/static-site-generator/README.md +33 -0
- package/src/static-site-generator/static-build-invalidation.d.ts +10 -0
- package/src/static-site-generator/static-build-invalidation.js +78 -0
- package/src/static-site-generator/static-export-context.d.ts +22 -0
- package/src/static-site-generator/static-export-context.js +0 -0
- package/src/static-site-generator/static-site-generator.d.ts +17 -8
- package/src/static-site-generator/static-site-generator.js +176 -62
- package/src/types/internal-types.d.ts +8 -0
- package/src/utils/parse-cli-args.d.ts +1 -0
- package/src/utils/parse-cli-args.js +3 -0
- package/src/watchers/project-watcher.d.ts +8 -0
- package/src/watchers/project-watcher.js +32 -12
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
3
|
+
import {
|
|
4
|
+
createBuildInputsFingerprint,
|
|
5
|
+
hashAppConfigFile,
|
|
6
|
+
haveBuildInputsChanged
|
|
7
|
+
} from "../build/build-input-fingerprint.js";
|
|
8
|
+
import {
|
|
9
|
+
ROUTE_MODULE_BUILD_CACHE_FILENAME
|
|
10
|
+
} from "../services/module-loading/route-module-build-manifest.js";
|
|
11
|
+
import {
|
|
12
|
+
getSharedRouteModuleBuildCache,
|
|
13
|
+
getServerModuleBuildCacheOutdir
|
|
14
|
+
} from "../services/module-loading/route-module-build-cache-registry.js";
|
|
15
|
+
import { resolveInternalExecutionDir } from "../utils/resolve-work-dir.js";
|
|
16
|
+
import {
|
|
17
|
+
collectBuildInputContributors,
|
|
18
|
+
createBuildInputsFingerprint as createBuildInputsFingerprint2,
|
|
19
|
+
didBuildInputContributorChange,
|
|
20
|
+
hashAppConfigFile as hashAppConfigFile2,
|
|
21
|
+
haveBuildInputsChanged as haveBuildInputsChanged2
|
|
22
|
+
} from "../build/build-input-fingerprint.js";
|
|
23
|
+
function createRouteModuleStaticRenderCacheContext(appConfig) {
|
|
24
|
+
return {
|
|
25
|
+
configHash: hashAppConfigFile(appConfig),
|
|
26
|
+
buildInputsFingerprint: createBuildInputsFingerprint(appConfig)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function shouldResetStaticExportDirectory(appConfig, force = false) {
|
|
30
|
+
if (force) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (haveBuildInputsChanged(appConfig)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const routeModuleCache = getSharedRouteModuleBuildCache(getServerModuleBuildCacheOutdir(appConfig), appConfig);
|
|
37
|
+
return !routeModuleCache.isIncrementalStaticGenerationAvailable(
|
|
38
|
+
createRouteModuleStaticRenderCacheContext(appConfig)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const PRODUCTION_BUILD_CACHE_OUTDIRS = [".server-modules", ".server-route-modules"];
|
|
42
|
+
function clearProductionBuildCaches(appConfig) {
|
|
43
|
+
if (process.env.NODE_ENV !== "production") {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const executionDir = resolveInternalExecutionDir(appConfig);
|
|
47
|
+
for (const subdir of PRODUCTION_BUILD_CACHE_OUTDIRS) {
|
|
48
|
+
const manifestPath = path.join(executionDir, subdir, ROUTE_MODULE_BUILD_CACHE_FILENAME);
|
|
49
|
+
if (fileSystem.exists(manifestPath)) {
|
|
50
|
+
fileSystem.remove(manifestPath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const serverEntryCachePath = path.join(executionDir, ".server-entry", ROUTE_MODULE_BUILD_CACHE_FILENAME);
|
|
54
|
+
if (fileSystem.exists(serverEntryCachePath)) {
|
|
55
|
+
fileSystem.remove(serverEntryCachePath);
|
|
56
|
+
}
|
|
57
|
+
const pagesGraphCachePath = path.join(executionDir, ".server-pages-graph", ROUTE_MODULE_BUILD_CACHE_FILENAME);
|
|
58
|
+
if (fileSystem.exists(pagesGraphCachePath)) {
|
|
59
|
+
fileSystem.remove(pagesGraphCachePath);
|
|
60
|
+
}
|
|
61
|
+
for (const cache of appConfig.runtime?.routeModuleBuildCaches?.values() ?? []) {
|
|
62
|
+
cache.resetMemory();
|
|
63
|
+
}
|
|
64
|
+
appConfig.runtime?.routeModuleBuildCaches?.clear();
|
|
65
|
+
if (appConfig.runtime) {
|
|
66
|
+
appConfig.runtime.serverEntryBuildExecutor = void 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
clearProductionBuildCaches,
|
|
71
|
+
collectBuildInputContributors,
|
|
72
|
+
createBuildInputsFingerprint2 as createBuildInputsFingerprint,
|
|
73
|
+
createRouteModuleStaticRenderCacheContext,
|
|
74
|
+
didBuildInputContributorChange,
|
|
75
|
+
hashAppConfigFile2 as hashAppConfigFile,
|
|
76
|
+
haveBuildInputsChanged2 as haveBuildInputsChanged,
|
|
77
|
+
shouldResetStaticExportDirectory
|
|
78
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EcoPagesAppConfig } from '../types/internal-types.js';
|
|
2
|
+
import type { StaticRoute } from '../types/public-types.js';
|
|
3
|
+
import type { StaticGenerationRendererResolver } from '../route-renderer/route-renderer.js';
|
|
4
|
+
import type { StaticGenerationRoute } from '../router/server/route-registry.js';
|
|
5
|
+
type StaticGenerationRouteSource = {
|
|
6
|
+
listStaticGenerationRoutes(input: {
|
|
7
|
+
runtimeOrigin: string;
|
|
8
|
+
}): Promise<readonly StaticGenerationRoute[]>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Context passed to integration plugins during static export lifecycle hooks.
|
|
12
|
+
*/
|
|
13
|
+
export interface StaticExportContext {
|
|
14
|
+
appConfig: EcoPagesAppConfig;
|
|
15
|
+
router: StaticGenerationRouteSource;
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
routeRendererFactory?: StaticGenerationRendererResolver;
|
|
18
|
+
staticRoutes?: StaticRoute[];
|
|
19
|
+
force: boolean;
|
|
20
|
+
preserveExportDirectory: boolean;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
File without changes
|
|
@@ -2,6 +2,7 @@ import type { EcoPagesAppConfig } from '../types/internal-types.js';
|
|
|
2
2
|
import type { StaticRoute } from '../types/public-types.js';
|
|
3
3
|
import type { PageRendererResolver, StaticGenerationRendererResolver } from '../route-renderer/route-renderer.js';
|
|
4
4
|
import type { StaticGenerationRoute } from '../router/server/route-registry.js';
|
|
5
|
+
import type { RouteModuleBuildCache } from '../services/module-loading/route-module-build-cache.store.js';
|
|
5
6
|
type StaticGenerationRouteSource = {
|
|
6
7
|
listStaticGenerationRoutes(input: {
|
|
7
8
|
runtimeOrigin: string;
|
|
@@ -27,12 +28,17 @@ export declare const STATIC_SITE_GENERATOR_ERRORS: {
|
|
|
27
28
|
*/
|
|
28
29
|
export declare class StaticSiteGenerator {
|
|
29
30
|
appConfig: EcoPagesAppConfig;
|
|
31
|
+
private readonly routeModuleBuildCacheOverride?;
|
|
32
|
+
private staticRenderCacheContext;
|
|
33
|
+
private forceFullStaticGeneration;
|
|
30
34
|
/**
|
|
31
35
|
* Creates the static-site generator for one app config.
|
|
32
36
|
*/
|
|
33
|
-
constructor({ appConfig }: {
|
|
37
|
+
constructor({ appConfig, routeModuleBuildCache, }: {
|
|
34
38
|
appConfig: EcoPagesAppConfig;
|
|
39
|
+
routeModuleBuildCache?: RouteModuleBuildCache;
|
|
35
40
|
});
|
|
41
|
+
private getRouteModuleBuildCache;
|
|
36
42
|
private getExportDir;
|
|
37
43
|
private shouldSkipStaticPageFile;
|
|
38
44
|
private shouldSkipStaticView;
|
|
@@ -49,32 +55,35 @@ export declare class StaticSiteGenerator {
|
|
|
49
55
|
*/
|
|
50
56
|
getDirectories(routes: string[]): string[];
|
|
51
57
|
private writeStaticOutput;
|
|
52
|
-
private
|
|
58
|
+
private writeStaticPageArtifact;
|
|
59
|
+
private pruneStaleStaticOutputs;
|
|
53
60
|
private createFilesystemStaticContents;
|
|
54
61
|
/**
|
|
55
62
|
* Generates static output for all filesystem-discovered routes.
|
|
56
63
|
*
|
|
57
64
|
* @remarks
|
|
58
|
-
* Routes
|
|
59
|
-
* issuing a request against the running server origin. Render-strategy routes
|
|
60
|
-
* go through the normal route renderer directly.
|
|
65
|
+
* Routes are rendered through the normal route renderer directly.
|
|
61
66
|
*/
|
|
62
|
-
generateStaticPages(router: StaticGenerationRouteSource, baseUrl: string, routeRendererFactory?: StaticPageRouteRendererFactory, skipped?: string[]): Promise<void>;
|
|
63
|
-
private
|
|
67
|
+
generateStaticPages(router: StaticGenerationRouteSource, baseUrl: string, routeRendererFactory?: StaticPageRouteRendererFactory, skipped?: string[], activeStaticPathnames?: Set<string>, preloadedRoutes?: readonly StaticGenerationRoute[]): Promise<void>;
|
|
68
|
+
private createStaticExportContext;
|
|
69
|
+
private invokeStaticExportHook;
|
|
64
70
|
/**
|
|
65
71
|
* Executes the full static-generation workflow for one app run.
|
|
66
72
|
*/
|
|
67
|
-
run({ router, baseUrl, routeRendererFactory, staticRoutes, }: {
|
|
73
|
+
run({ router, baseUrl, routeRendererFactory, staticRoutes, force, preserveExportDirectory, }: {
|
|
68
74
|
router: StaticGenerationRouteSource;
|
|
69
75
|
baseUrl: string;
|
|
70
76
|
routeRendererFactory?: StaticGenerationRendererFactory;
|
|
71
77
|
staticRoutes?: StaticRoute[];
|
|
78
|
+
force?: boolean;
|
|
79
|
+
preserveExportDirectory?: boolean;
|
|
72
80
|
}): Promise<void>;
|
|
73
81
|
/**
|
|
74
82
|
* Generates static pages from explicit static routes registered via app.static().
|
|
75
83
|
* These routes use eco.page views via loader functions for HMR support.
|
|
76
84
|
*/
|
|
77
85
|
private generateExplicitStaticPages;
|
|
86
|
+
private resolveExplicitViewSourceFile;
|
|
78
87
|
private generateExplicitStaticRoute;
|
|
79
88
|
private planExplicitStaticRoute;
|
|
80
89
|
private createExplicitStaticContents;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { availableParallelism } from "node:os";
|
|
2
3
|
import { appLogger } from "../global/app-logger.js";
|
|
3
4
|
import { fileSystem } from "@ecopages/file-system";
|
|
4
|
-
import { PathUtils } from "../utils/path-utils.module.js";
|
|
5
5
|
import { prepareExplicitStaticRender } from "../adapters/shared/explicit-static-render-preparation.js";
|
|
6
|
+
import { createRouteModuleStaticRenderCacheContext } from "./static-build-invalidation.js";
|
|
7
|
+
import {
|
|
8
|
+
getServerModuleBuildCacheOutdir,
|
|
9
|
+
getSharedRouteModuleBuildCache
|
|
10
|
+
} from "../services/module-loading/route-module-build-cache-registry.js";
|
|
11
|
+
import { ensurePagesUnifiedGraphBuilt, shouldBuildPagesUnifiedGraph } from "../build/pages-unified-graph-build.js";
|
|
6
12
|
const STATIC_SITE_GENERATOR_ERRORS = {
|
|
7
13
|
ROUTE_RENDERER_FACTORY_REQUIRED: "RouteRendererFactory is required for render strategy",
|
|
8
14
|
unsupportedBodyType: (bodyType) => `Unsupported body type for static generation: ${bodyType}`,
|
|
@@ -10,21 +16,47 @@ const STATIC_SITE_GENERATOR_ERRORS = {
|
|
|
10
16
|
noRendererForIntegration: (integrationName) => `No renderer found for integration: ${integrationName}`,
|
|
11
17
|
dynamicRouteRequiresStaticPaths: (routePath) => `Dynamic route ${routePath} requires staticPaths to be defined on the view.`
|
|
12
18
|
};
|
|
19
|
+
function resolveStaticPageConcurrency() {
|
|
20
|
+
return Math.max(1, availableParallelism() - 1);
|
|
21
|
+
}
|
|
22
|
+
async function runWithConcurrency(items, concurrency, worker) {
|
|
23
|
+
if (items.length === 0) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const limit = Math.max(1, concurrency);
|
|
27
|
+
let nextIndex = 0;
|
|
28
|
+
const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
29
|
+
while (nextIndex < items.length) {
|
|
30
|
+
const currentIndex = nextIndex++;
|
|
31
|
+
await worker(items[currentIndex]);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
await Promise.all(runners);
|
|
35
|
+
}
|
|
13
36
|
class StaticSiteGenerator {
|
|
14
37
|
appConfig;
|
|
38
|
+
routeModuleBuildCacheOverride;
|
|
39
|
+
staticRenderCacheContext;
|
|
40
|
+
forceFullStaticGeneration = false;
|
|
15
41
|
/**
|
|
16
42
|
* Creates the static-site generator for one app config.
|
|
17
43
|
*/
|
|
18
|
-
constructor({
|
|
44
|
+
constructor({
|
|
45
|
+
appConfig,
|
|
46
|
+
routeModuleBuildCache
|
|
47
|
+
}) {
|
|
19
48
|
this.appConfig = appConfig;
|
|
49
|
+
this.routeModuleBuildCacheOverride = routeModuleBuildCache;
|
|
50
|
+
this.staticRenderCacheContext = createRouteModuleStaticRenderCacheContext(appConfig);
|
|
51
|
+
}
|
|
52
|
+
getRouteModuleBuildCache() {
|
|
53
|
+
return this.routeModuleBuildCacheOverride ?? getSharedRouteModuleBuildCache(getServerModuleBuildCacheOutdir(this.appConfig), this.appConfig);
|
|
20
54
|
}
|
|
21
55
|
getExportDir() {
|
|
22
56
|
return this.appConfig.absolutePaths?.distDir ?? path.join(this.appConfig.rootDir, this.appConfig.distDir);
|
|
23
57
|
}
|
|
24
58
|
async shouldSkipStaticPageFile(filePath, routeRendererFactory) {
|
|
25
|
-
const module = await routeRendererFactory.getPageRenderer(filePath).loadPageModule(filePath
|
|
26
|
-
cacheScope: "static-page-probe"
|
|
27
|
-
});
|
|
59
|
+
const module = await routeRendererFactory.getPageRenderer(filePath).loadPageModule(filePath);
|
|
28
60
|
return module.default?.cache === "dynamic";
|
|
29
61
|
}
|
|
30
62
|
shouldSkipStaticView(_routePath, view) {
|
|
@@ -75,25 +107,51 @@ class StaticSiteGenerator {
|
|
|
75
107
|
fileSystem.write(outputPath, contents);
|
|
76
108
|
return outputPath;
|
|
77
109
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
110
|
+
async writeStaticPageArtifact(options) {
|
|
111
|
+
options.activeStaticPathnames?.add(options.pathname);
|
|
112
|
+
const directories = options.directories ?? this.getDirectories([options.pathname]);
|
|
113
|
+
const renderedOutputPath = this.getOutputPath(options.pathname, directories);
|
|
114
|
+
const routeModuleBuildCache = this.getRouteModuleBuildCache();
|
|
115
|
+
if (options.reuseRenderedOutput !== false && options.sourceFile && routeModuleBuildCache.canReuseStaticRender({
|
|
116
|
+
sourceFile: options.sourceFile,
|
|
117
|
+
pathname: options.pathname,
|
|
118
|
+
renderedOutputPath,
|
|
119
|
+
context: this.staticRenderCacheContext,
|
|
120
|
+
force: this.forceFullStaticGeneration
|
|
121
|
+
})) {
|
|
122
|
+
appLogger.debug(`Skipped unchanged static page: ${options.debugLabel ?? options.pathname}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const contents = await options.createContents();
|
|
126
|
+
if (contents === null) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const outputPath = this.writeStaticOutput(options.pathname, contents, directories);
|
|
130
|
+
if (options.sourceFile) {
|
|
131
|
+
routeModuleBuildCache.recordStaticRender({
|
|
132
|
+
filePath: options.sourceFile,
|
|
133
|
+
pathname: options.pathname,
|
|
134
|
+
sourceHash: fileSystem.hash(options.sourceFile),
|
|
135
|
+
renderedOutputPath: outputPath,
|
|
136
|
+
context: this.staticRenderCacheContext
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
pruneStaleStaticOutputs(activeStaticPathnames) {
|
|
141
|
+
const removedOutputPaths = this.getRouteModuleBuildCache().pruneStaleRenderedOutputs(activeStaticPathnames);
|
|
142
|
+
for (const outputPath of removedOutputPaths) {
|
|
143
|
+
const resolvedOutputPath = path.isAbsolute(outputPath) ? outputPath : path.resolve(outputPath);
|
|
144
|
+
if (fileSystem.exists(resolvedOutputPath)) {
|
|
145
|
+
fileSystem.remove(resolvedOutputPath);
|
|
146
|
+
appLogger.debug(`Removed stale static output: ${resolvedOutputPath}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
82
149
|
}
|
|
83
|
-
async createFilesystemStaticContents(route,
|
|
150
|
+
async createFilesystemStaticContents(route, _baseUrl, routeRendererFactory, skipped) {
|
|
84
151
|
const {
|
|
85
152
|
templateRoute: { filePath },
|
|
86
153
|
params
|
|
87
154
|
} = route;
|
|
88
|
-
if (this.getStaticBuildStrategy(filePath) === "fetch") {
|
|
89
|
-
const fetchUrl = this.resolveStaticFetchUrl(route.requestUrl, baseUrl);
|
|
90
|
-
const response = await fetch(fetchUrl);
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
appLogger.error(`Failed to fetch ${fetchUrl}. Status: ${response.status}`);
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
return response.text();
|
|
96
|
-
}
|
|
97
155
|
if (!routeRendererFactory) {
|
|
98
156
|
throw new Error(STATIC_SITE_GENERATOR_ERRORS.ROUTE_RENDERER_FACTORY_REQUIRED);
|
|
99
157
|
}
|
|
@@ -119,47 +177,51 @@ class StaticSiteGenerator {
|
|
|
119
177
|
* Generates static output for all filesystem-discovered routes.
|
|
120
178
|
*
|
|
121
179
|
* @remarks
|
|
122
|
-
* Routes
|
|
123
|
-
* issuing a request against the running server origin. Render-strategy routes
|
|
124
|
-
* go through the normal route renderer directly.
|
|
180
|
+
* Routes are rendered through the normal route renderer directly.
|
|
125
181
|
*/
|
|
126
|
-
async generateStaticPages(router, baseUrl, routeRendererFactory, skipped) {
|
|
127
|
-
const routes = await router.listStaticGenerationRoutes({ runtimeOrigin: baseUrl });
|
|
182
|
+
async generateStaticPages(router, baseUrl, routeRendererFactory, skipped, activeStaticPathnames, preloadedRoutes) {
|
|
183
|
+
const routes = preloadedRoutes ?? await router.listStaticGenerationRoutes({ runtimeOrigin: baseUrl });
|
|
128
184
|
appLogger.debug(
|
|
129
185
|
"Static Pages",
|
|
130
186
|
routes.map((route) => route.requestUrl)
|
|
131
187
|
);
|
|
132
188
|
const directories = this.getDirectories(routes.map((route) => route.requestUrl));
|
|
133
|
-
|
|
189
|
+
await runWithConcurrency(routes, resolveStaticPageConcurrency(), async (route) => {
|
|
134
190
|
try {
|
|
135
|
-
|
|
136
|
-
route,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
this.writeStaticOutput(route.pathname, contents, directories);
|
|
191
|
+
await this.writeStaticPageArtifact({
|
|
192
|
+
pathname: route.pathname,
|
|
193
|
+
sourceFile: route.templateRoute.filePath,
|
|
194
|
+
directories,
|
|
195
|
+
debugLabel: route.requestUrl,
|
|
196
|
+
activeStaticPathnames,
|
|
197
|
+
createContents: () => this.createFilesystemStaticContents(route, baseUrl, routeRendererFactory, skipped)
|
|
198
|
+
});
|
|
145
199
|
} catch (error) {
|
|
146
200
|
appLogger.error(
|
|
147
201
|
`Error generating static page for ${route.requestUrl}:`,
|
|
148
202
|
error instanceof Error ? error : String(error)
|
|
149
203
|
);
|
|
150
204
|
}
|
|
151
|
-
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
createStaticExportContext(input) {
|
|
208
|
+
return {
|
|
209
|
+
appConfig: this.appConfig,
|
|
210
|
+
router: input.router,
|
|
211
|
+
baseUrl: input.baseUrl,
|
|
212
|
+
routeRendererFactory: input.routeRendererFactory,
|
|
213
|
+
staticRoutes: input.staticRoutes,
|
|
214
|
+
force: input.force,
|
|
215
|
+
preserveExportDirectory: input.preserveExportDirectory
|
|
216
|
+
};
|
|
152
217
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
218
|
+
async invokeStaticExportHook(hook, context) {
|
|
219
|
+
for (const integration of this.appConfig.integrations) {
|
|
220
|
+
const handler = integration[hook];
|
|
221
|
+
if (typeof handler === "function") {
|
|
222
|
+
await handler.call(integration, context);
|
|
223
|
+
}
|
|
156
224
|
}
|
|
157
|
-
const targetUrl = new URL(route);
|
|
158
|
-
const buildUrl = new URL(baseUrl);
|
|
159
|
-
buildUrl.pathname = targetUrl.pathname;
|
|
160
|
-
buildUrl.search = targetUrl.search;
|
|
161
|
-
buildUrl.hash = targetUrl.hash;
|
|
162
|
-
return buildUrl.href;
|
|
163
225
|
}
|
|
164
226
|
/**
|
|
165
227
|
* Executes the full static-generation workflow for one app run.
|
|
@@ -168,13 +230,58 @@ class StaticSiteGenerator {
|
|
|
168
230
|
router,
|
|
169
231
|
baseUrl,
|
|
170
232
|
routeRendererFactory,
|
|
171
|
-
staticRoutes
|
|
233
|
+
staticRoutes,
|
|
234
|
+
force = false,
|
|
235
|
+
preserveExportDirectory = false
|
|
172
236
|
}) {
|
|
173
237
|
const skippedDynamicPages = [];
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
238
|
+
const activeStaticPathnames = /* @__PURE__ */ new Set();
|
|
239
|
+
this.forceFullStaticGeneration = force;
|
|
240
|
+
this.staticRenderCacheContext = createRouteModuleStaticRenderCacheContext(this.appConfig);
|
|
241
|
+
if (!force) {
|
|
242
|
+
this.getRouteModuleBuildCache().ensureIncrementalStaticGenerationContext(this.staticRenderCacheContext);
|
|
243
|
+
}
|
|
244
|
+
const routes = await router.listStaticGenerationRoutes({ runtimeOrigin: baseUrl });
|
|
245
|
+
if (shouldBuildPagesUnifiedGraph()) {
|
|
246
|
+
await ensurePagesUnifiedGraphBuilt({
|
|
247
|
+
appConfig: this.appConfig,
|
|
248
|
+
entryPaths: routes.map((route) => route.templateRoute.filePath),
|
|
249
|
+
outdir: getServerModuleBuildCacheOutdir(this.appConfig),
|
|
250
|
+
force
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const staticExportContext = this.createStaticExportContext({
|
|
254
|
+
router,
|
|
255
|
+
baseUrl,
|
|
256
|
+
routeRendererFactory,
|
|
257
|
+
staticRoutes,
|
|
258
|
+
force,
|
|
259
|
+
preserveExportDirectory
|
|
260
|
+
});
|
|
261
|
+
await this.invokeStaticExportHook("beforeStaticExport", staticExportContext);
|
|
262
|
+
try {
|
|
263
|
+
this.generateRobotsTxt();
|
|
264
|
+
await this.generateStaticPages(
|
|
265
|
+
router,
|
|
266
|
+
baseUrl,
|
|
267
|
+
routeRendererFactory,
|
|
268
|
+
skippedDynamicPages,
|
|
269
|
+
activeStaticPathnames,
|
|
270
|
+
routes
|
|
271
|
+
);
|
|
272
|
+
if (staticRoutes && staticRoutes.length > 0 && routeRendererFactory) {
|
|
273
|
+
await this.generateExplicitStaticPages(
|
|
274
|
+
staticRoutes,
|
|
275
|
+
routeRendererFactory,
|
|
276
|
+
skippedDynamicPages,
|
|
277
|
+
activeStaticPathnames
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (preserveExportDirectory) {
|
|
281
|
+
this.pruneStaleStaticOutputs(activeStaticPathnames);
|
|
282
|
+
}
|
|
283
|
+
} finally {
|
|
284
|
+
await this.invokeStaticExportHook("afterStaticExport", staticExportContext);
|
|
178
285
|
}
|
|
179
286
|
if (skippedDynamicPages.length > 0) {
|
|
180
287
|
appLogger.debug(
|
|
@@ -187,7 +294,7 @@ class StaticSiteGenerator {
|
|
|
187
294
|
* Generates static pages from explicit static routes registered via app.static().
|
|
188
295
|
* These routes use eco.page views via loader functions for HMR support.
|
|
189
296
|
*/
|
|
190
|
-
async generateExplicitStaticPages(staticRoutes, routeRendererFactory, skipped) {
|
|
297
|
+
async generateExplicitStaticPages(staticRoutes, routeRendererFactory, skipped, activeStaticPathnames) {
|
|
191
298
|
appLogger.debug(
|
|
192
299
|
"Generating explicit static routes",
|
|
193
300
|
staticRoutes.map((r) => r.path)
|
|
@@ -200,7 +307,7 @@ class StaticSiteGenerator {
|
|
|
200
307
|
skipped?.push(route.path);
|
|
201
308
|
continue;
|
|
202
309
|
}
|
|
203
|
-
await this.generateExplicitStaticRoute(route.path, view, routeRendererFactory);
|
|
310
|
+
await this.generateExplicitStaticRoute(route.path, view, routeRendererFactory, activeStaticPathnames);
|
|
204
311
|
} catch (error) {
|
|
205
312
|
appLogger.error(
|
|
206
313
|
`Error generating explicit static page for ${route.path}:`,
|
|
@@ -209,18 +316,25 @@ class StaticSiteGenerator {
|
|
|
209
316
|
}
|
|
210
317
|
}
|
|
211
318
|
}
|
|
212
|
-
|
|
319
|
+
resolveExplicitViewSourceFile(view) {
|
|
320
|
+
const sourceFile = view.config?.__eco?.file;
|
|
321
|
+
if (!sourceFile) {
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
return path.isAbsolute(sourceFile) ? sourceFile : path.join(this.appConfig.rootDir, sourceFile);
|
|
325
|
+
}
|
|
326
|
+
async generateExplicitStaticRoute(routePath, view, routeRendererFactory, activeStaticPathnames) {
|
|
213
327
|
const { renderer, routeEntries } = await this.planExplicitStaticRoute(routePath, view, routeRendererFactory);
|
|
328
|
+
const sourceFile = this.resolveExplicitViewSourceFile(view);
|
|
214
329
|
for (const { pathname, params } of routeEntries) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
renderer
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
appLogger.debug(`Generated static page: ${pathname} -> ${outputPath}`);
|
|
330
|
+
await this.writeStaticPageArtifact({
|
|
331
|
+
pathname,
|
|
332
|
+
sourceFile,
|
|
333
|
+
debugLabel: pathname,
|
|
334
|
+
activeStaticPathnames,
|
|
335
|
+
createContents: () => this.createExplicitStaticContents(routePath, view, params, routeRendererFactory, renderer)
|
|
336
|
+
});
|
|
337
|
+
appLogger.debug(`Generated static page: ${pathname}`);
|
|
224
338
|
}
|
|
225
339
|
}
|
|
226
340
|
async planExplicitStaticRoute(routePath, view, routeRendererFactory) {
|
|
@@ -13,6 +13,7 @@ import type { SourceModuleLoader } from '../services/module-loading/module-loadi
|
|
|
13
13
|
import type { EntrypointDependencyGraph } from '../services/runtime-state/entrypoint-dependency-graph.service.js';
|
|
14
14
|
import type { ServerInvalidationState } from '../services/runtime-state/server-invalidation-state.service.js';
|
|
15
15
|
import type { ServerModuleTranspiler } from '../services/module-loading/server-module-transpiler.service.js';
|
|
16
|
+
import type { RouteModuleBuildCache } from '../services/module-loading/route-module-build-cache.store.js';
|
|
16
17
|
export interface RobotsPreference {
|
|
17
18
|
/**
|
|
18
19
|
* The user agent
|
|
@@ -150,12 +151,19 @@ export type EcoPagesAppConfig = {
|
|
|
150
151
|
buildAdapter?: BuildAdapter;
|
|
151
152
|
buildManifest?: AppBuildManifest;
|
|
152
153
|
buildExecutor?: BuildExecutor;
|
|
154
|
+
hmrBuildExecutor?: BuildExecutor;
|
|
155
|
+
routeModuleBuildExecutor?: BuildExecutor;
|
|
153
156
|
devGraphService?: DevGraphService;
|
|
154
157
|
entrypointDependencyGraph?: EntrypointDependencyGraph;
|
|
155
158
|
hostModuleLoader?: SourceModuleLoader;
|
|
156
159
|
rendererModuleContext?: unknown;
|
|
157
160
|
serverInvalidationState?: ServerInvalidationState;
|
|
158
161
|
serverModuleTranspiler?: ServerModuleTranspiler;
|
|
162
|
+
routeModuleBuildCaches?: Map<string, RouteModuleBuildCache>;
|
|
163
|
+
/** Serialized server-entry bundler installed by {@link getInstalledServerEntryBuildExecutor}. */
|
|
164
|
+
serverEntryBuildExecutor?: BuildExecutor;
|
|
165
|
+
/** Set after {@link setupAppRuntimePlugins} runs processor/integration setup once per process. */
|
|
166
|
+
runtimeAssetsPrepared?: boolean;
|
|
159
167
|
};
|
|
160
168
|
/**
|
|
161
169
|
* Experimental features.
|
|
@@ -7,6 +7,7 @@ function getEmbeddedRuntimeCommandOptions() {
|
|
|
7
7
|
build: false,
|
|
8
8
|
start: !isDevelopment,
|
|
9
9
|
dev: isDevelopment,
|
|
10
|
+
force: false,
|
|
10
11
|
port: void 0,
|
|
11
12
|
hostname: void 0,
|
|
12
13
|
reactFastRefresh: void 0
|
|
@@ -25,6 +26,7 @@ function parseCliArgs(options = {}) {
|
|
|
25
26
|
dev: { type: "boolean" },
|
|
26
27
|
preview: { type: "boolean" },
|
|
27
28
|
build: { type: "boolean" },
|
|
29
|
+
force: { type: "boolean" },
|
|
28
30
|
port: { type: "string" },
|
|
29
31
|
hostname: { type: "string" },
|
|
30
32
|
"react-fast-refresh": { type: "boolean" }
|
|
@@ -48,6 +50,7 @@ function parseCliArgs(options = {}) {
|
|
|
48
50
|
build: isBuildCommand,
|
|
49
51
|
start: isStartCommand,
|
|
50
52
|
dev: isDevCommand,
|
|
53
|
+
force: !!values.force,
|
|
51
54
|
port: values.port ? Number(values.port) : void 0,
|
|
52
55
|
hostname: values.hostname,
|
|
53
56
|
reactFastRefresh: values["react-fast-refresh"]
|
|
@@ -133,4 +133,12 @@ export declare class ProjectWatcher {
|
|
|
133
133
|
* rapid file changes efficiently.
|
|
134
134
|
*/
|
|
135
135
|
createWatcherSubscription(): Promise<FSWatcher>;
|
|
136
|
+
/**
|
|
137
|
+
* Closes the active filesystem watcher subscription.
|
|
138
|
+
*
|
|
139
|
+
* @remarks
|
|
140
|
+
* Safe to call multiple times. Used when tearing down dev servers in tests
|
|
141
|
+
* and other short-lived Ecopages runtimes.
|
|
142
|
+
*/
|
|
143
|
+
close(): Promise<void>;
|
|
136
144
|
}
|
|
@@ -122,7 +122,7 @@ class ProjectWatcher {
|
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
await this.notifyProcessors(filePath, event);
|
|
125
|
-
if (plan.category === "include-source") {
|
|
125
|
+
if (plan.category === "include-source" || plan.category === "explicit-server-view") {
|
|
126
126
|
this.bridge.reload();
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
@@ -231,30 +231,36 @@ class ProjectWatcher {
|
|
|
231
231
|
if (this.watcher) {
|
|
232
232
|
return this.watcher;
|
|
233
233
|
}
|
|
234
|
-
const processorPaths =
|
|
234
|
+
const processorPaths = /* @__PURE__ */ new Set();
|
|
235
235
|
for (const processor of this.appConfig.processors.values()) {
|
|
236
236
|
const watchConfig = processor.getWatchConfig();
|
|
237
237
|
if (!watchConfig) continue;
|
|
238
|
-
|
|
238
|
+
for (const watchPath of watchConfig.paths) {
|
|
239
|
+
processorPaths.add(watchPath);
|
|
240
|
+
}
|
|
239
241
|
}
|
|
240
242
|
if (fileSystem.exists(this.appConfig.absolutePaths.includesDir)) {
|
|
241
|
-
processorPaths.
|
|
243
|
+
processorPaths.add(this.appConfig.absolutePaths.includesDir);
|
|
242
244
|
}
|
|
243
245
|
if (fileSystem.exists(this.appConfig.absolutePaths.srcDir)) {
|
|
244
|
-
processorPaths.
|
|
245
|
-
}
|
|
246
|
-
if (fileSystem.exists(this.appConfig.absolutePaths.pagesDir)) {
|
|
247
|
-
processorPaths.push(this.appConfig.absolutePaths.pagesDir);
|
|
246
|
+
processorPaths.add(this.appConfig.absolutePaths.srcDir);
|
|
248
247
|
}
|
|
249
248
|
if (fileSystem.exists(this.appConfig.absolutePaths.publicDir)) {
|
|
250
|
-
processorPaths.
|
|
249
|
+
processorPaths.add(this.appConfig.absolutePaths.publicDir);
|
|
251
250
|
}
|
|
252
|
-
|
|
253
|
-
processorPaths.
|
|
251
|
+
for (const watchPath of this.appConfig.additionalWatchPaths) {
|
|
252
|
+
processorPaths.add(watchPath);
|
|
254
253
|
}
|
|
255
|
-
|
|
254
|
+
const ignored = [
|
|
255
|
+
"**/node_modules/**",
|
|
256
|
+
"**/.git/**",
|
|
257
|
+
path.join(this.appConfig.absolutePaths.workDir, "**"),
|
|
258
|
+
path.join(this.appConfig.absolutePaths.distDir, "**")
|
|
259
|
+
];
|
|
260
|
+
this.watcher = chokidar.watch(Array.from(processorPaths), {
|
|
256
261
|
ignoreInitial: true,
|
|
257
262
|
ignorePermissionErrors: true,
|
|
263
|
+
ignored,
|
|
258
264
|
awaitWriteFinish: {
|
|
259
265
|
stabilityThreshold: 50,
|
|
260
266
|
pollInterval: 50
|
|
@@ -269,6 +275,20 @@ class ProjectWatcher {
|
|
|
269
275
|
}
|
|
270
276
|
return this.watcher;
|
|
271
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Closes the active filesystem watcher subscription.
|
|
280
|
+
*
|
|
281
|
+
* @remarks
|
|
282
|
+
* Safe to call multiple times. Used when tearing down dev servers in tests
|
|
283
|
+
* and other short-lived Ecopages runtimes.
|
|
284
|
+
*/
|
|
285
|
+
async close() {
|
|
286
|
+
if (!this.watcher) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
await this.watcher.close();
|
|
290
|
+
this.watcher = null;
|
|
291
|
+
}
|
|
272
292
|
}
|
|
273
293
|
export {
|
|
274
294
|
ProjectWatcher
|