yuku-codegen 0.5.8
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 +150 -0
- package/binding.js +63 -0
- package/encode.js +2069 -0
- package/index.d.ts +81 -0
- package/index.js +14 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# yuku-codegen
|
|
2
|
+
|
|
3
|
+
A high-performance JavaScript/TypeScript code generator written in Zig, powered by [Yuku](https://github.com/yuku-toolchain/yuku).
|
|
4
|
+
|
|
5
|
+
Renders an ESTree / TypeScript-ESTree AST back to source code, with optional Source Map V3 output. The input AST is exactly what [`yuku-parser`](https://www.npmjs.com/package/yuku-parser) produces.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install yuku-codegen
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { parse } from "yuku-parser";
|
|
17
|
+
import { print } from "yuku-codegen";
|
|
18
|
+
|
|
19
|
+
const { program } = parse("const x = 1 + 2;");
|
|
20
|
+
const { code } = print(program);
|
|
21
|
+
|
|
22
|
+
console.log(code); // "const x = 1 + 2;"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## API
|
|
26
|
+
|
|
27
|
+
Three entry points share the same options shape:
|
|
28
|
+
|
|
29
|
+
- `print(ast, options?)` renders verbatim, preserving TypeScript syntax.
|
|
30
|
+
- `strip(ast, options?)` drops type-only syntax and emits plain JavaScript.
|
|
31
|
+
- `minify(ast, options?)` applies size-reducing rewrites such as `true → !0` and shortest-numeric-form. Combine with `format: "compact"` for full minification.
|
|
32
|
+
|
|
33
|
+
All return the same `CodegenResult`:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
interface CodegenResult {
|
|
37
|
+
code: string;
|
|
38
|
+
errors: Diagnostic[];
|
|
39
|
+
map: SourceMap | null;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`errors` is empty when codegen succeeded cleanly. `map` is populated only when `sourceMaps` is enabled.
|
|
44
|
+
|
|
45
|
+
## Options
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
const result = print(ast, {
|
|
49
|
+
format: "pretty",
|
|
50
|
+
indent: 2,
|
|
51
|
+
quotes: "double",
|
|
52
|
+
sourceMaps: { source },
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Option | Values | Default | Description |
|
|
57
|
+
| ------------ | ----------------------- | ---------- | ------------------------------------------------------------------------------------------ |
|
|
58
|
+
| `format` | `"pretty"`, `"compact"` | `"pretty"` | Whitespace mode. `"compact"` emits only the separators the grammar requires. |
|
|
59
|
+
| `indent` | `number` | `2` | Spaces per indentation level. Used only when `format` is `"pretty"`. |
|
|
60
|
+
| `quotes` | `"double"`, `"single"` | `"double"` | Quote style for emitted string literals. |
|
|
61
|
+
| `sourceMaps` | `SourceMapOptions` | _disabled_ | Pass an object to emit a Source Map V3 alongside the code. Omit (or set to `null`) to skip. |
|
|
62
|
+
|
|
63
|
+
## Source maps
|
|
64
|
+
|
|
65
|
+
Pass `sourceMaps` to emit a Source Map V3 alongside the code. The original source text is required.
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { parse } from "yuku-parser";
|
|
69
|
+
import { print } from "yuku-codegen";
|
|
70
|
+
|
|
71
|
+
const source = `const greet = (name) => "Hello, " + name;`;
|
|
72
|
+
const { program } = parse(source);
|
|
73
|
+
|
|
74
|
+
const { code, map } = print(program, {
|
|
75
|
+
sourceMaps: {
|
|
76
|
+
source,
|
|
77
|
+
file: "out.js",
|
|
78
|
+
sourceFileName: "in.js",
|
|
79
|
+
sourcesContent: true,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await Bun.write("out.js", code + `\n//# sourceMappingURL=out.js.map`);
|
|
84
|
+
await Bun.write("out.js.map", JSON.stringify(map));
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `SourceMapOptions`
|
|
88
|
+
|
|
89
|
+
| Field | Type | Required | Description |
|
|
90
|
+
| ----------------- | --------- | -------- | ----------------------------------------------------------------- |
|
|
91
|
+
| `source` | `string` | yes | Original source the AST was parsed from. |
|
|
92
|
+
| `file` | `string` | no | Output filename, embedded as the map's `file`. |
|
|
93
|
+
| `sourceFileName` | `string` | no | Source filename, embedded as the single entry of `sources`. |
|
|
94
|
+
| `sourceRoot` | `string` | no | Prefix embedded as `sourceRoot`. |
|
|
95
|
+
| `sourcesContent` | `boolean` | no | When `true`, embeds `source` into the map's `sourcesContent`. |
|
|
96
|
+
|
|
97
|
+
### Result
|
|
98
|
+
|
|
99
|
+
The returned `map` is a Source Map V3 object, ready to `JSON.stringify`:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
interface SourceMap {
|
|
103
|
+
version: 3;
|
|
104
|
+
file: string | null;
|
|
105
|
+
sourceRoot: string | null;
|
|
106
|
+
sources: string[];
|
|
107
|
+
sourcesContent: (string | null)[] | null;
|
|
108
|
+
names: string[];
|
|
109
|
+
mappings: string;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Columns are 0-indexed UTF-16 code units, matching the convention used by Chrome DevTools and every consumer-side library (`@jridgewell/trace-mapping`, `source-map`, etc.).
|
|
114
|
+
|
|
115
|
+
## TypeScript stripping
|
|
116
|
+
|
|
117
|
+
`strip` rewrites the AST as plain JavaScript:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
import { parse } from "yuku-parser";
|
|
121
|
+
import { strip } from "yuku-codegen";
|
|
122
|
+
|
|
123
|
+
const { program } = parse(`const x: number = 1;`, { lang: "ts" });
|
|
124
|
+
console.log(strip(program).code); // "const x = 1;"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Type annotations, type aliases, interfaces, and other type-only constructs are dropped. Constructs that don't have a clean JavaScript equivalent (`enum`, `namespace`, `import = require()`, `export =`) are reported in `errors` and elided. The output is always syntactically valid JavaScript.
|
|
128
|
+
|
|
129
|
+
## Minification
|
|
130
|
+
|
|
131
|
+
`minify` applies size-reducing rewrites at print time:
|
|
132
|
+
|
|
133
|
+
- `true` / `false` → `!0` / `!1`
|
|
134
|
+
- `undefined` → `void 0` (in expression position)
|
|
135
|
+
- `Infinity` → `1/0`
|
|
136
|
+
- numeric literals shortened to their shortest form (`1000000` → `1e6`, `0.5` → `.5`, etc.)
|
|
137
|
+
- `obj["foo"]` → `obj.foo` when the key is a valid identifier
|
|
138
|
+
- `{ "foo": x }` → `{ foo: x }` when safe
|
|
139
|
+
|
|
140
|
+
Combine with `format: "compact"` for full minification:
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
import { minify } from "yuku-codegen";
|
|
144
|
+
|
|
145
|
+
const { code } = minify(program, { format: "compact" });
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
package/binding.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const { platform, arch } = process;
|
|
10
|
+
|
|
11
|
+
const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-');
|
|
12
|
+
|
|
13
|
+
function isMusl() {
|
|
14
|
+
if (platform !== 'linux') return false;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
if (readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')) return true;
|
|
18
|
+
} catch {}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const report = typeof process.report?.getReport === 'function'
|
|
22
|
+
? process.report.getReport()
|
|
23
|
+
: null;
|
|
24
|
+
if (report) {
|
|
25
|
+
const header = typeof report === 'string' ? JSON.parse(report).header : report.header;
|
|
26
|
+
if (header?.glibcVersionRuntime) return false;
|
|
27
|
+
if (Array.isArray(report.sharedObjects) && report.sharedObjects.some(isFileMusl)) return true;
|
|
28
|
+
}
|
|
29
|
+
} catch {}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
return execSync('ldd --version', { encoding: 'utf8' }).includes('musl');
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function loadBinding() {
|
|
39
|
+
const errors = [];
|
|
40
|
+
const libc = platform === 'linux' ? (isMusl() ? '-musl' : '-gnu') : '';
|
|
41
|
+
const suffix = `${platform}-${arch}${libc}`;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
return require(join(__dirname, '@yuku-codegen', 'binding-' + suffix, 'yuku-codegen.node'));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
errors.push(e);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
return require('@yuku-codegen/binding-' + suffix + '/yuku-codegen.node');
|
|
51
|
+
} catch (e) {
|
|
52
|
+
errors.push(e);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Failed to load native binding for ${platform}-${arch}.\n` +
|
|
57
|
+
`If this persists, try removing node_modules and reinstalling.\n` +
|
|
58
|
+
errors.map(e => ` - ${e.message}`).join('\n'),
|
|
59
|
+
{ cause: errors[errors.length - 1] }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default loadBinding();
|