Skip to content

Commit af6ba92

Browse files
committed
feat(oxlint2): bundle with tsdown (#12527)
fixes #12522
1 parent 22bc063 commit af6ba92

File tree

8 files changed

+544
-44
lines changed

8 files changed

+544
-44
lines changed

napi/oxlint2/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist/

napi/oxlint2/package.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
{
22
"name": "oxlint2",
33
"version": "0.1.0",
4-
"main": "src-js/index.js",
4+
"bin": "dist/index.js",
55
"type": "module",
66
"scripts": {
7-
"build-dev": "napi build --platform --js ./bindings.js --dts ./bindings.d.ts --output-dir src-js --no-dts-cache --esm",
8-
"build-test": "pnpm run build-dev --profile coverage --features force_test_reporter",
9-
"build": "pnpm run build-dev --release",
7+
"build": "pnpm run build-napi-release && pnpm run build-js",
8+
"build-dev": "pnpm run build-napi && pnpm run build-js",
9+
"build-test": "pnpm run build-napi-test && pnpm run build-js",
10+
"build-napi": "napi build --platform --js ./bindings.js --dts ./bindings.d.ts --output-dir src-js --no-dts-cache --esm",
11+
"build-napi-test": "pnpm run build-napi --profile coverage --features force_test_reporter",
12+
"build-napi-release": "pnpm run build-napi --release",
13+
"build-js": "node scripts/build.js",
1014
"test": "vitest"
1115
},
1216
"engines": {
@@ -26,8 +30,12 @@
2630
"registry": "https://registry.npmjs.org/",
2731
"access": "public"
2832
},
33+
"files": [
34+
"dist"
35+
],
2936
"devDependencies": {
3037
"execa": "^9.6.0",
38+
"tsdown": "^0.13.0",
3139
"typescript": "catalog:",
3240
"vitest": "catalog:"
3341
},

napi/oxlint2/scripts/build.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env node
2+
3+
import { execSync } from 'child_process';
4+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
5+
import { dirname, join } from 'path';
6+
import { fileURLToPath } from 'url';
7+
8+
const __dirname = dirname(fileURLToPath(import.meta.url));
9+
const rootDir = join(__dirname, '..');
10+
11+
console.log('Building with tsdown...');
12+
try {
13+
execSync('pnpm tsdown', { stdio: 'inherit', cwd: rootDir });
14+
} catch (error) {
15+
console.error('Build failed:', error);
16+
process.exit(1);
17+
}
18+
19+
// Create directories after tsdown build.
20+
const distDir = join(rootDir, 'dist');
21+
const distGeneratedDir = join(distDir, 'generated');
22+
const parserRawTransferDir = join(distDir, 'parser', 'raw-transfer');
23+
const parserGeneratedDir = join(distDir, 'parser', 'generated', 'lazy');
24+
25+
// Create all necessary directories
26+
if (!existsSync(distGeneratedDir)) {
27+
mkdirSync(distGeneratedDir, { recursive: true });
28+
}
29+
if (!existsSync(parserRawTransferDir)) {
30+
mkdirSync(parserRawTransferDir, { recursive: true });
31+
}
32+
if (!existsSync(parserGeneratedDir)) {
33+
mkdirSync(parserGeneratedDir, { recursive: true });
34+
}
35+
36+
console.log('Copying generated files...');
37+
// Copy generated constants file
38+
const constantsPath = join(rootDir, 'src-js/generated/constants.cjs');
39+
if (existsSync(constantsPath)) {
40+
copyFileSync(constantsPath, join(distGeneratedDir, 'constants.cjs'));
41+
console.log('Copied constants.cjs');
42+
} else {
43+
console.error(
44+
'Warning: generated/constants.cjs not found. Make sure to run napi build first.',
45+
);
46+
}
47+
48+
// Copy parser files
49+
const parserFiles = [
50+
{
51+
src: join(rootDir, '../parser/raw-transfer/lazy-common.js'),
52+
dest: join(parserRawTransferDir, 'lazy-common.cjs'),
53+
},
54+
{
55+
src: join(rootDir, '../parser/raw-transfer/node-array.js'),
56+
dest: join(parserRawTransferDir, 'node-array.cjs'),
57+
},
58+
{
59+
src: join(rootDir, '../parser/generated/lazy/walk.js'),
60+
dest: join(parserGeneratedDir, 'walk.cjs'),
61+
},
62+
{
63+
src: join(rootDir, '../parser/generated/lazy/types.js'),
64+
dest: join(parserGeneratedDir, 'types.cjs'),
65+
},
66+
{
67+
src: join(rootDir, '../parser/generated/lazy/constructors.js'),
68+
dest: join(parserGeneratedDir, 'constructors.cjs'),
69+
},
70+
];
71+
72+
for (const { src, dest } of parserFiles) {
73+
if (existsSync(src)) {
74+
// replace a any `require(`*.js`)` with `require(`*`.cjs`)`
75+
const content = String(readFileSync(src));
76+
const updatedContent = content.replace(
77+
/require\((['"])(.*?)(\.js)(['"])\)/g,
78+
(match, p1, p2, p3, p4) => {
79+
return `require(${p1}${p2}.cjs${p4})`;
80+
},
81+
);
82+
writeFileSync(dest, updatedContent);
83+
84+
//
85+
// copyFileSync(src, dest);
86+
console.log(`Copied ${src.split('/').pop()}`);
87+
} else {
88+
console.error(`Warning: parser file not found: ${src}`);
89+
}
90+
}
91+
92+
// Copy native .node files that might exist in src-js
93+
const nodeFiles = [
94+
'oxlint.darwin-arm64.node',
95+
'oxlint.darwin-x64.node',
96+
'oxlint.linux-x64-gnu.node',
97+
'oxlint.linux-arm64-gnu.node',
98+
'oxlint.linux-x64-musl.node',
99+
'oxlint.linux-arm64-musl.node',
100+
'oxlint.win32-x64-msvc.node',
101+
'oxlint.win32-arm64-msvc.node',
102+
];
103+
104+
for (const nodeFile of nodeFiles) {
105+
const srcPath = join(rootDir, 'src-js', nodeFile);
106+
if (existsSync(srcPath)) {
107+
copyFileSync(srcPath, join(distDir, nodeFile));
108+
console.log(`Copied ${nodeFile}`);
109+
}
110+
}
111+
112+
console.log('Build complete!');

napi/oxlint2/src-js/index.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { addVisitorToCompiled, compiledVisitor, finalizeCompiledVisitor, initCom
99
// and need to make sure get same instance of modules as it uses internally,
1010
// otherwise `TOKEN` here won't be same `TOKEN` as used within `oxc-parser`.
1111
const require = createRequire(import.meta.url);
12-
const { TOKEN } = require('../../parser/raw-transfer/lazy-common.js'),
13-
walkProgram = require('../../parser/generated/lazy/walk.js');
12+
const { TOKEN } = require('../dist/parser/raw-transfer/lazy-common.cjs'),
13+
walkProgram = require('../dist/parser/generated/lazy/walk.cjs');
1414

1515
// --------------------
1616
// Plugin loading
@@ -42,7 +42,9 @@ async function loadPlugin(path) {
4242

4343
async function loadPluginImpl(path) {
4444
if (registeredPluginPaths.has(path)) {
45-
return JSON.stringify({ Failure: 'This plugin has already been registered' });
45+
return JSON.stringify({
46+
Failure: 'This plugin has already been registered',
47+
});
4648
}
4749

4850
const { default: plugin } = await import(path);
@@ -56,7 +58,10 @@ async function loadPluginImpl(path) {
5658

5759
for (const [ruleName, rule] of Object.entries(plugin.rules)) {
5860
ruleNames.push(ruleName);
59-
registeredRules.push({ rule, context: new Context(`${pluginName}/${ruleName}`) });
61+
registeredRules.push({
62+
rule,
63+
context: new Context(`${pluginName}/${ruleName}`),
64+
});
6065
}
6166

6267
return JSON.stringify({ Success: { name: pluginName, offset, ruleNames } });
@@ -194,7 +199,14 @@ function lintFile([filePath, bufferId, buffer, ruleIds]) {
194199

195200
const sourceText = textDecoder.decode(buffer.subarray(0, sourceByteLen));
196201
const sourceIsAscii = sourceText.length === sourceByteLen;
197-
const ast = { buffer, sourceText, sourceByteLen, sourceIsAscii, nodes: new Map(), token: TOKEN };
202+
const ast = {
203+
buffer,
204+
sourceText,
205+
sourceByteLen,
206+
sourceIsAscii,
207+
nodes: new Map(),
208+
token: TOKEN,
209+
};
198210

199211
walkProgram(programPos, ast, compiledVisitor);
200212
}

napi/oxlint2/src-js/visitor.js

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
// for objects created by user code in visitors. If ephemeral user-created objects all fit in new space,
7373
// it will avoid full GC runs, which should greatly improve performance.
7474

75-
import types from '../../parser/generated/lazy/types.js';
75+
import types from '../dist/parser/generated/lazy/types.cjs';
7676

7777
const { LEAF_NODE_TYPES_COUNT, NODE_TYPE_IDS_MAP, NODE_TYPES_COUNT } = types;
7878

@@ -190,7 +190,9 @@ export function initCompiledVisitor() {
190190
*/
191191
export function addVisitorToCompiled(visitor) {
192192
if (visitor === null || typeof visitor !== 'object') {
193-
throw new TypeError('Visitor returned from `create` method must be an object');
193+
throw new TypeError(
194+
'Visitor returned from `create` method must be an object',
195+
);
194196
}
195197

196198
// Exit if is empty visitor
@@ -203,14 +205,18 @@ export function addVisitorToCompiled(visitor) {
203205
for (let name of keys) {
204206
const visitFn = visitor[name];
205207
if (typeof visitFn !== 'function') {
206-
throw new TypeError(`'${name}' property of visitor object is not a function`);
208+
throw new TypeError(
209+
`'${name}' property of visitor object is not a function`,
210+
);
207211
}
208212

209213
const isExit = name.endsWith(':exit');
210214
if (isExit) name = name.slice(0, -5);
211215

212216
const typeId = NODE_TYPE_IDS_MAP.get(name);
213-
if (typeId === void 0) throw new Error(`Unknown node type '${name}' in visitor object`);
217+
if (typeId === void 0) {
218+
throw new Error(`Unknown node type '${name}' in visitor object`);
219+
}
214220

215221
const existing = compiledVisitor[typeId];
216222
if (typeId < LEAF_NODE_TYPES_COUNT) {
@@ -230,13 +236,15 @@ export function addVisitorToCompiled(visitor) {
230236
}
231237
} else {
232238
// Same as above, enter visitor is put to front of list to make sure enter is called before exit
233-
compiledVisitor[typeId] = isExit ? [existing, visitFn] : [visitFn, existing];
239+
compiledVisitor[typeId] = isExit
240+
? [existing, visitFn]
241+
: [visitFn, existing];
234242
mergedLeafVisitorTypeIds.push(typeId);
235243
}
236244
} else {
237245
// Not leaf node - store enter+exit pair
238246
if (existing === null) {
239-
const enterExit = compiledVisitor[typeId] = getEnterExitObject();
247+
const enterExit = (compiledVisitor[typeId] = getEnterExitObject());
240248
if (isExit) {
241249
enterExit.exit = visitFn;
242250
} else {
@@ -332,7 +340,9 @@ function mergeVisitFns(visitFns) {
332340
mergers.push(merger);
333341
} else {
334342
merger = mergers[numVisitFns];
335-
if (merger === null) merger = mergers[numVisitFns] = createMerger(numVisitFns);
343+
if (merger === null) {
344+
merger = mergers[numVisitFns] = createMerger(numVisitFns);
345+
}
336346
}
337347

338348
// Merge functions
@@ -366,22 +376,22 @@ function createMerger(fnCount) {
366376
const mergers = [
367377
null, // No merger for 0 functions
368378
null, // No merger for 1 function
369-
(visit1, visit2) => node => {
379+
(visit1, visit2) => (node) => {
370380
visit1(node);
371381
visit2(node);
372382
},
373-
(visit1, visit2, visit3) => node => {
383+
(visit1, visit2, visit3) => (node) => {
374384
visit1(node);
375385
visit2(node);
376386
visit3(node);
377387
},
378-
(visit1, visit2, visit3, visit4) => node => {
388+
(visit1, visit2, visit3, visit4) => (node) => {
379389
visit1(node);
380390
visit2(node);
381391
visit3(node);
382392
visit4(node);
383393
},
384-
(visit1, visit2, visit3, visit4, visit5) => node => {
394+
(visit1, visit2, visit3, visit4, visit5) => (node) => {
385395
visit1(node);
386396
visit2(node);
387397
visit3(node);

napi/oxlint2/test/e2e.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest';
55
import { execa } from 'execa';
66

77
const PACKAGE_ROOT_PATH = path.dirname(import.meta.dirname);
8-
const ENTRY_POINT_PATH = path.join(PACKAGE_ROOT_PATH, 'src-js/index.js');
8+
const ENTRY_POINT_PATH = path.join(PACKAGE_ROOT_PATH, 'dist/index.js');
99

1010
async function runOxlint(cwd: string, args: string[] = []) {
1111
return await execa('node', [ENTRY_POINT_PATH, ...args], {

napi/oxlint2/tsdown.config.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { defineConfig } from 'tsdown';
2+
3+
export default defineConfig({
4+
entry: ['src-js/index.js'],
5+
format: ['esm'],
6+
platform: 'node',
7+
target: 'node20',
8+
outDir: 'dist',
9+
clean: true,
10+
bundle: true,
11+
external: [
12+
// External native bindings
13+
'./oxlint.*.node',
14+
'oxlint2-*',
15+
// External the generated constants file - we'll copy it separately
16+
'./generated/constants.cjs',
17+
// These are generated (also used by oxc-parser, so we'll copy them separately)
18+
/..\/parser\/.*/,
19+
],
20+
});

0 commit comments

Comments
 (0)