gofront 1.4.0 → 1.6.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/README.md +174 -403
- package/package.json +16 -19
- package/src/{codegen → backend/js}/expressions.js +265 -77
- package/src/{codegen → backend/js}/index.js +120 -89
- package/src/{codegen → backend/js}/runtime.js +102 -0
- package/src/{codegen → backend/js}/source-map.js +32 -0
- package/src/{codegen → backend/js}/statements.js +139 -64
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
- package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
- package/src/{codegen → backend/js}/stdlib/index.js +6 -0
- package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
- package/src/{codegen → backend/js}/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
- package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +58 -0
- package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
- package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
- package/src/{codegen → backend/js}/stdlib/time.js +3 -3
- package/src/{codegen → backend/js}/templ.js +3 -3
- package/src/backend/wasm/boundary.js +1798 -0
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +1376 -0
- package/src/backend/wasm/encode.js +937 -0
- package/src/backend/wasm/glue.js +144 -0
- package/src/backend/wasm/index.js +2790 -0
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +544 -0
- package/src/backend/wasm/wat.js +302 -0
- package/src/cli-core.js +323 -75
- package/src/compiler.js +447 -14
- package/src/dev-server.js +56 -15
- package/src/index.js +99 -120
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +35 -0
- package/src/lower/captures.js +172 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +164 -0
- package/src/lower/functions.js +302 -0
- package/src/lower/globals.js +316 -0
- package/src/lower/index.js +118 -0
- package/src/lower/ownership.js +137 -0
- package/src/lower/range.js +67 -0
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +3 -1
- package/src/test-runner.js +94 -20
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +182 -3
- package/src/typechecker/index.js +261 -5
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/stdlib/core.js +34 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +259 -6
- package/src/vendor.js +2 -2
- package/src/codegen/stdlib/strconv.js +0 -33
- /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
package/src/dev-server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Minimal dev server for gofront --serve watch mode.
|
|
2
2
|
// Serves static files and pushes reload / error / css-update events via SSE.
|
|
3
3
|
|
|
4
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
import { extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { colors, log } from "./colors.js";
|
|
@@ -9,8 +9,10 @@ import { colors, log } from "./colors.js";
|
|
|
9
9
|
export const MIME = {
|
|
10
10
|
".html": "text/html; charset=utf-8",
|
|
11
11
|
".js": "application/javascript; charset=utf-8",
|
|
12
|
+
".wasm": "application/wasm",
|
|
12
13
|
".css": "text/css; charset=utf-8",
|
|
13
14
|
".json": "application/json; charset=utf-8",
|
|
15
|
+
".map": "application/json; charset=utf-8",
|
|
14
16
|
".go": "text/plain; charset=utf-8",
|
|
15
17
|
".svg": "image/svg+xml",
|
|
16
18
|
".png": "image/png",
|
|
@@ -151,7 +153,11 @@ function handleSseRequest(req, res, clients, lastError = null) {
|
|
|
151
153
|
if (lastError)
|
|
152
154
|
res.write(`event: build-error\ndata: ${JSON.stringify(lastError)}\n\n`);
|
|
153
155
|
clients.add(res);
|
|
154
|
-
|
|
156
|
+
const cleanup = () => clients.delete(res);
|
|
157
|
+
req.on?.("close", cleanup);
|
|
158
|
+
res.on?.("close", cleanup);
|
|
159
|
+
res.on?.("finish", cleanup);
|
|
160
|
+
res.on?.("error", cleanup);
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
function resolveStaticPath(serveDir, urlPath) {
|
|
@@ -161,49 +167,73 @@ function resolveStaticPath(serveDir, urlPath) {
|
|
|
161
167
|
: urlPath.startsWith("/")
|
|
162
168
|
? urlPath
|
|
163
169
|
: `/${urlPath}`;
|
|
164
|
-
const
|
|
165
|
-
|
|
170
|
+
const rootPath = existsSync(serveDir)
|
|
171
|
+
? realpathSync(resolve(serveDir))
|
|
172
|
+
: resolve(serveDir);
|
|
173
|
+
const filePath = resolve(rootPath, `.${cleanPath}`);
|
|
174
|
+
if (!isInsideDir(rootPath, filePath)) return { forbidden: true };
|
|
166
175
|
|
|
167
176
|
if (existsSync(filePath)) {
|
|
168
|
-
const
|
|
177
|
+
const realTarget = realpathSync(filePath);
|
|
178
|
+
if (!isInsideDir(rootPath, realTarget)) return { forbidden: true };
|
|
179
|
+
const stat = statSync(realTarget);
|
|
169
180
|
if (stat.isDirectory()) {
|
|
170
|
-
const dirIndex = join(
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
const dirIndex = join(realTarget, "index.html");
|
|
182
|
+
if (
|
|
183
|
+
existsSync(dirIndex) &&
|
|
184
|
+
isInsideDir(rootPath, realpathSync(dirIndex))
|
|
185
|
+
) {
|
|
186
|
+
return { filePath: dirIndex };
|
|
187
|
+
}
|
|
188
|
+
const rootIndex = join(rootPath, "index.html");
|
|
189
|
+
if (
|
|
190
|
+
existsSync(rootIndex) &&
|
|
191
|
+
isInsideDir(rootPath, realpathSync(rootIndex))
|
|
192
|
+
) {
|
|
193
|
+
return { filePath: rootIndex };
|
|
194
|
+
}
|
|
174
195
|
return { notFound: true };
|
|
175
196
|
}
|
|
176
|
-
return { filePath };
|
|
197
|
+
return { filePath: realTarget };
|
|
177
198
|
}
|
|
178
199
|
|
|
179
200
|
// SPA fallback: clean paths with no file extension fall back to index.html
|
|
180
201
|
if (!extname(cleanPath)) {
|
|
181
|
-
const indexPath = join(
|
|
202
|
+
const indexPath = join(rootPath, "index.html");
|
|
182
203
|
if (existsSync(indexPath)) return { filePath: indexPath };
|
|
183
204
|
}
|
|
184
205
|
|
|
185
206
|
return { notFound: true };
|
|
186
207
|
}
|
|
187
208
|
|
|
188
|
-
function sendStaticFile(res, filePath, shouldInject) {
|
|
209
|
+
function sendStaticFile(res, filePath, shouldInject, req = null) {
|
|
189
210
|
try {
|
|
190
211
|
const ext = extname(filePath);
|
|
212
|
+
const isHead = req?.method === "HEAD";
|
|
191
213
|
if (ext === ".html") {
|
|
192
214
|
const data = readFileSync(filePath, "utf8");
|
|
193
215
|
const body = shouldInject ? injectLiveReload(data) : data;
|
|
194
216
|
res.writeHead(200, {
|
|
195
217
|
"Content-Type": MIME[".html"],
|
|
218
|
+
"X-Content-Type-Options": "nosniff",
|
|
196
219
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
197
220
|
});
|
|
198
|
-
res.end(
|
|
221
|
+
if (isHead) res.end();
|
|
222
|
+
else res.end(body);
|
|
199
223
|
return;
|
|
200
224
|
}
|
|
201
225
|
|
|
202
226
|
const data = readFileSync(filePath);
|
|
203
227
|
res.writeHead(200, {
|
|
204
228
|
"Content-Type": MIME[ext] ?? "application/octet-stream",
|
|
205
|
-
"
|
|
229
|
+
"X-Content-Type-Options": "nosniff",
|
|
230
|
+
"Cache-Control":
|
|
231
|
+
ext === ".wasm" ? "no-store" : "no-cache, no-store, must-revalidate",
|
|
206
232
|
});
|
|
233
|
+
if (isHead) {
|
|
234
|
+
res.end();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
207
237
|
res.end(data);
|
|
208
238
|
} catch {
|
|
209
239
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
@@ -218,7 +248,18 @@ export function handleDevRequest(
|
|
|
218
248
|
clients = new Set(),
|
|
219
249
|
options = {},
|
|
220
250
|
) {
|
|
251
|
+
if (req.method && req.method !== "GET" && req.method !== "HEAD") {
|
|
252
|
+
res.writeHead(405, { "Content-Type": "text/plain", Allow: "GET, HEAD" });
|
|
253
|
+
res.end("Method Not Allowed");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
221
257
|
if (req.url === "/_gofront/events") {
|
|
258
|
+
if (req.method && req.method !== "GET") {
|
|
259
|
+
res.writeHead(405, { "Content-Type": "text/plain", Allow: "GET" });
|
|
260
|
+
res.end("Method Not Allowed");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
222
263
|
handleSseRequest(req, res, clients, options.getLastError?.() ?? null);
|
|
223
264
|
return;
|
|
224
265
|
}
|
|
@@ -240,7 +281,7 @@ export function handleDevRequest(
|
|
|
240
281
|
return;
|
|
241
282
|
}
|
|
242
283
|
|
|
243
|
-
sendStaticFile(res, resolved.filePath, options.injectReload ?? true);
|
|
284
|
+
sendStaticFile(res, resolved.filePath, options.injectReload ?? true, req);
|
|
244
285
|
}
|
|
245
286
|
|
|
246
287
|
function broadcastToClients(clients, event, payload) {
|
package/src/index.js
CHANGED
|
@@ -18,12 +18,13 @@ import { createRequire } from "node:module";
|
|
|
18
18
|
const _require = createRequire(import.meta.url);
|
|
19
19
|
const { version } = _require("../package.json");
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { statSync } from "node:fs";
|
|
22
22
|
import { dirname, relative, resolve } from "node:path";
|
|
23
23
|
import { copyAssets } from "./asset-manager.js";
|
|
24
24
|
import {
|
|
25
25
|
formatBuildSummary,
|
|
26
26
|
formatPrepSummary,
|
|
27
|
+
formatWrittenDesc,
|
|
27
28
|
handleBuild,
|
|
28
29
|
handleCheck,
|
|
29
30
|
handleDev,
|
|
@@ -34,12 +35,13 @@ import {
|
|
|
34
35
|
parseBuildArgs,
|
|
35
36
|
parseCheckArgs,
|
|
36
37
|
parseDevArgs,
|
|
38
|
+
parseLegacyArgs,
|
|
37
39
|
parsePrepArgs,
|
|
38
40
|
parseTestArgs,
|
|
39
41
|
runCompile,
|
|
42
|
+
writeCompileOutput,
|
|
40
43
|
} from "./cli-core.js";
|
|
41
44
|
import { colors, formatDiagnostic, log, ms, stamp } from "./colors.js";
|
|
42
|
-
import { createDevServer } from "./dev-server.js";
|
|
43
45
|
|
|
44
46
|
// ── Parse CLI args ───────────────────────────────────────────
|
|
45
47
|
|
|
@@ -52,18 +54,20 @@ if (args[0] === "--version" || args[0] === "-v") {
|
|
|
52
54
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
53
55
|
console.log(
|
|
54
56
|
`
|
|
55
|
-
GoFront — a Go-inspired language that compiles to JavaScript
|
|
57
|
+
GoFront — a Go-inspired language that compiles to JavaScript and WebAssembly
|
|
56
58
|
|
|
57
59
|
Usage:
|
|
58
60
|
gofront dev [dir] [options] Start dev server with live reload (default port 3000)
|
|
59
|
-
gofront build [dir] [options] Build production bundle (-o <dir>, --pwa, --minify)
|
|
61
|
+
gofront build [dir] [options] Build production bundle (-o <dir>, --pwa, --minify, --emit-wat, --release, --wasm-opt, --js-only)
|
|
60
62
|
gofront check [dir|dir/...] Type-check only (dir/... recurses into every package)
|
|
61
63
|
gofront test [dir|dir/...] [--dom] Run unit tests (-v verbose, -run <regex>)
|
|
62
64
|
gofront prep [dir] [--minify] Copy static assets and bundle vendor dependencies
|
|
63
65
|
gofront init [dir] Scaffold a new GoFront project
|
|
64
66
|
gofront <file.go> Compile single file and print to stdout
|
|
65
67
|
gofront <dir> (or gofront .) Compile all *.go in directory as one bundle
|
|
66
|
-
gofront <input> -o out.js Compile and write to file
|
|
68
|
+
gofront <input> -o out.js Compile and write to file (wasm packages also emit app.wasm)
|
|
69
|
+
gofront <input> --release Production build with minification and Binaryen wasm optimization
|
|
70
|
+
gofront <input> --wasm-opt Optimize wasm output with Binaryen (-O3 + GUFA)
|
|
67
71
|
gofront <input> --check Type-check only
|
|
68
72
|
gofront <input> --watch Watch for changes and recompile
|
|
69
73
|
gofront <input> -o out.js --serve Watch + serve with live reload (default port 3000)
|
|
@@ -129,9 +133,9 @@ if (args[0] === "test") {
|
|
|
129
133
|
// ── check subcommand ──────────────────────────────────────────
|
|
130
134
|
|
|
131
135
|
if (args[0] === "check") {
|
|
132
|
-
const { targetDir } = parseCheckArgs(args.slice(1));
|
|
136
|
+
const { targetDir, forceTarget } = parseCheckArgs(args.slice(1));
|
|
133
137
|
try {
|
|
134
|
-
const { elapsedMs, packages } = handleCheck(targetDir);
|
|
138
|
+
const { elapsedMs, packages } = handleCheck(targetDir, { forceTarget });
|
|
135
139
|
const okLine = (label, t) =>
|
|
136
140
|
log.info(`${colors.cyan(label)} — ${colors.green("OK")} ${ms(t)}`);
|
|
137
141
|
if (packages) {
|
|
@@ -180,7 +184,7 @@ if (args[0] === "dev") {
|
|
|
180
184
|
if (dev.initialError) {
|
|
181
185
|
log.error(dev.initialError.message);
|
|
182
186
|
} else {
|
|
183
|
-
log.ok(`— wrote ${
|
|
187
|
+
log.ok(`— wrote ${formatWrittenDesc(dev.written, dev.outputFile)}`);
|
|
184
188
|
}
|
|
185
189
|
log.info(
|
|
186
190
|
`dev server running → ${colors.cyan(`http://localhost:${dev.port}`)}`,
|
|
@@ -200,20 +204,22 @@ if (args[0] === "dev") {
|
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
206
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
207
|
+
const {
|
|
208
|
+
inputArg,
|
|
209
|
+
outputFile,
|
|
210
|
+
checkOnly,
|
|
211
|
+
dumpAst,
|
|
212
|
+
dumpTokens,
|
|
213
|
+
sourceMap,
|
|
214
|
+
serve: serveMode,
|
|
215
|
+
watch: watchMode,
|
|
216
|
+
copyAssets: copyAssetsFlag,
|
|
217
|
+
wasmOpt,
|
|
218
|
+
minify: minifyOutput,
|
|
219
|
+
mangle: mangleOutput,
|
|
220
|
+
port: servePort,
|
|
221
|
+
forceTarget,
|
|
222
|
+
} = parseLegacyArgs(args);
|
|
217
223
|
|
|
218
224
|
// ── Determine input mode ─────────────────────────────────────
|
|
219
225
|
|
|
@@ -226,6 +232,17 @@ try {
|
|
|
226
232
|
process.exit(1);
|
|
227
233
|
}
|
|
228
234
|
|
|
235
|
+
function copyProjectAssets(verbose) {
|
|
236
|
+
try {
|
|
237
|
+
const { copied, skipped } = copyAssets(resolve("."));
|
|
238
|
+
if (verbose && (copied > 0 || skipped > 0)) {
|
|
239
|
+
log.info(`copied ${copied} assets (${skipped} skipped)`);
|
|
240
|
+
}
|
|
241
|
+
} catch (e) {
|
|
242
|
+
log.warn(`asset copy failed: ${e.message}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
229
246
|
// ── Single-shot mode ─────────────────────────────────────────
|
|
230
247
|
|
|
231
248
|
if (!watchMode) {
|
|
@@ -237,6 +254,8 @@ if (!watchMode) {
|
|
|
237
254
|
outputFile,
|
|
238
255
|
dumpTokens,
|
|
239
256
|
dumpAst,
|
|
257
|
+
wasmOpt,
|
|
258
|
+
forceTarget,
|
|
240
259
|
});
|
|
241
260
|
} catch (e) {
|
|
242
261
|
log.fail(e.message);
|
|
@@ -276,121 +295,81 @@ if (!watchMode) {
|
|
|
276
295
|
|
|
277
296
|
if (outputFile) {
|
|
278
297
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
298
|
+
const written = writeCompileOutput(resolve(outputFile), result, js);
|
|
299
|
+
log.info(
|
|
300
|
+
`wrote ${formatWrittenDesc(written, outputFile)} ${ms(elapsedMs)}`,
|
|
301
|
+
);
|
|
302
|
+
if (result.wasmOptInfo && result.wasmOptInfo.originalSize > 0) {
|
|
303
|
+
const origKb = (result.wasmOptInfo.originalSize / 1024).toFixed(1);
|
|
304
|
+
const optKb = (result.wasmOptInfo.optimizedSize / 1024).toFixed(1);
|
|
305
|
+
log.info(
|
|
306
|
+
`wasm-opt: app.wasm ${origKb} kB → ${optKb} kB (-${result.wasmOptInfo.percentSaved}%) [${result.wasmOptInfo.engine}]`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
282
309
|
} catch (e) {
|
|
283
310
|
log.fail(`cannot write '${outputFile}': ${e.message}`);
|
|
284
311
|
process.exit(1);
|
|
285
312
|
}
|
|
286
313
|
} else {
|
|
287
314
|
console.log(js);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const projectDir = resolve(".");
|
|
293
|
-
const { copied, skipped } = copyAssets(projectDir);
|
|
294
|
-
if (copied > 0 || skipped > 0) {
|
|
295
|
-
log.info(`copied ${copied} assets (${skipped} skipped)`);
|
|
296
|
-
}
|
|
297
|
-
} catch (e) {
|
|
298
|
-
log.warn(`asset copy failed: ${e.message}`);
|
|
315
|
+
if (result.wasm) {
|
|
316
|
+
log.warn(
|
|
317
|
+
"wasm packages were linked but app.wasm needs -o <file> to be written",
|
|
318
|
+
);
|
|
299
319
|
}
|
|
300
320
|
}
|
|
301
321
|
|
|
322
|
+
if (copyAssetsFlag) copyProjectAssets(true);
|
|
323
|
+
|
|
302
324
|
process.exit(0);
|
|
303
325
|
}
|
|
304
326
|
|
|
305
327
|
// ── Watch mode ───────────────────────────────────────────────
|
|
306
328
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (!outputFile) {
|
|
311
|
-
log.fail("--serve requires -o <output file>");
|
|
312
|
-
process.exit(1);
|
|
313
|
-
}
|
|
314
|
-
const serveDir = dirname(resolve(outputFile));
|
|
315
|
-
devServer = createDevServer(serveDir, servePort);
|
|
329
|
+
if (serveMode && !outputFile) {
|
|
330
|
+
log.fail("--serve requires -o <output file>");
|
|
331
|
+
process.exit(1);
|
|
316
332
|
}
|
|
317
333
|
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
const startMs = performance.now();
|
|
321
|
-
const result = runCompile(inputPath, isDir, { sourceMap, outputFile });
|
|
322
|
-
const js = maybeMinify(result.js, {
|
|
323
|
-
minify: minifyOutput,
|
|
324
|
-
mangle: mangleOutput,
|
|
325
|
-
sourceMap,
|
|
326
|
-
});
|
|
327
|
-
const elapsedMs = (performance.now() - startMs).toFixed(0);
|
|
328
|
-
const changeNote = changedFile ? ` — ${changedFile} changed` : "";
|
|
329
|
-
const timing = colors.dim(`(${elapsedMs}ms${changeNote})`);
|
|
330
|
-
if (outputFile) {
|
|
331
|
-
writeFileSync(outputFile, `${js}\n`);
|
|
332
|
-
console.error(
|
|
333
|
-
`${stamp()} ${colors.bold("gofront:")} ${colors.green("OK")} — wrote ${colors.cyan(outputFile)} ${timing}`,
|
|
334
|
-
);
|
|
335
|
-
} else {
|
|
336
|
-
// Clear screen then print
|
|
337
|
-
process.stdout.write("\x1Bc");
|
|
338
|
-
console.log(js);
|
|
339
|
-
console.error(
|
|
340
|
-
`${stamp()} ${colors.bold("gofront:")} ${colors.green("OK")} ${timing}`,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
334
|
+
const prefix = () => `${stamp()} ${colors.bold("gofront:")}`;
|
|
343
335
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
336
|
+
try {
|
|
337
|
+
await handleDev(".", {
|
|
338
|
+
srcDir: inputPath,
|
|
339
|
+
outputFile,
|
|
340
|
+
serve: serveMode,
|
|
341
|
+
serveDir: outputFile ? dirname(resolve(outputFile)) : undefined,
|
|
342
|
+
port: servePort,
|
|
343
|
+
silent: false,
|
|
344
|
+
sourceMap,
|
|
345
|
+
minify: minifyOutput,
|
|
346
|
+
mangle: mangleOutput,
|
|
347
|
+
copyAssets: copyAssetsFlag,
|
|
348
|
+
forceTarget,
|
|
349
|
+
onBuild: ({ js, written, elapsedMs, changedFile }) => {
|
|
350
|
+
const changeNote = changedFile ? ` — ${changedFile} changed` : "";
|
|
351
|
+
const timing = colors.dim(`(${elapsedMs}ms${changeNote})`);
|
|
352
|
+
if (outputFile) {
|
|
353
|
+
console.error(
|
|
354
|
+
`${prefix()} ${colors.green("OK")} — wrote ${formatWrittenDesc(written, outputFile)} ${timing}`,
|
|
355
|
+
);
|
|
356
|
+
} else {
|
|
357
|
+
// Clear screen then print
|
|
358
|
+
process.stdout.write("\x1Bc");
|
|
359
|
+
console.log(js);
|
|
360
|
+
console.error(`${prefix()} ${colors.green("OK")} ${timing}`);
|
|
349
361
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Initial build
|
|
364
|
-
buildOnce();
|
|
365
|
-
|
|
366
|
-
// Determine what to watch
|
|
367
|
-
const watchTarget = isDir ? inputPath : dirname(inputPath);
|
|
368
|
-
|
|
369
|
-
function handleCssWatch(filename) {
|
|
370
|
-
if (copyAssetsFlag) {
|
|
371
|
-
try {
|
|
372
|
-
copyAssets(resolve("."));
|
|
373
|
-
} catch (e) {
|
|
374
|
-
log.warn(`asset copy failed: ${e.message}`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
devServer?.notifyCss?.(filename);
|
|
362
|
+
if (copyAssetsFlag) copyProjectAssets(false);
|
|
363
|
+
},
|
|
364
|
+
onError: (e) => {
|
|
365
|
+
console.error(`${prefix()} ${colors.bold(colors.red("ERROR"))}`);
|
|
366
|
+
for (const line of formatDiagnostic(e.message).split("\n"))
|
|
367
|
+
console.error(` ${line}`);
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
} catch (e) {
|
|
371
|
+
log.fail(e.message);
|
|
372
|
+
process.exit(1);
|
|
378
373
|
}
|
|
379
374
|
|
|
380
|
-
|
|
381
|
-
let cssDebounce = null;
|
|
382
|
-
watch(watchTarget, { recursive: true }, (_event, filename) => {
|
|
383
|
-
if (!filename) return;
|
|
384
|
-
if (filename.endsWith(".css")) {
|
|
385
|
-
clearTimeout(cssDebounce);
|
|
386
|
-
cssDebounce = setTimeout(() => handleCssWatch(filename), 50);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (!filename.endsWith(".go") && !filename.endsWith(".templ")) return;
|
|
390
|
-
clearTimeout(debounce);
|
|
391
|
-
debounce = setTimeout(() => buildOnce(filename), 80);
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
console.error(
|
|
395
|
-
`${stamp()} ${colors.bold("gofront:")} watching ${colors.cyan(inputArg)} ...`,
|
|
396
|
-
);
|
|
375
|
+
console.error(`${prefix()} watching ${colors.cyan(inputArg)} ...`);
|
package/src/lexer.js
CHANGED
|
@@ -111,6 +111,8 @@ export class Lexer {
|
|
|
111
111
|
this.col = 1;
|
|
112
112
|
this.tokens = [];
|
|
113
113
|
this.filename = filename;
|
|
114
|
+
this.target = null;
|
|
115
|
+
this.targetDirective = null;
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
// ── Primitives ──────────────────────────────────────────────
|
|
@@ -150,8 +152,22 @@ export class Lexer {
|
|
|
150
152
|
if (ch === " " || ch === "\t" || ch === "\r") {
|
|
151
153
|
this.advance();
|
|
152
154
|
} else if (ch === "/" && this.peek(1) === "/") {
|
|
155
|
+
const start = this.pos;
|
|
156
|
+
const line = this.line,
|
|
157
|
+
col = this.col;
|
|
153
158
|
while (this.pos < this.src.length && this.peek() !== "\n")
|
|
154
159
|
this.advance();
|
|
160
|
+
const comment = this.src.slice(start, this.pos);
|
|
161
|
+
const match = comment.match(/^\/\/gofront:target\s+(\S+)/);
|
|
162
|
+
if (match) {
|
|
163
|
+
this.target = match[1];
|
|
164
|
+
this.targetDirective = {
|
|
165
|
+
value: match[1],
|
|
166
|
+
line,
|
|
167
|
+
col,
|
|
168
|
+
beforePackage: this.tokens.length === 0,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
155
171
|
} else if (ch === "/" && this.peek(1) === "*") {
|
|
156
172
|
const startLine = this.line,
|
|
157
173
|
startCol = this.col;
|
|
@@ -472,6 +488,8 @@ export class Lexer {
|
|
|
472
488
|
|
|
473
489
|
if (this.shouldSemi()) this.push(T.SEMICOLON, ";", this.line, this.col);
|
|
474
490
|
this.push(T.EOF, "", this.line, this.col);
|
|
491
|
+
this.tokens.target = this.target;
|
|
492
|
+
this.tokens.targetDirective = this.targetDirective;
|
|
475
493
|
return this.tokens;
|
|
476
494
|
}
|
|
477
495
|
_readPlusOp(l, c) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/lower/boxing.js
|
|
2
|
+
// Identifies address-taken scalar variables that require heap boxing ({ value: x }).
|
|
3
|
+
|
|
4
|
+
import { forEachChild } from "./walk.js";
|
|
5
|
+
|
|
6
|
+
export function isReferenceType(t) {
|
|
7
|
+
if (!t) return false;
|
|
8
|
+
const base = t.kind === "named" ? t.underlying : t;
|
|
9
|
+
return (
|
|
10
|
+
base?.kind === "struct" ||
|
|
11
|
+
base?.kind === "slice" ||
|
|
12
|
+
base?.kind === "map" ||
|
|
13
|
+
base?.kind === "func" ||
|
|
14
|
+
base?.kind === "interface"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Scan AST node for _addressTaken idents on scalars and populate a Set of boxed variable names.
|
|
19
|
+
export function scanAddressTaken(node, boxedVars = new Set()) {
|
|
20
|
+
if (!node || typeof node !== "object") return boxedVars;
|
|
21
|
+
if (Array.isArray(node)) {
|
|
22
|
+
for (const child of node) scanAddressTaken(child, boxedVars);
|
|
23
|
+
return boxedVars;
|
|
24
|
+
}
|
|
25
|
+
// &x — the operand ident will have _addressTaken set by typechecker
|
|
26
|
+
if (node.kind === "Ident" && node._addressTaken) {
|
|
27
|
+
const t = node._type;
|
|
28
|
+
if (t && !isReferenceType(t)) {
|
|
29
|
+
boxedVars.add(node.name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Recurse into FuncLit too — closures may take address of outer vars
|
|
33
|
+
forEachChild(node, (child) => scanAddressTaken(child, boxedVars));
|
|
34
|
+
return boxedVars;
|
|
35
|
+
}
|