gofront 1.3.8 → 1.3.10
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 +16 -0
- package/package.json +1 -1
- package/src/asset-manager.js +4 -25
- package/src/cli-core.js +18 -22
- package/src/codegen/templ.js +22 -7
- package/src/minifier.js +52 -3
- package/src/project-config.js +23 -0
- package/src/vendor.js +2 -9
package/README.md
CHANGED
|
@@ -401,7 +401,23 @@ gofront init [dir] scaffold a new project
|
|
|
401
401
|
gofront --version / -v print version
|
|
402
402
|
gofront --help / -h print this help
|
|
403
403
|
```
|
|
404
|
+
### Project configuration
|
|
404
405
|
|
|
406
|
+
`gofront dev`, `build`, `prep`, `check` and `test` read optional settings from a `gofront.json`
|
|
407
|
+
file or a `"gofront": { … }` object in `package.json`:
|
|
408
|
+
|
|
409
|
+
| Key | Default | Description |
|
|
410
|
+
|---|---|---|
|
|
411
|
+
| `src` | `app/src`, `src` or `main.go` | Package directory (or single file) to compile |
|
|
412
|
+
| `serveDir` | `app`, `.` or `public` (first with `index.html`) | Directory served by `dev` and mirrored into the build |
|
|
413
|
+
| `outDir` | `public` | Release output directory for `build` |
|
|
414
|
+
| `output` | `app/app.js` | Dev-mode compiled bundle path |
|
|
415
|
+
| `port` | `3000` | Dev server port |
|
|
416
|
+
| `assetExtensions` | `[]` | Extra file extensions (e.g. `[".bmesh", ".mat"]`) copied from `serveDir` into `outDir` on `build`, in addition to the built-in web asset list (html, css, js, json, images, fonts, audio, video, wasm) |
|
|
417
|
+
| `vendor` | `app/vendor.js` | Vendor bundle written by `prep`/`build` from `package.json` `dependencies`. Either a destination path string or `{ "dest": string \| string[], "packages": string[], "minify": boolean, "globals": { "<pkg>": string \| string[] } }` |
|
|
418
|
+
| `assetCopy` | `[]` | Static files copied by `prep`/`build`: `[{ "source": "node_modules/x/font.woff2", "dest": "app/fonts/font.woff2" }]` |
|
|
419
|
+
|
|
420
|
+
All settings live in this one place — top-level `"vendor"` / `"assetCopy"` keys in `package.json` are not read.
|
|
405
421
|
---
|
|
406
422
|
|
|
407
423
|
## Multi-file packages
|
package/package.json
CHANGED
package/src/asset-manager.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
// Native static asset manager for GoFront projects.
|
|
2
2
|
// Zero-dependency file and directory copier using node:fs.
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
copyFileSync,
|
|
6
|
-
cpSync,
|
|
7
|
-
existsSync,
|
|
8
|
-
mkdirSync,
|
|
9
|
-
readFileSync,
|
|
10
|
-
statSync,
|
|
11
|
-
} from "node:fs";
|
|
4
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, statSync } from "node:fs";
|
|
12
5
|
import {
|
|
13
6
|
basename,
|
|
14
7
|
dirname,
|
|
@@ -18,25 +11,11 @@ import {
|
|
|
18
11
|
resolve,
|
|
19
12
|
sep,
|
|
20
13
|
} from "node:path";
|
|
14
|
+
import { loadProjectConfig } from "./project-config.js";
|
|
21
15
|
|
|
22
16
|
export function loadAssetConfig(projectDir) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const parsed = JSON.parse(readFileSync(gofrontJsonPath, "utf8"));
|
|
27
|
-
if (Array.isArray(parsed.assetCopy)) return parsed.assetCopy;
|
|
28
|
-
} catch {}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const pkgJsonPath = join(projectDir, "package.json");
|
|
32
|
-
if (existsSync(pkgJsonPath)) {
|
|
33
|
-
try {
|
|
34
|
-
const parsed = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
35
|
-
if (Array.isArray(parsed.assetCopy)) return parsed.assetCopy;
|
|
36
|
-
} catch {}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return [];
|
|
17
|
+
const entries = loadProjectConfig(projectDir).assetCopy;
|
|
18
|
+
return Array.isArray(entries) ? entries : [];
|
|
40
19
|
}
|
|
41
20
|
|
|
42
21
|
function copyFileAsset(srcPath, destPath, dest) {
|
package/src/cli-core.js
CHANGED
|
@@ -26,6 +26,7 @@ import { copyAssets } from "./asset-manager.js";
|
|
|
26
26
|
import { compileDir, compileSingleFile } from "./compiler.js";
|
|
27
27
|
import { createDevServer } from "./dev-server.js";
|
|
28
28
|
import { minify } from "./minifier.js";
|
|
29
|
+
import { loadProjectConfig } from "./project-config.js";
|
|
29
30
|
import { generatePwa } from "./pwa.js";
|
|
30
31
|
import { runTests } from "./test-runner.js";
|
|
31
32
|
import { bundleVendor } from "./vendor.js";
|
|
@@ -314,25 +315,6 @@ export function findPackageDirs(root) {
|
|
|
314
315
|
|
|
315
316
|
// ── Project detection ─────────────────────────────────────────
|
|
316
317
|
|
|
317
|
-
function loadProjectConfig(projectRoot) {
|
|
318
|
-
const gofrontJson = join(projectRoot, "gofront.json");
|
|
319
|
-
if (existsSync(gofrontJson)) {
|
|
320
|
-
try {
|
|
321
|
-
return JSON.parse(readFileSync(gofrontJson, "utf8"));
|
|
322
|
-
} catch {}
|
|
323
|
-
}
|
|
324
|
-
const pkgJson = join(projectRoot, "package.json");
|
|
325
|
-
if (existsSync(pkgJson)) {
|
|
326
|
-
try {
|
|
327
|
-
const pkg = JSON.parse(readFileSync(pkgJson, "utf8"));
|
|
328
|
-
if (pkg.gofront && typeof pkg.gofront === "object") {
|
|
329
|
-
return pkg.gofront;
|
|
330
|
-
}
|
|
331
|
-
} catch {}
|
|
332
|
-
}
|
|
333
|
-
return {};
|
|
334
|
-
}
|
|
335
|
-
|
|
336
318
|
function resolveSrcDir(projectRoot, config) {
|
|
337
319
|
const src = config.src || config.input;
|
|
338
320
|
if (src) return isAbsolute(src) ? src : resolve(projectRoot, src);
|
|
@@ -562,10 +544,23 @@ const STATIC_ASSET_EXTS = new Set([
|
|
|
562
544
|
|
|
563
545
|
const ROOT_CONFIG_EXTS = new Set([".js", ".mjs", ".json"]);
|
|
564
546
|
|
|
547
|
+
// Builds the asset extension whitelist: defaults plus any `assetExtensions` from project config.
|
|
548
|
+
export function resolveAssetExtensions(config = {}) {
|
|
549
|
+
const exts = new Set(STATIC_ASSET_EXTS);
|
|
550
|
+
const extra = config.assetExtensions;
|
|
551
|
+
if (!Array.isArray(extra)) return exts;
|
|
552
|
+
for (const raw of extra) {
|
|
553
|
+
if (typeof raw !== "string" || raw.trim() === "") continue;
|
|
554
|
+
const ext = raw.trim().toLowerCase();
|
|
555
|
+
exts.add(ext.startsWith(".") ? ext : `.${ext}`);
|
|
556
|
+
}
|
|
557
|
+
return exts;
|
|
558
|
+
}
|
|
559
|
+
|
|
565
560
|
// Whether a file in the served directory is a web asset worth copying to the build output.
|
|
566
|
-
function isStaticAsset(name, atProjectRoot) {
|
|
561
|
+
function isStaticAsset(name, atProjectRoot, exts = STATIC_ASSET_EXTS) {
|
|
567
562
|
const ext = extname(name).toLowerCase();
|
|
568
|
-
if (!
|
|
563
|
+
if (!exts.has(ext)) return false;
|
|
569
564
|
// At the project root, .js/.json files are tooling config, not web assets.
|
|
570
565
|
if (atProjectRoot && ROOT_CONFIG_EXTS.has(ext))
|
|
571
566
|
return name === "manifest.json";
|
|
@@ -578,6 +573,7 @@ function copyServeDirAssets(project, outDir, srcDir) {
|
|
|
578
573
|
const out = resolve(outDir);
|
|
579
574
|
if (!existsSync(serveDir) || serveDir === out) return 0;
|
|
580
575
|
const isRoot = serveDir === resolve(project.projectRoot);
|
|
576
|
+
const exts = resolveAssetExtensions(project.config);
|
|
581
577
|
const skipDirs = [out, resolve(srcDir)];
|
|
582
578
|
const devOutput = resolve(project.devOutputFile);
|
|
583
579
|
const skipFiles = new Set([devOutput, `${devOutput}.map`]);
|
|
@@ -600,7 +596,7 @@ function copyServeDirAssets(project, outDir, srcDir) {
|
|
|
600
596
|
} else if (
|
|
601
597
|
entry.isFile() &&
|
|
602
598
|
!skipFiles.has(full) &&
|
|
603
|
-
isStaticAsset(entry.name, isRoot)
|
|
599
|
+
isStaticAsset(entry.name, isRoot, exts)
|
|
604
600
|
) {
|
|
605
601
|
copyFile(full);
|
|
606
602
|
}
|
package/src/codegen/templ.js
CHANGED
|
@@ -92,21 +92,36 @@ export const templGenMethods = {
|
|
|
92
92
|
_genTemplElement(node, p) {
|
|
93
93
|
const { tag, attrs, children } = node;
|
|
94
94
|
const elVar = this._freshVar("e");
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
95
|
+
// <svg> and its descendants must be created in the SVG namespace.
|
|
96
|
+
const isSvg = tag === "svg" || this._templSvgDepth > 0;
|
|
97
|
+
if (isSvg) {
|
|
98
|
+
this.line(
|
|
99
|
+
`const ${elVar} = document.createElementNS("http://www.w3.org/2000/svg", ${JSON.stringify(tag)});`,
|
|
100
|
+
);
|
|
101
|
+
} else {
|
|
102
|
+
this.line(
|
|
103
|
+
`const ${elVar} = document.createElement(${JSON.stringify(tag)});`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
this._templSvgDepth = (this._templSvgDepth || 0) + (isSvg ? 1 : 0);
|
|
98
107
|
for (const attr of attrs) {
|
|
99
|
-
this._genTemplAttr(attr, elVar);
|
|
108
|
+
this._genTemplAttr(attr, elVar, isSvg);
|
|
100
109
|
}
|
|
101
110
|
this._genTemplNodes(children, elVar);
|
|
111
|
+
if (isSvg) this._templSvgDepth--;
|
|
102
112
|
this.line(`${p}.appendChild(${elVar});`);
|
|
103
113
|
},
|
|
104
114
|
|
|
105
|
-
_genTemplAttr(attr, el) {
|
|
115
|
+
_genTemplAttr(attr, el, isSvg) {
|
|
106
116
|
const { name } = attr;
|
|
117
|
+
// SVGElement.className is a read-only SVGAnimatedString.
|
|
118
|
+
const setClass = (js) =>
|
|
119
|
+
isSvg
|
|
120
|
+
? `${el}.setAttribute("class", ${js});`
|
|
121
|
+
: `${el}.className = ${js};`;
|
|
107
122
|
if (attr.kind === "static") {
|
|
108
123
|
if (name === "class") {
|
|
109
|
-
this.line(
|
|
124
|
+
this.line(setClass(JSON.stringify(attr.value)));
|
|
110
125
|
} else {
|
|
111
126
|
this.line(
|
|
112
127
|
`${el}.setAttribute(${JSON.stringify(name)}, ${JSON.stringify(attr.value)});`,
|
|
@@ -115,7 +130,7 @@ export const templGenMethods = {
|
|
|
115
130
|
} else if (attr.kind === "expr") {
|
|
116
131
|
const exprJs = this._genTemplTokenExpr(attr.tokens);
|
|
117
132
|
if (name === "class") {
|
|
118
|
-
this.line(
|
|
133
|
+
this.line(setClass(exprJs));
|
|
119
134
|
} else {
|
|
120
135
|
this.line(
|
|
121
136
|
`${el}.setAttribute(${JSON.stringify(name)}, String(${exprJs}));`,
|
package/src/minifier.js
CHANGED
|
@@ -117,10 +117,14 @@ const REGEX_CONTEXT_VALUES = new Set([
|
|
|
117
117
|
"||",
|
|
118
118
|
]);
|
|
119
119
|
|
|
120
|
+
// Ops after which `/` is division, not a regex start
|
|
121
|
+
const DIVISION_AFTER_OPS = new Set([")", "]", "++", "--"]);
|
|
122
|
+
|
|
120
123
|
function _isRegexContext(tokens) {
|
|
121
124
|
const prev = lastNonWhitespaceToken(tokens);
|
|
122
125
|
if (!prev) return true;
|
|
123
|
-
|
|
126
|
+
if (prev.type === "op") return !DIVISION_AFTER_OPS.has(prev.value);
|
|
127
|
+
return REGEX_CONTEXT_VALUES.has(prev.value);
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
function _scanRegexLiteral(code, i, len) {
|
|
@@ -129,6 +133,7 @@ function _scanRegexLiteral(code, i, len) {
|
|
|
129
133
|
let inCharClass = false;
|
|
130
134
|
while (j < len) {
|
|
131
135
|
const rc = code[j];
|
|
136
|
+
if (rc === "\n") break;
|
|
132
137
|
if (escaped) {
|
|
133
138
|
escaped = false;
|
|
134
139
|
} else if (rc === "\\") {
|
|
@@ -457,12 +462,29 @@ function _collectVarLocalDecls(tokens, i, declaredLocals) {
|
|
|
457
462
|
collectDestructured(tokens, next.idx, declaredLocals);
|
|
458
463
|
}
|
|
459
464
|
|
|
465
|
+
function _matchingParen(tokens, openIdx) {
|
|
466
|
+
let depth = 1;
|
|
467
|
+
for (let i = openIdx + 1; i < tokens.length; i++) {
|
|
468
|
+
const v = tokens[i].value;
|
|
469
|
+
if (v === "(") depth++;
|
|
470
|
+
else if (v === ")" && --depth === 0) return i;
|
|
471
|
+
}
|
|
472
|
+
return -1;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// A `(` opens a parameter list only when its `)` is followed by a body `{` or `=>`;
|
|
476
|
+
// otherwise `name(...)` is a call and its arguments may be free globals.
|
|
460
477
|
function _collectParamDecls(tokens, i, declaredLocals) {
|
|
461
478
|
const prev = findPrevNonWsToken(tokens, i);
|
|
462
479
|
if (
|
|
463
|
-
prev
|
|
464
|
-
(prev.value
|
|
480
|
+
!prev ||
|
|
481
|
+
(prev.value !== "function" && prev.type !== "ident" && prev.value !== ")")
|
|
465
482
|
)
|
|
483
|
+
return;
|
|
484
|
+
const close = _matchingParen(tokens, i);
|
|
485
|
+
if (close < 0) return;
|
|
486
|
+
const after = nextNonWs(tokens, close);
|
|
487
|
+
if (after && (after.tok.value === "{" || after.tok.value === "=>"))
|
|
466
488
|
collectParams(tokens, i, declaredLocals);
|
|
467
489
|
}
|
|
468
490
|
|
|
@@ -514,6 +536,33 @@ const MANGLE_GLOBALS = new Set([
|
|
|
514
536
|
"Infinity",
|
|
515
537
|
"TextEncoder",
|
|
516
538
|
"TextDecoder",
|
|
539
|
+
"globalThis",
|
|
540
|
+
"Reflect",
|
|
541
|
+
"Proxy",
|
|
542
|
+
"WeakMap",
|
|
543
|
+
"WeakSet",
|
|
544
|
+
"ArrayBuffer",
|
|
545
|
+
"DataView",
|
|
546
|
+
"Int8Array",
|
|
547
|
+
"Uint8Array",
|
|
548
|
+
"Uint8ClampedArray",
|
|
549
|
+
"Int16Array",
|
|
550
|
+
"Uint16Array",
|
|
551
|
+
"Int32Array",
|
|
552
|
+
"Uint32Array",
|
|
553
|
+
"Float32Array",
|
|
554
|
+
"Float64Array",
|
|
555
|
+
"BigInt64Array",
|
|
556
|
+
"BigUint64Array",
|
|
557
|
+
"Image",
|
|
558
|
+
"URL",
|
|
559
|
+
"Event",
|
|
560
|
+
"fetch",
|
|
561
|
+
"navigator",
|
|
562
|
+
"performance",
|
|
563
|
+
"localStorage",
|
|
564
|
+
"requestAnimationFrame",
|
|
565
|
+
"cancelAnimationFrame",
|
|
517
566
|
"setTimeout",
|
|
518
567
|
"setInterval",
|
|
519
568
|
"clearTimeout",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Project configuration loader shared by the CLI, asset manager and vendor bundler.
|
|
2
|
+
// Settings live in `gofront.json` at the project root, or under `"gofront"` in `package.json`.
|
|
3
|
+
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
export function loadProjectConfig(projectRoot) {
|
|
8
|
+
const gofrontJson = join(projectRoot, "gofront.json");
|
|
9
|
+
if (existsSync(gofrontJson)) {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(readFileSync(gofrontJson, "utf8"));
|
|
12
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
13
|
+
} catch {}
|
|
14
|
+
}
|
|
15
|
+
const pkgJson = join(projectRoot, "package.json");
|
|
16
|
+
if (existsSync(pkgJson)) {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(readFileSync(pkgJson, "utf8"));
|
|
19
|
+
if (pkg.gofront && typeof pkg.gofront === "object") return pkg.gofront;
|
|
20
|
+
} catch {}
|
|
21
|
+
}
|
|
22
|
+
return {};
|
|
23
|
+
}
|
package/src/vendor.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
13
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
14
14
|
import { pathToFileURL } from "node:url";
|
|
15
|
+
import { loadProjectConfig } from "./project-config.js";
|
|
15
16
|
|
|
16
17
|
export function getExportNames(pkgName, globals = {}) {
|
|
17
18
|
const base = pkgName.replace(/^@[^/]+\//, "");
|
|
@@ -111,15 +112,7 @@ export function loadVendorConfig(projectDir) {
|
|
|
111
112
|
} catch {}
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
const
|
|
115
|
-
let gfj = {};
|
|
116
|
-
if (existsSync(gfjPath)) {
|
|
117
|
-
try {
|
|
118
|
-
gfj = JSON.parse(readFileSync(gfjPath, "utf8"));
|
|
119
|
-
} catch {}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const vendorConfig = gfj.vendor ?? pkg.vendor ?? null;
|
|
115
|
+
const vendorConfig = loadProjectConfig(projectDir).vendor ?? null;
|
|
123
116
|
const dependencies = Object.keys(pkg.dependencies ?? {});
|
|
124
117
|
|
|
125
118
|
let dest = null;
|