@freshpack/runtime 0.0.0-stage → 1.0.0
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/LICENSE +20 -0
- package/client-hmr.ts +113 -0
- package/internal.ts +5 -0
- package/mod.ts +20 -0
- package/package.json +14 -3
- package/server-middleware.ts +100 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lach and Fresh authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/client-hmr.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/// <reference types="webpack/module" />
|
|
2
|
+
|
|
3
|
+
let lastHash: string | undefined;
|
|
4
|
+
function upToDate() {
|
|
5
|
+
return lastHash!.indexOf(__webpack_hash__) >= 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function logUpdate(
|
|
9
|
+
updatedModules: (string | number)[],
|
|
10
|
+
renewedModules: (string | number)[] | null,
|
|
11
|
+
) {
|
|
12
|
+
const unacceptedModules = updatedModules.filter((moduleId) => {
|
|
13
|
+
return renewedModules && renewedModules.indexOf(moduleId) < 0;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (unacceptedModules.length > 0) {
|
|
17
|
+
console.warn(
|
|
18
|
+
"[HMR]",
|
|
19
|
+
"The following modules couldn't be hot updated: (They would need a full reload!)",
|
|
20
|
+
unacceptedModules,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!renewedModules || renewedModules.length === 0) {
|
|
25
|
+
console.info("[HMR]", "Nothing hot updated.");
|
|
26
|
+
} else {
|
|
27
|
+
console.info("[HMR]", "Updated modules:");
|
|
28
|
+
renewedModules.forEach(function (moduleId) {
|
|
29
|
+
if (typeof moduleId === "string" && moduleId.indexOf("!") !== -1) {
|
|
30
|
+
const parts = moduleId.split("!");
|
|
31
|
+
console.info("[HMR]", "-", parts.pop());
|
|
32
|
+
console.info("[HMR]", " ", "-", moduleId);
|
|
33
|
+
} else {
|
|
34
|
+
console.info("[HMR]", "-", moduleId);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const numberIds = renewedModules.every((moduleId) => {
|
|
38
|
+
return typeof moduleId === "number";
|
|
39
|
+
});
|
|
40
|
+
if (numberIds) {
|
|
41
|
+
console.info(
|
|
42
|
+
"[HMR]",
|
|
43
|
+
'Consider using the optimization.moduleIds: "named" for module names.',
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function checkUpdates(fromUpdate = false) {
|
|
50
|
+
try {
|
|
51
|
+
const updatedModules = await import.meta.webpackHot.check();
|
|
52
|
+
if (!updatedModules) {
|
|
53
|
+
if (fromUpdate) console.info("[HMR]", "Update applied");
|
|
54
|
+
else console.warn("[HMR]", "Promised update was not found");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const renewedModules = await import.meta.webpackHot.apply({
|
|
58
|
+
ignoreUnaccepted: true,
|
|
59
|
+
onUnaccepted(data) {
|
|
60
|
+
console.warn(
|
|
61
|
+
"[HMR]",
|
|
62
|
+
"Update was not accepted:",
|
|
63
|
+
data.chain.join(" => "),
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
logUpdate(updatedModules, renewedModules);
|
|
69
|
+
|
|
70
|
+
if (!upToDate()) {
|
|
71
|
+
await checkUpdates(true);
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
const status = import.meta.webpackHot.status();
|
|
75
|
+
if (["abort", "fail"].indexOf(status) >= 0) {
|
|
76
|
+
console.warn("[HMR]", "Can't apply update, restart required!", e);
|
|
77
|
+
} else {
|
|
78
|
+
console.warn("[HMR]", "Update failed", e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function signalled() {
|
|
84
|
+
if (import.meta.webpackHot.status() !== "idle") {
|
|
85
|
+
console.warn(
|
|
86
|
+
"[HMR]",
|
|
87
|
+
`Bundle update, but state is not idle: ${import.meta.webpackHot.status()}`,
|
|
88
|
+
);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await checkUpdates();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (import.meta.webpackHot) {
|
|
95
|
+
const ws = new WebSocket("/_freshpack_hmr");
|
|
96
|
+
ws.addEventListener("open", () => {
|
|
97
|
+
console.info("[HMR]", "opened");
|
|
98
|
+
});
|
|
99
|
+
ws.addEventListener("message", (msg) => {
|
|
100
|
+
const data = JSON.parse(msg.data);
|
|
101
|
+
if (data === "invalid") {
|
|
102
|
+
console.info("[HMR]", "webpack is working...");
|
|
103
|
+
} else if ("done" in data) {
|
|
104
|
+
const hash = data.done.hash;
|
|
105
|
+
lastHash = hash;
|
|
106
|
+
console.info("[HMR]", "webpack rebuilt", hash);
|
|
107
|
+
signalled();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
ws.addEventListener("close", () => {
|
|
111
|
+
console.info("[HMR]", "closed");
|
|
112
|
+
});
|
|
113
|
+
}
|
package/internal.ts
ADDED
package/mod.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { unmapping } from "./internal.ts";
|
|
2
|
+
|
|
3
|
+
const IS_BROWSER = typeof document !== "undefined";
|
|
4
|
+
|
|
5
|
+
export function unmap(name: string, url: URL): string {
|
|
6
|
+
if (IS_BROWSER) {
|
|
7
|
+
console.error("browser unmapping should be done using webpack plugin");
|
|
8
|
+
return url.href;
|
|
9
|
+
}
|
|
10
|
+
if (!unmapping) {
|
|
11
|
+
console.error("missing unmapping registration");
|
|
12
|
+
return url.href;
|
|
13
|
+
}
|
|
14
|
+
const unmapped = unmapping[name];
|
|
15
|
+
if (!unmapped) {
|
|
16
|
+
console.error("missing unmapping registration for", name);
|
|
17
|
+
return url.href;
|
|
18
|
+
}
|
|
19
|
+
return unmapped.toString();
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freshpack/runtime",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./mod.ts",
|
|
7
|
+
"./internal": "./internal.ts",
|
|
8
|
+
"./client-hmr": "./client-hmr.ts",
|
|
9
|
+
"./server-middleware": "./server-middleware.ts"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@freshpack/core": "3.0.1",
|
|
13
|
+
"@freshpack/webpack": "1.0.0",
|
|
14
|
+
"memfs": "^4.80.0",
|
|
15
|
+
"webpack": "^5.111.1"
|
|
16
|
+
}
|
|
6
17
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { App, Context, Middleware } from "@freshpack/core";
|
|
2
|
+
import { setBuildCache } from "@freshpack/core/internal";
|
|
3
|
+
import type Webpack from "webpack";
|
|
4
|
+
import { assertFreshCompilation } from "@freshpack/webpack/fresh";
|
|
5
|
+
import { assertUnmapCompilation } from "@freshpack/webpack/unmap";
|
|
6
|
+
import { setUnmapping } from "./internal.ts";
|
|
7
|
+
import * as memfs from "memfs";
|
|
8
|
+
|
|
9
|
+
export function webpackHandler<State>(
|
|
10
|
+
app: App<State>,
|
|
11
|
+
compiler: Webpack.Compiler,
|
|
12
|
+
): (ctx: Context<State>) => Promise<Response | Middleware<State>> {
|
|
13
|
+
compiler.outputFileSystem = memfs.createFsFromVolume(
|
|
14
|
+
new memfs.Volume(),
|
|
15
|
+
) as any;
|
|
16
|
+
|
|
17
|
+
const hmrWatchers = new Set<WebSocket>();
|
|
18
|
+
const broadcast = (v: unknown) => {
|
|
19
|
+
const json = JSON.stringify(v);
|
|
20
|
+
for (const watcher of hmrWatchers) {
|
|
21
|
+
watcher.send(json);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
compiler.hooks.failed.tap("FreshpackHMR", (e) => {
|
|
26
|
+
console.error("webpack failed", e);
|
|
27
|
+
});
|
|
28
|
+
compiler.hooks.invalid.tap("FreshpackHMR", () => {
|
|
29
|
+
console.error("webpack invalid");
|
|
30
|
+
broadcast("invalid");
|
|
31
|
+
});
|
|
32
|
+
// deno-ignore: require-await
|
|
33
|
+
compiler.hooks.done.tapPromise({
|
|
34
|
+
name: "FreshpackHMR",
|
|
35
|
+
before: "FreshpackCompilingWait",
|
|
36
|
+
}, async (stats) => {
|
|
37
|
+
console.info(stats.toString(compiler.options.stats));
|
|
38
|
+
|
|
39
|
+
assertFreshCompilation(stats.compilation);
|
|
40
|
+
assertUnmapCompilation(stats.compilation);
|
|
41
|
+
|
|
42
|
+
setBuildCache(
|
|
43
|
+
app as App<unknown>,
|
|
44
|
+
stats.compilation.prodBuildCache,
|
|
45
|
+
// development cache works not the way we want to
|
|
46
|
+
"production",
|
|
47
|
+
);
|
|
48
|
+
setUnmapping(stats.compilation.unmapping);
|
|
49
|
+
broadcast({ done: { hash: stats.hash } });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (compiler.options.watch) {
|
|
53
|
+
compiler.watch(compiler.options.watchOptions, () => {
|
|
54
|
+
// Failure/success is handled by hooks
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
console.warn(
|
|
58
|
+
"watching is disabled, server won't be reloaded automatically!",
|
|
59
|
+
);
|
|
60
|
+
compiler.compile(() => {
|
|
61
|
+
// Failure/success is handled by hooks
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (ctx) => {
|
|
66
|
+
if (ctx.url.pathname.endsWith("/_freshpack_hmr")) {
|
|
67
|
+
const { response, socket } = Deno.upgradeWebSocket(ctx.req);
|
|
68
|
+
|
|
69
|
+
socket.addEventListener("open", () => {
|
|
70
|
+
hmrWatchers.add(socket);
|
|
71
|
+
});
|
|
72
|
+
socket.addEventListener("close", () => {
|
|
73
|
+
hmrWatchers.delete(socket);
|
|
74
|
+
});
|
|
75
|
+
return Promise.resolve(response);
|
|
76
|
+
}
|
|
77
|
+
if (ctx.req.method !== "GET" && ctx.req.method !== "HEAD") {
|
|
78
|
+
return ctx.next();
|
|
79
|
+
}
|
|
80
|
+
return ctx.next();
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function mkCompilingWaiter(
|
|
85
|
+
compiler: Webpack.Compiler,
|
|
86
|
+
): () => Promise<void> {
|
|
87
|
+
let waiter = Promise.withResolvers<void>();
|
|
88
|
+
|
|
89
|
+
compiler.hooks.failed.tap("FreshpackCompilingWait", () => {
|
|
90
|
+
waiter.resolve();
|
|
91
|
+
});
|
|
92
|
+
compiler.hooks.invalid.tap("FreshpackCompilingWait", () => {
|
|
93
|
+
waiter = Promise.withResolvers();
|
|
94
|
+
});
|
|
95
|
+
compiler.hooks.done.tap("FreshpackCompilingWait", () => {
|
|
96
|
+
waiter.resolve();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return () => waiter.promise;
|
|
100
|
+
}
|
package/README.md
DELETED