Skip to content

Commit 5776002

Browse files
committed
build: target Node.js for portable dist output
Add conditional exports (bun/node/default) so each runtime resolves the correct variant, and add a packed dist smoke test that verifies imports work in both Node.js and Bun.
1 parent baefc92 commit 5776002

5 files changed

Lines changed: 429 additions & 85 deletions

File tree

bun.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
"bench:text-table": "bun src/benchmark/text-table-benchmark.ts",
2424
"bench:ts": "bun src/benchmark/native-span-feed-benchmark.ts --suite=quick --json=src/benchmark/latest-quick-bench-run.json && bun src/benchmark/native-span-feed-benchmark.ts --suite=default --json=src/benchmark/latest-default-bench-run.json && bun src/benchmark/native-span-feed-benchmark.ts --suite=large --json=src/benchmark/latest-large-bench-run.json && bun src/benchmark/native-span-feed-benchmark.ts --suite=all --json=src/benchmark/latest-all-bench-run.json && bun src/benchmark/native-span-feed-async-benchmark.ts --json=src/benchmark/latest-async-bench-run.json",
2525
"publish": "bun scripts/publish.ts",
26+
"test:dist": "bun scripts/dist-test.ts",
2627
"test:js": "bun test",
28+
"test:js:node": "bun scripts/test-node.ts",
2729
"test": "bun run test:native && bun run test:js"
2830
},
2931
"license": "MIT",
@@ -36,7 +38,7 @@
3638
"web-tree-sitter": "0.25.10"
3739
},
3840
"dependencies": {
39-
"bun-ffi-structs": "0.2.2",
41+
"bun-ffi-structs": "0.2.3",
4042
"diff": "9.0.0",
4143
"marked": "17.0.1",
4244
"string-width": "7.2.0",

packages/core/scripts/build.ts

Lines changed: 134 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { spawnSync, type SpawnSyncReturns } from "node:child_process"
22
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs"
33
import { dirname, join, resolve } from "path"
4+
import { ModuleKind, ScriptTarget, transpileModule } from "typescript"
45
import { fileURLToPath } from "url"
56
import process from "process"
67
import path from "path"
@@ -30,6 +31,13 @@ interface PackageJson {
3031
peerDependencies?: Record<string, string>
3132
}
3233

34+
interface BunBuildOptions {
35+
entryPoints: string[]
36+
externalPatterns?: string[]
37+
splitting?: boolean
38+
target: "bun" | "node"
39+
}
40+
3341
const __filename = fileURLToPath(import.meta.url)
3442
const __dirname = dirname(__filename)
3543
const rootDir = resolve(__dirname, "..")
@@ -88,6 +96,55 @@ if (missingRequired.length > 0) {
8896
process.exit(1)
8997
}
9098

99+
const runCommand = (command: string, commandArgs: string[], cwd: string, errorMessage: string): void => {
100+
const result: SpawnSyncReturns<Buffer> = spawnSync(command, commandArgs, {
101+
cwd,
102+
stdio: "inherit",
103+
})
104+
105+
if (result.error) {
106+
console.error(`${errorMessage}: ${result.error.message}`)
107+
process.exit(1)
108+
}
109+
110+
if (result.status !== 0) {
111+
console.error(errorMessage)
112+
process.exit(1)
113+
}
114+
}
115+
116+
const runBunBuild = ({ entryPoints, externalPatterns = [], splitting = false, target }: BunBuildOptions): void => {
117+
const buildArgs = [
118+
"build",
119+
`--target=${target}`,
120+
"--outdir=dist",
121+
"--sourcemap",
122+
...(splitting ? ["--splitting"] : []),
123+
...externalPatterns.flatMap((pattern) => ["--external", pattern]),
124+
...entryPoints,
125+
]
126+
127+
runCommand("bun", buildArgs, rootDir, `Error: Bun ${target} build failed for ${entryPoints.join(", ")}`)
128+
}
129+
130+
const transpileEntryPoint = (entryPoint: string, outputPath: string): void => {
131+
const sourcePath = join(rootDir, entryPoint)
132+
const sourceText = readFileSync(sourcePath, "utf8")
133+
const result = transpileModule(sourceText, {
134+
compilerOptions: {
135+
module: ModuleKind.ESNext,
136+
sourceMap: true,
137+
target: ScriptTarget.ES2022,
138+
},
139+
fileName: sourcePath,
140+
})
141+
142+
writeFileSync(outputPath, result.outputText)
143+
if (result.sourceMapText) {
144+
writeFileSync(`${outputPath}.map`, result.sourceMapText)
145+
}
146+
}
147+
91148
if (buildNative) {
92149
console.log(`Building native ${isDev ? "dev" : "prod"} binaries${buildAll ? " for all platforms" : ""}...`)
93150

@@ -99,20 +156,7 @@ if (buildNative) {
99156
zigArgs.push("-Dgpa-safe-stats=true")
100157
}
101158

102-
const zigBuild: SpawnSyncReturns<Buffer> = spawnSync("zig", zigArgs, {
103-
cwd: join(rootDir, "src", "zig"),
104-
stdio: "inherit",
105-
})
106-
107-
if (zigBuild.error) {
108-
console.error("Error: Zig is not installed or not in PATH")
109-
process.exit(1)
110-
}
111-
112-
if (zigBuild.status !== 0) {
113-
console.error("Error: Zig build failed")
114-
process.exit(1)
115-
}
159+
runCommand("zig", zigArgs, join(rootDir, "src", "zig"), "Error: Zig build failed")
116160

117161
const variantsToPackage = buildAll ? variants : [getHostVariant()]
118162

@@ -221,12 +265,21 @@ if (buildLib) {
221265
process.exit(1)
222266
}
223267

224-
const entryPoints: string[] = [
225-
packageJson.module,
226-
"src/testing.ts",
227-
"src/runtime-plugin.ts",
228-
"src/runtime-plugin-support.ts",
229-
"src/runtime-plugin-support-configure.ts",
268+
const portableEntryPoints: string[] = [packageJson.module, "src/testing.ts"]
269+
270+
const bunOnlyEntryPoints = [
271+
{
272+
entryPoint: "src/runtime-plugin.ts",
273+
outputFile: "runtime-plugin.js",
274+
},
275+
{
276+
entryPoint: "src/runtime-plugin-support-configure.ts",
277+
outputFile: "runtime-plugin-support-configure.js",
278+
},
279+
{
280+
entryPoint: "src/runtime-plugin-support.ts",
281+
outputFile: "runtime-plugin-support.js",
282+
},
230283
]
231284

232285
// Build main entry points with code splitting
@@ -241,25 +294,19 @@ if (buildLib) {
241294
"./lib/tree-sitter/default-parsers.ts",
242295
]
243296

244-
spawnSync(
245-
"bun",
246-
[
247-
"build",
248-
"--target=bun",
249-
"--splitting",
250-
"--outdir=dist",
251-
"--sourcemap",
252-
...externalPatterns.flatMap((dep) => ["--external", dep]),
253-
...entryPoints,
254-
],
255-
{
256-
cwd: rootDir,
257-
stdio: "inherit",
258-
},
259-
)
297+
runBunBuild({
298+
entryPoints: portableEntryPoints,
299+
externalPatterns,
300+
splitting: true,
301+
target: "node",
302+
})
303+
304+
for (const { entryPoint, outputFile } of bunOnlyEntryPoints) {
305+
transpileEntryPoint(entryPoint, join(distDir, outputFile))
306+
}
260307

261308
// Build updater as a separate entry so generator code stays out of the core runtime bundle.
262-
spawnSync(
309+
runCommand(
263310
"bun",
264311
[
265312
"build",
@@ -269,31 +316,17 @@ if (buildLib) {
269316
...externalDeps.flatMap((dep) => ["--external", dep]),
270317
"src/lib/tree-sitter/update-assets.ts",
271318
],
272-
{
273-
cwd: rootDir,
274-
stdio: "inherit",
275-
},
319+
rootDir,
320+
"Error: Bun build failed for src/lib/tree-sitter/update-assets.ts",
276321
)
277322

278323
// Build parser worker as standalone bundle (no splitting) so it can be loaded as a Worker
279324
// Make web-tree-sitter external so it loads from node_modules with its WASM file
280-
spawnSync(
281-
"bun",
282-
[
283-
"build",
284-
"--target=bun",
285-
"--outdir=dist",
286-
"--sourcemap",
287-
...externalDeps.flatMap((dep) => ["--external", dep]),
288-
"--external",
289-
"web-tree-sitter",
290-
"src/lib/tree-sitter/parser.worker.ts",
291-
],
292-
{
293-
cwd: rootDir,
294-
stdio: "inherit",
295-
},
296-
)
325+
runBunBuild({
326+
entryPoints: ["src/lib/tree-sitter/parser.worker.ts"],
327+
externalPatterns: [...externalDeps, "web-tree-sitter"],
328+
target: "node",
329+
})
297330

298331
// Post-process to fix Bun's duplicate export issue
299332
// See: https://github.com/oven-sh/bun/issues/5344
@@ -306,7 +339,7 @@ if (buildLib) {
306339
"dist/runtime-plugin-support.js",
307340
"dist/runtime-plugin-support-configure.js",
308341
"dist/lib/tree-sitter/update-assets.js",
309-
"dist/lib/tree-sitter/parser.worker.js",
342+
"dist/parser.worker.js",
310343
]
311344
for (const filePath of bundledFiles) {
312345
const fullPath = join(rootDir, filePath)
@@ -339,17 +372,8 @@ if (buildLib) {
339372

340373
const tsconfigBuildPath = join(rootDir, "tsconfig.build.json")
341374

342-
const tscResult: SpawnSyncReturns<Buffer> = spawnSync("bunx", ["tsc", "-p", tsconfigBuildPath], {
343-
cwd: rootDir,
344-
stdio: "inherit",
345-
})
346-
347-
if (tscResult.status !== 0) {
348-
console.error("Error: TypeScript declaration generation failed")
349-
process.exit(1)
350-
} else {
351-
console.log("TypeScript declarations generated")
352-
}
375+
runCommand("bunx", ["tsc", "-p", tsconfigBuildPath], rootDir, "Error: TypeScript declaration generation failed")
376+
console.log("TypeScript declarations generated")
353377

354378
const treeSitterSrcDir = join(rootDir, "src", "lib", "tree-sitter")
355379

@@ -370,41 +394,71 @@ if (buildLib) {
370394
copyAssets(join(treeSitterSrcDir, "assets"), join(distDir, "assets"))
371395
console.log(" Copied tree-sitter assets (*.wasm, *.scm) to dist/assets/")
372396

397+
const writeBunOnlyStub = (fileName: string, specifier: string, exportNames: string[]): void => {
398+
const errorMessage = `${specifier} is Bun-only and is not available in Node.js. Use Bun to import this entrypoint.`
399+
const namedExports = exportNames
400+
.map(
401+
(exportName) => `export function ${exportName}() {\n throw new Error(${JSON.stringify(errorMessage)})\n}`,
402+
)
403+
.join("\n\n")
404+
405+
writeFileSync(
406+
join(distDir, fileName),
407+
`const errorMessage = ${JSON.stringify(errorMessage)}\n\n${namedExports}\n\nthrow new Error(errorMessage)\n`,
408+
)
409+
}
410+
411+
writeBunOnlyStub("runtime-plugin.node.js", `${packageJson.name}/runtime-plugin`, [
412+
"createRuntimePlugin",
413+
"isCoreRuntimeModuleSpecifier",
414+
"runtimeModuleIdForSpecifier",
415+
])
416+
writeBunOnlyStub("runtime-plugin-support.node.js", `${packageJson.name}/runtime-plugin-support`, [
417+
"ensureRuntimePluginSupport",
418+
"createRuntimePlugin",
419+
"runtimeModuleIdForSpecifier",
420+
])
421+
writeBunOnlyStub("runtime-plugin-support-configure.node.js", `${packageJson.name}/runtime-plugin-support/configure`, [
422+
"ensureRuntimePluginSupport",
423+
"createRuntimePlugin",
424+
"runtimeModuleIdForSpecifier",
425+
])
426+
373427
// Configure exports for multiple entry points
374428
const exports = {
375429
".": {
376430
import: "./index.js",
377-
require: "./index.js",
378431
types: "./index.d.ts",
379432
},
380433
"./testing": {
381434
import: "./testing.js",
382-
require: "./testing.js",
383435
types: "./testing.d.ts",
384436
},
385437
"./runtime-plugin": {
386-
import: "./runtime-plugin.js",
387-
require: "./runtime-plugin.js",
388438
types: "./runtime-plugin.d.ts",
439+
bun: "./runtime-plugin.js",
440+
node: "./runtime-plugin.node.js",
441+
default: "./runtime-plugin.node.js",
389442
},
390443
"./runtime-plugin-support": {
391-
import: "./runtime-plugin-support.js",
392-
require: "./runtime-plugin-support.js",
393444
types: "./runtime-plugin-support.d.ts",
445+
bun: "./runtime-plugin-support.js",
446+
node: "./runtime-plugin-support.node.js",
447+
default: "./runtime-plugin-support.node.js",
394448
},
395449
"./runtime-plugin-support/configure": {
396-
import: "./runtime-plugin-support-configure.js",
397-
require: "./runtime-plugin-support-configure.js",
398450
types: "./runtime-plugin-support-configure.d.ts",
451+
bun: "./runtime-plugin-support-configure.js",
452+
node: "./runtime-plugin-support-configure.node.js",
453+
default: "./runtime-plugin-support-configure.node.js",
399454
},
400455
"./tree-sitter/update-assets": {
401456
import: "./lib/tree-sitter/update-assets.js",
402457
require: "./lib/tree-sitter/update-assets.js",
403458
types: "./lib/tree-sitter/update-assets.d.ts",
404459
},
405460
"./parser.worker": {
406-
import: "./lib/tree-sitter/parser.worker.js",
407-
require: "./lib/tree-sitter/parser.worker.js",
461+
import: "./parser.worker.js",
408462
types: "./lib/tree-sitter/parser.worker.d.ts",
409463
},
410464
}
@@ -432,7 +486,6 @@ if (buildLib) {
432486
bugs: packageJson.bugs,
433487
exports,
434488
dependencies: packageJson.dependencies,
435-
devDependencies: packageJson.devDependencies,
436489
peerDependencies: packageJson.peerDependencies,
437490
optionalDependencies: {
438491
...packageJson.optionalDependencies,

0 commit comments

Comments
 (0)