refactor(napi): use vitest (#6307)

This commit is contained in:
Boshen 2024-10-06 02:57:48 +00:00
parent 58a8615747
commit 5b5daec392
15 changed files with 926 additions and 341 deletions

View file

@ -274,13 +274,7 @@ jobs:
cache-key: warm
- uses: ./.github/actions/pnpm
if: steps.filter.outputs.src == 'true'
- name: Test napi/parser
run: pnpm build && pnpm test
- run: pnpm run build
if: steps.filter.outputs.src == 'true'
working-directory: napi/parser
- name: Test napi/transform
if: steps.filter.outputs.src == 'true'
working-directory: napi/transform
run: pnpm build && pnpm test
- run: git diff --exit-code
- run: pnpm run test
if: steps.filter.outputs.src == 'true'

View file

@ -3,7 +3,7 @@
"private": true,
"scripts": {
"build": "napi build --platform --release",
"test": "node test.mjs"
"test": "echo 'skip'"
},
"engines": {
"node": ">=14.*"

View file

@ -4,8 +4,7 @@
"private": true,
"scripts": {
"build": "napi build --platform --release",
"test": "node test.mjs",
"bench": "node parse.bench.mjs"
"test": "vitest run ./test"
},
"engines": {
"node": ">=14.*"
@ -22,10 +21,5 @@
"x86_64-apple-darwin",
"aarch64-apple-darwin"
]
},
"devDependencies": {
"@napi-rs/cli": "catalog:",
"es-module-lexer": "^1.4.1",
"tinybench": "^2.6.0"
}
}

View file

@ -1,75 +0,0 @@
import { readFile, writeFile } from 'fs/promises';
import { join as pathJoin } from 'path';
import { Bench } from 'tinybench';
import { fileURLToPath } from 'url';
import { parseSync } from './index.js';
const IS_CI = !!process.env.CI,
ACCURATE = IS_CI || process.env.ACCURATE;
const urls = [
// TypeScript syntax (2.81MB)
'https://raw.githubusercontent.com/microsoft/TypeScript/v5.3.3/src/compiler/checker.ts',
// Real world app tsx (1.0M)
'https://raw.githubusercontent.com/oxc-project/benchmark-files/main/cal.com.tsx',
// Real world content-heavy app jsx (3K)
'https://raw.githubusercontent.com/oxc-project/benchmark-files/main/RadixUIAdoptionSection.jsx',
// Heavy with classes (554K)
'https://cdn.jsdelivr.net/npm/pdfjs-dist@4.0.269/build/pdf.mjs',
// ES5 (3.9M)
'https://cdn.jsdelivr.net/npm/antd@5.12.5/dist/antd.js',
];
// Same directory as Rust benchmarks use for downloaded files
const cacheDirPath = pathJoin(fileURLToPath(import.meta.url), '../../../target');
const files = await Promise.all(urls.map(async (url) => {
const filename = url.split('/').at(-1),
path = pathJoin(cacheDirPath, filename);
let code;
try {
code = await readFile(path, 'utf8');
if (IS_CI) console.log('Found cached file:', filename);
} catch {
if (IS_CI) console.log('Downloading:', filename);
const res = await fetch(url);
code = await res.text();
await writeFile(path, code);
}
return { filename, code };
}));
const bench = new Bench(
ACCURATE
? {
warmupIterations: 20, // Default is 5
time: 5000, // 5 seconds, default is 500 ms
iterations: 100, // Default is 10
}
: undefined,
);
for (const { filename, code } of files) {
bench.add(`parser_napi[${filename}]`, () => {
const res = parseSync(code, { sourceFilename: filename });
JSON.parse(res.program);
});
}
console.log('Warming up');
await bench.warmup();
console.log('Running benchmarks');
await bench.run();
console.table(bench.table());
// If running on CI, save results to file
if (IS_CI) {
const dataDir = process.env.DATA_DIR;
const results = bench.tasks.map(task => ({
filename: task.name.match(/\[(.+)\]$/)[1],
duration: task.result.period / 1000, // In seconds
}));
await writeFile(pathJoin(dataDir, 'results.json'), JSON.stringify(results));
}

View file

@ -1,21 +0,0 @@
import assert from 'assert';
import oxc from './index.js';
console.log(`Testing on ${process.platform}-${process.arch}`);
function test(ret) {
console.log(ret);
assert(JSON.parse(ret.program).body.length == 1);
assert(ret.errors.length == 0);
assert(ret.comments.length == 1);
}
const sourceText = '/* comment */ foo';
test(oxc.parseSync(sourceText));
async function main() {
test(await oxc.parseAsync(sourceText));
}
main();

View file

@ -0,0 +1,17 @@
import { assert, describe, it } from 'vitest';
import oxc from './index.js';
describe('module lexer', () => {
const code = 'export { foo }';
it('matches output', () => {
const ret = oxc.moduleLexerSync(code);
assert(ret.exports.length == 1);
});
it('matches output async ', async () => {
const ret = await oxc.moduleLexerAsync(code);
assert(ret.exports.length == 1);
});
});

View file

@ -0,0 +1,21 @@
import { assert, describe, it } from 'vitest';
import oxc from './index.js';
describe('parse', () => {
const code = '/* comment */ foo';
it('matches output', () => {
const ret = oxc.parseSync(code);
assert(JSON.parse(ret.program).body.length == 1);
assert(ret.errors.length == 0);
assert(ret.comments.length == 1);
});
it('matches output async ', async () => {
const ret = await oxc.parseAsync(code);
assert(JSON.parse(ret.program).body.length == 1);
assert(ret.errors.length == 0);
assert(ret.comments.length == 1);
});
});

View file

@ -1,102 +0,0 @@
/*
* Test and bench against `es-module-lexer`
*/
import assert from 'assert';
import fs from 'fs';
import * as esModuleLexer from 'es-module-lexer';
import oxc from './index.js';
const root = '/Users/boshen/github/es-module-lexer/test/samples';
main();
async function main() {
await esModuleLexer.init;
const files = fs
.readdirSync(root)
.filter((x) => x.endsWith('.js'))
.map((file) => {
const source = fs.readFileSync(`${root}/${file}`);
return {
file,
code: source.toString(),
size: source.byteLength,
};
});
function test(source) {
const [imports, exports, facade, hasModuleSyntax] = esModuleLexer.parse(source);
const moduleLexer = oxc.moduleLexerSync(source);
assert(moduleLexer.imports.length == imports.length);
assert(moduleLexer.exports.length == exports.length);
assert(moduleLexer.facade == facade);
assert(moduleLexer.hasModuleSyntax == hasModuleSyntax);
}
function testAll(files) {
for (const file of files) {
test(file.code);
}
}
testAll(files);
const n = 25;
bench('es-module-lexer', esModuleLexer.parse);
bench('oxc', oxc.moduleLexerSync);
function bench(name, fn) {
console.log('-----------');
console.log(name);
doRun();
function timeRun(code) {
const start = process.hrtime.bigint();
fn(code);
const end = process.hrtime.bigint();
return Math.round(Number(end - start) / 1e6);
}
function doRun() {
console.log('Cold Run, All Samples');
let totalSize = 0;
{
let total = 0;
files.forEach(({ code, size }) => {
totalSize += size;
total += timeRun(code);
});
console.log(`test/samples/*.js (${Math.round(totalSize / 1e3)} KiB)`);
console.log(`> ${total + 'ms'}`);
}
console.log(`\nWarm Runs (average of ${n} runs)`);
files.forEach(({ file, code, size }) => {
console.log(`${file} (${Math.round(size / 1e3)} KiB)`);
let total = 0;
for (let i = 0; i < n; i++) {
total += timeRun(code);
}
console.log(`> ${total / n + 'ms'}`);
});
console.log(`\nWarm Runs, All Samples (average of ${n} runs)`);
{
let total = 0;
for (let i = 0; i < n; i++) {
files.forEach(({ code }) => {
total += timeRun(code);
});
}
console.log(`test/samples/*.js (${Math.round(totalSize / 1e3)} KiB)`);
console.log(`> ${total / n + 'ms'}`);
}
}
}
}

View file

@ -3,7 +3,7 @@
"private": true,
"scripts": {
"build": "napi build --platform --release",
"test": "node test.mjs"
"test": "vitest run ./test"
},
"engines": {
"node": ">=14.*"

View file

@ -1,111 +0,0 @@
import assert from 'assert';
import oxc from './index.js';
console.log(`Testing on ${process.platform}-${process.arch}`);
function test(ret, expected) {
assert.equal(ret.code, expected.code);
assert.deepEqual(ret.map, expected.map);
assert(ret.errors.length == 0);
}
const id = `
/**
* jsdoc 1
*/
export class A {
/**
* jsdoc 2
*/
foo = "bar";
}
`;
test(oxc.isolatedDeclaration('test.ts', id, { sourcemap: true }), {
code: '/**\n' +
'* jsdoc 1\n' +
'*/\n' +
'export declare class A {\n' +
'\t/**\n' +
'\t* jsdoc 2\n' +
'\t*/\n' +
'\tfoo: string;\n' +
'}\n',
map: {
mappings: ';;;AAIA,OAAO,cAAM,EAAE;;;;CAIb;AACD',
names: [],
sources: ['test.ts'],
sourcesContent: [id],
version: 3,
},
});
test(oxc.transform('test.ts', 'class A<T> {}', { sourcemap: true }), {
code: 'class A {}\n',
map: {
mappings: 'AAAA,MAAM,EAAK,CAAE',
names: [],
sources: ['test.ts'],
sourcesContent: ['class A<T> {}'],
version: 3,
},
});
// Test react refresh plugin
test(
oxc.transform(
'test.tsx',
`
import { useState } from "react";
export const App = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>count is {count}</button>;
};
`,
{ jsx: { refresh: {} } },
),
{
code: 'var _s = $RefreshSig$();\n' +
'import { useState } from "react";\n' +
'import { jsxs as _jsxs } from "react/jsx-runtime";\n' +
'export const App = () => {\n' +
'\t_s();\n' +
'\tconst [count, setCount] = useState(0);\n' +
'\treturn _jsxs("button", {\n' +
'\t\tonClick: () => setCount(count + 1),\n' +
'\t\tchildren: ["count is ", count]\n' +
'\t});\n' +
'};\n' +
'_s(App, "oDgYfYHkD9Wkv4hrAPCkI/ev3YU=");\n' +
'_c = App;\n' +
'var _c;\n' +
'$RefreshReg$(_c, "App");\n',
},
);
// Test define plugin
// TODO: should be constant folded
test(
oxc.transform('test.ts', 'if (process.env.NODE_ENV === "production") { foo; }', {
define: {
'process.env.NODE_ENV': 'false',
},
}),
{
code: 'if (false === "production") {\n\tfoo;\n}\n',
},
);
// Test inject plugin
test(
oxc.transform('test.ts', 'let _ = Object.assign', {
inject: {
'Object.assign': 'foo',
},
}),
{
code: 'import $inject_Object_assign from "foo";\nlet _ = $inject_Object_assign;\n',
},
);
console.log('Success.');

View file

@ -0,0 +1,39 @@
import { assert, describe, it } from 'vitest';
import oxc from './index.js';
describe('isolated declaration', () => {
const code = `
/**
* jsdoc 1
*/
export class A {
/**
* jsdoc 2
*/
foo = "bar";
}
`;
it('matches output', () => {
const ret = oxc.isolatedDeclaration('test.ts', code, { sourcemap: true });
assert(ret, {
code: '/**\n' +
'* jsdoc 1\n' +
'*/\n' +
'export declare class A {\n' +
'\t/**\n' +
'\t* jsdoc 2\n' +
'\t*/\n' +
'\tfoo: string;\n' +
'}\n',
map: {
mappings: ';;;AAIA,OAAO,cAAM,EAAE;;;;CAIb;AACD',
names: [],
sources: ['test.ts'],
sourcesContent: [code],
version: 3,
},
});
});
});

View file

@ -0,0 +1,81 @@
import { assert, describe, it } from 'vitest';
import oxc from './index.js';
describe('transform', () => {
const code = 'class A<T> {}';
it('matches output', () => {
const ret = oxc.transform('test.ts', code, { sourcemap: true });
assert(ret, {
code: 'class A {}\n',
map: {
mappings: 'AAAA,MAAM,EAAK,CAAE',
names: [],
sources: ['test.ts'],
sourcesContent: ['class A<T> {}'],
version: 3,
},
});
});
});
describe('react refresh plugin', () => {
const code = `import { useState } from "react";
export const App = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>count is {count}</button>;
};`;
it('matches output', () => {
const ret = oxc.transform('test.tsx', code, { jsx: { refresh: {} } });
assert(ret, {
code: 'var _s = $RefreshSig$();\n' +
'import { useState } from "react";\n' +
'import { jsxs as _jsxs } from "react/jsx-runtime";\n' +
'export const App = () => {\n' +
'\t_s();\n' +
'\tconst [count, setCount] = useState(0);\n' +
'\treturn _jsxs("button", {\n' +
'\t\tonClick: () => setCount(count + 1),\n' +
'\t\tchildren: ["count is ", count]\n' +
'\t});\n' +
'};\n' +
'_s(App, "oDgYfYHkD9Wkv4hrAPCkI/ev3YU=");\n' +
'_c = App;\n' +
'var _c;\n' +
'$RefreshReg$(_c, "App");\n',
});
});
});
describe('define plugin', () => {
const code = 'if (process.env.NODE_ENV === "production") { foo; }';
it('matches output', () => {
const ret = oxc.transform('test.tsx', code, {
define: {
'process.env.NODE_ENV': 'false',
},
});
assert(ret, {
// TODO: should be constant folded
code: 'if (false === "production") {\n\tfoo;\n}\n',
});
});
});
describe('inject plugin', () => {
const code = 'let _ = Object.assign';
it('matches output', () => {
const ret = oxc.transform('test.tsx', code, {
define: {
'process.env.NODE_ENV': 'false',
},
});
assert(ret, {
code: 'import $inject_Object_assign from "foo";\nlet _ = $inject_Object_assign;\n',
});
});
});

View file

@ -5,7 +5,12 @@
"volta": {
"node": "22.7.0"
},
"scripts": {
"build": "pnpm --workspace-concurrency=1 --filter './napi/*' build",
"test": "pnpm --workspace-concurrency=1 --filter './napi/*' test"
},
"devDependencies": {
"@napi-rs/cli": "catalog:"
"@napi-rs/cli": "catalog:",
"vitest": "catalog:"
}
}

File diff suppressed because it is too large Load diff

View file

@ -6,3 +6,4 @@ packages:
catalog:
"@napi-rs/cli": 3.0.0-alpha.61
"vitest": 2.1.2