Skip to content

[v3] Various JS/CS CJS/ESM updates#2066

Merged
jameshadfield merged 9 commits into
v3from
js-updates
Jun 29, 2026
Merged

[v3] Various JS/CS CJS/ESM updates#2066
jameshadfield merged 9 commits into
v3from
js-updates

Conversation

@jameshadfield

Copy link
Copy Markdown
Member

See commit messages for details. All are changes I've wanted to implement for a while but have let slide for more pressing work, but the upcoming v3 release is a great reason to get them done.

@jameshadfield jameshadfield force-pushed the james/cli-path-improvements branch from 364a170 to ab795ed Compare June 9, 2026 01:02
@jameshadfield jameshadfield changed the title Various JS/CS CJS/ESM updates [v3] Various JS/CS CJS/ESM updates Jun 9, 2026
@jameshadfield jameshadfield mentioned this pull request Jun 9, 2026
1 task
jameshadfield and others added 9 commits June 30, 2026 11:15
Convert all server-side code (cli/, auspice.js, index.js, src/version.js) and
the test file from CommonJS require/module.exports syntax to ES module
import/export syntax. This also addresses the downstream effects on webpack
bundling for the frontend code in src/.

package.json: Added "type": "module" to declare the package as ESM. This makes
Node treat all .js files as ES modules by default. Replaced the "main" field
with "exports" to use the modern package entry point specification. Added
babel-plugin-transform-import-meta as a dev dependency for Jest compatibility.

Server code (cli/, auspice.js, index.js, src/version.js): Converted all
require() calls to import statements and all module.exports/exports assignments
to named or default exports. For packages that use CommonJS internally (argparse,
webpack config), createRequire(import.meta.url) is used to obtain a require
function that can load CJS modules from an ESM context.

__dirname shim: Since __dirname is not available in ES modules, files that
reference it (cli/utils.js, cli/build.js, cli/develop.js, cli/view.js) now
derive it via path.dirname(fileURLToPath(import.meta.url)). The test file also
uses this pattern.

Dynamic require(handlersPath) in cli/view.js: The user-provided custom handlers
path was previously loaded with require(). This is now await import(handlersPath),
which required making customRouteHandlers() and run() async functions.

Config file renames: webpack.config.js and babel.config.js are renamed to .cjs
since they use CommonJS syntax (module.exports) and would otherwise be treated as
ESM due to the package-level "type": "module" setting.

Babel transform for Jest: Jest runs tests through babel-jest which transforms ESM
to CJS. However, babel leaves import.meta.url untouched, causing a runtime error
when Jest executes the transformed code. Added babel-plugin-transform-import-meta
(test env only) to convert import.meta.url to its CJS equivalent during testing.
The test/server-fetch.test.js file itself was also converted to ESM syntax.

webpack.config.cjs updates: Added resolve.fullySpecified: false (both top-level
and as a per-module rule) to allow extensionless imports in the bundled frontend
code -- without this, webpack enforces mandatory file extensions for all imports
in "type": "module" packages. The JS/TS rule also sets type: "javascript/auto"
so webpack parses source files in mixed mode, recognizing both require() and
import syntax.

module.hot to import.meta.webpackHot: In src/root.js and src/store.ts, the
webpack HMR API was accessed via module.hot which is a CJS-only global. Replaced
with import.meta.webpackHot, the ESM equivalent. The HMR accept callback in
store.ts now uses dynamic import() instead of require() to reload reducers.

Frontend require() calls for assets: Several React components used require() to
load images (SVG, PNG, JPG). These are converted to static import statements at
the top of each file, which webpack asset/resource loader handles identically.
Affected files: navBar/content.js, splash/splash.js, framework/fine-print.js,
framework/footer-descriptions.js, info/byline.js.

Dynamic extension loading (src/util/extensions.ts): The extensions.ts file
retains its synchronous require(`@extensions/...`) pattern. This works because
webpack's javascript/auto parser mode (set via the module rule) handles require()
at build time even in a "type": "module" package. An earlier approach using
top-level await with dynamic import() caused async module semantics to cascade
through the dependency graph (via util/globals.js), which broke component
rendering by wrapping module exports in async module namespace objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert 8 cli files from .js to .ts, leveraging Node 24's native type
stripping to run them directly without a build step. tsc is used for
type-checking only (noEmit).

Files converted:
- cli/utils.ts
- cli/server/getDataset.ts
- cli/server/getDatasetHelpers.ts
- cli/server/getAvailable.ts
- cli/server/getNarrative.ts
- cli/server/processPaths.ts
- cli/view.ts
- cli/develop.ts
- cli/build.ts

Supporting changes:
- Add cli/tsconfig.json extending root with nodenext module resolution
  and allowImportingTsExtensions for Node-native .ts imports
- Add allowImportingTsExtensions to root tsconfig.json (needed because
  cli .ts files are pulled in transitively via test imports)
- Exclude cli/**/* from root tsconfig include/add to exclude (prevents
  checking cli files under the wrong moduleResolution)
- Update package.json type-check script to run both tsconfigs
- Update all import paths across 11 files (.js -> .ts extensions)
- Update webpack.config.cjs require for cli/utils.ts
- Add eslint-disable for explicit-function-return-type and
  consistent-type-assertions in converted files (incremental migration)
- Apply chalk 2.x workaround (type assertion) for nodenext compat
- Fix TypeScript strict errors: unknown in catch blocks, Object.entries
  casting for Set methods, undefined-in-object check

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The docs and code had fallen out of sync over the years.

* `parseNarrativeFile` was not only deprecated as the docs indicated
  ("This function is deprecated as of vXXX"), it no longer existed in the codebase.
* The docs for the getNarrative route incorrectly indicated the `type` query as optional
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Triggered by existing warnings:

WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.
Errors were new since version bump in parent commit
This is an old and now unused test that's been fully superseded by the Playwright smoke tests. It was the only test that used "chai" (and "mocha", although that wasn't listed as a dep).

I only noticed this as the eslint version bump flagged up an error in the test file
(This was from an ancient method of deploying auspice)
@jameshadfield jameshadfield force-pushed the james/cli-path-improvements branch from ab795ed to fbc6283 Compare June 29, 2026 23:39
Base automatically changed from james/cli-path-improvements to v3 June 29, 2026 23:45
@jameshadfield jameshadfield merged commit c563a1f into v3 Jun 29, 2026
26 of 27 checks passed
@jameshadfield jameshadfield deleted the js-updates branch June 29, 2026 23:46
jameshadfield added a commit that referenced this pull request Jul 10, 2026
[v3] Various JS/CS CJS/ESM updates
jameshadfield added a commit that referenced this pull request Jul 13, 2026
[v3] Various JS/CS CJS/ESM updates
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.

1 participant