@tschk/moonshine-compiler 0.3.3 → 0.3.5
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tschk/moonshine-compiler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"test": "bun test"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@tschk/moonshine-framework": "^0.3.
|
|
19
|
-
"@tschk/moonshine-router": "^0.3.
|
|
18
|
+
"@tschk/moonshine-framework": "^0.3.5",
|
|
19
|
+
"@tschk/moonshine-router": "^0.3.5"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/bun": "1.3.14",
|
package/src/build.ts
CHANGED
|
@@ -108,6 +108,19 @@ async function runBuild(
|
|
|
108
108
|
naming: "[name].js",
|
|
109
109
|
splitting: false,
|
|
110
110
|
sourcemap: "none",
|
|
111
|
+
// Browsers have no `process`. Any dependency that reads `process.env.X` —
|
|
112
|
+
// and most of them do, behind a `typeof` guard or not — otherwise throws
|
|
113
|
+
// `ReferenceError: process is not defined` while the entry module is being
|
|
114
|
+
// evaluated. That happens before any render, so the app silently mounts
|
|
115
|
+
// nothing: no error boundary runs and the page is simply blank.
|
|
116
|
+
//
|
|
117
|
+
// `??=` so an app that injects real values ahead of this bundle keeps them;
|
|
118
|
+
// this only guarantees the property exists. Values are deliberately not
|
|
119
|
+
// inlined here — inlining the process environment into a browser bundle
|
|
120
|
+
// would ship every secret the build host happens to hold.
|
|
121
|
+
...(target === "browser"
|
|
122
|
+
? { banner: "globalThis.process ??= { env: {} };" }
|
|
123
|
+
: {}),
|
|
111
124
|
});
|
|
112
125
|
if (!result.success) {
|
|
113
126
|
const messages = result.logs.map((log) => log.message).join("\n");
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { afterAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync, rmSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { buildProject } from "../src/manifest";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A browser bundle must not carry a bare `process` reference into a page.
|
|
8
|
+
*
|
|
9
|
+
* Client code and its dependencies routinely read `process.env.X`. `process`
|
|
10
|
+
* does not exist in a browser, so the reference throws while the client entry
|
|
11
|
+
* module is still evaluating — before any component renders and before any
|
|
12
|
+
* error boundary can mount. The page stays blank and the console stays empty,
|
|
13
|
+
* which is close to undiagnosable from the outside.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const projectDir = resolve(import.meta.dir, "fixtures", "project");
|
|
17
|
+
const outDir = join(projectDir, ".moonshine");
|
|
18
|
+
|
|
19
|
+
function clean() {
|
|
20
|
+
if (existsSync(outDir)) {
|
|
21
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
afterAll(clean);
|
|
26
|
+
|
|
27
|
+
describe("client bundle process shim", () => {
|
|
28
|
+
test("defines globalThis.process before any module code runs", async () => {
|
|
29
|
+
await buildProject({ projectDir });
|
|
30
|
+
|
|
31
|
+
const client = await Bun.file(join(outDir, "public", "client.js")).text();
|
|
32
|
+
|
|
33
|
+
expect(client.startsWith("globalThis.process ??= { env: {} };")).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("evaluating the bundle does not throw on a process.env read", async () => {
|
|
37
|
+
await buildProject({ projectDir });
|
|
38
|
+
|
|
39
|
+
const client = await Bun.file(join(outDir, "public", "client.js")).text();
|
|
40
|
+
// The banner is the whole fix, so assert it actually guards a real read
|
|
41
|
+
// rather than trusting that the fixture still contains one.
|
|
42
|
+
expect(client).toContain("process.env");
|
|
43
|
+
|
|
44
|
+
// Evaluate the banner in isolation with no ambient `process`, which is what
|
|
45
|
+
// a browser gives the bundle.
|
|
46
|
+
const scope: Record<string, unknown> = {};
|
|
47
|
+
const run = new Function(
|
|
48
|
+
"globalThis",
|
|
49
|
+
"return (globalThis.process ??= { env: {} });",
|
|
50
|
+
);
|
|
51
|
+
expect(run(scope)).toEqual({ env: {} });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("does not overwrite an environment injected ahead of the bundle", async () => {
|
|
55
|
+
// Apps prepend their own public values; `??=` must leave those intact.
|
|
56
|
+
const scope: Record<string, unknown> = { process: { env: { A: "1" } } };
|
|
57
|
+
const run = new Function(
|
|
58
|
+
"globalThis",
|
|
59
|
+
"return (globalThis.process ??= { env: {} });",
|
|
60
|
+
);
|
|
61
|
+
expect(run(scope)).toEqual({ env: { A: "1" } });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("the server bundle is left alone", async () => {
|
|
65
|
+
await buildProject({ projectDir });
|
|
66
|
+
|
|
67
|
+
const server = await Bun.file(join(outDir, "dist", "server.js")).text();
|
|
68
|
+
|
|
69
|
+
// Bun and Node both have a real `process`; shimming it there would mask
|
|
70
|
+
// genuine environment problems at runtime.
|
|
71
|
+
expect(server.startsWith("globalThis.process ??=")).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// Reads process.env deliberately: browsers have no `process`, so this is the
|
|
2
|
+
// shape that used to throw `ReferenceError: process is not defined` while the
|
|
3
|
+
// client entry evaluated, leaving the page blank with no error boundary.
|
|
4
|
+
const apiBase = process.env.MOONSHINE_TEST_API_BASE ?? 'unset';
|
|
5
|
+
|
|
1
6
|
export default function ClientPage() {
|
|
2
|
-
return <h1>client</h1>;
|
|
7
|
+
return <h1>client {apiBase}</h1>;
|
|
3
8
|
}
|