@ecopages/core 0.2.0-beta.2 → 0.2.0-beta.21
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 +1 -1
- package/package.json +163 -3
- package/src/adapters/abstract/application-adapter.d.ts +104 -5
- package/src/adapters/abstract/application-adapter.js +125 -1
- package/src/adapters/abstract/server-adapter.d.ts +6 -2
- package/src/adapters/abstract/ws-pattern-matcher.d.ts +60 -0
- package/src/adapters/abstract/ws-pattern-matcher.js +61 -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 +6 -4
- package/src/adapters/bun/create-app.js +65 -43
- package/src/adapters/bun/server-adapter.d.ts +90 -22
- package/src/adapters/bun/server-adapter.js +266 -84
- package/src/adapters/bun/static-preview-host.js +25 -20
- package/src/adapters/create-app.d.ts +13 -11
- package/src/adapters/create-app.js +2 -51
- package/src/adapters/index.d.ts +1 -1
- package/src/adapters/index.js +1 -2
- package/src/adapters/node/create-app.d.ts +5 -1
- package/src/adapters/node/create-app.js +51 -12
- 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-dependencies.js +4 -0
- package/src/adapters/node/server-adapter.d.ts +62 -20
- package/src/adapters/node/server-adapter.js +160 -131
- package/src/adapters/node/static-content-server.d.ts +1 -0
- package/src/adapters/node/static-content-server.js +10 -2
- package/src/adapters/node/static-preview-host.js +27 -12
- package/src/adapters/shared/bun-user-websocket-lifecycle.d.ts +22 -0
- package/src/adapters/shared/bun-user-websocket-lifecycle.js +96 -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/fs-server-response-matcher.d.ts +2 -0
- package/src/adapters/shared/fs-server-response-matcher.js +40 -18
- package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +21 -30
- package/src/adapters/shared/hmr-entrypoint-registrar.js +49 -54
- package/src/adapters/shared/hmr-html-response.d.ts +1 -1
- package/src/adapters/shared/hmr-html-response.js +4 -1
- package/src/adapters/shared/node-http-websocket-upgrades.d.ts +21 -0
- package/src/adapters/shared/node-http-websocket-upgrades.js +100 -0
- package/src/adapters/shared/port-manager.d.ts +116 -0
- package/src/adapters/shared/port-manager.js +196 -0
- package/src/adapters/shared/runtime-app-bootstrap.d.ts +2 -1
- package/src/adapters/shared/runtime-app-bootstrap.js +3 -6
- package/src/adapters/shared/runtime-server-lifecycle.d.ts +46 -0
- package/src/adapters/shared/runtime-server-lifecycle.js +50 -0
- package/src/adapters/shared/server-adapter.d.ts +2 -1
- package/src/adapters/shared/server-adapter.js +13 -6
- package/src/adapters/shared/server-route-handler.d.ts +4 -1
- package/src/adapters/shared/server-route-handler.js +5 -2
- package/src/adapters/shared/server-static-builder.d.ts +23 -32
- package/src/adapters/shared/server-static-builder.js +100 -73
- package/src/adapters/shared/shared-hmr-manager.d.ts +45 -11
- package/src/adapters/shared/shared-hmr-manager.js +204 -58
- package/src/adapters/shared/static-preview-host.d.ts +5 -0
- package/src/adapters/shared/websocket-lifecycle.d.ts +24 -0
- package/src/adapters/shared/websocket-lifecycle.js +55 -0
- package/src/build/README.md +77 -14
- package/src/build/app-build-manifest-runtime.d.ts +9 -0
- package/src/build/app-build-manifest-runtime.js +87 -0
- package/src/build/build-adapter.d.ts +17 -319
- package/src/build/build-adapter.js +28 -94
- package/src/build/build-contracts.d.ts +97 -0
- package/src/build/build-contracts.js +0 -0
- package/src/build/build-input-fingerprint.d.ts +28 -0
- package/src/build/build-input-fingerprint.js +31 -0
- package/src/build/build-profile-options.d.ts +7 -0
- package/src/build/build-profile-options.js +37 -0
- package/src/build/build-runtime.d.ts +24 -0
- package/src/build/build-runtime.js +65 -0
- package/src/build/deduping-build-executor.d.ts +28 -0
- package/src/build/deduping-build-executor.js +56 -0
- package/src/build/jsx-ownership-plugins.d.ts +24 -0
- package/src/build/jsx-ownership-plugins.js +41 -0
- package/src/build/pages-unified-graph-build.d.ts +31 -0
- package/src/build/pages-unified-graph-build.js +206 -0
- package/src/build/parallel-build-executor.d.ts +21 -0
- package/src/build/parallel-build-executor.js +52 -0
- package/src/build/production-build-cache.d.ts +20 -0
- package/src/build/production-build-cache.js +63 -0
- package/src/build/rolldown-adapter-helpers.d.ts +3 -6
- package/src/build/rolldown-adapter-helpers.js +71 -6
- package/src/build/rolldown-build-adapter.d.ts +2 -6
- package/src/build/rolldown-build-adapter.js +3 -3
- 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-plugin-bridge.d.ts +15 -1
- package/src/build/rolldown-plugin-bridge.js +20 -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 +8 -15
- package/src/build/runtime-build-executor.js +12 -15
- package/src/build/runtime-build-output-normalizer.d.ts +7 -0
- package/src/build/runtime-build-output-normalizer.js +94 -2
- package/src/build/server-entry-build-cache.d.ts +44 -0
- package/src/build/server-entry-build-cache.js +161 -0
- package/src/client/navigation-scripts/classification.d.ts +16 -0
- package/src/client/navigation-scripts/classification.js +41 -0
- package/src/client/navigation-scripts/index.d.ts +3 -0
- package/src/client/navigation-scripts/index.js +23 -0
- package/src/client/navigation-scripts/navigation-scripts.test.browser.js +65 -0
- package/src/client/navigation-scripts/registry.d.ts +11 -0
- package/src/client/navigation-scripts/registry.js +12 -0
- package/src/client/navigation-scripts/rerun-queue.d.ts +37 -0
- package/src/client/navigation-scripts/rerun-queue.js +73 -0
- package/src/client/scroll.d.ts +17 -0
- package/src/client/scroll.js +25 -0
- package/src/client/scroll.test.browser.js +37 -0
- package/src/client/view-transitions.d.ts +16 -0
- package/src/client/view-transitions.js +63 -0
- package/src/config/config-builder.d.ts +12 -7
- package/src/config/config-builder.js +12 -9
- package/src/dev/client-bridge-registry.d.ts +5 -0
- package/src/dev/client-bridge-registry.js +15 -0
- package/src/dev/dev-client-ownership.d.ts +7 -0
- package/src/dev/dev-client-ownership.js +6 -0
- package/src/dev/hmr-manager-registry.d.ts +8 -0
- package/src/dev/hmr-manager-registry.js +15 -0
- package/src/dev/host-runtime.d.ts +3 -0
- package/src/dev/host-runtime.js +7 -0
- package/src/dev/runtime-server-started-message.d.ts +2 -0
- package/src/dev/runtime-server-started-message.js +6 -0
- package/src/eco/README.md +11 -0
- package/src/eco/eco-declared-component.d.ts +14 -0
- package/src/eco/eco-declared-component.js +16 -0
- package/src/eco/eco.browser.js +5 -6
- package/src/eco/eco.js +5 -6
- package/src/eco/eco.types.d.ts +2 -2
- package/src/eco/page-layout-normalization.d.ts +21 -0
- package/src/eco/page-layout-normalization.js +59 -0
- package/src/errors/index.d.ts +1 -0
- package/src/errors/index.js +7 -1
- package/src/errors/undeclared-component-dependency-error.d.ts +11 -0
- package/src/errors/undeclared-component-dependency-error.js +17 -0
- package/src/hmr/client/hmr-runtime.js +5 -2
- package/src/hmr/hmr-entrypoint-output.d.ts +43 -0
- package/src/hmr/hmr-entrypoint-output.js +53 -0
- package/src/hmr/hmr-strategy.d.ts +15 -0
- package/src/hmr/hmr-strategy.js +19 -0
- package/src/hmr/strategies/js-hmr-strategy.d.ts +3 -5
- package/src/hmr/strategies/js-hmr-strategy.js +67 -19
- package/src/hmr/strategies/server-rendered-template-hmr-strategy.d.ts +17 -0
- package/src/hmr/strategies/server-rendered-template-hmr-strategy.js +29 -0
- package/src/integrations/ghtml/ghtml-renderer.d.ts +2 -6
- package/src/integrations/ghtml/ghtml-renderer.js +2 -51
- package/src/integrations/ghtml/ghtml.plugin.d.ts +3 -16
- package/src/integrations/ghtml/ghtml.plugin.js +7 -14
- package/src/plugins/define-integration.d.ts +26 -0
- package/src/plugins/define-integration.js +19 -0
- package/src/plugins/eco-component-meta-plugin.js +5 -8
- package/src/plugins/foreign-jsx-override-plugin.js +4 -6
- package/src/plugins/integration-plugin.d.ts +19 -9
- package/src/plugins/integration-plugin.js +7 -4
- package/src/plugins/jsx-import-source.utils.d.ts +8 -0
- package/src/plugins/jsx-import-source.utils.js +26 -0
- package/src/plugins/processor.d.ts +19 -4
- package/src/plugins/processor.js +22 -4
- package/src/plugins/source-transform.d.ts +31 -0
- package/src/plugins/source-transform.js +23 -1
- package/src/route-renderer/GRAPH.md +8 -8
- package/src/route-renderer/README.md +30 -28
- package/src/route-renderer/orchestration/component-graph-collectors.d.ts +10 -0
- package/src/route-renderer/orchestration/component-graph-collectors.js +143 -0
- package/src/route-renderer/orchestration/component-graph.d.ts +36 -0
- package/src/route-renderer/orchestration/component-graph.js +98 -0
- package/src/route-renderer/orchestration/component-render-context.d.ts +3 -10
- package/src/route-renderer/orchestration/component-render-context.js +3 -1
- package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +1 -18
- package/src/route-renderer/orchestration/declared-ownership-graph.js +5 -31
- package/src/route-renderer/orchestration/document-shell-render.service.d.ts +90 -0
- package/src/route-renderer/orchestration/document-shell-render.service.js +120 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +59 -29
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +164 -35
- package/src/route-renderer/orchestration/global-injector-assets.service.d.ts +7 -0
- package/src/route-renderer/orchestration/global-injector-assets.service.js +32 -0
- package/src/route-renderer/orchestration/integration-renderer.d.ts +18 -54
- package/src/route-renderer/orchestration/integration-renderer.js +113 -195
- package/src/route-renderer/orchestration/integration-route-render-adapter.d.ts +38 -0
- package/src/route-renderer/orchestration/integration-route-render-adapter.js +27 -0
- package/src/route-renderer/orchestration/layout-shell-props.service.d.ts +20 -0
- package/src/route-renderer/orchestration/layout-shell-props.service.js +31 -0
- package/src/route-renderer/orchestration/ownership-validation.service.d.ts +5 -0
- package/src/route-renderer/orchestration/ownership-validation.service.js +29 -1
- package/src/route-renderer/orchestration/page-browser-graph-contribution.loader.d.ts +9 -0
- package/src/route-renderer/orchestration/page-browser-graph-contribution.loader.js +7 -0
- package/src/route-renderer/orchestration/page-browser-graph.service.d.ts +25 -0
- package/src/route-renderer/orchestration/page-browser-graph.service.js +174 -0
- 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-html-finalization.service.d.ts +17 -0
- package/src/route-renderer/orchestration/route-html-finalization.service.js +26 -0
- package/src/route-renderer/orchestration/route-prepared-options.builder.d.ts +26 -0
- package/src/route-renderer/orchestration/route-prepared-options.builder.js +54 -0
- package/src/route-renderer/orchestration/route-prepared-options.utils.d.ts +4 -0
- package/src/route-renderer/orchestration/route-prepared-options.utils.js +33 -0
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +8 -39
- package/src/route-renderer/orchestration/route-render-orchestrator.js +53 -461
- package/src/route-renderer/orchestration/string-markup-renderer.d.ts +14 -0
- package/src/route-renderer/orchestration/string-markup-renderer.js +55 -0
- package/src/route-renderer/page-loading/component-dependency-collection.js +5 -2
- package/src/router/client/link-intent.d.ts +11 -21
- package/src/router/client/link-intent.js +10 -0
- package/src/router/client/link-intent.test.browser.js +12 -1
- package/src/router/client/link-navigation-policy.d.ts +45 -0
- package/src/router/client/link-navigation-policy.js +101 -0
- package/src/router/client/link-navigation-policy.test.browser.d.ts +1 -0
- package/src/router/client/link-navigation-policy.test.browser.js +92 -0
- package/src/router/client/navigation-coordinator.d.ts +1 -1
- package/src/router/client/navigation-coordinator.js +12 -2
- 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/file-script.processor.d.ts +0 -1
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +18 -15
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +17 -3
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.d.ts +0 -3
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -31
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.d.ts +0 -3
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +2 -30
- package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.d.ts +2 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.js +43 -0
- package/src/services/assets/browser-bundle.service.d.ts +3 -1
- package/src/services/assets/browser-bundle.service.js +17 -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 +20 -2
- package/src/services/invalidation/development-invalidation.service.js +56 -2
- package/src/services/module-loading/README.md +49 -0
- package/src/services/module-loading/app-server-module-transpiler.service.js +5 -17
- package/src/services/module-loading/page-module-import.service.d.ts +5 -1
- package/src/services/module-loading/page-module-import.service.js +96 -45
- 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 +207 -0
- package/src/services/module-loading/route-module-build-manifest.d.ts +61 -0
- package/src/services/module-loading/route-module-build-manifest.js +116 -0
- package/src/services/module-loading/route-module-dependency-hasher.d.ts +81 -0
- package/src/services/module-loading/route-module-dependency-hasher.js +122 -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 +59 -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 -20
- package/src/static-site-generator/static-site-generator.js +188 -85
- package/src/types/internal-types.d.ts +16 -9
- package/src/types/public-types.d.ts +144 -40
- package/src/utils/parse-cli-args.d.ts +2 -0
- package/src/utils/parse-cli-args.js +6 -0
- package/src/utils/server-utils.module.d.ts +3 -2
- package/src/utils/server-utils.module.js +5 -6
- package/src/utils/static-file-extensions.d.ts +12 -0
- package/src/utils/static-file-extensions.js +48 -0
- package/src/watchers/project-watcher-ignore.d.ts +11 -0
- package/src/watchers/project-watcher-ignore.js +16 -0
- package/src/watchers/project-watcher.d.ts +26 -3
- package/src/watchers/project-watcher.js +132 -30
- package/src/watchers/project-watcher.test-helpers.js +5 -5
- package/src/build/rolldown-dev-build-adapter.d.ts +0 -82
- package/src/build/rolldown-dev-build-adapter.js +0 -166
- package/src/hmr/hmr.postcss.test.e2e.js +0 -31
- package/src/hmr/hmr.test.e2e.js +0 -43
- package/src/route-renderer/orchestration/ownership-planning.service.d.ts +0 -24
- package/src/route-renderer/orchestration/ownership-planning.service.js +0 -63
- package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +0 -91
- package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.js +0 -170
- /package/src/{hmr/hmr.postcss.test.e2e.d.ts → client/navigation-scripts/navigation-scripts.test.browser.d.ts} +0 -0
- /package/src/{hmr/hmr.test.e2e.d.ts → client/scroll.test.browser.d.ts} +0 -0
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* Timestamped navigation intent captured from pointer or hover activity.
|
|
16
16
|
*/
|
|
17
17
|
export type EcoPendingNavigationIntent = {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
href: string;
|
|
19
|
+
timestamp: number;
|
|
20
20
|
};
|
|
21
21
|
/**
|
|
22
22
|
* Finds the nearest matching anchor within an event's composed path.
|
|
@@ -28,26 +28,16 @@ export type EcoPendingNavigationIntent = {
|
|
|
28
28
|
* @param linkSelector - Selector that identifies navigable anchors.
|
|
29
29
|
* @returns The matched anchor element, or `null` when no matching anchor exists.
|
|
30
30
|
*/
|
|
31
|
-
export declare function getAnchorFromNavigationEvent(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
export declare function getAnchorFromNavigationEvent(event: MouseEvent | PointerEvent, linkSelector: string): HTMLAnchorElement | null;
|
|
32
|
+
/**
|
|
33
|
+
* Returns whether an href targets a static asset that should use a full document load.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isStaticAssetHref(href: string): boolean;
|
|
35
36
|
/**
|
|
36
37
|
* Resolves a previously captured intent while a navigation is still in flight.
|
|
37
38
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* @param intent - Previously captured pointer or hover intent.
|
|
43
|
-
* @param hasInFlightNavigation - Whether a router navigation is still active.
|
|
44
|
-
* @param now - Current monotonic timestamp, usually from `performance.now()`.
|
|
45
|
-
* @param maxAgeMs - Maximum allowed age for the recovered intent.
|
|
46
|
-
* @returns The intended href when still valid, otherwise `null`.
|
|
39
|
+
* @remarks
|
|
40
|
+
* Pending intents expire quickly because they bridge pointer or hover capture
|
|
41
|
+
* and the later click when the DOM changes during a rapid navigation sequence.
|
|
47
42
|
*/
|
|
48
|
-
export declare function recoverPendingNavigationHref(
|
|
49
|
-
intent: EcoPendingNavigationIntent | null,
|
|
50
|
-
hasInFlightNavigation: boolean,
|
|
51
|
-
now: number,
|
|
52
|
-
maxAgeMs?: number,
|
|
53
|
-
): string | null;
|
|
43
|
+
export declare function recoverPendingNavigationHref(intent: EcoPendingNavigationIntent | null, hasInFlightNavigation: boolean, now: number, maxAgeMs?: number): string | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasKnownStaticExtension } from "../../utils/static-file-extensions.js";
|
|
1
2
|
function getAnchorFromNavigationEvent(event, linkSelector) {
|
|
2
3
|
const eventTarget = event.target;
|
|
3
4
|
if (eventTarget instanceof HTMLAnchorElement && eventTarget.matches(linkSelector)) {
|
|
@@ -19,6 +20,14 @@ function getAnchorFromNavigationEvent(event, linkSelector) {
|
|
|
19
20
|
(target) => target instanceof HTMLAnchorElement && target.matches(linkSelector)
|
|
20
21
|
);
|
|
21
22
|
}
|
|
23
|
+
function isStaticAssetHref(href) {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(href, "http://localhost");
|
|
26
|
+
return hasKnownStaticExtension(url.pathname);
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
22
31
|
function recoverPendingNavigationHref(intent, hasInFlightNavigation, now, maxAgeMs = 1e3) {
|
|
23
32
|
if (!intent || !hasInFlightNavigation) {
|
|
24
33
|
return null;
|
|
@@ -30,5 +39,6 @@ function recoverPendingNavigationHref(intent, hasInFlightNavigation, now, maxAge
|
|
|
30
39
|
}
|
|
31
40
|
export {
|
|
32
41
|
getAnchorFromNavigationEvent,
|
|
42
|
+
isStaticAssetHref,
|
|
33
43
|
recoverPendingNavigationHref
|
|
34
44
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { getAnchorFromNavigationEvent, recoverPendingNavigationHref } from "./link-intent.js";
|
|
2
|
+
import { getAnchorFromNavigationEvent, isStaticAssetHref, recoverPendingNavigationHref } from "./link-intent.js";
|
|
3
3
|
describe("getAnchorFromNavigationEvent", () => {
|
|
4
4
|
it("returns the anchor when the event target is a text node inside it", () => {
|
|
5
5
|
const anchor = document.createElement("a");
|
|
@@ -31,6 +31,17 @@ describe("getAnchorFromNavigationEvent", () => {
|
|
|
31
31
|
expect(getAnchorFromNavigationEvent(event, "a[data-eco-link]")).toBe(anchor);
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
|
+
describe("isStaticAssetHref", () => {
|
|
35
|
+
it("returns true for known static asset paths", () => {
|
|
36
|
+
expect(isStaticAssetHref("/skill.txt")).toBe(true);
|
|
37
|
+
expect(isStaticAssetHref("/skill/reference/full-stack.md")).toBe(true);
|
|
38
|
+
expect(isStaticAssetHref("/assets/logo.png")).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it("returns false for application routes", () => {
|
|
41
|
+
expect(isStaticAssetHref("/docs/getting-started/introduction")).toBe(false);
|
|
42
|
+
expect(isStaticAssetHref("/")).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
34
45
|
describe("recoverPendingNavigationHref", () => {
|
|
35
46
|
it("returns null for stale or missing pending intent state", () => {
|
|
36
47
|
expect(recoverPendingNavigationHref(null, true, 10)).toBeNull();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical link interception, prefetch eligibility, and HTML fetch guards.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
export type LinkNavigationPolicyOptions = {
|
|
6
|
+
/** Attribute that forces a full document reload instead of SPA navigation. */
|
|
7
|
+
reloadAttribute?: string;
|
|
8
|
+
/** Attribute that opts a link out of prefetch. */
|
|
9
|
+
noPrefetchAttribute?: string;
|
|
10
|
+
};
|
|
11
|
+
export type LinkNavigationDecision = {
|
|
12
|
+
shouldIntercept: true;
|
|
13
|
+
href: string;
|
|
14
|
+
} | {
|
|
15
|
+
shouldIntercept: false;
|
|
16
|
+
reason: 'modified-click' | 'non-left-click' | 'external-target' | 'explicit-reload' | 'download' | 'no-prefetch' | 'invalid-href' | 'cross-origin' | 'static-asset' | 'same-page-hash';
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Returns whether an href only adds a hash fragment on the current page.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isSamePageHashNavigationHref(href: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Decides whether a pointer or click on an anchor should be handled as SPA navigation.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getLinkNavigationDecision(event: MouseEvent | PointerEvent, link: HTMLAnchorElement, options: LinkNavigationPolicyOptions): LinkNavigationDecision;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the href when SPA navigation should intercept the click, otherwise `null`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getNavigableHrefFromClick(event: MouseEvent | PointerEvent, link: HTMLAnchorElement, options: LinkNavigationPolicyOptions): string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Returns whether a link is eligible for hover or viewport prefetch.
|
|
32
|
+
*/
|
|
33
|
+
export declare function shouldPrefetchLink(link: HTMLAnchorElement, options: LinkNavigationPolicyOptions): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* @remarks
|
|
36
|
+
* Prefetch and SPA fetches request HTML. Only explicit `text/html` and
|
|
37
|
+
* `application/xhtml+xml` responses are accepted; missing or asset types are rejected.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isHtmlPageResponse(response: Response): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* @remarks
|
|
42
|
+
* Servers should send `text/html`. When the type is missing or the runtime
|
|
43
|
+
* defaults to `text/plain` (common in tests), a document-shaped body is accepted.
|
|
44
|
+
*/
|
|
45
|
+
export declare function assertHtmlPageResponse(response: Response): Promise<void>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { isStaticAssetHref } from "./link-intent.js";
|
|
2
|
+
function getLinkIneligibilityReason(link, options) {
|
|
3
|
+
if (options.noPrefetchAttribute && link.hasAttribute(options.noPrefetchAttribute)) {
|
|
4
|
+
return "no-prefetch";
|
|
5
|
+
}
|
|
6
|
+
if (options.reloadAttribute && link.hasAttribute(options.reloadAttribute)) {
|
|
7
|
+
return "explicit-reload";
|
|
8
|
+
}
|
|
9
|
+
if (link.hasAttribute("download")) {
|
|
10
|
+
return "download";
|
|
11
|
+
}
|
|
12
|
+
const href = link.getAttribute("href");
|
|
13
|
+
if (!href || href.startsWith("#") || href.startsWith("javascript:")) {
|
|
14
|
+
return "invalid-href";
|
|
15
|
+
}
|
|
16
|
+
const url = new URL(href, window.location.origin);
|
|
17
|
+
if (url.origin !== window.location.origin) {
|
|
18
|
+
return "cross-origin";
|
|
19
|
+
}
|
|
20
|
+
if (isStaticAssetHref(href)) {
|
|
21
|
+
return "static-asset";
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function isSamePageHashNavigationHref(href) {
|
|
26
|
+
if (!href) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const currentUrl = new URL(window.location.href);
|
|
30
|
+
const targetUrl = new URL(href, currentUrl);
|
|
31
|
+
return targetUrl.origin === currentUrl.origin && targetUrl.hash.length > 0 && targetUrl.pathname === currentUrl.pathname && targetUrl.search === currentUrl.search;
|
|
32
|
+
}
|
|
33
|
+
function getLinkNavigationDecision(event, link, options) {
|
|
34
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
35
|
+
return { shouldIntercept: false, reason: "modified-click" };
|
|
36
|
+
}
|
|
37
|
+
if (event.button !== 0) {
|
|
38
|
+
return { shouldIntercept: false, reason: "non-left-click" };
|
|
39
|
+
}
|
|
40
|
+
const target = link.getAttribute("target");
|
|
41
|
+
if (target && target !== "_self") {
|
|
42
|
+
return { shouldIntercept: false, reason: "external-target" };
|
|
43
|
+
}
|
|
44
|
+
const ineligibility = getLinkIneligibilityReason(link, options);
|
|
45
|
+
if (ineligibility) {
|
|
46
|
+
return { shouldIntercept: false, reason: ineligibility };
|
|
47
|
+
}
|
|
48
|
+
const href = link.getAttribute("href");
|
|
49
|
+
if (isSamePageHashNavigationHref(href)) {
|
|
50
|
+
return { shouldIntercept: false, reason: "same-page-hash" };
|
|
51
|
+
}
|
|
52
|
+
return { shouldIntercept: true, href };
|
|
53
|
+
}
|
|
54
|
+
function getNavigableHrefFromClick(event, link, options) {
|
|
55
|
+
const decision = getLinkNavigationDecision(event, link, options);
|
|
56
|
+
return decision.shouldIntercept ? decision.href : null;
|
|
57
|
+
}
|
|
58
|
+
function shouldPrefetchLink(link, options) {
|
|
59
|
+
if (getLinkIneligibilityReason(link, options)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const href = link.getAttribute("href");
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(href, window.location.origin);
|
|
65
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
66
|
+
const targetPath = url.pathname + url.search;
|
|
67
|
+
return currentPath !== targetPath;
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function isHtmlPageResponse(response) {
|
|
73
|
+
const contentType = response.headers.get("Content-Type");
|
|
74
|
+
if (!contentType) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const normalized = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
78
|
+
return normalized === "text/html" || normalized === "application/xhtml+xml";
|
|
79
|
+
}
|
|
80
|
+
async function assertHtmlPageResponse(response) {
|
|
81
|
+
if (isHtmlPageResponse(response)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const contentType = response.headers.get("Content-Type");
|
|
85
|
+
const normalized = contentType?.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
86
|
+
if (normalized === "text/plain" || normalized === "") {
|
|
87
|
+
const sample = (await response.clone().text()).trimStart();
|
|
88
|
+
if (sample.startsWith("<")) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw new Error(`Expected HTML page response, received ${contentType ?? "unknown content type"}`);
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
assertHtmlPageResponse,
|
|
96
|
+
getLinkNavigationDecision,
|
|
97
|
+
getNavigableHrefFromClick,
|
|
98
|
+
isHtmlPageResponse,
|
|
99
|
+
isSamePageHashNavigationHref,
|
|
100
|
+
shouldPrefetchLink
|
|
101
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
getLinkNavigationDecision,
|
|
4
|
+
getNavigableHrefFromClick,
|
|
5
|
+
isHtmlPageResponse,
|
|
6
|
+
isSamePageHashNavigationHref,
|
|
7
|
+
shouldPrefetchLink
|
|
8
|
+
} from "./link-navigation-policy.js";
|
|
9
|
+
function createLink(href, attributes = {}) {
|
|
10
|
+
const link = document.createElement("a");
|
|
11
|
+
link.href = href;
|
|
12
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
13
|
+
link.setAttribute(key, value);
|
|
14
|
+
}
|
|
15
|
+
document.body.append(link);
|
|
16
|
+
return link;
|
|
17
|
+
}
|
|
18
|
+
function createMouseEvent(button = 0, modifiers = {}) {
|
|
19
|
+
return new MouseEvent("click", { bubbles: true, cancelable: true, button, ...modifiers });
|
|
20
|
+
}
|
|
21
|
+
const policy = { reloadAttribute: "data-eco-reload", noPrefetchAttribute: "data-eco-no-prefetch" };
|
|
22
|
+
describe("isSamePageHashNavigationHref", () => {
|
|
23
|
+
it("returns true for same pathname with a new hash", () => {
|
|
24
|
+
history.replaceState({}, "", "/docs/page");
|
|
25
|
+
expect(isSamePageHashNavigationHref("/docs/page#section")).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe("getLinkNavigationDecision", () => {
|
|
29
|
+
it("allows same-origin page links", () => {
|
|
30
|
+
const link = createLink("/page");
|
|
31
|
+
const decision = getLinkNavigationDecision(createMouseEvent(), link, policy);
|
|
32
|
+
expect(decision).toEqual({ shouldIntercept: true, href: "/page" });
|
|
33
|
+
});
|
|
34
|
+
it("rejects static asset links", () => {
|
|
35
|
+
const link = createLink("/skill.txt");
|
|
36
|
+
expect(getLinkNavigationDecision(createMouseEvent(), link, policy)).toEqual({
|
|
37
|
+
shouldIntercept: false,
|
|
38
|
+
reason: "static-asset"
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it("rejects explicit reload links", () => {
|
|
42
|
+
const link = createLink("/logout", { "data-eco-reload": "" });
|
|
43
|
+
expect(getLinkNavigationDecision(createMouseEvent(), link, policy)).toEqual({
|
|
44
|
+
shouldIntercept: false,
|
|
45
|
+
reason: "explicit-reload"
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("getNavigableHrefFromClick", () => {
|
|
50
|
+
it("returns href when navigation should be intercepted", () => {
|
|
51
|
+
const link = createLink("/page");
|
|
52
|
+
expect(getNavigableHrefFromClick(createMouseEvent(), link, policy)).toBe("/page");
|
|
53
|
+
});
|
|
54
|
+
it("returns null for static assets", () => {
|
|
55
|
+
const link = createLink("/llms.txt");
|
|
56
|
+
expect(getNavigableHrefFromClick(createMouseEvent(), link, policy)).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("shouldPrefetchLink", () => {
|
|
60
|
+
it("prefetches internal page links", () => {
|
|
61
|
+
history.replaceState({}, "", "/");
|
|
62
|
+
const link = createLink("/docs/intro");
|
|
63
|
+
expect(shouldPrefetchLink(link, policy)).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it("skips static assets and opt-outs", () => {
|
|
66
|
+
history.replaceState({}, "", "/");
|
|
67
|
+
expect(shouldPrefetchLink(createLink("/skill.txt"), policy)).toBe(false);
|
|
68
|
+
expect(shouldPrefetchLink(createLink("/page", { "data-eco-no-prefetch": "" }), policy)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe("isHtmlPageResponse", () => {
|
|
72
|
+
it("accepts html content types", () => {
|
|
73
|
+
expect(isHtmlPageResponse(new Response("", { headers: { "Content-Type": "text/html; charset=utf-8" } }))).toBe(
|
|
74
|
+
true
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
it("rejects plain text responses", () => {
|
|
78
|
+
expect(isHtmlPageResponse(new Response("", { headers: { "Content-Type": "text/plain" } }))).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe("isHtmlPageResponse", () => {
|
|
82
|
+
it("accepts html and xhtml content types", () => {
|
|
83
|
+
expect(isHtmlPageResponse(new Response("", { headers: { "Content-Type": "text/html" } }))).toBe(true);
|
|
84
|
+
expect(isHtmlPageResponse(new Response("", { headers: { "Content-Type": "application/xhtml+xml" } }))).toBe(
|
|
85
|
+
true
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
it("rejects plain text and markdown responses", () => {
|
|
89
|
+
expect(isHtmlPageResponse(new Response("text", { headers: { "Content-Type": "text/plain" } }))).toBe(false);
|
|
90
|
+
expect(isHtmlPageResponse(new Response("# doc", { headers: { "Content-Type": "text/markdown" } }))).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -96,7 +96,7 @@ export type EcoNavigationRuntimeRegistration = {
|
|
|
96
96
|
* Implementations may honor `request.moduleUrl` to force a specific page entry
|
|
97
97
|
* and `request.clearCache` to discard persisted runtime state before reloading.
|
|
98
98
|
*/
|
|
99
|
-
reloadCurrentPage?: (request?: EcoReloadRequest) => Promise<void>;
|
|
99
|
+
reloadCurrentPage?: (request?: EcoReloadRequest) => Promise<boolean | void>;
|
|
100
100
|
/**
|
|
101
101
|
* Releases runtime-owned client state before another runtime commits a new
|
|
102
102
|
* document.
|
|
@@ -176,8 +176,8 @@ function createEcoNavigationRuntime() {
|
|
|
176
176
|
if (!registration?.reloadCurrentPage) {
|
|
177
177
|
continue;
|
|
178
178
|
}
|
|
179
|
-
await registration.reloadCurrentPage(request);
|
|
180
|
-
return
|
|
179
|
+
const handled = await registration.reloadCurrentPage(request);
|
|
180
|
+
return handled !== false;
|
|
181
181
|
}
|
|
182
182
|
return false;
|
|
183
183
|
},
|
|
@@ -198,6 +198,16 @@ function createEcoNavigationRuntime() {
|
|
|
198
198
|
await runtime.cleanupOwner(owner);
|
|
199
199
|
}
|
|
200
200
|
};
|
|
201
|
+
if (typeof window !== "undefined") {
|
|
202
|
+
window.addEventListener("pageshow", (event) => {
|
|
203
|
+
if (event.persisted) {
|
|
204
|
+
runtime.cancelCurrentNavigationTransaction();
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
window.addEventListener("pagehide", () => {
|
|
208
|
+
runtime.cancelCurrentNavigationTransaction();
|
|
209
|
+
});
|
|
210
|
+
}
|
|
201
211
|
return runtime;
|
|
202
212
|
}
|
|
203
213
|
function getEcoNavigationRuntime(windowObject = window) {
|
|
@@ -31,6 +31,7 @@ function createBrowserRuntimeModuleAsset(options) {
|
|
|
31
31
|
modules: options.modules,
|
|
32
32
|
fileName: `${options.name}-entry.mjs`,
|
|
33
33
|
rootDir: options.rootDir,
|
|
34
|
+
workDir: options.workDir,
|
|
34
35
|
cacheDirName: options.cacheDirName
|
|
35
36
|
});
|
|
36
37
|
return createBrowserRuntimeScriptAsset({
|
|
@@ -9,12 +9,14 @@ export type BrowserRuntimeEntryModuleConfig = {
|
|
|
9
9
|
* @remarks
|
|
10
10
|
* Integrations use this helper when they need a browser runtime asset composed
|
|
11
11
|
* from multiple bare specifiers but do not want to own temporary file assembly
|
|
12
|
-
* logic themselves. The generated file lives under
|
|
13
|
-
*
|
|
12
|
+
* logic themselves. The generated file lives under the app work directory so
|
|
13
|
+
* repeated runs can reuse the same location without placing sources inside
|
|
14
|
+
* `node_modules`, which can cause bundlers to externalize bare imports.
|
|
14
15
|
*/
|
|
15
16
|
export declare function createBrowserRuntimeEntryModule(options: {
|
|
16
17
|
modules: BrowserRuntimeEntryModuleConfig[];
|
|
17
18
|
fileName: string;
|
|
18
19
|
rootDir?: string;
|
|
20
|
+
workDir?: string;
|
|
19
21
|
cacheDirName?: string;
|
|
20
22
|
}): string;
|
|
@@ -1,37 +1,57 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
import { DEFAULT_ECOPAGES_WORK_DIR } from "../../../config/constants.js";
|
|
4
5
|
function createBrowserRuntimeEntryModule(options) {
|
|
6
|
+
if (options.modules.some((module) => !module.specifier.startsWith("node:")) && !options.rootDir) {
|
|
7
|
+
throw new Error("createBrowserRuntimeEntryModule requires rootDir to resolve package specifiers");
|
|
8
|
+
}
|
|
5
9
|
const rootDir = options.rootDir ?? process.cwd();
|
|
10
|
+
const workDir = options.workDir ?? path.join(rootDir, DEFAULT_ECOPAGES_WORK_DIR);
|
|
6
11
|
const artifactsDir = path.join(
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
".cache",
|
|
12
|
+
workDir,
|
|
13
|
+
".browser-runtime-entries",
|
|
10
14
|
options.cacheDirName ?? "ecopages-browser-runtime"
|
|
11
15
|
);
|
|
12
16
|
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
13
17
|
const requireFromRoot = createRequire(path.join(rootDir, "package.json"));
|
|
14
18
|
const seenExports = /* @__PURE__ */ new Set();
|
|
15
19
|
const statements = [];
|
|
20
|
+
const filePath = path.join(artifactsDir, options.fileName);
|
|
21
|
+
const entryDir = path.dirname(filePath);
|
|
16
22
|
for (const module of options.modules) {
|
|
23
|
+
const importSpecifier = resolveEntryImportSpecifier(module.specifier, requireFromRoot, entryDir);
|
|
17
24
|
if (module.defaultExport) {
|
|
18
|
-
statements.push(`import __ecopages_default_export__ from '${
|
|
25
|
+
statements.push(`import __ecopages_default_export__ from '${importSpecifier}';`);
|
|
19
26
|
statements.push("export default __ecopages_default_export__;");
|
|
20
27
|
}
|
|
21
28
|
const exportNames = getModuleExportNames(module.specifier, requireFromRoot).filter(
|
|
22
29
|
(name) => !seenExports.has(name)
|
|
23
30
|
);
|
|
24
31
|
if (exportNames.length > 0) {
|
|
25
|
-
statements.push(`export { ${exportNames.join(", ")} } from '${
|
|
32
|
+
statements.push(`export { ${exportNames.join(", ")} } from '${importSpecifier}';`);
|
|
26
33
|
for (const exportName of exportNames) {
|
|
27
34
|
seenExports.add(exportName);
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
37
|
}
|
|
31
|
-
const
|
|
32
|
-
fs.
|
|
38
|
+
const content = statements.join("\n");
|
|
39
|
+
if (!fs.existsSync(filePath) || fs.readFileSync(filePath, "utf-8") !== content) {
|
|
40
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
41
|
+
}
|
|
33
42
|
return filePath;
|
|
34
43
|
}
|
|
44
|
+
function resolveEntryImportSpecifier(specifier, requireFromRoot, entryDir) {
|
|
45
|
+
if (specifier.startsWith("node:") || specifier.startsWith("file:")) {
|
|
46
|
+
return specifier;
|
|
47
|
+
}
|
|
48
|
+
const resolvedPath = requireFromRoot.resolve(specifier);
|
|
49
|
+
let relativePath = path.relative(entryDir, resolvedPath).replace(/\\/g, "/");
|
|
50
|
+
if (!relativePath.startsWith(".")) {
|
|
51
|
+
relativePath = `./${relativePath}`;
|
|
52
|
+
}
|
|
53
|
+
return relativePath;
|
|
54
|
+
}
|
|
35
55
|
function getModuleExportNames(specifier, requireFromRoot) {
|
|
36
56
|
const moduleExports = requireFromRoot(specifier);
|
|
37
57
|
return Object.keys(moduleExports).filter((name) => name !== "__esModule" && name !== "default").filter((name) => /^[$A-Z_a-z][$\w]*$/.test(name)).sort();
|
package/src/services/assets/asset-processing-service/processors/script/file-script.processor.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { FileScriptAsset, ProcessedAsset } from '../../assets.types.js';
|
|
|
3
3
|
import { BaseScriptProcessor } from '../base/base-script-processor.js';
|
|
4
4
|
export declare class FileScriptProcessor extends BaseScriptProcessor<FileScriptAsset> {
|
|
5
5
|
private hmrManager?;
|
|
6
|
-
private resolveHmrOutputFilepath;
|
|
7
6
|
setHmrManager(hmrManager: IHmrManager): void;
|
|
8
7
|
process(dep: FileScriptAsset): Promise<ProcessedAsset>;
|
|
9
8
|
}
|
package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js
CHANGED
|
@@ -4,28 +4,31 @@ import { fileSystem } from "@ecopages/file-system";
|
|
|
4
4
|
import { BaseScriptProcessor } from "../base/base-script-processor.js";
|
|
5
5
|
class FileScriptProcessor extends BaseScriptProcessor {
|
|
6
6
|
hmrManager;
|
|
7
|
-
resolveHmrOutputFilepath(entrypointPath) {
|
|
8
|
-
if (!this.hmrManager || !("getDistDir" in this.hmrManager)) {
|
|
9
|
-
return void 0;
|
|
10
|
-
}
|
|
11
|
-
const getDistDir = this.hmrManager.getDistDir;
|
|
12
|
-
if (typeof getDistDir !== "function") {
|
|
13
|
-
return void 0;
|
|
14
|
-
}
|
|
15
|
-
const relativePathJs = path.relative(this.appConfig.absolutePaths.srcDir, entrypointPath).replace(/\.(tsx?|jsx?|mdx?)$/, ".js").replace(/\[([^\]]+)\]/g, "_$1_");
|
|
16
|
-
return path.join(getDistDir.call(this.hmrManager), relativePathJs);
|
|
17
|
-
}
|
|
18
7
|
setHmrManager(hmrManager) {
|
|
19
8
|
this.hmrManager = hmrManager;
|
|
20
9
|
}
|
|
21
10
|
async process(dep) {
|
|
22
11
|
if (this.hmrManager?.isEnabled() && !dep.inline) {
|
|
23
|
-
const
|
|
24
|
-
|
|
12
|
+
const resolvedOutput = this.hmrManager.getResolvedScriptOutput?.(dep.filepath);
|
|
13
|
+
if (resolvedOutput) {
|
|
14
|
+
return {
|
|
15
|
+
filepath: resolvedOutput.outputPath,
|
|
16
|
+
sourceFilepath: dep.filepath,
|
|
17
|
+
srcUrl: resolvedOutput.outputUrl,
|
|
18
|
+
kind: "script",
|
|
19
|
+
position: dep.position,
|
|
20
|
+
attributes: dep.attributes,
|
|
21
|
+
inline: false,
|
|
22
|
+
excludeFromHtml: dep.excludeFromHtml,
|
|
23
|
+
packageRole: dep.packageRole,
|
|
24
|
+
bundledSourceFilepaths: dep.bundledSourceFilepaths
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const registered = await this.hmrManager.registerScriptEntrypoint(dep.filepath);
|
|
25
28
|
return {
|
|
26
|
-
filepath:
|
|
29
|
+
filepath: registered.outputPath,
|
|
27
30
|
sourceFilepath: dep.filepath,
|
|
28
|
-
srcUrl: outputUrl,
|
|
31
|
+
srcUrl: registered.outputUrl,
|
|
29
32
|
kind: "script",
|
|
30
33
|
position: dep.position,
|
|
31
34
|
attributes: dep.attributes,
|
|
@@ -8,19 +8,33 @@ class NodeModuleScriptProcessor extends BaseScriptProcessor {
|
|
|
8
8
|
const modulePath = this.resolveModulePath(dep.importPath, this.appConfig.rootDir);
|
|
9
9
|
const moduleName = path.basename(modulePath);
|
|
10
10
|
const filename = dep.name ?? `nm-${moduleName}`;
|
|
11
|
+
const shouldBundle = this.shouldBundle(dep);
|
|
11
12
|
const configHash = this.generateHash(
|
|
12
|
-
JSON.stringify({
|
|
13
|
+
JSON.stringify({
|
|
14
|
+
bundle: shouldBundle,
|
|
15
|
+
inline: dep.inline,
|
|
16
|
+
minify: shouldBundle && !dep.inline && this.isProduction,
|
|
17
|
+
opts: dep.bundleOptions
|
|
18
|
+
})
|
|
13
19
|
);
|
|
14
20
|
const cachekey = `${this.buildCacheKey(filename, this.generateHash(modulePath), dep)}:${configHash}`;
|
|
15
21
|
return this.getOrProcess(cachekey, async () => {
|
|
16
|
-
if (dep.inline) {
|
|
22
|
+
if (dep.inline || !shouldBundle) {
|
|
17
23
|
const content = fileSystem.readFileAsBuffer(modulePath).toString();
|
|
24
|
+
const filepath = dep.inline ? void 0 : path.join(this.getAssetsDir(), "vendors", filename);
|
|
25
|
+
if (filepath) {
|
|
26
|
+
fileSystem.ensureDir(path.dirname(filepath));
|
|
27
|
+
if (!fileSystem.exists(filepath) || fileSystem.readFileAsBuffer(filepath).toString() !== content) {
|
|
28
|
+
fileSystem.copyFile(modulePath, filepath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
18
31
|
return {
|
|
32
|
+
filepath,
|
|
19
33
|
content,
|
|
20
34
|
kind: dep.kind,
|
|
21
35
|
position: dep.position,
|
|
22
36
|
attributes: dep.attributes,
|
|
23
|
-
inline:
|
|
37
|
+
inline: dep.inline,
|
|
24
38
|
excludeFromHtml: dep.excludeFromHtml,
|
|
25
39
|
packageRole: dep.packageRole,
|
|
26
40
|
bundledSourceFilepaths: dep.bundledSourceFilepaths
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { ContentStylesheetAsset, ProcessedAsset } from '../../assets.types.js';
|
|
2
2
|
import { BaseProcessor } from '../base/base-processor.js';
|
|
3
3
|
export declare class ContentStylesheetProcessor extends BaseProcessor<ContentStylesheetAsset> {
|
|
4
|
-
private static readonly PROCESSABLE_STYLESHEET_EXTENSIONS;
|
|
5
|
-
private isProcessableStylesheet;
|
|
6
|
-
private applyStylesheetProcessors;
|
|
7
4
|
process(dep: ContentStylesheetAsset): Promise<ProcessedAsset>;
|
|
8
5
|
}
|
|
@@ -1,40 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileSystem } from "@ecopages/file-system";
|
|
3
3
|
import { BaseProcessor } from "../base/base-processor.js";
|
|
4
|
+
import { applyStylesheetProcessors } from "./stylesheet-processor-pipeline.js";
|
|
4
5
|
class ContentStylesheetProcessor extends BaseProcessor {
|
|
5
|
-
static PROCESSABLE_STYLESHEET_EXTENSIONS = /* @__PURE__ */ new Set([".css", ".scss", ".sass", ".less"]);
|
|
6
|
-
isProcessableStylesheet(filepath) {
|
|
7
|
-
return ContentStylesheetProcessor.PROCESSABLE_STYLESHEET_EXTENSIONS.has(path.extname(filepath));
|
|
8
|
-
}
|
|
9
|
-
async applyStylesheetProcessors(content, filepath) {
|
|
10
|
-
if (!this.isProcessableStylesheet(filepath)) {
|
|
11
|
-
return content;
|
|
12
|
-
}
|
|
13
|
-
let transformedContent = content;
|
|
14
|
-
const processors = this.appConfig.processors ? Array.from(this.appConfig.processors.values()) : [];
|
|
15
|
-
for (const processor of processors) {
|
|
16
|
-
const hasCapabilities = processor.getAssetCapabilities().length > 0;
|
|
17
|
-
const canProcessStylesheet = processor.canProcessAsset("stylesheet", filepath);
|
|
18
|
-
if (!canProcessStylesheet && (hasCapabilities || !processor.getName().includes("postcss"))) {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
if (!processor.matchesFileFilter(filepath)) {
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
const result = await processor.process(transformedContent, filepath);
|
|
25
|
-
if (typeof result === "string") {
|
|
26
|
-
transformedContent = result;
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (result instanceof Buffer) {
|
|
30
|
-
transformedContent = result.toString();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return transformedContent;
|
|
34
|
-
}
|
|
35
6
|
async process(dep) {
|
|
36
7
|
const virtualFilepath = path.join(this.appConfig.absolutePaths.distDir, "styles", "page-bundle.css");
|
|
37
|
-
const processedContent = await this.
|
|
8
|
+
const processedContent = await applyStylesheetProcessors(this.appConfig, dep.content, virtualFilepath);
|
|
38
9
|
const hash = this.generateHash(processedContent);
|
|
39
10
|
const filename = `style-${hash}.css`;
|
|
40
11
|
const cachekey = this.buildCacheKey(filename, hash, dep);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { FileStylesheetAsset, ProcessedAsset } from '../../assets.types.js';
|
|
2
2
|
import { BaseProcessor } from '../base/base-processor.js';
|
|
3
3
|
export declare class FileStylesheetProcessor extends BaseProcessor<FileStylesheetAsset> {
|
|
4
|
-
private static readonly PROCESSABLE_STYLESHEET_EXTENSIONS;
|
|
5
4
|
getStyleContent: (srcUrl: string) => Buffer;
|
|
6
|
-
private isProcessableStylesheet;
|
|
7
|
-
private applyStylesheetProcessors;
|
|
8
5
|
process(dep: FileStylesheetAsset): Promise<ProcessedAsset>;
|
|
9
6
|
}
|