@ecopages/core 0.2.0-alpha.1 → 0.2.0-alpha.3
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/CHANGELOG.md +2 -0
- package/package.json +2 -2
- package/src/adapters/bun/hmr-manager.d.ts +1 -0
- package/src/adapters/bun/hmr-manager.js +13 -0
- package/src/adapters/bun/hmr-manager.ts +15 -0
- package/src/adapters/node/node-hmr-manager.d.ts +1 -0
- package/src/adapters/node/node-hmr-manager.js +16 -0
- package/src/adapters/node/node-hmr-manager.ts +18 -0
- package/src/hmr/strategies/js-hmr-strategy.js +10 -1
- package/src/hmr/strategies/js-hmr-strategy.ts +13 -1
- package/src/public-types.d.ts +4 -0
- package/src/public-types.ts +5 -0
- package/src/watchers/project-watcher.d.ts +5 -0
- package/src/watchers/project-watcher.js +27 -0
- package/src/watchers/project-watcher.test-helpers.js +1 -0
- package/src/watchers/project-watcher.test-helpers.ts +1 -0
- package/src/watchers/project-watcher.ts +38 -0
package/CHANGELOG.md
CHANGED
|
@@ -65,6 +65,8 @@ All notable changes to `@ecopages/core` are documented here.
|
|
|
65
65
|
- Fixed invariant checks for route paths with improved error messaging in `AbstractApplicationAdapter` (`9c2a6242`).
|
|
66
66
|
- Fixed dependency import name extraction in `extractEcopagesVirtualImports` (`39bbc472`).
|
|
67
67
|
- Removed an invalid npm export entry that pointed to a non-existent `utils/ecopages-url-resolver` declaration target.
|
|
68
|
+
- Kept source module HMR active when stylesheet processors also watch TSX and JSX files for Tailwind-driven CSS rebuilds.
|
|
69
|
+
- Triggered HMR current-page refreshes instead of fallback reload suppression for processor-watched TSX and JSX changes that are not client entrypoints.
|
|
68
70
|
|
|
69
71
|
### Tests
|
|
70
72
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.3",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"directory": "packages/core"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ecopages/file-system": "0.2.0-alpha.
|
|
20
|
+
"@ecopages/file-system": "0.2.0-alpha.3",
|
|
21
21
|
"@ecopages/logger": "latest",
|
|
22
22
|
"@ecopages/scripts-injector": "^0.1.3",
|
|
23
23
|
"@worker-tools/html-rewriter": "0.1.0-pre.19",
|
|
@@ -58,6 +58,7 @@ export declare class HmrManager implements IHmrManager {
|
|
|
58
58
|
buildRuntime(): Promise<void>;
|
|
59
59
|
getRuntimePath(): string;
|
|
60
60
|
broadcast(event: ClientBridgeEvent): void;
|
|
61
|
+
canHandleFileChange(filePath: string): boolean;
|
|
61
62
|
/**
|
|
62
63
|
* Handles file changes using registered HMR strategies.
|
|
63
64
|
* Strategies are evaluated in priority order until one matches.
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { RESOLVED_ASSETS_DIR } from "../../constants";
|
|
4
4
|
import { defaultBuildAdapter } from "../../build/build-adapter.js";
|
|
5
5
|
import { fileSystem } from "@ecopages/file-system";
|
|
6
|
+
import { HmrStrategyType } from "../../hmr/hmr-strategy";
|
|
6
7
|
import { DefaultHmrStrategy } from "../../hmr/strategies/default-hmr-strategy";
|
|
7
8
|
import { JsHmrStrategy } from "../../hmr/strategies/js-hmr-strategy";
|
|
8
9
|
import { appLogger } from "../../global/app-logger";
|
|
@@ -121,6 +122,18 @@ class HmrManager {
|
|
|
121
122
|
);
|
|
122
123
|
this.bridge.broadcast(event);
|
|
123
124
|
}
|
|
125
|
+
canHandleFileChange(filePath) {
|
|
126
|
+
const sorted = [...this.strategies].sort((a, b) => b.priority - a.priority);
|
|
127
|
+
const strategy = sorted.find((candidate) => {
|
|
128
|
+
try {
|
|
129
|
+
return candidate.matches(filePath);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
appLogger.error(`[HmrManager] Error checking match for ${candidate.constructor.name}:`, err);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return strategy !== void 0 && strategy.type !== HmrStrategyType.FALLBACK;
|
|
136
|
+
}
|
|
124
137
|
/**
|
|
125
138
|
* Handles file changes using registered HMR strategies.
|
|
126
139
|
* Strategies are evaluated in priority order until one matches.
|
|
@@ -7,6 +7,7 @@ import type { DefaultHmrContext, EcoPagesAppConfig, IHmrManager } from '../../in
|
|
|
7
7
|
import type { EcoBuildPlugin } from '../../build/build-types.ts';
|
|
8
8
|
import { fileSystem } from '@ecopages/file-system';
|
|
9
9
|
import type { HmrStrategy } from '../../hmr/hmr-strategy';
|
|
10
|
+
import { HmrStrategyType } from '../../hmr/hmr-strategy';
|
|
10
11
|
import { DefaultHmrStrategy } from '../../hmr/strategies/default-hmr-strategy';
|
|
11
12
|
import { JsHmrStrategy } from '../../hmr/strategies/js-hmr-strategy';
|
|
12
13
|
import { appLogger } from '../../global/app-logger';
|
|
@@ -158,6 +159,20 @@ export class HmrManager implements IHmrManager {
|
|
|
158
159
|
this.bridge.broadcast(event);
|
|
159
160
|
}
|
|
160
161
|
|
|
162
|
+
public canHandleFileChange(filePath: string): boolean {
|
|
163
|
+
const sorted = [...this.strategies].sort((a, b) => b.priority - a.priority);
|
|
164
|
+
const strategy = sorted.find((candidate) => {
|
|
165
|
+
try {
|
|
166
|
+
return candidate.matches(filePath);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
appLogger.error(`[HmrManager] Error checking match for ${candidate.constructor.name}:`, err as Error);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return strategy !== undefined && strategy.type !== HmrStrategyType.FALLBACK;
|
|
174
|
+
}
|
|
175
|
+
|
|
161
176
|
/**
|
|
162
177
|
* Handles file changes using registered HMR strategies.
|
|
163
178
|
* Strategies are evaluated in priority order until one matches.
|
|
@@ -41,6 +41,7 @@ export declare class NodeHmrManager implements IHmrManager {
|
|
|
41
41
|
buildRuntime(): Promise<void>;
|
|
42
42
|
getRuntimePath(): string;
|
|
43
43
|
broadcast(event: ClientBridgeEvent): void;
|
|
44
|
+
canHandleFileChange(filePath: string): boolean;
|
|
44
45
|
handleFileChange(filePath: string): Promise<void>;
|
|
45
46
|
getOutputUrl(entrypointPath: string): string | undefined;
|
|
46
47
|
getWatchedFiles(): Map<string, string>;
|
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { RESOLVED_ASSETS_DIR } from "../../constants.js";
|
|
5
5
|
import { defaultBuildAdapter } from "../../build/build-adapter.js";
|
|
6
6
|
import { fileSystem } from "@ecopages/file-system";
|
|
7
|
+
import { HmrStrategyType } from "../../hmr/hmr-strategy.js";
|
|
7
8
|
import { DefaultHmrStrategy } from "../../hmr/strategies/default-hmr-strategy.js";
|
|
8
9
|
import { JsHmrStrategy } from "../../hmr/strategies/js-hmr-strategy.js";
|
|
9
10
|
import { appLogger } from "../../global/app-logger.js";
|
|
@@ -94,6 +95,21 @@ class NodeHmrManager {
|
|
|
94
95
|
);
|
|
95
96
|
this.bridge.broadcast(event);
|
|
96
97
|
}
|
|
98
|
+
canHandleFileChange(filePath) {
|
|
99
|
+
const sorted = [...this.strategies].sort((a, b) => b.priority - a.priority);
|
|
100
|
+
const strategy = sorted.find((candidate) => {
|
|
101
|
+
try {
|
|
102
|
+
return candidate.matches(filePath);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
appLogger.error(
|
|
105
|
+
`[NodeHmrManager] Error checking match for ${candidate.constructor.name}:`,
|
|
106
|
+
err
|
|
107
|
+
);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return strategy !== void 0 && strategy.type !== HmrStrategyType.FALLBACK;
|
|
112
|
+
}
|
|
97
113
|
async handleFileChange(filePath) {
|
|
98
114
|
const sorted = [...this.strategies].sort((a, b) => b.priority - a.priority);
|
|
99
115
|
const strategy = sorted.find((s) => {
|
|
@@ -7,6 +7,7 @@ import type { DefaultHmrContext, EcoPagesAppConfig, IHmrManager, IClientBridge }
|
|
|
7
7
|
import type { EcoBuildPlugin } from '../../build/build-types.ts';
|
|
8
8
|
import { fileSystem } from '@ecopages/file-system';
|
|
9
9
|
import type { HmrStrategy } from '../../hmr/hmr-strategy.ts';
|
|
10
|
+
import { HmrStrategyType } from '../../hmr/hmr-strategy.ts';
|
|
10
11
|
import { DefaultHmrStrategy } from '../../hmr/strategies/default-hmr-strategy.ts';
|
|
11
12
|
import { JsHmrStrategy } from '../../hmr/strategies/js-hmr-strategy.ts';
|
|
12
13
|
import { appLogger } from '../../global/app-logger.ts';
|
|
@@ -121,6 +122,23 @@ export class NodeHmrManager implements IHmrManager {
|
|
|
121
122
|
this.bridge.broadcast(event);
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
public canHandleFileChange(filePath: string): boolean {
|
|
126
|
+
const sorted = [...this.strategies].sort((a, b) => b.priority - a.priority);
|
|
127
|
+
const strategy = sorted.find((candidate) => {
|
|
128
|
+
try {
|
|
129
|
+
return candidate.matches(filePath);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
appLogger.error(
|
|
132
|
+
`[NodeHmrManager] Error checking match for ${candidate.constructor.name}:`,
|
|
133
|
+
err as Error,
|
|
134
|
+
);
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return strategy !== undefined && strategy.type !== HmrStrategyType.FALLBACK;
|
|
140
|
+
}
|
|
141
|
+
|
|
124
142
|
public async handleFileChange(filePath: string): Promise<void> {
|
|
125
143
|
const sorted = [...this.strategies].sort((a, b) => b.priority - a.priority);
|
|
126
144
|
const strategy = sorted.find((s) => {
|
|
@@ -26,7 +26,16 @@ class JsHmrStrategy extends HmrStrategy {
|
|
|
26
26
|
if (watchedFiles.size === 0) {
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
if (!isJsTs || !isInSrc) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (watchedFiles.has(filePath)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (this.context.getDependencyEntrypoints) {
|
|
36
|
+
return this.context.getDependencyEntrypoints(filePath).size > 0;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
30
39
|
}
|
|
31
40
|
/**
|
|
32
41
|
* Processes a file change by rebuilding affected entrypoints.
|
|
@@ -121,7 +121,19 @@ export class JsHmrStrategy extends HmrStrategy {
|
|
|
121
121
|
return false;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
if (!isJsTs || !isInSrc) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (watchedFiles.has(filePath)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (this.context.getDependencyEntrypoints) {
|
|
133
|
+
return this.context.getDependencyEntrypoints(filePath).size > 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return true;
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
/**
|
package/src/public-types.d.ts
CHANGED
|
@@ -178,6 +178,10 @@ export interface IHmrManager {
|
|
|
178
178
|
* Returns whether HMR is enabled.
|
|
179
179
|
*/
|
|
180
180
|
isEnabled(): boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Returns true when a changed file matches a non-fallback HMR strategy.
|
|
183
|
+
*/
|
|
184
|
+
canHandleFileChange(path: string): boolean;
|
|
181
185
|
/**
|
|
182
186
|
* Broadcasts an HMR event to connected clients.
|
|
183
187
|
*/
|
package/src/public-types.ts
CHANGED
|
@@ -209,6 +209,11 @@ export interface IHmrManager {
|
|
|
209
209
|
*/
|
|
210
210
|
isEnabled(): boolean;
|
|
211
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Returns true when a changed file matches a non-fallback HMR strategy.
|
|
214
|
+
*/
|
|
215
|
+
canHandleFileChange(path: string): boolean;
|
|
216
|
+
|
|
212
217
|
/**
|
|
213
218
|
* Broadcasts an HMR event to connected clients.
|
|
214
219
|
*/
|
|
@@ -77,6 +77,11 @@ export declare class ProjectWatcher {
|
|
|
77
77
|
* Checks if file path matches any additionalWatchPaths patterns.
|
|
78
78
|
*/
|
|
79
79
|
private matchesAdditionalWatchPaths;
|
|
80
|
+
/**
|
|
81
|
+
* Checks whether a file is watched by any processor, even if that processor
|
|
82
|
+
* does not own the file as a primary asset.
|
|
83
|
+
*/
|
|
84
|
+
private isWatchedByProcessor;
|
|
80
85
|
/**
|
|
81
86
|
* Checks if a file is handled by a processor.
|
|
82
87
|
* Processors that declare extensions own those file types.
|
|
@@ -97,6 +97,10 @@ class ProjectWatcher {
|
|
|
97
97
|
if (this.isHandledByProcessor(filePath)) {
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
|
+
if (this.isWatchedByProcessor(filePath) && !this.hmrManager.canHandleFileChange(filePath)) {
|
|
101
|
+
this.hmrManager.broadcast({ type: "layout-update" });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
100
104
|
await this.hmrManager.handleFileChange(filePath);
|
|
101
105
|
} catch (error) {
|
|
102
106
|
if (error instanceof Error) {
|
|
@@ -127,12 +131,35 @@ class ProjectWatcher {
|
|
|
127
131
|
}
|
|
128
132
|
return false;
|
|
129
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Checks whether a file is watched by any processor, even if that processor
|
|
136
|
+
* does not own the file as a primary asset.
|
|
137
|
+
*/
|
|
138
|
+
isWatchedByProcessor(filePath) {
|
|
139
|
+
for (const processor of this.appConfig.processors.values()) {
|
|
140
|
+
const watchConfig = processor.getWatchConfig();
|
|
141
|
+
if (!watchConfig) continue;
|
|
142
|
+
const { extensions = [] } = watchConfig;
|
|
143
|
+
if (extensions.length && extensions.some((ext) => filePath.endsWith(ext))) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
130
149
|
/**
|
|
131
150
|
* Checks if a file is handled by a processor.
|
|
132
151
|
* Processors that declare extensions own those file types.
|
|
133
152
|
*/
|
|
134
153
|
isHandledByProcessor(filePath) {
|
|
135
154
|
for (const processor of this.appConfig.processors.values()) {
|
|
155
|
+
const capabilities = processor.getAssetCapabilities?.() ?? [];
|
|
156
|
+
if (capabilities.length > 0) {
|
|
157
|
+
const matchesConfiguredAsset = typeof processor.matchesFileFilter !== "function" || processor.matchesFileFilter(filePath);
|
|
158
|
+
if (matchesConfiguredAsset && capabilities.some((capability) => processor.canProcessAsset?.(capability.kind, filePath))) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
136
163
|
const watchConfig = processor.getWatchConfig();
|
|
137
164
|
if (!watchConfig) continue;
|
|
138
165
|
const { extensions = [] } = watchConfig;
|
|
@@ -14,6 +14,7 @@ const createMockHmrManager = () => ({
|
|
|
14
14
|
registerStrategy: vi.fn(() => {
|
|
15
15
|
}),
|
|
16
16
|
isEnabled: vi.fn(() => true),
|
|
17
|
+
canHandleFileChange: vi.fn(() => true),
|
|
17
18
|
getOutputUrl: vi.fn(() => void 0),
|
|
18
19
|
getWatchedFiles: vi.fn(() => /* @__PURE__ */ new Map()),
|
|
19
20
|
getSpecifierMap: vi.fn(() => /* @__PURE__ */ new Map()),
|
|
@@ -12,6 +12,7 @@ export const createMockHmrManager = (): IHmrManager =>
|
|
|
12
12
|
registerSpecifierMap: vi.fn(() => {}),
|
|
13
13
|
registerStrategy: vi.fn(() => {}),
|
|
14
14
|
isEnabled: vi.fn(() => true),
|
|
15
|
+
canHandleFileChange: vi.fn(() => true),
|
|
15
16
|
getOutputUrl: vi.fn(() => undefined),
|
|
16
17
|
getWatchedFiles: vi.fn(() => new Map()),
|
|
17
18
|
getSpecifierMap: vi.fn(() => new Map()),
|
|
@@ -144,6 +144,11 @@ export class ProjectWatcher {
|
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
if (this.isWatchedByProcessor(filePath) && !this.hmrManager.canHandleFileChange(filePath)) {
|
|
148
|
+
this.hmrManager.broadcast({ type: 'layout-update' });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
147
152
|
await this.hmrManager.handleFileChange(filePath);
|
|
148
153
|
} catch (error) {
|
|
149
154
|
if (error instanceof Error) {
|
|
@@ -178,12 +183,45 @@ export class ProjectWatcher {
|
|
|
178
183
|
return false;
|
|
179
184
|
}
|
|
180
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Checks whether a file is watched by any processor, even if that processor
|
|
188
|
+
* does not own the file as a primary asset.
|
|
189
|
+
*/
|
|
190
|
+
private isWatchedByProcessor(filePath: string): boolean {
|
|
191
|
+
for (const processor of this.appConfig.processors.values()) {
|
|
192
|
+
const watchConfig = processor.getWatchConfig();
|
|
193
|
+
if (!watchConfig) continue;
|
|
194
|
+
|
|
195
|
+
const { extensions = [] } = watchConfig;
|
|
196
|
+
if (extensions.length && extensions.some((ext) => filePath.endsWith(ext))) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
181
204
|
/**
|
|
182
205
|
* Checks if a file is handled by a processor.
|
|
183
206
|
* Processors that declare extensions own those file types.
|
|
184
207
|
*/
|
|
185
208
|
private isHandledByProcessor(filePath: string): boolean {
|
|
186
209
|
for (const processor of this.appConfig.processors.values()) {
|
|
210
|
+
const capabilities = processor.getAssetCapabilities?.() ?? [];
|
|
211
|
+
if (capabilities.length > 0) {
|
|
212
|
+
const matchesConfiguredAsset =
|
|
213
|
+
typeof processor.matchesFileFilter !== 'function' || processor.matchesFileFilter(filePath);
|
|
214
|
+
|
|
215
|
+
if (
|
|
216
|
+
matchesConfiguredAsset &&
|
|
217
|
+
capabilities.some((capability) => processor.canProcessAsset?.(capability.kind, filePath))
|
|
218
|
+
) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
187
225
|
const watchConfig = processor.getWatchConfig();
|
|
188
226
|
if (!watchConfig) continue;
|
|
189
227
|
|