Skip to content

Commit 67a2b5f

Browse files
feat: First release!
1 parent 678568d commit 67a2b5f

File tree

11 files changed

+229
-100
lines changed

11 files changed

+229
-100
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,20 @@ This package does not cache tokenizers or inference sessions. Each call creates
108108
- Coverage runner: `node test/run-coverage.mjs`
109109
- Coverage result: `100%` statements, branches, functions, and lines
110110
- Runtime e2e coverage:
111-
- Node
112-
- Bun
113-
- Deno
114-
- Vercel Edge Runtime (`GPUAccelerationSupported()` / wasm-path smoke)
111+
- Node ESM
112+
- Node CommonJS
113+
- Bun ESM
114+
- Bun CommonJS
115+
- Deno ESM
116+
- Vercel Edge Runtime ESM (`GPUAccelerationSupported()` / wasm-path smoke)
115117
- Chromium
116118
- Firefox
117119
- WebKit
118120
- Mobile Chrome (`Pixel 5`)
119121
- Mobile Safari (`iPhone 12`)
120122
- CI matrix: Node `22.x` and `24.x`
121123

122-
`npm test` builds the package, runs the `node:test` unit/integration suite under `c8`, then runs the end-to-end smoke suite against the built package across all supported runtimes above.
124+
`npm test` builds the package, runs the `node:test` unit/integration suite under `c8`, then runs the end-to-end smoke suite against the built package across the ESM/CJS/runtime matrix above.
123125

124126
## Benchmarks
125127

playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const baseURL = `http://127.0.0.1:${port}`
55

66
export default defineConfig({
77
testDir: 'test/e2e/runsInBrowsers',
8-
timeout: 60000,
8+
timeout: 120000,
99
use: {
1010
baseURL,
1111
},

test/e2e/run.mjs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ const runtimeCommands = [
4141
]
4242

4343
for (const runtime of runtimeCommands) {
44-
console.log(`Running ${runtime.label} e2e`)
45-
4644
const result = spawnSync(runtime.command, runtime.args, {
4745
shell: runtime.shell ?? false,
4846
stdio: 'inherit',

test/e2e/runsInBrowsers/package-name.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ test('browser scenario passes in this project', async ({ page }) => {
1919
() => globalThis.__E2E_RESULT__?.status,
2020
undefined,
2121
{
22-
timeout: 60000,
22+
timeout: 120000,
2323
}
2424
)
2525

test/e2e/runsInBrowsers/run.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const serverProcess = spawn(process.execPath, ['test/e2e/server.mjs'], {
7171
})
7272

7373
try {
74+
console.log('=== Browsers E2E ===')
7475
await waitForServer(serverProcess)
7576

7677
const result = spawnSync(

test/e2e/runsInBun/run.mjs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
import { createRequire } from 'node:module'
12
import { runEndToEndScenario } from '../scenario.mjs'
23

3-
await runEndToEndScenario('bun')
4+
const require = createRequire(import.meta.url)
5+
6+
const runAndReport = async (moduleFormat, loadRuntime) => {
7+
const { Tensor, utils } = await loadRuntime()
8+
const { passed, total } = await runEndToEndScenario({
9+
runtime: 'bun',
10+
Tensor,
11+
...utils,
12+
})
13+
14+
console.log(`bun ${moduleFormat}: ${passed}/${total} passed`)
15+
}
16+
17+
console.log('=== Bun E2E ===')
18+
19+
await runAndReport('esm', async () => {
20+
const [{ Tensor }, utils] = await Promise.all([
21+
import('onnxruntime-web'),
22+
import('../../../dist/index.js'),
23+
])
24+
25+
return { Tensor, utils }
26+
})
27+
28+
await runAndReport('cjs', async () => {
29+
const { Tensor } = require('onnxruntime-web')
30+
const utils = require('../../../dist/index.cjs')
31+
32+
return { Tensor, utils }
33+
})

test/e2e/runsInDeno/run.mjs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
11
import { runEndToEndScenario } from '../scenario.mjs'
22

3-
await runEndToEndScenario('deno')
3+
const [{ Tensor }, utils] = await Promise.all([
4+
import('onnxruntime-web'),
5+
import('../../../dist/index.js'),
6+
])
7+
8+
const { passed, total } = await runEndToEndScenario({
9+
runtime: 'deno',
10+
Tensor,
11+
...utils,
12+
})
13+
14+
console.log('=== Deno E2E ===')
15+
console.log(`deno esm: ${passed}/${total} passed`)
Lines changed: 114 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,134 @@
11
import assert from 'node:assert/strict'
22
import { readFile } from 'node:fs/promises'
33
import path from 'node:path'
4-
import test from 'node:test'
5-
import { pathToFileURL } from 'node:url'
6-
import { createRequire } from 'node:module'
74
import { EdgeRuntime } from 'edge-runtime'
85

9-
const distEntryPath = path.resolve('dist/index.cjs')
10-
const distEntryUrl = pathToFileURL(distEntryPath)
6+
const distEntryPath = path.resolve('dist/index.js')
117
const distSource = await readFile(distEntryPath, 'utf8')
12-
const nodeRequire = createRequire(distEntryUrl)
13-
14-
test('edge runtime uses the wasm-only inference path and reports no GPU acceleration', async () => {
15-
let usedWasmRuntime = false
16-
let usedBrowserRuntime = false
17-
let executionProviders
18-
19-
const runtime = new EdgeRuntime({
20-
extend(context) {
21-
const module = { exports: {} }
22-
23-
context.module = module
24-
context.exports = module.exports
25-
context.__filename = distEntryPath
26-
context.__dirname = path.dirname(distEntryPath)
27-
context.require = (specifier) => {
28-
if (specifier === 'onnxruntime-web') {
29-
return {
30-
env: { wasm: { numThreads: 0 } },
31-
InferenceSession: {
32-
async create(_model, options) {
33-
usedWasmRuntime = true
34-
executionProviders = options.executionProviders
35-
throw new Error('forced edge-runtime wasm inference failure')
36-
},
8+
9+
const transformEsmForEdgeRuntime = (source) =>
10+
source
11+
.replace(
12+
'import { toBase64String } from "@sovereignbase/bytecodec";',
13+
'const { toBase64String } = await __import("@sovereignbase/bytecodec");'
14+
)
15+
.replace(
16+
'import { SentencePieceProcessor } from "@sctg/sentencepiece-js";',
17+
'const { SentencePieceProcessor } = await __import("@sctg/sentencepiece-js");'
18+
)
19+
.replace(
20+
'return import("onnxruntime-web/all");',
21+
'return __import("onnxruntime-web/all");'
22+
)
23+
.replace(
24+
'const runtime = await import("onnxruntime-web");',
25+
'const runtime = await __import("onnxruntime-web");'
26+
)
27+
.replace(
28+
/export\s*\{\s*GPUAccelerationSupported,\s*LocalInferenceUtilsError,\s*createInferenceSession,\s*createTokenizer\s*\};/,
29+
'return { GPUAccelerationSupported, LocalInferenceUtilsError, createInferenceSession, createTokenizer };'
30+
)
31+
32+
const runtime = new EdgeRuntime({
33+
extend(context) {
34+
let usedWasmRuntime = false
35+
let usedBrowserRuntime = false
36+
let executionProviders
37+
38+
class SentencePieceProcessor {
39+
async loadFromB64StringModel(model) {
40+
this.loadedModel = model
41+
}
42+
}
43+
44+
context.__edgeTestState = {
45+
getExecutionProviders() {
46+
return executionProviders
47+
},
48+
usedBrowserRuntime() {
49+
return usedBrowserRuntime
50+
},
51+
usedWasmRuntime() {
52+
return usedWasmRuntime
53+
},
54+
}
55+
56+
context.__import = async (specifier) => {
57+
if (specifier === 'onnxruntime-web') {
58+
return {
59+
env: { wasm: { numThreads: 0 } },
60+
InferenceSession: {
61+
async create(_model, options) {
62+
usedWasmRuntime = true
63+
executionProviders = options.executionProviders
64+
throw new Error('forced edge-runtime wasm inference failure')
3765
},
38-
}
66+
},
3967
}
68+
}
4069

41-
if (specifier === 'onnxruntime-web/all') {
42-
return {
43-
InferenceSession: {
44-
async create() {
45-
usedBrowserRuntime = true
46-
throw new Error('browser runtime should not be selected')
47-
},
70+
if (specifier === 'onnxruntime-web/all') {
71+
return {
72+
InferenceSession: {
73+
async create() {
74+
usedBrowserRuntime = true
75+
throw new Error('browser runtime should not be selected')
4876
},
49-
}
77+
},
5078
}
79+
}
5180

52-
if (specifier === '@sctg/sentencepiece-js') {
53-
return {
54-
SentencePieceProcessor: class SentencePieceProcessor {},
55-
}
56-
}
81+
if (specifier === '@sctg/sentencepiece-js') {
82+
return { SentencePieceProcessor }
83+
}
5784

58-
if (specifier === '@sovereignbase/bytecodec') {
59-
return {
60-
toBase64String() {
61-
return ''
62-
},
63-
}
85+
if (specifier === '@sovereignbase/bytecodec') {
86+
return {
87+
toBase64String() {
88+
return 'edge-tokenizer-model'
89+
},
6490
}
65-
66-
return nodeRequire(specifier)
6791
}
6892

69-
return context
70-
},
71-
})
93+
throw new Error(`unexpected edge-runtime import: ${specifier}`)
94+
}
7295

73-
runtime.evaluate(distSource)
96+
return context
97+
},
98+
})
7499

75-
const utils = runtime.context.module.exports
100+
runtime.evaluate(`
101+
globalThis.__utilsPromise = (async (__import) => {
102+
${transformEsmForEdgeRuntime(distSource)}
103+
})(globalThis.__import);
104+
`)
76105

77-
assert.equal(typeof utils.GPUAccelerationSupported, 'function')
78-
assert.equal(typeof utils.createInferenceSession, 'function')
79-
assert.equal(utils.GPUAccelerationSupported(), false)
106+
const utils = await runtime.context.__utilsPromise
107+
const state = runtime.context.__edgeTestState
80108

81-
await assert.rejects(
82-
() => utils.createInferenceSession(new Uint8Array([0, 1, 2])),
83-
(error) => {
84-
assert.equal(error.name, 'LocalInferenceUtilsError')
85-
assert.equal(error.code, 'INFERENCE_SESSION_CREATE_FAILED')
86-
assert.equal(usedWasmRuntime, true)
87-
assert.equal(usedBrowserRuntime, false)
88-
assert.deepEqual(Array.from(executionProviders), ['wasm'])
89-
assert.ok(error.cause instanceof Error)
90-
return true
91-
}
92-
)
93-
})
109+
let passed = 0
110+
const total = 3
111+
112+
const tokenizer = await utils.createTokenizer(new Uint8Array([1, 2, 3]))
113+
assert.equal(tokenizer.loadedModel, 'edge-tokenizer-model')
114+
passed += 1
115+
116+
assert.equal(utils.GPUAccelerationSupported(), false)
117+
passed += 1
118+
119+
await assert.rejects(
120+
() => utils.createInferenceSession(new Uint8Array([0, 1, 2])),
121+
(error) => {
122+
assert.equal(error.name, 'LocalInferenceUtilsError')
123+
assert.equal(error.code, 'INFERENCE_SESSION_CREATE_FAILED')
124+
assert.equal(state.usedWasmRuntime(), true)
125+
assert.equal(state.usedBrowserRuntime(), false)
126+
assert.deepEqual(Array.from(state.getExecutionProviders()), ['wasm'])
127+
assert.ok(error.cause instanceof Error)
128+
return true
129+
}
130+
)
131+
passed += 1
132+
133+
console.log('=== Edge Runtimes E2E ===')
134+
console.log(`edge-runtime esm: ${passed}/${total} passed`)

test/e2e/runsInNode/run.mjs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
import { createRequire } from 'node:module'
12
import { runEndToEndScenario } from '../scenario.mjs'
23

3-
await runEndToEndScenario('node')
4+
const require = createRequire(import.meta.url)
5+
6+
const runAndReport = async (moduleFormat, loadRuntime) => {
7+
const { Tensor, utils } = await loadRuntime()
8+
const { passed, total } = await runEndToEndScenario({
9+
runtime: 'node',
10+
Tensor,
11+
...utils,
12+
})
13+
14+
console.log(`node ${moduleFormat}: ${passed}/${total} passed`)
15+
}
16+
17+
console.log('=== Node E2E ===')
18+
19+
await runAndReport('esm', async () => {
20+
const [{ Tensor }, utils] = await Promise.all([
21+
import('onnxruntime-web'),
22+
import('../../../dist/index.js'),
23+
])
24+
25+
return { Tensor, utils }
26+
})
27+
28+
await runAndReport('cjs', async () => {
29+
const { Tensor } = require('onnxruntime-web')
30+
const utils = require('../../../dist/index.cjs')
31+
32+
return { Tensor, utils }
33+
})

0 commit comments

Comments
 (0)