@utoo/web 1.2.8 → 1.3.0-rc.2

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 CHANGED
@@ -9,7 +9,8 @@
9
9
  - 📂 **Real File System**: Uses Origin Private File System (OPFS) for a Node.js-like file system experience.
10
10
  - 📦 **Browser Dependency Resolution**: Resolve dependencies directly from `package.json` without needing a pre-existing lock file. Supports custom registries (npm, npmmirror, private registries).
11
11
  - 🌐 **Browser-based Bundling**: Run the Utoo bundler directly in the browser.
12
- - 🔌 **Webpack Compatibility**: Supports a subset of Webpack configurations in the browser. See [Features List](../pack/docs/features-list.md) for details.
12
+ - **Hot Module Replacement (HMR)**: Full HMR support in the browser via MessagePort communication with preview iframes.
13
+ - �🔌 **Webpack Compatibility**: Supports a subset of Webpack configurations in the browser. See [Features List](../pack/docs/features-list.md) for details.
13
14
  - 🔄 **Webpack Loaders**: Compatible with standard Webpack loaders (css-loader, style-loader, etc.).
14
15
  - 🎨 **PostCSS & Tailwind**: Support for PostCSS and Tailwind CSS processing.
15
16
 
@@ -0,0 +1,89 @@
1
+ /**
2
+ * HmrServer - Simulates a WebSocket server using MessagePort for HMR communication.
3
+ *
4
+ * This class manages HMR connections with preview iframes, forwarding Turbopack
5
+ * HMR events from the main project to the preview iframe's HMR client.
6
+ *
7
+ * Architecture:
8
+ * - Main Thread (Project) -> HmrServer -> MessagePort -> Preview Iframe (HMR Client)
9
+ */
10
+ import type { HMR_ACTION_TYPES, TurbopackUpdate } from "@utoo/pack-shared";
11
+ export interface HmrServerOptions {
12
+ /** Session ID for this HMR server instance */
13
+ sessionId?: number;
14
+ /** Callback when a client subscribes to an HMR path */
15
+ onSubscribe?: (path: string, client: HmrClient) => void;
16
+ /** Callback when a client unsubscribes from an HMR path */
17
+ onUnsubscribe?: (path: string, client: HmrClient) => void;
18
+ }
19
+ export interface HmrClient {
20
+ /** Unique client ID */
21
+ id: string;
22
+ /** The MessagePort for this client */
23
+ port: MessagePort;
24
+ /** Send a message to this client */
25
+ send(message: HMR_ACTION_TYPES): void;
26
+ /** Close the connection to this client */
27
+ close(): void;
28
+ }
29
+ /**
30
+ * HmrServer manages HMR connections with preview iframes.
31
+ */
32
+ export declare class HmrServer {
33
+ private clients;
34
+ private subscriptions;
35
+ private sessionId;
36
+ private options;
37
+ private hmrHash;
38
+ constructor(options?: HmrServerOptions);
39
+ /**
40
+ * Create a MessageChannel and return the port that should be sent to the iframe.
41
+ * The other port is kept by the server for communication.
42
+ */
43
+ createConnection(): {
44
+ clientPort: MessagePort;
45
+ client: HmrClient;
46
+ };
47
+ /**
48
+ * Connect an iframe to the HMR server.
49
+ * This posts a message to the iframe with the client port.
50
+ */
51
+ connectIframe(iframe: HTMLIFrameElement, origin?: string): HmrClient | null;
52
+ private handleClientMessage;
53
+ private handleClientEvent;
54
+ private subscribe;
55
+ private unsubscribe;
56
+ private removeClient;
57
+ /**
58
+ * Send a Turbopack update to all subscribed clients for a specific path.
59
+ */
60
+ sendUpdate(path: string, update: TurbopackUpdate): void;
61
+ /**
62
+ * Broadcast an HMR message to all connected clients.
63
+ */
64
+ broadcast(message: HMR_ACTION_TYPES): void;
65
+ /**
66
+ * Send building notification to all clients.
67
+ */
68
+ sendBuilding(): void;
69
+ /**
70
+ * Send built notification to all clients.
71
+ */
72
+ sendBuilt(errors?: readonly unknown[], warnings?: readonly unknown[]): void;
73
+ /**
74
+ * Send reload request to all clients.
75
+ */
76
+ sendReload(reason: string): void;
77
+ /**
78
+ * Get all currently subscribed paths.
79
+ */
80
+ getSubscribedPaths(): string[];
81
+ /**
82
+ * Get number of connected clients.
83
+ */
84
+ get clientCount(): number;
85
+ /**
86
+ * Close all client connections.
87
+ */
88
+ close(): void;
89
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * HmrServer - Simulates a WebSocket server using MessagePort for HMR communication.
3
+ *
4
+ * This class manages HMR connections with preview iframes, forwarding Turbopack
5
+ * HMR events from the main project to the preview iframe's HMR client.
6
+ *
7
+ * Architecture:
8
+ * - Main Thread (Project) -> HmrServer -> MessagePort -> Preview Iframe (HMR Client)
9
+ */
10
+ import { HMR_ACTIONS_SENT_TO_BROWSER } from "@utoo/pack-shared";
11
+ let clientIdCounter = 0;
12
+ /**
13
+ * HmrServer manages HMR connections with preview iframes.
14
+ */
15
+ export class HmrServer {
16
+ constructor(options = {}) {
17
+ var _a;
18
+ this.clients = new Set();
19
+ this.subscriptions = new Map();
20
+ this.hmrHash = 0;
21
+ this.options = options;
22
+ this.sessionId =
23
+ (_a = options.sessionId) !== null && _a !== void 0 ? _a : Math.floor(Number.MAX_SAFE_INTEGER * Math.random());
24
+ }
25
+ /**
26
+ * Create a MessageChannel and return the port that should be sent to the iframe.
27
+ * The other port is kept by the server for communication.
28
+ */
29
+ createConnection() {
30
+ const { port1, port2 } = new MessageChannel();
31
+ const clientId = `hmr-client-${++clientIdCounter}`;
32
+ const client = {
33
+ id: clientId,
34
+ port: port1,
35
+ send: (message) => {
36
+ port1.postMessage(JSON.stringify(message));
37
+ },
38
+ close: () => {
39
+ this.removeClient(client);
40
+ port1.close();
41
+ },
42
+ };
43
+ port1.onmessage = (event) => {
44
+ this.handleClientMessage(client, event.data);
45
+ };
46
+ port1.onmessageerror = () => {
47
+ this.removeClient(client);
48
+ };
49
+ this.clients.add(client);
50
+ // Send connected message
51
+ client.send({
52
+ action: HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_CONNECTED,
53
+ data: { sessionId: this.sessionId },
54
+ });
55
+ // Send initial sync
56
+ client.send({
57
+ action: HMR_ACTIONS_SENT_TO_BROWSER.SYNC,
58
+ hash: String(this.hmrHash),
59
+ errors: [],
60
+ warnings: [],
61
+ });
62
+ return { clientPort: port2, client };
63
+ }
64
+ /**
65
+ * Connect an iframe to the HMR server.
66
+ * This posts a message to the iframe with the client port.
67
+ */
68
+ connectIframe(iframe, origin = "*") {
69
+ if (!iframe.contentWindow) {
70
+ console.warn("[HmrServer] Cannot connect: iframe has no contentWindow");
71
+ return null;
72
+ }
73
+ const { clientPort, client } = this.createConnection();
74
+ // Send the port to the iframe
75
+ iframe.contentWindow.postMessage({ type: "hmr-connect", sessionId: this.sessionId }, origin, [clientPort]);
76
+ return client;
77
+ }
78
+ handleClientMessage(client, data) {
79
+ try {
80
+ const message = typeof data === "string" ? JSON.parse(data) : data;
81
+ if ("type" in message) {
82
+ switch (message.type) {
83
+ case "turbopack-subscribe":
84
+ this.subscribe(client, message.path);
85
+ break;
86
+ case "turbopack-unsubscribe":
87
+ this.unsubscribe(client, message.path);
88
+ break;
89
+ }
90
+ }
91
+ // Handle other client events (client-error, client-success, etc.)
92
+ if ("event" in message) {
93
+ this.handleClientEvent(client, message);
94
+ }
95
+ }
96
+ catch (e) {
97
+ console.error("[HmrServer] Failed to parse client message:", e);
98
+ }
99
+ }
100
+ handleClientEvent(_client, message) {
101
+ switch (message.event) {
102
+ case "client-error":
103
+ case "client-warning":
104
+ case "client-success":
105
+ // These are informational, can be logged or forwarded
106
+ break;
107
+ case "client-full-reload":
108
+ if (message.hadRuntimeError) {
109
+ console.warn("[HmrServer] Fast Refresh had to perform a full reload due to a runtime error.");
110
+ }
111
+ break;
112
+ }
113
+ }
114
+ subscribe(client, path) {
115
+ var _a, _b;
116
+ let clients = this.subscriptions.get(path);
117
+ if (!clients) {
118
+ clients = new Set();
119
+ this.subscriptions.set(path, clients);
120
+ }
121
+ clients.add(client);
122
+ (_b = (_a = this.options).onSubscribe) === null || _b === void 0 ? void 0 : _b.call(_a, path, client);
123
+ }
124
+ unsubscribe(client, path) {
125
+ var _a, _b;
126
+ const clients = this.subscriptions.get(path);
127
+ if (clients) {
128
+ clients.delete(client);
129
+ if (clients.size === 0) {
130
+ this.subscriptions.delete(path);
131
+ }
132
+ }
133
+ (_b = (_a = this.options).onUnsubscribe) === null || _b === void 0 ? void 0 : _b.call(_a, path, client);
134
+ }
135
+ removeClient(client) {
136
+ var _a, _b;
137
+ this.clients.delete(client);
138
+ // Remove from all subscriptions
139
+ for (const [path, clients] of this.subscriptions) {
140
+ if (clients.has(client)) {
141
+ clients.delete(client);
142
+ (_b = (_a = this.options).onUnsubscribe) === null || _b === void 0 ? void 0 : _b.call(_a, path, client);
143
+ }
144
+ if (clients.size === 0) {
145
+ this.subscriptions.delete(path);
146
+ }
147
+ }
148
+ }
149
+ /**
150
+ * Send a Turbopack update to all subscribed clients for a specific path.
151
+ */
152
+ sendUpdate(path, update) {
153
+ const clients = this.subscriptions.get(path);
154
+ if (!clients)
155
+ return;
156
+ const message = {
157
+ action: HMR_ACTIONS_SENT_TO_BROWSER.TURBOPACK_MESSAGE,
158
+ data: update,
159
+ };
160
+ for (const client of clients) {
161
+ client.send(message);
162
+ }
163
+ }
164
+ /**
165
+ * Broadcast an HMR message to all connected clients.
166
+ */
167
+ broadcast(message) {
168
+ for (const client of this.clients) {
169
+ client.send(message);
170
+ }
171
+ }
172
+ /**
173
+ * Send building notification to all clients.
174
+ */
175
+ sendBuilding() {
176
+ this.broadcast({
177
+ action: HMR_ACTIONS_SENT_TO_BROWSER.BUILDING,
178
+ });
179
+ }
180
+ /**
181
+ * Send built notification to all clients.
182
+ */
183
+ sendBuilt(errors = [], warnings = []) {
184
+ this.hmrHash++;
185
+ this.broadcast({
186
+ action: HMR_ACTIONS_SENT_TO_BROWSER.BUILT,
187
+ hash: String(this.hmrHash),
188
+ errors: errors,
189
+ warnings: warnings,
190
+ });
191
+ }
192
+ /**
193
+ * Send reload request to all clients.
194
+ */
195
+ sendReload(reason) {
196
+ this.broadcast({
197
+ action: HMR_ACTIONS_SENT_TO_BROWSER.RELOAD,
198
+ data: reason,
199
+ });
200
+ }
201
+ /**
202
+ * Get all currently subscribed paths.
203
+ */
204
+ getSubscribedPaths() {
205
+ return Array.from(this.subscriptions.keys());
206
+ }
207
+ /**
208
+ * Get number of connected clients.
209
+ */
210
+ get clientCount() {
211
+ return this.clients.size;
212
+ }
213
+ /**
214
+ * Close all client connections.
215
+ */
216
+ close() {
217
+ for (const client of this.clients) {
218
+ client.close();
219
+ }
220
+ this.clients.clear();
221
+ this.subscriptions.clear();
222
+ }
223
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * HMR (Hot Module Replacement) module for browser-based utoopack.
3
+ *
4
+ * This module provides HMR support for preview iframes using MessagePort
5
+ * instead of WebSocket for communication.
6
+ *
7
+ * Usage:
8
+ *
9
+ * ```typescript
10
+ * import { Project } from "@utoo/web";
11
+ *
12
+ * const project = new Project({ ... });
13
+ *
14
+ * // Start dev mode (creates HmrServer internally)
15
+ * project.dev((result) => {
16
+ * console.log("Build complete", result);
17
+ * });
18
+ *
19
+ * // Connect iframe to receive HMR updates
20
+ * const client = project.connectHmrIframe(iframeElement);
21
+ * ```
22
+ *
23
+ * The HMR client runtime is in crates/pack-core/js/src/hmr/
24
+ * and is injected into the preview iframe by the build system.
25
+ */
26
+ export { type BaseUpdate, type BuildingAction, type BuiltAction, type CompilationError, type EcmascriptMergedUpdate, type HMR_ACTION_TYPES, HMR_ACTIONS_SENT_TO_BROWSER, type HmrActionType, type HmrClientMessage, type HmrIssue, type HmrIssueSource, type IssuesUpdate, type NotFoundUpdate, type PartialUpdate, type ReloadAction, type ResourceIdentifier, type RestartUpdate, type SyncAction, type TurbopackConnectedAction, type TurbopackMessageAction, type TurbopackSubscribeMessage, type TurbopackUnsubscribeMessage, type TurbopackUpdate, type UpdateEndMessage, type UpdateInfo, type UpdateMessage, type UpdateStartMessage, } from "@utoo/pack-shared";
27
+ export { type HmrClient, HmrServer, type HmrServerOptions } from "./HmrServer";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * HMR (Hot Module Replacement) module for browser-based utoopack.
3
+ *
4
+ * This module provides HMR support for preview iframes using MessagePort
5
+ * instead of WebSocket for communication.
6
+ *
7
+ * Usage:
8
+ *
9
+ * ```typescript
10
+ * import { Project } from "@utoo/web";
11
+ *
12
+ * const project = new Project({ ... });
13
+ *
14
+ * // Start dev mode (creates HmrServer internally)
15
+ * project.dev((result) => {
16
+ * console.log("Build complete", result);
17
+ * });
18
+ *
19
+ * // Connect iframe to receive HMR updates
20
+ * const client = project.connectHmrIframe(iframeElement);
21
+ * ```
22
+ *
23
+ * The HMR client runtime is in crates/pack-core/js/src/hmr/
24
+ * and is injected into the preview iframe by the build system.
25
+ */
26
+ export { HMR_ACTIONS_SENT_TO_BROWSER, } from "@utoo/pack-shared";
27
+ export { HmrServer } from "./HmrServer";
package/esm/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { compatOptionsFromWebpack, type WebpackConfig, } from "@utoo/pack-shared";
2
+ export * from "./hmr";
2
3
  export { Project } from "./project/Project";
3
4
  export * from "./types";
package/esm/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { compatOptionsFromWebpack, } from "@utoo/pack-shared";
2
+ export * from "./hmr";
2
3
  export { Project } from "./project/Project";
3
4
  export * from "./types";