@zntc/wasm 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ohah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # @zntc/wasm
2
+
3
+ ZNTC 의 WASM 빌드 — 브라우저 / Bun / Node 환경에서 native binary (`zntc.node`) 없이 순수 WASM 으로 트랜스파일/번들 실행.
4
+
5
+ `@zntc/core` 의 NAPI 모듈을 쓸 수 없는 환경 (Edge runtime, Cloudflare Workers, 브라우저 in-page transpile 등) 용.
6
+
7
+ ## 설치
8
+
9
+ ```bash
10
+ bun add -D @zntc/wasm
11
+ # 또는
12
+ npm i -D @zntc/wasm
13
+ ```
14
+
15
+ WASM binary (`zntc.wasm` ~1.1MB / `zntc-bundler.wasm` ~2.0MB, gzipped 시 388KB / 675KB) 가 함께 install 됨.
16
+
17
+ ## 사용 예
18
+
19
+ ### Browser / Edge runtime
20
+
21
+ ```ts
22
+ import { init, transpile } from '@zntc/wasm';
23
+ import wasmUrl from '@zntc/wasm/zntc.wasm?url'; // Vite 전용 syntax
24
+
25
+ await init(wasmUrl);
26
+ const r = transpile('const x: number = 1;', { filename: 'input.ts' });
27
+ console.log(r.code);
28
+ ```
29
+
30
+ > Vite 가 아닌 bundler 는 `fetch(new URL('@zntc/wasm/zntc.wasm', import.meta.url))` 또는 각자의 asset import 패턴 사용.
31
+
32
+ ### Node.js / Bun (auto-resolve)
33
+
34
+ ```ts
35
+ import { init, transpile } from '@zntc/wasm';
36
+
37
+ await init(); // 동봉 zntc.wasm 자동 로드 (fs.readFileSync)
38
+ const r = transpile('const x: number = 1;', { filename: 'input.ts' });
39
+ ```
40
+
41
+ ### Bundler 사용
42
+
43
+ ```ts
44
+ import { bundlerLastErrorMessage, build, initBundler } from '@zntc/wasm';
45
+ import wasmUrl from '@zntc/wasm/zntc-bundler.wasm?url';
46
+
47
+ await initBundler(wasmUrl); // transpile 용 init() 와 별도
48
+ const out = build('/main.ts', { format: 'esm', target: 'es2020' });
49
+ if (out === null) throw new Error(bundlerLastErrorMessage());
50
+ ```
51
+
52
+ `build` 는 sync 이며 실패 시 `null` 반환 — `bundlerLastErrorMessage()` 로 마지막 에러 조회.
53
+
54
+ ## NAPI 와 차이
55
+
56
+ | 항목 | @zntc/core (NAPI) | @zntc/wasm |
57
+ | ----------- | ----------------- | ------------------------------------- |
58
+ | 환경 | Node.js / Bun | + Browser / Edge / Workers |
59
+ | 속도 | 빠름 (~native) | 50~70% (WASM 오버헤드) |
60
+ | binary 크기 | ~4MB (.node) | 1.1MB + 2.0MB (gzipped 388KB + 675KB) |
61
+ | install | 플랫폼별 prebuilt | 단일 wasm (universal) |
62
+
63
+ 대부분의 server-side 빌드 사용 사례는 `@zntc/core` 권장. `@zntc/wasm` 은 환경 제약 시.
64
+
65
+ ## 관련
66
+
67
+ - [@zntc/core](https://npmjs.com/package/@zntc/core) — Native (NAPI) 빌드
68
+ - [docs/USAGE.md](https://github.com/ohah/zts/blob/main/docs/USAGE.md)
69
+
70
+ ## 라이센스
71
+
72
+ MIT.
package/dist/index.js ADDED
@@ -0,0 +1,493 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+ // ../shared/index.ts
9
+ var ES_TARGET_BITS = {
10
+ es5: 268435455,
11
+ es2015: 117438464,
12
+ es2016: 117436416,
13
+ es2017: 117432320,
14
+ es2018: 16760832,
15
+ es2019: 16744448,
16
+ es2020: 16646144,
17
+ es2021: 16515072,
18
+ es2022: 12582912,
19
+ es2023: 8388608,
20
+ es2024: 8388608,
21
+ es2025: 0,
22
+ esnext: 0
23
+ };
24
+ function targetToUnsupported(target) {
25
+ if (!target)
26
+ return 0;
27
+ return ES_TARGET_BITS[target] ?? 0;
28
+ }
29
+ function buildOptionsJson(opts = {}, unsupportedOverride) {
30
+ const payload = {};
31
+ if (opts.target)
32
+ payload.target = opts.target;
33
+ if (unsupportedOverride !== undefined && unsupportedOverride !== 0)
34
+ payload.unsupported = unsupportedOverride;
35
+ if (opts.flow)
36
+ payload.flow = true;
37
+ if (opts.jsxInJs)
38
+ payload.jsxInJs = true;
39
+ if (opts.reactRefresh)
40
+ payload.reactRefresh = true;
41
+ if (opts.reactRefreshHookSignatures)
42
+ payload.reactRefreshHookSignatures = true;
43
+ if (opts.jsx !== undefined) {
44
+ payload.jsx = opts.jsx === "automatic-dev" ? "automatic_dev" : opts.jsx;
45
+ }
46
+ if (opts.jsxFactory)
47
+ payload.jsxFactory = opts.jsxFactory;
48
+ if (opts.jsxFragment)
49
+ payload.jsxFragment = opts.jsxFragment;
50
+ if (opts.jsxImportSource)
51
+ payload.jsxImportSource = opts.jsxImportSource;
52
+ if (opts.dropConsole)
53
+ payload.dropConsole = true;
54
+ if (opts.dropDebugger)
55
+ payload.dropDebugger = true;
56
+ if (opts.asciiOnly)
57
+ payload.asciiOnly = true;
58
+ if (opts.charsetUtf8)
59
+ payload.charsetUtf8 = true;
60
+ if (opts.experimentalDecorators)
61
+ payload.experimentalDecorators = true;
62
+ if (opts.emitDecoratorMetadata)
63
+ payload.emitDecoratorMetadata = true;
64
+ if (opts.useDefineForClassFields === false)
65
+ payload.useDefineForClassFields = false;
66
+ if (opts.verbatimModuleSyntax !== undefined)
67
+ payload.verbatimModuleSyntax = opts.verbatimModuleSyntax;
68
+ if (opts.tsconfigPath)
69
+ payload.tsconfigPath = opts.tsconfigPath;
70
+ if (opts.tsconfigRaw)
71
+ payload.tsconfigRaw = opts.tsconfigRaw;
72
+ if (opts.format)
73
+ payload.format = opts.format;
74
+ if (opts.quotes)
75
+ payload.quotes = opts.quotes;
76
+ if (opts.platform === "react-native")
77
+ payload.platform = "react_native";
78
+ else if (opts.platform)
79
+ payload.platform = opts.platform;
80
+ if (opts.minifyWhitespace || opts.minify)
81
+ payload.minifyWhitespace = true;
82
+ if (opts.minifyIdentifiers || opts.minify)
83
+ payload.minifyIdentifiers = true;
84
+ if (opts.minifySyntax || opts.minify)
85
+ payload.minifySyntax = true;
86
+ if (opts.sourcemap)
87
+ payload.sourcemap = true;
88
+ if (opts.sourcemapDebugIds)
89
+ payload.sourcemapDebugIds = true;
90
+ if (opts.sourcesContent === false)
91
+ payload.sourcesContent = false;
92
+ if (opts.sourceRoot)
93
+ payload.sourceRoot = opts.sourceRoot;
94
+ if (opts.define && opts.define.length > 0)
95
+ payload.define = opts.define;
96
+ if (opts.stopAfter)
97
+ payload.stopAfter = opts.stopAfter;
98
+ return JSON.stringify(payload);
99
+ }
100
+
101
+ // index.ts
102
+ var wasm = null;
103
+ var encoder = new TextEncoder;
104
+ var decoder = new TextDecoder;
105
+ function createWasiImports(memory) {
106
+ const ok = () => 0;
107
+ return {
108
+ wasi_snapshot_preview1: {
109
+ fd_write(fd, iovs_ptr, iovs_len, nwritten_ptr) {
110
+ const mem = new DataView(memory().buffer);
111
+ const bytes = new Uint8Array(memory().buffer);
112
+ let written = 0;
113
+ for (let i = 0;i < iovs_len; i++) {
114
+ const ptr = mem.getUint32(iovs_ptr + i * 8, true);
115
+ const len = mem.getUint32(iovs_ptr + i * 8 + 4, true);
116
+ const chunk = bytes.slice(ptr, ptr + len);
117
+ if (fd === 2)
118
+ console.error(decoder.decode(chunk));
119
+ written += len;
120
+ }
121
+ mem.setUint32(nwritten_ptr, written, true);
122
+ return 0;
123
+ },
124
+ fd_read: ok,
125
+ fd_seek: ok,
126
+ fd_pwrite: ok,
127
+ fd_pread: ok,
128
+ fd_close: ok,
129
+ fd_fdstat_get: ok,
130
+ fd_fdstat_set_flags: ok,
131
+ fd_filestat_get: ok,
132
+ fd_prestat_get: () => 8,
133
+ fd_prestat_dir_name: ok,
134
+ fd_sync: ok,
135
+ random_get(buf_ptr, buf_len) {
136
+ const bytes = new Uint8Array(memory().buffer, buf_ptr, buf_len);
137
+ if (typeof globalThis.crypto !== "undefined") {
138
+ crypto.getRandomValues(bytes);
139
+ } else {
140
+ for (let i = 0;i < buf_len; i++)
141
+ bytes[i] = Math.random() * 256 | 0;
142
+ }
143
+ return 0;
144
+ },
145
+ clock_time_get(clock_id, _precision, ts_ptr) {
146
+ const mem = new DataView(memory().buffer);
147
+ const ns = clock_id === 0 ? BigInt(Date.now()) * 1000000n : BigInt(Math.floor(performance.now() * 1e6));
148
+ mem.setBigUint64(ts_ptr, ns, true);
149
+ return 0;
150
+ },
151
+ clock_res_get(_clock_id, ts_ptr) {
152
+ const mem = new DataView(memory().buffer);
153
+ mem.setBigUint64(ts_ptr, 1000000n, true);
154
+ return 0;
155
+ },
156
+ args_sizes_get(argc_ptr, argv_buf_size_ptr) {
157
+ const mem = new DataView(memory().buffer);
158
+ mem.setUint32(argc_ptr, 0, true);
159
+ mem.setUint32(argv_buf_size_ptr, 0, true);
160
+ return 0;
161
+ },
162
+ args_get: ok,
163
+ environ_sizes_get(envc_ptr, env_buf_size_ptr) {
164
+ const mem = new DataView(memory().buffer);
165
+ mem.setUint32(envc_ptr, 0, true);
166
+ mem.setUint32(env_buf_size_ptr, 0, true);
167
+ return 0;
168
+ },
169
+ environ_get: ok,
170
+ proc_exit(code) {
171
+ throw new Error(`zntc-wasm: proc_exit(${code})`);
172
+ },
173
+ sched_yield: ok,
174
+ poll_oneoff: ok,
175
+ path_open: () => 8,
176
+ path_filestat_get: () => 8,
177
+ path_unlink_file: ok,
178
+ path_create_directory: ok,
179
+ path_remove_directory: ok,
180
+ path_rename: ok,
181
+ path_link: ok,
182
+ path_symlink: ok,
183
+ path_readlink: () => 8
184
+ },
185
+ wasi: {
186
+ "thread-spawn": () => -1
187
+ }
188
+ };
189
+ }
190
+ function writeString(s) {
191
+ const bytes = encoder.encode(s);
192
+ const ptr = wasm.alloc(bytes.length);
193
+ if (ptr === 0)
194
+ throw new Error("zntc-wasm: alloc failed");
195
+ new Uint8Array(wasm.memory.buffer, ptr, bytes.length).set(bytes);
196
+ return [ptr, bytes.length];
197
+ }
198
+ function readString(ptr, len) {
199
+ return decoder.decode(new Uint8Array(wasm.memory.buffer, ptr, len));
200
+ }
201
+ async function resolveWasmSource(input, defaultFile) {
202
+ if (input === undefined) {
203
+ const fs = await import("node:fs");
204
+ const path = await import("node:path");
205
+ const url = await import("node:url");
206
+ const __dirname2 = path.dirname(url.fileURLToPath(import.meta.url));
207
+ const candidates = [path.join(__dirname2, defaultFile), path.join(__dirname2, "..", defaultFile)];
208
+ for (const candidate of candidates) {
209
+ if (fs.existsSync(candidate))
210
+ return fs.readFileSync(candidate);
211
+ }
212
+ throw new Error(`zntc-wasm: ${defaultFile} not found near ${__dirname2}`);
213
+ }
214
+ if (input instanceof WebAssembly.Module)
215
+ return input;
216
+ if (input instanceof Response)
217
+ return input;
218
+ if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {
219
+ return input;
220
+ }
221
+ return await fetch(input);
222
+ }
223
+ async function instantiateWasm(source, imports) {
224
+ if (source instanceof WebAssembly.Module) {
225
+ return new WebAssembly.Instance(source, imports);
226
+ }
227
+ if (source instanceof Response) {
228
+ const result2 = await WebAssembly.instantiateStreaming(source, imports);
229
+ return result2.instance;
230
+ }
231
+ const result = await WebAssembly.instantiate(source, imports);
232
+ return result.instance;
233
+ }
234
+ async function init(input) {
235
+ if (wasm)
236
+ return;
237
+ const source = await resolveWasmSource(input, "zntc.wasm");
238
+ let memory;
239
+ const imports = createWasiImports(() => memory);
240
+ const instance = await instantiateWasm(source, imports);
241
+ memory = instance.exports.memory;
242
+ wasm = instance.exports;
243
+ }
244
+ function initSync(input) {
245
+ if (wasm)
246
+ return;
247
+ let memory;
248
+ const imports = createWasiImports(() => memory);
249
+ const mod = input instanceof WebAssembly.Module ? input : new WebAssembly.Module(input instanceof ArrayBuffer ? input : input.buffer);
250
+ const instance = new WebAssembly.Instance(mod, imports);
251
+ memory = instance.exports.memory;
252
+ wasm = instance.exports;
253
+ }
254
+ function transpile(source, options = {}) {
255
+ if (!wasm) {
256
+ throw new Error("zntc-wasm: not initialized. Call init() or initSync() first.");
257
+ }
258
+ const [srcPtr, srcLen] = writeString(source);
259
+ const [filePtr, fileLen] = options.filename ? writeString(options.filename) : [0, 0];
260
+ const optsJson = buildOptionsJson(options, targetToUnsupported(options.target));
261
+ const [optsPtr, optsLen] = writeString(optsJson);
262
+ try {
263
+ const packed = wasm.transpile(srcPtr, srcLen, filePtr, fileLen, optsPtr, optsLen);
264
+ const outPtr = Number(packed >> 32n);
265
+ const outLen = Number(packed & 0xffffffffn);
266
+ const errLen = wasm.get_error_len();
267
+ const errMsg = errLen === 0 ? "" : readString(wasm.get_error_ptr(), errLen);
268
+ if (outPtr === 0) {
269
+ if (outLen === 0 && errLen === 0)
270
+ return { code: "" };
271
+ throw new Error(`zntc-wasm: ${errMsg || "unknown error"}`);
272
+ }
273
+ const raw = readString(outPtr, outLen);
274
+ wasm.dealloc(outPtr, outLen);
275
+ const nullIdx = options.sourcemap ? raw.indexOf("\x00") : -1;
276
+ const result = nullIdx !== -1 ? { code: raw.slice(0, nullIdx), map: raw.slice(nullIdx + 1) } : { code: raw };
277
+ if (errMsg)
278
+ result.errors = errMsg;
279
+ return result;
280
+ } finally {
281
+ wasm.dealloc(srcPtr, srcLen);
282
+ if (filePtr)
283
+ wasm.dealloc(filePtr, fileLen);
284
+ wasm.dealloc(optsPtr, optsLen);
285
+ }
286
+ }
287
+
288
+ class VirtualFileSystem {
289
+ files = new Map;
290
+ set(path, content) {
291
+ const bytes = typeof content === "string" ? encoder.encode(content) : content;
292
+ this.files.set(path, bytes);
293
+ }
294
+ get(path) {
295
+ return this.files.get(path);
296
+ }
297
+ has(path) {
298
+ return this.files.has(path);
299
+ }
300
+ delete(path) {
301
+ return this.files.delete(path);
302
+ }
303
+ paths() {
304
+ return this.files.keys();
305
+ }
306
+ size() {
307
+ return this.files.size;
308
+ }
309
+ clear() {
310
+ this.files.clear();
311
+ }
312
+ }
313
+ var bundler = null;
314
+ var bundlerMemory = null;
315
+ var bundlerVfs = null;
316
+ function readBundlerString(ptr, len) {
317
+ const view = new Uint8Array(bundlerMemory.buffer, ptr, len);
318
+ return decoder.decode(view.slice());
319
+ }
320
+ function packBytes(data) {
321
+ if (!bundler || !bundlerMemory)
322
+ return 0n;
323
+ const ptr = bundler.alloc(data.length);
324
+ if (ptr === 0)
325
+ return 0n;
326
+ new Uint8Array(bundlerMemory.buffer, ptr, data.length).set(data);
327
+ return BigInt(ptr) << 32n | BigInt(data.length);
328
+ }
329
+ function createBundlerImports(memory) {
330
+ const wasi = createWasiImports(memory);
331
+ return {
332
+ ...wasi,
333
+ zntc_fs: {
334
+ readFile(pathPtr, pathLen, _maxBytes) {
335
+ const path = readBundlerString(pathPtr, pathLen);
336
+ const data = bundlerVfs?.get(path);
337
+ if (!data)
338
+ return 0n;
339
+ return packBytes(data);
340
+ },
341
+ statFile(pathPtr, pathLen, outSize, outKind, outMtimeLo, outMtimeHi) {
342
+ const path = readBundlerString(pathPtr, pathLen);
343
+ const data = bundlerVfs?.get(path);
344
+ if (!data)
345
+ return 1;
346
+ const view = new DataView(bundlerMemory.buffer);
347
+ view.setBigUint64(outSize, BigInt(data.length), true);
348
+ new Uint8Array(bundlerMemory.buffer, outKind, 1)[0] = 0;
349
+ view.setBigUint64(outMtimeLo, 0n, true);
350
+ view.setBigUint64(outMtimeHi, 0n, true);
351
+ return 0;
352
+ },
353
+ access(pathPtr, pathLen) {
354
+ const path = readBundlerString(pathPtr, pathLen);
355
+ return bundlerVfs?.has(path) ? 0 : 1;
356
+ },
357
+ realpath(pathPtr, pathLen) {
358
+ const path = readBundlerString(pathPtr, pathLen);
359
+ if (!bundlerVfs?.has(path))
360
+ return 0n;
361
+ return packBytes(encoder.encode(path));
362
+ },
363
+ listDir(_pathPtr, _pathLen) {
364
+ return 0n;
365
+ },
366
+ hostFreeBytes(ptr, len) {
367
+ bundler?.dealloc(ptr, len);
368
+ }
369
+ }
370
+ };
371
+ }
372
+ async function initBundler(vfs, input) {
373
+ if (bundler)
374
+ return;
375
+ const source = await resolveWasmSource(input, "zntc-bundler.wasm");
376
+ const memory = new WebAssembly.Memory({
377
+ initial: 1024,
378
+ maximum: 65536,
379
+ shared: true
380
+ });
381
+ const bundlerImports = createBundlerImports(() => memory);
382
+ const imports = {
383
+ ...bundlerImports,
384
+ env: { memory }
385
+ };
386
+ const instance = await instantiateWasm(source, imports);
387
+ bundler = instance.exports;
388
+ bundlerMemory = memory;
389
+ bundlerVfs = vfs;
390
+ }
391
+ function bundlerVersion() {
392
+ if (!bundler) {
393
+ throw new Error("zntc-wasm: bundler not initialized. Call initBundler() first.");
394
+ }
395
+ return bundler.bundler_version();
396
+ }
397
+ function encodeBundleOptions(options) {
398
+ if (!options)
399
+ return { ptr: 0, len: 0 };
400
+ const expanded = { ...options };
401
+ if (expanded.minify) {
402
+ expanded.minifyWhitespace = expanded.minifyWhitespace ?? true;
403
+ expanded.minifyIdentifiers = expanded.minifyIdentifiers ?? true;
404
+ expanded.minifySyntax = expanded.minifySyntax ?? true;
405
+ }
406
+ delete expanded.minify;
407
+ if (expanded.target && expanded.unsupported == null) {
408
+ const bits = targetToUnsupported(expanded.target);
409
+ if (bits !== 0)
410
+ expanded.unsupported = bits;
411
+ }
412
+ delete expanded.target;
413
+ const optsBytes = encoder.encode(JSON.stringify(expanded));
414
+ const ptr = bundler.alloc(optsBytes.length);
415
+ if (ptr === 0)
416
+ throw new Error("zntc-wasm: bundler alloc failed");
417
+ new Uint8Array(bundlerMemory.buffer, ptr, optsBytes.length).set(optsBytes);
418
+ return { ptr, len: optsBytes.length };
419
+ }
420
+ function bundlerLastErrorMessage() {
421
+ if (!bundler || !bundlerMemory)
422
+ return "";
423
+ const packed = bundler.last_error_message_get();
424
+ if (packed === 0n)
425
+ return "";
426
+ const ptr = Number(packed >> 32n);
427
+ const len = Number(packed & 0xffffffffn);
428
+ const view = new Uint8Array(bundlerMemory.buffer, ptr, len);
429
+ return decoder.decode(view.slice());
430
+ }
431
+ function build(entryPath, options) {
432
+ if (!bundler || !bundlerMemory) {
433
+ throw new Error("zntc-wasm: bundler not initialized. Call initBundler() first.");
434
+ }
435
+ const entryBytes = encoder.encode(entryPath);
436
+ const entryPtr = bundler.alloc(entryBytes.length);
437
+ if (entryPtr === 0)
438
+ throw new Error("zntc-wasm: bundler alloc failed");
439
+ new Uint8Array(bundlerMemory.buffer, entryPtr, entryBytes.length).set(entryBytes);
440
+ const { ptr: optsPtr, len: optsLen } = encodeBundleOptions(options);
441
+ try {
442
+ const packed = bundler.build(entryPtr, entryBytes.length, optsPtr, optsLen);
443
+ if (packed === 0n)
444
+ return null;
445
+ const outPtr = Number(packed >> 32n);
446
+ const outLen = Number(packed & 0xffffffffn);
447
+ const view = new Uint8Array(bundlerMemory.buffer, outPtr, outLen);
448
+ const code = decoder.decode(view.slice());
449
+ bundler.dealloc(outPtr, outLen);
450
+ return { code };
451
+ } finally {
452
+ bundler.dealloc(entryPtr, entryBytes.length);
453
+ if (optsPtr)
454
+ bundler.dealloc(optsPtr, optsLen);
455
+ }
456
+ }
457
+ function buildChunks(entryPath, options) {
458
+ if (!bundler || !bundlerMemory) {
459
+ throw new Error("zntc-wasm: bundler not initialized. Call initBundler() first.");
460
+ }
461
+ const entryBytes = encoder.encode(entryPath);
462
+ const entryPtr = bundler.alloc(entryBytes.length);
463
+ if (entryPtr === 0)
464
+ throw new Error("zntc-wasm: bundler alloc failed");
465
+ new Uint8Array(bundlerMemory.buffer, entryPtr, entryBytes.length).set(entryBytes);
466
+ const { ptr: optsPtr, len: optsLen } = encodeBundleOptions(options);
467
+ try {
468
+ const packed = bundler.build_chunks(entryPtr, entryBytes.length, optsPtr, optsLen);
469
+ if (packed === 0n)
470
+ return null;
471
+ const outPtr = Number(packed >> 32n);
472
+ const outLen = Number(packed & 0xffffffffn);
473
+ const view = new Uint8Array(bundlerMemory.buffer, outPtr, outLen);
474
+ const json = decoder.decode(view.slice());
475
+ bundler.dealloc(outPtr, outLen);
476
+ return JSON.parse(json);
477
+ } finally {
478
+ bundler.dealloc(entryPtr, entryBytes.length);
479
+ if (optsPtr)
480
+ bundler.dealloc(optsPtr, optsLen);
481
+ }
482
+ }
483
+ export {
484
+ transpile,
485
+ initSync,
486
+ initBundler,
487
+ init,
488
+ bundlerVersion,
489
+ bundlerLastErrorMessage,
490
+ buildChunks,
491
+ build,
492
+ VirtualFileSystem
493
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Engine × Feature 지원 버전 테이블 (JS 미러).
3
+ *
4
+ * src/transformer/compat.zig의 compat_table을 거울 복사.
5
+ * browserslist → UnsupportedFeatures bitmask 계산에 사용.
6
+ *
7
+ * **주의**: compat.zig를 수정하면 이 파일도 함께 업데이트.
8
+ * 장기적으론 scripts/compat-from-kangax.ts를 확장해 자동 생성.
9
+ *
10
+ * Bit 레이아웃은 packages/shared/index.ts ES_TARGET_BITS와 동일 (0-27).
11
+ */
12
+ export type Engine = 'chrome' | 'firefox' | 'safari' | 'edge' | 'node' | 'deno' | 'ios' | 'opera' | 'hermes';
13
+ /** compat.zig의 Feature enum과 동일 순서. bit 위치 = 배열 인덱스. */
14
+ export declare const FEATURES: readonly ["arrow", "class", "template_literal", "destructuring", "for_of", "spread", "object_extensions", "default_params", "block_scoping", "generator", "new_target", "exponentiation", "async_await", "object_spread", "optional_catch_binding", "nullish_coalescing", "optional_chaining", "logical_assignment", "class_static_block", "class_private_method", "class_private_field", "top_level_await", "hashbang", "using", "regex_sticky", "regex_dotall", "regex_named_groups", "unicode_brace_escape"];
15
+ export type Feature = (typeof FEATURES)[number];
16
+ /**
17
+ * feature × engine → 최소 지원 버전 [major, minor].
18
+ * 엔진에 키가 없으면 "해당 엔진에서 미지원" (compat.zig의 보수적 해석).
19
+ */
20
+ export declare const SUPPORT: Partial<Record<Feature, Partial<Record<Engine, [number, number]>>>>;
21
+ export type EngineVersion = {
22
+ engine: Engine;
23
+ major: number;
24
+ minor: number;
25
+ };
26
+ /**
27
+ * 엔진 목록에 대해 unsupported feature bitmask 계산.
28
+ * 하나라도 미지원인 feature는 set (가장 보수적).
29
+ */
30
+ export declare function computeUnsupportedFromEngines(engines: EngineVersion[]): number;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * @zntc/shared — Types and utilities shared by @zntc/core and @zntc/wasm.
3
+ */
4
+ export type Target = 'es5' | 'es2015' | 'es2016' | 'es2017' | 'es2018' | 'es2019' | 'es2020' | 'es2021' | 'es2022' | 'es2023' | 'es2024' | 'es2025' | 'esnext';
5
+ export type Platform = 'browser' | 'node' | 'neutral' | 'react-native';
6
+ export interface TranspileOptions {
7
+ /** File path (used for extension detection, default: "input.ts"). */
8
+ filename?: string;
9
+ /** Generate a source map. */
10
+ sourcemap?: boolean;
11
+ /** Source map Debug ID (Sentry-compatible). */
12
+ sourcemapDebugIds?: boolean;
13
+ /** Include the original source in the source map (default: true). */
14
+ sourcesContent?: boolean;
15
+ /** Minify whitespace. */
16
+ minifyWhitespace?: boolean;
17
+ /** Minify identifiers. */
18
+ minifyIdentifiers?: boolean;
19
+ /** Minify syntax. */
20
+ minifySyntax?: boolean;
21
+ /** Full minification (whitespace + identifiers + syntax). */
22
+ minify?: boolean;
23
+ /** JSX runtime. `'preserve'` emits JSX unchanged — only TypeScript annotations
24
+ * are stripped. Use this to delegate JSX handling to a downstream tool (e.g.
25
+ * @vitejs/plugin-react, @preact/preset-vite, vite-plugin-solid). Equivalent to
26
+ * tsc `"jsx": "preserve"`. */
27
+ jsx?: 'classic' | 'automatic' | 'automatic-dev' | 'preserve';
28
+ /** classic-mode JSX factory (default: "React.createElement"). */
29
+ jsxFactory?: string;
30
+ /** classic-mode Fragment factory (default: "React.Fragment"). */
31
+ jsxFragment?: string;
32
+ /** automatic-mode import source (default: "react"). */
33
+ jsxImportSource?: string;
34
+ /** Allow JSX in .js files as well. */
35
+ jsxInJs?: boolean;
36
+ /**
37
+ * React Fast Refresh transform — registers components via
38
+ * `$RefreshReg$(_c, "Name")`. Equivalent to the SWC builtin
39
+ * `jsc.transform.react.refresh: true` / babel-plugin-react-refresh component
40
+ * registration. Used by loaders (rspack-loader, webpack/vite plugin) when
41
+ * enabled on the single-file transpile path.
42
+ *
43
+ * The default behavior is Metro-compatible (registration only, no hook
44
+ * signatures). The babel/SWC-equivalent hook signature emit is opt-in via
45
+ * `reactRefreshHookSignatures: true`. Defining the HMR runtime
46
+ * ($RefreshReg$/$RefreshSig$) is the consumer's responsibility.
47
+ */
48
+ reactRefresh?: boolean;
49
+ /**
50
+ * On top of `reactRefresh: true`, adds hook signature emit
51
+ * (`var _s = $RefreshSig$();` + `_s(Component, "sig")`). Equivalent to
52
+ * babel-plugin-react-refresh. Default false — preserves the Metro policy
53
+ * (no signatures).
54
+ *
55
+ * When enabled, the transformer scans hook calls inside function bodies to
56
+ * build the signature string, then emits a `_s(Comp, "sig")` call right after
57
+ * component registration. RN HMR keeps the default (false), so it is
58
+ * unaffected.
59
+ */
60
+ reactRefreshHookSignatures?: boolean;
61
+ /** Remove console.* calls. */
62
+ dropConsole?: boolean;
63
+ /** Remove debugger statements. */
64
+ dropDebugger?: boolean;
65
+ /** Escape non-ASCII to \uXXXX. */
66
+ asciiOnly?: boolean;
67
+ /** Do not escape non-ASCII. */
68
+ charsetUtf8?: boolean;
69
+ /** Strip Flow types. */
70
+ flow?: boolean;
71
+ /** legacy decorator transform. */
72
+ experimentalDecorators?: boolean;
73
+ /** decorator metadata emit */
74
+ emitDecoratorMetadata?: boolean;
75
+ /** Transform class fields → `constructor` this.x = v (default: true). */
76
+ useDefineForClassFields?: boolean;
77
+ /** verbatimModuleSyntax (TS 5.0+): when true, unused value imports are not elided. */
78
+ verbatimModuleSyntax?: boolean;
79
+ /**
80
+ * Path to tsconfig.json (file or directory). When set, compilerOptions are
81
+ * auto-loaded and merged. Fields set explicitly via JS options take
82
+ * precedence — only unspecified fields are filled from the tsconfig values.
83
+ * e.g. "./tsconfig.json" or "./project-dir".
84
+ */
85
+ tsconfigPath?: string;
86
+ /**
87
+ * Inline tsconfig JSON string (same meaning as esbuild's `tsconfigRaw`).
88
+ * When set, both `tsconfigPath` and autodiscovery are ignored — raw is the
89
+ * single source of truth. The Zig-side `tsconfig_merge` applies
90
+ * compilerOptions such as jsx/target/decorators directly.
91
+ */
92
+ tsconfigRaw?: string;
93
+ /** Module format. */
94
+ format?: 'esm' | 'cjs';
95
+ /** String quote style. */
96
+ quotes?: 'double' | 'single' | 'preserve';
97
+ /** Target platform. */
98
+ platform?: Platform;
99
+ /** ES downlevel target. */
100
+ target?: Target;
101
+ /**
102
+ * browserslist query (e.g. "last 2 versions", ">1%, not dead").
103
+ * Takes precedence over target when set. Resolved only in the core package
104
+ * (depends on browserslist).
105
+ */
106
+ browserslist?: string | string[];
107
+ /** The source map's sourceRoot field (default: empty string). */
108
+ sourceRoot?: string;
109
+ /**
110
+ * Identifier substitution pairs. `value` is raw JSON (strings must include
111
+ * the surrounding quotes).
112
+ * e.g. `[{ key: "process.env.NODE_ENV", value: "\"production\"" }]`
113
+ */
114
+ define?: Array<{
115
+ key: string;
116
+ value: string;
117
+ }>;
118
+ /**
119
+ * Pipeline early-exit point — for debug/profile. When set, all stages after
120
+ * the given phase are skipped and empty output is returned. Useful combined
121
+ * with `profile` to measure a specific phase's cost in isolation.
122
+ *
123
+ * - "scan": Scanner token drain only
124
+ * - "parse": after Parser AST construction
125
+ * - "semantic": after the Semantic analyzer
126
+ * - "transform": after the Transformer
127
+ * - "codegen": full run (same as the default behavior)
128
+ */
129
+ stopAfter?: 'scan' | 'parse' | 'semantic' | 'transform' | 'codegen';
130
+ }
131
+ export interface TranspileResult {
132
+ /** Transformed JavaScript code. */
133
+ code: string;
134
+ /** Source map JSON (when sourcemap: true). */
135
+ map?: string;
136
+ /**
137
+ * Full error text rendered in the same format as the CLI when semantic
138
+ * errors are present. tsc-compatible policy: code is still returned even
139
+ * when there are errors. Playgrounds/IDEs parse this field to display
140
+ * markers.
141
+ */
142
+ errors?: string;
143
+ }
144
+ export declare const ES_TARGET_BITS: Record<string, number>;
145
+ export declare function targetToUnsupported(target?: Target): number;
146
+ /**
147
+ * Narrows whether `value` is a plain object (non-null, not an array).
148
+ * Use this to narrow a value of unknown shape — such as a `JSON.parse` result
149
+ * or a dynamic config object — to `Record<string, unknown>`. Shared across
150
+ * functional config / workspace entries / tsconfigRaw validation, etc.
151
+ */
152
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
153
+ /**
154
+ * Pre-validates user-supplied `tsconfigRaw` input.
155
+ *
156
+ * NAPI (`napi_entry.zig` / `transpile.zig`) silently falls back to an empty
157
+ * TsConfig on raw parse failure, so an invalid raw passed through looks to the
158
+ * user as if the option were ignored. Called from both the CLI and JS API
159
+ * entry points so they uniformly throw the same explicit error
160
+ * (`failed to parse --tsconfig-raw: ...`). No-op when undefined.
161
+ */
162
+ export declare function validateTsConfigRaw(raw: string | undefined): void;
163
+ /**
164
+ * Serializes TranspileOptions into Zig `ConfigOptionsDto` JSON.
165
+ *
166
+ * - Default values are omitted (minimizes payload size).
167
+ * - enum keys match the Zig enum name (e.g. "react-native" → "react_native").
168
+ * - The browserslist resolution result can be injected as the
169
+ * `unsupportedOverride` number. When set, it takes precedence over the
170
+ * `target`-based fallback on the Zig side.
171
+ * - `define` is serialized as a `[{ key, value }]` array (compatible with Zig
172
+ * DefineEntry).
173
+ */
174
+ export declare function buildOptionsJson(opts?: TranspileOptions, unsupportedOverride?: number): string;
175
+ export type { Engine, EngineVersion, Feature } from './compat-engines';
176
+ export { computeUnsupportedFromEngines, FEATURES } from './compat-engines';
177
+ import type { EngineVersion } from './compat-engines';
178
+ /** browserslist result string ("chrome 100", "ios_saf 14.5") → EngineVersion. */
179
+ export declare function parseBrowserslistEntry(entry: string): EngineVersion | null;
180
+ /**
181
+ * browserslist result string array → unsupported bitmask.
182
+ * Unmappable engines (samsung, kaios, etc.) are ignored (engines ZNTC does not
183
+ * target).
184
+ */
185
+ export declare function browserslistToUnsupported(entries: string[]): number;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @zntc/wasm — ZNTC TypeScript 트랜스파일러 WASM 바인딩
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { init, transpile } from "@zntc/wasm";
7
+ * await init();
8
+ * const result = transpile("const x: number = 1;", { target: "es5" });
9
+ * console.log(result.code);
10
+ * ```
11
+ */
12
+ export type { Target, Platform, TranspileOptions, TranspileResult } from '../shared/index';
13
+ import type { Target, TranspileOptions, TranspileResult } from '../shared/index';
14
+ export declare function init(input?: URL | string | Request | Response | BufferSource | WebAssembly.Module): Promise<void>;
15
+ export declare function initSync(input: WebAssembly.Module | BufferSource): void;
16
+ export declare function transpile(source: string, options?: TranspileOptions): TranspileResult;
17
+ export declare class VirtualFileSystem {
18
+ private files;
19
+ set(path: string, content: string | Uint8Array): void;
20
+ get(path: string): Uint8Array | undefined;
21
+ has(path: string): boolean;
22
+ delete(path: string): boolean;
23
+ paths(): IterableIterator<string>;
24
+ size(): number;
25
+ clear(): void;
26
+ }
27
+ export declare function initBundler(vfs: VirtualFileSystem, input?: URL | string | Request | Response | BufferSource | WebAssembly.Module): Promise<void>;
28
+ export declare function bundlerVersion(): number;
29
+ export interface BundleResult {
30
+ code: string;
31
+ }
32
+ export interface BundleOptionsInput {
33
+ format?: 'esm' | 'cjs' | 'iife' | 'umd' | 'amd';
34
+ platform?: 'browser' | 'node' | 'neutral' | 'react-native';
35
+ external?: string[];
36
+ minify?: boolean;
37
+ minifyWhitespace?: boolean;
38
+ minifyIdentifiers?: boolean;
39
+ minifySyntax?: boolean;
40
+ codeSplitting?: boolean;
41
+ preserveModules?: boolean;
42
+ target?: Target;
43
+ unsupported?: number;
44
+ jsx?: 'classic' | 'automatic' | 'automatic-dev';
45
+ jsxFactory?: string;
46
+ jsxFragment?: string;
47
+ jsxImportSource?: string;
48
+ flow?: boolean;
49
+ jsxInJs?: boolean;
50
+ experimentalDecorators?: boolean;
51
+ emitDecoratorMetadata?: boolean;
52
+ useDefineForClassFields?: boolean;
53
+ charsetUtf8?: boolean;
54
+ keepNames?: boolean;
55
+ sourcemap?: boolean;
56
+ }
57
+ export declare function bundlerLastErrorMessage(): string;
58
+ export declare function build(entryPath: string, options?: BundleOptionsInput): BundleResult | null;
59
+ export interface OutputChunk {
60
+ path: string;
61
+ code: string;
62
+ }
63
+ export declare function buildChunks(entryPath: string, options?: BundleOptionsInput): OutputChunk[] | null;
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@zntc/wasm",
3
+ "version": "0.1.0",
4
+ "description": "ZNTC native transpiler & compiler — WASM build",
5
+ "keywords": [
6
+ "browser",
7
+ "bundler",
8
+ "compiler",
9
+ "deno",
10
+ "javascript",
11
+ "transpiler",
12
+ "typescript",
13
+ "wasm",
14
+ "webassembly",
15
+ "workers",
16
+ "zntc"
17
+ ],
18
+ "homepage": "https://ohah.github.io/zntc",
19
+ "bugs": {
20
+ "url": "https://github.com/ohah/zntc/issues"
21
+ },
22
+ "license": "MIT",
23
+ "author": "ohah",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/ohah/zntc.git",
27
+ "directory": "packages/wasm"
28
+ },
29
+ "files": [
30
+ "dist/",
31
+ "zntc.wasm",
32
+ "zntc-bundler.wasm"
33
+ ],
34
+ "type": "module",
35
+ "main": "dist/index.js",
36
+ "types": "dist/wasm/index.d.ts",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/wasm/index.d.ts",
40
+ "import": "./dist/index.js"
41
+ },
42
+ "./zntc.wasm": "./zntc.wasm",
43
+ "./zntc-bundler.wasm": "./zntc-bundler.wasm"
44
+ },
45
+ "scripts": {
46
+ "build:wasm": "cd ../.. && zig build wasm && zig build wasm-bundler",
47
+ "build:js": "bun build index.ts --outdir dist --format esm --target browser --external 'node:*'",
48
+ "build:dts": "bun x tsc -b",
49
+ "build": "bun run build:wasm && cp ../../zig-out/bin/zntc.wasm . && cp ../../zig-out/bin/zntc-bundler.wasm . && bun run build:js && bun run build:dts",
50
+ "test": "bun test --timeout 10000",
51
+ "prepublishOnly": "bun run build && bunx publint --strict --level error ."
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^25.5.2"
55
+ },
56
+ "engines": {
57
+ "node": ">=24.0.0"
58
+ }
59
+ }
Binary file
package/zntc.wasm ADDED
Binary file