Skip to content

fix(sdk): make the published build importable from plain Node.js - #99

Merged
kosuke55 merged 1 commit into
mainfrom
fix/esm-node-resolution
Aug 10, 2026
Merged

fix(sdk): make the published build importable from plain Node.js#99
kosuke55 merged 1 commit into
mainfrom
fix/esm-node-resolution

Conversation

@kosuke55

Copy link
Copy Markdown
Owner

Problem

The package ships "type": "module" and the README documents it for headless tooling, but importing it from plain Node.js fails:

$ node -e "import('@drawtonomy/sdk')"
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../dist/types'
    imported from .../dist/index.js

So node script.mjs, CLI tools and CI scripts cannot use the SDK at all today — only bundler-based consumers can.

Cause

tsconfig.json used moduleResolution: "bundler". That mode permits extensionless relative imports and emits them verbatim:

// dist/index.js (before)
export * from './types';

Bundlers resolve those, but Node's ESM loader requires explicit file extensions. 45 such specifiers were emitted across dist/. The existing test suite stayed green because Vite rewrites imports in-process, so nothing surfaced the breakage.

Fix

Change Detail
tsconfig.json module / moduleResolution: bundlerNodeNext, so the compiler enforces Node's ESM rules at build time
15 source files added .js to relative imports (mechanical)
src/index.ts directory import './exporter''./exporter/index.js'

No source logic and no public API changed. Bundler consumers are unaffected.

Verification

Ran against the built dist/ with plain Node (no bundler):

plain node import      : OK (24 exports, exporter 37)
exportToOpenDrive      : 1458 bytes, roads=1, width=2.999400
exportToLanelet2       : 1258 bytes
exportToOpenScenario   : 1841 bytes, entities=1
parse + odrToShapes    : lanes=1 linestrings=2 points=20

That covers the full headless cycle: createLaneWithBoundariescreateSnapshot → each exporter, plus a parseOpenDriveXmlodrToShapes round-trip that recovers the lane with the correct 2.9994 m width.

Existing suite: 323 passed, 4 skipped.

Regression guard

Added __tests__/nodeEsmResolution.test.ts. It spawns a real Node process against dist/ and runs a generate + export + round-trip cycle.

An in-process test cannot catch this class of bug, because Vite rewrites the specifiers before Node ever sees them — which is exactly why the original breakage went unnoticed. The test skips when dist/ is absent, so it does not require a build to be present.

I verified the guard actually detects the problem: removing the extension from a single import in dist/index.js makes it fail with ERR_MODULE_NOT_FOUND.

The package ships "type": "module" and is documented for headless tooling,
but `import '@drawtonomy/sdk'` from plain Node failed:

    Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../dist/types'

Cause: tsconfig used `moduleResolution: "bundler"`, which permits
extensionless relative imports and emits them verbatim. Bundlers resolve
those, but Node's ESM loader requires explicit file extensions. The bundled
test suite stayed green, so only consumers running `node script.mjs` hit it.

- switch tsconfig to module/moduleResolution NodeNext so the compiler
  enforces Node's ESM rules at build time
- add the .js extension to relative imports (15 files)
- point the directory import at ./exporter/index.js

No source logic and no public API changed; bundler consumers are unaffected.

Verified with plain Node against dist/: createLaneWithBoundaries →
createSnapshot → exportToOpenDrive / exportToLanelet2 / exportToOpenScenario,
plus parseOpenDriveXml → odrToShapes round-trip (1 road, 2.9994 m lane width,
1 lane recovered). Existing suite: 323 passed.

Also adds __tests__/nodeEsmResolution.test.ts, which spawns a real Node
process against dist/ so a regression in the emitted specifiers fails the
build. An in-process test cannot catch this, since Vite rewrites the imports.
Confirmed the guard fails (ERR_MODULE_NOT_FOUND) when one extension is removed.
@kosuke55 kosuke55 added the run-tests Trigger CI test execution label Aug 10, 2026
@kosuke55
kosuke55 merged commit a32e10c into main Aug 10, 2026
4 checks passed
@kosuke55
kosuke55 deleted the fix/esm-node-resolution branch August 10, 2026 12:56
kosuke55 added a commit that referenced this pull request Aug 10, 2026
The README documented only the extension (iframe) workflow, while the
factories and exporters also work from plain Node.js since the ESM fix
(#99) and the factory fixes (#100, #101). Add a copy-paste runnable
example that builds a lane + driving path + vehicle and exports
OpenDRIVE / OpenSCENARIO / Lanelet2, notes the 0.17.0 requirement, and
points scenario logic (triggers, speed profiles) to the editor.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-tests Trigger CI test execution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant