gofront 1.3.5 → 1.3.6

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.
@@ -28,12 +28,17 @@ const BUILDER_COMMON = {
28
28
  export const builderMethods = {
29
29
  _genBuilderCall(typeName, method, expr) {
30
30
  const recv = expr.func.expr;
31
- const recvJs = this.genExpr(recv);
32
- const base = `(${recvJs}?.value ?? ${recvJs})`;
31
+ const base = this._unboxWriter(this.genExpr(recv));
33
32
  const args = expr.args.map((a) => this.genExpr(a));
34
33
  const isStr = typeName === "strings.Builder";
35
34
  const table = isStr ? BUILDER_STR : BUILDER_BYTES;
36
35
  const gen = table[method] ?? BUILDER_COMMON[method];
37
36
  return gen ? gen(base, args) : undefined;
38
37
  },
38
+
39
+ // Accepts a Builder/Buffer or a pointer box around one; evaluates `js` once.
40
+ _unboxWriter(js) {
41
+ if (/^[\w$.]+$/.test(js)) return `(${js}?.value ?? ${js})`;
42
+ return `((__w) => __w?.value ?? __w)(${js})`;
43
+ },
39
44
  };
@@ -80,12 +80,12 @@ export const fmtMethods = {
80
80
  const sprintfCall = this._buildFprintSprintfCall(fn, rest.length, restJs);
81
81
  if (targetTypeName === "strings.Builder") {
82
82
  const w = this.genExpr(writerArg);
83
- const base = `(${w}?.value ?? ${w})`;
83
+ const base = this._unboxWriter(w);
84
84
  return `(${base}._buf += ${sprintfCall})`;
85
85
  }
86
86
  if (targetTypeName === "bytes.Buffer") {
87
87
  const w = this.genExpr(writerArg);
88
- const base = `(${w}?.value ?? ${w})`;
88
+ const base = this._unboxWriter(w);
89
89
  return `((__b,__s)=>{ __b._buf.push(...new TextEncoder().encode(__s)); })(${base}, ${sprintfCall})`;
90
90
  }
91
91
  // Generic io.Writer fallback — call .WriteString if available
@@ -14,7 +14,7 @@ export const ioMethods = {
14
14
  (writerType?.kind === "pointer" ? writerType.base?.name : null);
15
15
  const w = this.genExpr(writerArg);
16
16
  const s = this.genExpr(expr.args[1]);
17
- const base = `(${w}?.value ?? ${w})`;
17
+ const base = this._unboxWriter(w);
18
18
  if (typeName === "strings.Builder") {
19
19
  return `((b,s) => { b._buf += s; return [s.length, null]; })(${base}, ${s})`;
20
20
  }
package/src/dev-server.js CHANGED
@@ -140,13 +140,15 @@ function isInsideDir(root, target) {
140
140
  return rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
141
141
  }
142
142
 
143
- function handleSseRequest(req, res, clients) {
143
+ function handleSseRequest(req, res, clients, lastError = null) {
144
144
  res.writeHead(200, {
145
145
  "Content-Type": "text/event-stream",
146
146
  "Cache-Control": "no-cache",
147
147
  Connection: "keep-alive",
148
148
  });
149
149
  res.write(": connected\n\n");
150
+ if (lastError)
151
+ res.write(`event: build-error\ndata: ${JSON.stringify(lastError)}\n\n`);
150
152
  clients.add(res);
151
153
  req.on?.("close", () => clients.delete(res));
152
154
  }
@@ -216,7 +218,7 @@ export function handleDevRequest(
216
218
  options = {},
217
219
  ) {
218
220
  if (req.url === "/_gofront/events") {
219
- handleSseRequest(req, res, clients);
221
+ handleSseRequest(req, res, clients, options.getLastError?.() ?? null);
220
222
  return;
221
223
  }
222
224
 
@@ -312,15 +314,19 @@ function closeServer(server, clients, timer) {
312
314
  export function createDevServer(serveDir, port = 3000, options = {}) {
313
315
  const clients = new Set();
314
316
  const heartbeatMs = options.heartbeatInterval ?? 15000;
317
+ const host = options.host ?? "localhost";
318
+ // Kept so browsers that connect (or reload) after a failed build still see the overlay.
319
+ let lastError = null;
320
+ const requestOptions = { ...options, getLastError: () => lastError };
315
321
 
316
322
  const server = createServer((req, res) => {
317
- handleDevRequest(req, res, serveDir, clients, options);
323
+ handleDevRequest(req, res, serveDir, clients, requestOptions);
318
324
  });
319
325
 
320
326
  server.on("error", (err) => handleServerError(err, port));
321
- server.listen(port, () => {
327
+ server.listen(port, host, () => {
322
328
  const actualPort = server.address()?.port ?? port;
323
- if (port !== 0) {
329
+ if (port !== 0 && !options.silent) {
324
330
  console.error(`gofront: dev server → http://localhost:${actualPort}`);
325
331
  }
326
332
  });
@@ -329,17 +335,23 @@ export function createDevServer(serveDir, port = 3000, options = {}) {
329
335
  heartbeatMs > 0 ? setInterval(() => sendPings(clients), heartbeatMs) : null;
330
336
  heartbeatTimer?.unref?.();
331
337
 
332
- const notify = createNotify(clients);
338
+ const broadcastNotify = createNotify(clients);
339
+ const notify = (arg) => {
340
+ if (!arg?.type) lastError = null;
341
+ broadcastNotify(arg);
342
+ };
343
+ const notifyError = (err) => {
344
+ lastError = buildErrorPayload(err);
345
+ broadcastToClients(clients, "build-error", lastError);
346
+ };
333
347
 
334
348
  return {
335
349
  broadcast: (event, data) => broadcastToClients(clients, event, data),
336
350
  notify,
337
351
  notifyCss: (file) =>
338
352
  broadcastToClients(clients, "css-update", { type: "css-update", file }),
339
- notifyError: (err) =>
340
- broadcastToClients(clients, "build-error", buildErrorPayload(err)),
341
- notifyBuildError: (err) =>
342
- broadcastToClients(clients, "build-error", buildErrorPayload(err)),
353
+ notifyError,
354
+ notifyBuildError: notifyError,
343
355
  ping: () => sendPings(clients),
344
356
  clients,
345
357
  server,
package/src/index.js CHANGED
@@ -162,8 +162,14 @@ if (args[0] === "dev") {
162
162
  const devOptions = parseDevArgs(args.slice(1));
163
163
  try {
164
164
  const dev = await handleDev(devOptions.targetDir, devOptions);
165
- const port = dev.port ?? devOptions.port ?? 3000;
166
- console.error(`gofront: dev server running → http://localhost:${port}`);
165
+ if (dev.initialError) {
166
+ console.error("gofront: ERROR");
167
+ for (const line of dev.initialError.message.split("\n"))
168
+ console.error(` ${line}`);
169
+ } else {
170
+ console.error(`gofront: OK — wrote ${dev.outputFile}`);
171
+ }
172
+ console.error(`gofront: dev server running → http://localhost:${dev.port}`);
167
173
  console.error(`gofront: watching ${dev.srcDir} for changes...`);
168
174
 
169
175
  const shutdown = async () => {
@@ -29,7 +29,7 @@ export const declarationParseMethods = {
29
29
  this.check(T.IDENT) && !this.isReceiverTerminator(this.peek2())
30
30
  ? this.advance().value
31
31
  : "_";
32
- this.match(T.STAR); // pointer receiver: (c *Counter) — strip *, treat same as value receiver
32
+ const recvPointer = Boolean(this.match(T.STAR));
33
33
  const recvType = this.parseTypeName();
34
34
  this.expect(T.RPAREN);
35
35
  const name = this.expect(T.IDENT).value;
@@ -40,6 +40,7 @@ export const declarationParseMethods = {
40
40
  kind: "MethodDecl",
41
41
  recvName,
42
42
  recvType,
43
+ recvPointer,
43
44
  name,
44
45
  params,
45
46
  returnType,
package/src/pwa.js CHANGED
@@ -86,7 +86,9 @@ self.addEventListener("fetch", (event) => {
86
86
  return response;
87
87
  }).catch(() => {
88
88
  if (event.request.headers?.get?.("accept")?.includes("text/html")) {
89
- return caches.match("./") || caches.match("./index.html");
89
+ return caches
90
+ .match("./")
91
+ .then((r) => r || caches.match("./index.html"));
90
92
  }
91
93
  });
92
94
  }),
@@ -14,6 +14,7 @@ import {
14
14
  isNumeric,
15
15
  isPointer,
16
16
  isString,
17
+ isTypedArraySlice,
17
18
  isUntyped,
18
19
  LOG_OPS,
19
20
  SIZED_FLOAT_NAMES,
@@ -157,6 +158,7 @@ export const assignabilityMethods = {
157
158
  if (!target || !source) return;
158
159
  target = this.resolveType(target);
159
160
  source = this.resolveType(source);
161
+ this._markIfaceBox(target, source, node);
160
162
  if (this._assertAssignableEarlyReturn(target, source)) return;
161
163
  if (isPointer(target) && isPointer(source)) {
162
164
  this.assertAssignable(
@@ -170,10 +172,34 @@ export const assignabilityMethods = {
170
172
  if (this._isNumericCoercible(target, source)) return;
171
173
  if (this._checkArrayAssignable(target, source, node)) return;
172
174
  if (this._checkTypedArrayAssignable(target, source, node)) return;
175
+ if (this._checkFuncAssignable(target, source, node)) return;
173
176
  if (typeStr(target) !== typeStr(source))
174
177
  this._assertAssignableTypeMismatch(target, source, node);
175
178
  },
176
179
 
180
+ // Tags struct values / struct pointers flowing into an interface so codegen can
181
+ // tell `T` from `*T` at runtime (both compile to the same class instance).
182
+ _markIfaceBox(target, source, node) {
183
+ if (this._skipIfaceMark) return;
184
+ if (!node || typeof node !== "object" || !target || !source) return;
185
+ if (target.kind === "typeParam") return;
186
+ const tBase = this.resolveType(
187
+ target.kind === "named" ? target.underlying : target,
188
+ );
189
+ if (!isAny(target) && tBase?.kind !== "interface") return;
190
+ const structOf = (t) => {
191
+ const r = this.resolveType(t);
192
+ const b = r?.kind === "named" ? this.resolveType(r.underlying) : r;
193
+ return b?.kind === "struct";
194
+ };
195
+ const sBase = this.resolveType(
196
+ source.kind === "named" ? source.underlying : source,
197
+ );
198
+ if (sBase?.kind === "struct") node._ifaceBox = "value";
199
+ else if (sBase?.kind === "pointer" && structOf(sBase.base))
200
+ node._ifaceBox = "ptr";
201
+ },
202
+
177
203
  _assertAssignableEarlyReturn(target, source) {
178
204
  return (
179
205
  target?.kind === "typeParam" ||
@@ -211,9 +237,13 @@ export const assignabilityMethods = {
211
237
 
212
238
  _isNumericCoercible(target, source) {
213
239
  if (target.kind !== "basic" || source.kind !== "basic") return false;
240
+ const pair = `${target.name}:${source.name}`;
214
241
  return (
215
- (target.name === "float64" && source.name === "int") ||
216
- (target.name === "int" && source.name === "float64")
242
+ pair === "float64:int" ||
243
+ pair === "int:float64" ||
244
+ // rune was historically int in GoFront; keep rune/int interchangeable.
245
+ pair === "int:int32" ||
246
+ pair === "int32:int"
217
247
  );
218
248
  },
219
249
 
@@ -258,8 +288,9 @@ export const assignabilityMethods = {
258
288
  const taElem = targetTa
259
289
  ? (target.underlying?._elemType ?? target._elemType)
260
290
  : (source.underlying?._elemType ?? source._elemType);
261
- const slElem = targetSlice ? targetSlice.elem : sourceSlice.elem;
262
- if (typeStr(taElem) === typeStr(slElem)) return true;
291
+ const slice = targetSlice ?? sourceSlice;
292
+ if (isTypedArraySlice(slice) && typeStr(taElem) === typeStr(slice.elem))
293
+ return true;
263
294
  this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
264
295
  return true;
265
296
  }
@@ -275,6 +306,21 @@ export const assignabilityMethods = {
275
306
  return false;
276
307
  },
277
308
 
309
+ _checkFuncAssignable(target, source, node) {
310
+ const tFunc = target.kind === "named" ? target.underlying : target;
311
+ const sFunc = source.kind === "named" ? source.underlying : source;
312
+ if (tFunc?.kind === "func" && sFunc?.kind === "func") {
313
+ if (!this._implementsMethod(tFunc, sFunc)) {
314
+ this.err(
315
+ `Cannot assign ${typeStr(source)} to ${typeStr(target)}`,
316
+ node,
317
+ );
318
+ }
319
+ return true;
320
+ }
321
+ return false;
322
+ },
323
+
278
324
  // ── Interface satisfaction ────────────────────────────────────────
279
325
 
280
326
  _sigParamsMatch(reqList, actList) {
@@ -15,6 +15,7 @@ import {
15
15
  isNumeric,
16
16
  isString,
17
17
  NIL,
18
+ RUNE,
18
19
  Scope,
19
20
  STRING,
20
21
  TAINTED_ANY,
@@ -40,6 +41,17 @@ const BUILTIN_CHECK = {
40
41
  s.err(`cannot append to array (type ${typeStr(st)})`, e);
41
42
  return st;
42
43
  }
44
+ const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
45
+ if (sb?.kind === "slice") {
46
+ for (let i = 1; i < e.args.length; i++) {
47
+ if (!e.args[i]._spread && at[i])
48
+ s._markIfaceBox(
49
+ s.resolveType(sb.elem),
50
+ s.resolveType(at[i]),
51
+ e.args[i],
52
+ );
53
+ }
54
+ }
43
55
  return st;
44
56
  },
45
57
  copy: () => INT,
@@ -379,7 +391,7 @@ export const expressionCheckMethods = {
379
391
  },
380
392
 
381
393
  _rangeCollTypeTuple(resolved, collType) {
382
- if (isString(resolved)) return { kind: "tuple", types: [INT, INT] };
394
+ if (isString(resolved)) return { kind: "tuple", types: [INT, RUNE] };
383
395
  if (resolved?.kind === "slice" || resolved?.kind === "array")
384
396
  return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
385
397
  if (resolved?.kind === "map")
@@ -185,12 +185,55 @@ export class TypeChecker {
185
185
  }
186
186
  }
187
187
  }
188
+ const all = [];
188
189
  for (const p of programs) {
189
- this._setCurrentFile(p);
190
- for (const d of p.decls) if (d.kind === "TypeDecl") this.collectType(d);
190
+ for (const d of p.decls) if (d.kind === "TypeDecl") all.push({ p, d });
191
+ }
192
+ // Collect in dependency order so aliases, generics, embedded and by-value
193
+ // field types declared later (or in other files) are ready when needed.
194
+ let pending = all;
195
+ while (pending.length > 0) {
196
+ const pendingNames = new Map(pending.map((x) => [x.d.name, x.d]));
197
+ const ready = pending.filter(
198
+ (x) => !this._typeDeclBlocked(x.d, pendingNames),
199
+ );
200
+ const batch = ready.length > 0 ? ready : pending;
201
+ for (const { p, d } of batch) {
202
+ this._setCurrentFile(p);
203
+ this.collectType(d);
204
+ }
205
+ pending =
206
+ ready.length > 0 ? pending.filter((x) => !ready.includes(x)) : [];
191
207
  }
192
208
  }
193
209
 
210
+ _typeDeclBlocked(decl, pendingNames) {
211
+ const visit = (node, indirect) => {
212
+ if (!node || typeof node !== "object") return false;
213
+ if (Array.isArray(node)) return node.some((n) => visit(n, indirect));
214
+ if (
215
+ (node.kind === "TypeName" || node.kind === "GenericTypeName") &&
216
+ node.name !== decl.name
217
+ ) {
218
+ const dep = pendingNames.get(node.name);
219
+ if (dep && (!indirect || dep.isAlias || dep.typeParams)) return true;
220
+ }
221
+ const nextIndirect =
222
+ indirect ||
223
+ [
224
+ "PointerType",
225
+ "SliceType",
226
+ "MapType",
227
+ "FuncType",
228
+ "ChanType",
229
+ ].includes(node.kind);
230
+ return Object.keys(node).some(
231
+ (k) => !k.startsWith("_") && visit(node[k], nextIndirect),
232
+ );
233
+ };
234
+ return visit(decl.type, false);
235
+ }
236
+
194
237
  _collectFuncsPass(programs) {
195
238
  for (const p of programs) {
196
239
  this._setCurrentFile(p);
@@ -390,6 +433,7 @@ export class TypeChecker {
390
433
  }
391
434
 
392
435
  _attachMethod(decl, funcType) {
436
+ funcType._ptrRecv = Boolean(decl.recvPointer);
393
437
  const recvNamedType = this.types.get(decl.recvType.name);
394
438
  if (recvNamedType?._generic)
395
439
  this._attachGenericMethod(recvNamedType, decl, funcType);
@@ -411,6 +411,8 @@ export const resolveMethods = {
411
411
  _fieldTypeInterface(base, baseType, field, node) {
412
412
  if (base.methods?.has(field)) return base.methods.get(field);
413
413
  if (base.fields?.has(field)) return base.fields.get(field);
414
+ if (base._excluded?.has(field))
415
+ return this.err(`No member '${field}' on ${typeStr(baseType)}`, node);
414
416
  if (base._isOpen) return ANY;
415
417
  return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
416
418
  },
@@ -148,8 +148,17 @@ export const statementCheckMethods = {
148
148
  for (let i = 0; i < lhsTypes.length; i++) {
149
149
  if (stmt.lhs[i]?.kind === "Ident" && stmt.lhs[i]?.name === "_") continue;
150
150
  const r = rhsFlat[i] ?? ANY;
151
- if (!isAny(lhsTypes[i]) && !isAny(r))
151
+ if (stmt.rhs.length === lhsTypes.length && lhsTypes[i] && r)
152
+ this._markIfaceBox(
153
+ this.resolveType(lhsTypes[i]),
154
+ this.resolveType(r),
155
+ stmt.rhs[i],
156
+ );
157
+ if (!isAny(lhsTypes[i]) && !isAny(r)) {
158
+ this._skipIfaceMark = true;
152
159
  this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
160
+ this._skipIfaceMark = false;
161
+ }
153
162
  }
154
163
  },
155
164
 
@@ -23,6 +23,25 @@ const func = (params, returns = [VOID], variadic = false) => ({
23
23
  variadic,
24
24
  });
25
25
 
26
+ // Last parameter is optional (modelled as variadic).
27
+ const optLast = (params, returns = [VOID]) => func(params, returns, true);
28
+
29
+ const WEBGL2_ONLY_CONSTANTS = new Set([
30
+ "UNIFORM_BUFFER",
31
+ "HALF_FLOAT",
32
+ "RGBA8",
33
+ "RGBA32F",
34
+ ]);
35
+
36
+ const WEBGL2_ONLY_METHODS = new Set([
37
+ "VertexAttribIPointer",
38
+ "DrawArraysInstanced",
39
+ "DrawElementsInstanced",
40
+ "CreateVertexArray",
41
+ "BindVertexArray",
42
+ "DeleteVertexArray",
43
+ ]);
44
+
26
45
  const WEBGL_CONSTANTS_LIST = {
27
46
  ARRAY_BUFFER: 0x8892,
28
47
  ELEMENT_ARRAY_BUFFER: 0x8893,
@@ -113,7 +132,7 @@ function buildArrayBufferType() {
113
132
  methods,
114
133
  };
115
134
  named.underlying = iface;
116
- methods.set("slice", func([INT, INT], [named]));
135
+ methods.set("slice", optLast([INT, INT], [named]));
117
136
  return named;
118
137
  }
119
138
 
@@ -126,20 +145,20 @@ function buildDataViewType(arrayBufferType) {
126
145
  const methods = new Map([
127
146
  ["getInt8", func([INT], [INT8])],
128
147
  ["getUint8", func([INT], [UINT8])],
129
- ["getInt16", func([INT, BOOL], [INT16])],
130
- ["getUint16", func([INT, BOOL], [UINT16])],
131
- ["getInt32", func([INT, BOOL], [INT32])],
132
- ["getUint32", func([INT, BOOL], [UINT32])],
133
- ["getFloat32", func([INT, BOOL], [FLOAT32])],
134
- ["getFloat64", func([INT, BOOL], [FLOAT64])],
148
+ ["getInt16", optLast([INT, BOOL], [INT16])],
149
+ ["getUint16", optLast([INT, BOOL], [UINT16])],
150
+ ["getInt32", optLast([INT, BOOL], [INT32])],
151
+ ["getUint32", optLast([INT, BOOL], [UINT32])],
152
+ ["getFloat32", optLast([INT, BOOL], [FLOAT32])],
153
+ ["getFloat64", optLast([INT, BOOL], [FLOAT64])],
135
154
  ["setInt8", func([INT, INT8])],
136
155
  ["setUint8", func([INT, UINT8])],
137
- ["setInt16", func([INT, INT16, BOOL])],
138
- ["setUint16", func([INT, UINT16, BOOL])],
139
- ["setInt32", func([INT, INT32, BOOL])],
140
- ["setUint32", func([INT, UINT32, BOOL])],
141
- ["setFloat32", func([INT, FLOAT32, BOOL])],
142
- ["setFloat64", func([INT, FLOAT64, BOOL])],
156
+ ["setInt16", optLast([INT, INT16, BOOL])],
157
+ ["setUint16", optLast([INT, UINT16, BOOL])],
158
+ ["setInt32", optLast([INT, INT32, BOOL])],
159
+ ["setUint32", optLast([INT, UINT32, BOOL])],
160
+ ["setFloat32", optLast([INT, FLOAT32, BOOL])],
161
+ ["setFloat64", optLast([INT, FLOAT64, BOOL])],
143
162
  ]);
144
163
  const named = { kind: "named", name: "DataView", underlying: null };
145
164
  named.underlying = { kind: "interface", name: "DataView", fields, methods };
@@ -164,15 +183,16 @@ function buildTypedArrayType(name, elemType, arrayBufferType) {
164
183
  _isTypedArray: true,
165
184
  };
166
185
  named.underlying = iface;
167
- methods.set("subarray", func([INT, INT], [named]));
168
- methods.set("slice", func([INT, INT], [named]));
169
- methods.set("set", func([ANY, INT], [VOID]));
186
+ methods.set("subarray", optLast([INT, INT], [named]));
187
+ methods.set("slice", optLast([INT, INT], [named]));
188
+ methods.set("set", optLast([ANY, INT], [VOID]));
170
189
  return named;
171
190
  }
172
191
 
173
- function createWebGLMethods() {
192
+ function createWebGLMethods(isWebGL2) {
174
193
  const methods = new Map();
175
194
  const add = (name, fn) => {
195
+ if (!isWebGL2 && WEBGL2_ONLY_METHODS.has(name)) return;
176
196
  methods.set(name, fn);
177
197
  const camel = name[0].toLowerCase() + name.slice(1);
178
198
  if (camel !== name) methods.set(camel, fn);
@@ -296,7 +316,7 @@ function createWebGPUMethods() {
296
316
  addDev("CreateShaderModule", func([ANY], [ANY]));
297
317
  addDev("CreateRenderPipeline", func([ANY], [ANY]));
298
318
  addDev("CreateComputePipeline", func([ANY], [ANY]));
299
- addDev("CreateCommandEncoder", func([ANY], [ANY]));
319
+ addDev("CreateCommandEncoder", optLast([ANY], [ANY]));
300
320
  addDev("CreateBindGroup", func([ANY], [ANY]));
301
321
  addDev("CreateBindGroupLayout", func([ANY], [ANY]));
302
322
  addDev("CreatePipelineLayout", func([ANY], [ANY]));
@@ -307,7 +327,7 @@ function createWebGPUMethods() {
307
327
  const camel = name[0].toLowerCase() + name.slice(1);
308
328
  if (camel !== name) adapterMethods.set(camel, fn);
309
329
  };
310
- addAdapt("RequestDevice", func([ANY], [ANY]));
330
+ addAdapt("RequestDevice", optLast([ANY], [ANY]));
311
331
 
312
332
  return { queueMethods, deviceMethods, adapterMethods };
313
333
  }
@@ -339,21 +359,29 @@ export function setupWebGlobals(globals, types) {
339
359
  }
340
360
 
341
361
  // WebGL constants map
342
- const constFields = new Map();
362
+ const constFields2 = new Map();
343
363
  for (const [k] of Object.entries(WEBGL_CONSTANTS_LIST)) {
344
- constFields.set(k, INT);
364
+ constFields2.set(k, INT);
365
+ }
366
+ const constFields1 = new Map(
367
+ [...constFields2].filter(([k]) => !WEBGL2_ONLY_CONSTANTS.has(k)),
368
+ );
369
+ const webgl2Only = new Set(WEBGL2_ONLY_CONSTANTS);
370
+ for (const m of WEBGL2_ONLY_METHODS) {
371
+ webgl2Only.add(m);
372
+ webgl2Only.add(m[0].toLowerCase() + m.slice(1));
345
373
  }
346
374
 
347
- const webglMethods = createWebGLMethods();
348
375
  const webglContext = {
349
376
  kind: "named",
350
377
  name: "WebGLRenderingContext",
351
378
  underlying: {
352
379
  kind: "interface",
353
380
  name: "WebGLRenderingContext",
354
- fields: constFields,
355
- methods: webglMethods,
381
+ fields: constFields1,
382
+ methods: createWebGLMethods(false),
356
383
  _isOpen: true,
384
+ _excluded: webgl2Only,
357
385
  },
358
386
  };
359
387
  const webgl2Context = {
@@ -362,8 +390,8 @@ export function setupWebGlobals(globals, types) {
362
390
  underlying: {
363
391
  kind: "interface",
364
392
  name: "WebGL2RenderingContext",
365
- fields: constFields,
366
- methods: webglMethods,
393
+ fields: constFields2,
394
+ methods: createWebGLMethods(true),
367
395
  _isOpen: true,
368
396
  },
369
397
  };
@@ -55,7 +55,7 @@ export const UINT16 = { kind: "basic", name: "uint16" };
55
55
  export const UINT32 = { kind: "basic", name: "uint32" };
56
56
  export const UINT64 = { kind: "basic", name: "uint64" };
57
57
  export const UINTPTR = { kind: "basic", name: "uintptr" };
58
- export const RUNE = INT;
58
+ export const RUNE = INT32;
59
59
  export const FLOAT32 = { kind: "basic", name: "float32" };
60
60
  export const FLOAT64 = { kind: "basic", name: "float64" };
61
61
  export const STRING = { kind: "basic", name: "string" };
@@ -149,9 +149,9 @@ export const isString = makeBasicPredicate("string");
149
149
  export const isBool = makeBasicPredicate("bool");
150
150
 
151
151
  // ── TypedArray mappings ──────────────────────────────────────
152
+ // []float64 deliberately stays a plain Array for JSON / JS-library interop.
152
153
  export const TYPED_ARRAY_CONSTRUCTORS = {
153
154
  float32: "Float32Array",
154
- float64: "Float64Array",
155
155
  uint8: "Uint8Array",
156
156
  byte: "Uint8Array",
157
157
  int8: "Int8Array",
@@ -159,6 +159,7 @@ export const TYPED_ARRAY_CONSTRUCTORS = {
159
159
  int16: "Int16Array",
160
160
  uint32: "Uint32Array",
161
161
  int32: "Int32Array",
162
+ rune: "Int32Array",
162
163
  };
163
164
 
164
165
  export function typedArrayConstructorForElem(elemType) {
package/src/vendor.js CHANGED
@@ -227,9 +227,12 @@ export async function loadNodePolyfillsPlugin(projectRoot, customPlugin) {
227
227
  if (!info) return null;
228
228
  try {
229
229
  const mod = await import(pathToFileURL(info.path).href);
230
- const fn =
231
- mod.nodePolyfills || mod.default?.nodePolyfills || mod.default || mod;
232
- return typeof fn === "function" ? fn() : mod;
230
+ const fn = [
231
+ mod.nodePolyfills,
232
+ mod.default?.nodePolyfills,
233
+ mod.default,
234
+ ].find((f) => typeof f === "function");
235
+ return fn ? fn() : null;
233
236
  } catch {
234
237
  return null;
235
238
  }
@@ -339,10 +342,12 @@ export async function bundleVendor(projectDir = ".", options = {}) {
339
342
  options.nodePolyfillsPlugin ??
340
343
  bundlerInfo.nodePolyfillsPlugin ??
341
344
  (options.nodePolyfills === false ? null : undefined);
342
- const polyfillPlugin = await loadNodePolyfillsPlugin(
343
- projectRoot,
344
- customPlugin,
345
- );
345
+ // The polyfill plugin is a Rolldown plugin; esbuild cannot use it.
346
+ const supportsPlugins =
347
+ typeof bundlerInfo.bundle === "function" || bundlerInfo.name === "rolldown";
348
+ const polyfillPlugin = supportsPlugins
349
+ ? await loadNodePolyfillsPlugin(projectRoot, customPlugin)
350
+ : null;
346
351
 
347
352
  if (typeof bundlerInfo.bundle === "function") {
348
353
  try {