Skip to content

ESM Migration Phase 3 — dual ESM + CommonJS build#1531

Merged
minggangw merged 8 commits into
RobotWebTools:developfrom
minggangw:esm-migration-phase3
Jun 10, 2026
Merged

ESM Migration Phase 3 — dual ESM + CommonJS build#1531
minggangw merged 8 commits into
RobotWebTools:developfrom
minggangw:esm-migration-phase3

Conversation

@minggangw

@minggangw minggangw commented Jun 9, 2026

Copy link
Copy Markdown
Member

Build system

  • Add tsup dual build (tsup.config.js, new): authored source stays native ESM but tsup now emits both formats into a flat dist/dist/index.{js,cjs}, dist/web.{js,cjs}, dist/server.{js,cjs}, dist/rosocket.{js,cjs}. CJS-island generators (rosidl_gen/*, rostsd_gen/*) are kept external so their __dirname-relative template lookups stay valid; a CJS footer collapses the lone export default to module.exports so require('rclnodejs') works without .default.
  • package.json: main./dist/index.cjs, added module./dist/index.js; every exports subpath (., ./web, ./web/server, ./rosocket) now has import/require/default conditions pointing at the dual dist/ output. Added build:dist script, prepack hook (builds dist before publish), and tsup devDependency.
  • scripts/npm-pack.sh: pack tarball now goes to ./pack/ instead of ./dist/, so it no longer collides with the tsup-owned, published dist/.

Source

  • web/client.js: replaced top-level await import('ws') with a lazy _ensureWS() resolver and made _WsLink.connect() async, so the CJS build (no top-level await) works; browsers still use globalThis.WebSocket.

Examples & demos (type:module compliance)

  • Renamed all legacy CommonJS examples and in-repo demo scripts from .js to .cjs (example/**, demo/**) and updated their relative requires.
  • Added native ESM .mjs variants for representative cases (publisher, subscriber, services client/service, timer).
  • Updated example/services/README.md and example/topics/README.md to reference the new file names.

Verification

  • npm run build:dist (tsup) produces both ESM + CJS bundles successfully; npm run lint clean; node --check passes on all renamed/new .cjs/.mjs files. (Native addon build still requires a sourced ROS environment — unrelated to these changes.)

Fix: #1358

Copilot AI review requested due to automatic review settings June 9, 2026 08:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR advances the ESM migration by introducing a dual ESM + CommonJS dist/ build (via tsup) and updating package entrypoints/exports so downstream consumers can use either import or require. It also updates the web client implementation and renames/adjusts in-repo examples/demos to be type: module compliant.

Changes:

  • Add tsup configuration to emit dual ESM/CJS bundles into a flat dist/, with special handling for CommonJS “island” generators.
  • Update package.json entrypoints/exports, publishing workflow, and packing script to publish dist/ output and avoid tarball collisions.
  • Convert examples/demos/docs for type: module compliance and adjust web/client.js to avoid top-level await.

Reviewed changes

Copilot reviewed 71 out of 75 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web/client.js Reworks Node WebSocket loading to avoid top-level await and keep CJS build compatible.
tsup.config.js New dual-build configuration emitting flat dist/ ESM+CJS bundles with island externalization.
scripts/npm-pack.sh Moves packed tarball output to ./pack/ to avoid polluting published dist/.
README.md Updates Quick Start command to new example filename (needs build step alignment).
package.json Points main/module/exports to dist/ dual outputs; adds build:dist and prepack; adds tsup.
example/topics/validator/validator-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-service-event-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-serialization-modes-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-raw-message.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-qos-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-observable-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-multiarray-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-message-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-json-utilities-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-example.mjs Adds an ESM variant using top-level await.
example/topics/subscriber/subscription-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/subscriber/subscription-content-filter-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/README.md Updates documentation references to .cjs examples.
example/topics/publisher/publisher-validation-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/publisher/publisher-raw-message.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/publisher/publisher-qos-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/publisher/publisher-multiarray-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/publisher/publisher-message-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/publisher/publisher-example.mjs Adds an ESM variant using top-level await.
example/topics/publisher/publisher-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/topics/publisher/publisher-content-filter-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/timer/timer-example.mjs Adds an ESM variant (contains minor typos in log strings).
example/timer/timer-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/services/service/service-example.mjs Adds an ESM variant using top-level await.
example/services/service/service-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/services/service/getmap-service-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/services/README.md Updates documentation references to .cjs examples.
example/services/client/getmap-client-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/services/client/client-validation-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/services/client/client-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/services/client/async-client-example.mjs Adds an ESM async/await variant.
example/services/client/async-client-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/rosidl/rosidl-parse-srv-example.cjs New .cjs rosidl parser example using rosidl_parser.cjs.
example/rosidl/rosidl-parse-msg-example.cjs New .cjs rosidl parser example using rosidl_parser.cjs.
example/rosidl/rosidl-parse-action-example.cjs New .cjs rosidl parser example using rosidl_parser.cjs.
example/rosidl/README.md Updates rosidl docs to .cjs filenames and rosidl_parser.cjs.
example/rate/README.md Updates documentation references to .cjs examples.
example/rate/rate-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/parameter/README.md Updates documentation references to .cjs examples.
example/parameter/parameter-watcher-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/parameter/parameter-override-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/parameter/parameter-declaration-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/parameter/parameter-client-basic-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/parameter/parameter-client-advanced-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/message-introspector/README.md Updates documentation references to .cjs examples.
example/message-introspector/message-introspector-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/lifecycle/README.md Updates documentation references to .cjs examples.
example/lifecycle/lifecycle-node-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/graph/ros-graph-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/graph/README.md Updates documentation references to .cjs examples.
example/error-handling/README.md Updates documentation references to .cjs examples.
example/error-handling/error-handling-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/actions/README.md Updates documentation references to .cjs examples.
example/actions/action_server/action-server-single-goal-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/actions/action_server/action-server-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/actions/action_server/action-server-defer-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/actions/action_client/action-client-validation-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/actions/action_client/action-client-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
example/actions/action_client/action-client-cancel-example.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
eslint.config.mjs Updates lint globs to cover .mjs examples and treat .cjs examples as CommonJS.
demo/web/typescript/server.ts Updates comments to match renamed JS demo filenames.
demo/web/javascript/static.cjs Updates comments to match renamed runtime/static filenames.
demo/web/javascript/runtime.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
demo/web/javascript/README.md Updates documentation references to .cjs demo scripts.
demo/web/javascript/index.html Updates instructions to reference .cjs demo scripts.
demo/rosocket/server.cjs Renamed to .cjs and adjusted import style (currently requires ESM entry).
demo/rosocket/README.md Updates documentation references to .cjs demo scripts.
.npmignore Stops ignoring dist/ wholesale; ignores pack artifacts and build-only files.
.gitignore Adds pack/ to ignore list.
.github/workflows/npm-publish.yml Builds dist/ explicitly before packing/publishing; updates publish path to ./pack/*.tgz.
Comments suppressed due to low confidence (43)

example/topics/publisher/publisher-example.cjs:17

  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/timer/timer-example.cjs:17
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    demo/web/javascript/runtime.cjs:21
  • This demo script is CommonJS but requires the repo’s ESM entrypoint (index.js). In Node 20, require() of an ES module throws ERR_REQUIRE_ESM. Require the generated CJS bundle from dist/ (after npm run build:dist) or switch this script to dynamic import().
    demo/rosocket/server.cjs:30
  • This demo script is CommonJS but requires the repo’s ESM entrypoint (index.js). In Node 20, require() of an ES module throws ERR_REQUIRE_ESM. Require the generated CJS bundle from dist/ (after npm run build:dist) or switch this script to dynamic import().
    example/topics/validator/validator-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-content-filter-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-service-event-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-serialization-modes-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-raw-message.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-qos-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-observable-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-multiarray-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-message-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/subscriber/subscription-json-utilities-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/publisher/publisher-content-filter-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/publisher/publisher-message-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/publisher/publisher-validation-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/publisher/publisher-raw-message.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/publisher/publisher-qos-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/topics/publisher/publisher-multiarray-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/services/service/service-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/services/service/getmap-service-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. If you want CJS examples to run, load the generated CJS bundle from dist/ (after npm run build:dist) or convert the example to use dynamic import().
    example/services/client/client-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/services/client/client-validation-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/services/client/getmap-client-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/services/client/async-client-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/actions/action_server/action-server-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/actions/action_server/action-server-defer-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/actions/action_server/action-server-single-goal-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/actions/action_client/action-client-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/actions/action_client/action-client-cancel-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/actions/action_client/action-client-validation-example.cjs:17
  • This CommonJS example uses require('../../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/rate/rate-example.cjs:15
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/parameter/parameter-declaration-example.cjs:17
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/parameter/parameter-override-example.cjs:13
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/parameter/parameter-client-basic-example.cjs:17
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/parameter/parameter-client-advanced-example.cjs:17
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/parameter/parameter-watcher-example.cjs:17
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/message-introspector/message-introspector-example.cjs:17
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/lifecycle/lifecycle-node-example.cjs:17
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/graph/ros-graph-example.cjs:17
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().
    example/error-handling/error-handling-example.cjs:17
  • This CommonJS example uses require('../../index.js'), but index.js is an ES module (package.json has "type": "module"). In Node 20 this will throw ERR_REQUIRE_ESM. For a CJS example, require the built CJS bundle from dist/ (after npm run build:dist) or switch to dynamic import().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread example/timer/timer-example.mjs Outdated
const node = rclnodejs.createNode('timer_example_node');

const timer = node.createTimer(BigInt(1000000000), () => {
console.log('One second escaped!');
Comment thread example/timer/timer-example.mjs Outdated
'The next call will be ' + timer.timeUntilNextCall() + 'ms later.'
);

console.log('Shuting down...');
Comment thread README.md
Comment on lines 105 to 109
2. Run a publisher example from this checkout.

```bash
node example/topics/publisher/publisher-example.js
node example/topics/publisher/publisher-example.cjs
```
@coveralls

coveralls commented Jun 9, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 91.22%. remained the same — minggangw:esm-migration-phase3 into RobotWebTools:develop

minggangw added 7 commits June 9, 2026 17:23
Authored source is native ESM; external consumers using
`require('rclnodejs')` need a CommonJS build, so add a tsup dual build.

- tsup.config.js dual-emits ESM + CJS for the four public entries (".",
  "./web", "./web/server", "./rosocket") into a flat dist/. The depth-1
  layout keeps the native loader's `..`-relative paths to prebuilds/ and
  the .node addon (resolved via `bindings`) correct.
- An esbuild plugin externalizes the rosidl_gen/rostsd_gen CommonJS islands
  (they resolve their own template and generated/ paths from disk) and
  rewrites each specifier relative to dist/ (./rosidl_gen -> ../rosidl_gen).
- A per-format footer hoists a lone `export default` to `module.exports`
  so `require('rclnodejs')` returns the value directly (no `.default`).
  Needed because engines.node >= 20.20.2 predates require(ESM). Named-only
  entries (web/server/rosocket) are left untouched.
- package.json: main -> dist/index.cjs, module -> dist/index.js, and each
  entry's exports gains types/import/require/default conditions; ./lib/*
  stays ESM-only. Add build:dist (tsup) and prepack scripts; add tsup dep.
- .npmignore: ship dist/ (un-ignore) and exclude tsup.config.js.
- web/client.js: replace a top-level `await import('ws')` (unsupported in
  the CJS output) with a lazy async resolver awaited inside connect().

Verified: dual build + require()/import smoke tests + functional
init/pub/sub/shutdown via the CJS bundle + 56 web tests + full mocha
suite (1356 passing) + eslint 0 errors.
The dual ESM + CommonJS build made ./dist the published tsup ESM+CJS build output and un-ignored
it for npm. scripts/npm-pack.sh historically dropped its packed .tgz into
./dist, so its extracted `package/` tree and tarball would now ship inside
the published package.

- npm-pack.sh now writes to ./pack instead of ./dist.
- .gitignore: ignore the new pack/ output dir.
- .npmignore: belt-and-suspenders excludes for pack/, dist/package/ and
  dist/*.tgz so stale pack artifacts can never leak into the tarball.

Verified: `npm pack --dry-run` ships only the 16 dist bundles + sourcemaps
and no pack artifacts.
Add native-ESM versions of a few representative examples to complement the
existing CommonJS ones (publisher, subscriber, async service client,
service server, timer). They use `import rclnodejs from '../../../index.js'`
and top-level `await` instead of `require` + `.then()`.

The `.mjs` extension makes them unambiguously ESM regardless of the example
directory's package type and avoids colliding with the existing
`example/**/*.js` CommonJS eslint glob; `example/**/*.mjs` is registered in
the ESM eslint block.

Verified: node --check + eslint 0 errors; timer example runs to completion
and the pub/sub pair round-trips messages under a sourced ROS 2 env.
Rename all 44 example/**/*.js to .cjs so they run under the package's
"type":"module" setting. Unwrap the default export when requiring the
now-ESM index.js (require('../../index.js').default); rosidl examples that
require the CommonJS rosidl_parser.cjs island are renamed only.

Update eslint CJS glob example/**/*.js -> example/**/*.cjs, the main README
run command, and all example README run-command references to .cjs.
The rosocket and web/javascript demos run straight from a clone (no npm
install) and use CommonJS (require/__dirname), which breaks under the
package's "type":"module". Rename them to .cjs and unwrap the now-ESM
default export when requiring index.js:
- demo/rosocket/server.js -> server.cjs (require('../../index.js').default)
- demo/web/javascript/runtime.js -> runtime.cjs (.default unwrap)
- demo/web/javascript/static.js -> static.cjs (builtins/__dirname only)

Update run-command and prose references in the demo READMEs, index.html,
and the web/typescript server.ts comment. The electron and typescript
demos are standalone projects that consume the published dual package via
its exports map (require + import both supported) and need no changes.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 71 out of 75 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (4)

example/topics/publisher/publisher-example.cjs:17

  • index.js is an ES module (package.json has "type": "module" and the file uses export default). Requiring it from a .cjs example will throw ERR_REQUIRE_ESM in Node 20+, so this example won’t run as written. Use import() from CommonJS (or point at the built CJS entry in dist/) instead.
    example/topics/subscriber/subscription-example.cjs:17
  • This .cjs example uses require('../../../index.js'), but index.js is ESM (export default under type: module). In Node this will throw ERR_REQUIRE_ESM, so the example won’t start. Load the module via import() (or require the built dist/index.cjs).
    example/topics/subscriber/subscription-service-event-example.cjs:20
  • index.js is ESM, so require('../../../index.js') from this .cjs script will throw ERR_REQUIRE_ESM. Since this file already has an async main(), the simplest fix is to await import('../../../index.js') inside main() and keep everything else the same.
    demo/rosocket/server.cjs:31
  • This demo script is now .cjs, but it still require()s ESM modules (../../index.js and ../../rosocket/index.js under type: module). That will throw ERR_REQUIRE_ESM at runtime. Use import() to load the ESM entrypoints (or change these requires to target the generated dist/*.cjs outputs).

Comment thread README.md

```bash
node example/topics/publisher/publisher-example.js
node example/topics/publisher/publisher-example.cjs
Comment thread package.json Outdated
@@ -52,6 +64,7 @@
"lint": "eslint && node ./scripts/cpplint.cjs",
"test:asan": "bash scripts/run_asan_test.sh",
"format": "clang-format -i -style=file ./src/*.cpp ./src/*.h && npx --yes prettier --write \"{lib,rosidl_gen,rostsd_gen,rosidl_parser,types,example,test,scripts,benchmark,rostsd_gen}/**/*.{js,cjs,md,ts}\" ./*.{js,md,ts}",
@minggangw minggangw merged commit 774b637 into RobotWebTools:develop Jun 10, 2026
29 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ES Module Migration Plan for rclnodejs

3 participants