Skip to content

chore: trim unused devDependencies and tooling; upgrade mocha to 12 - #1716

Merged
johnnyreilly merged 27 commits into
nextfrom
dependency-better
Nov 8, 2026
Merged

johnnyreilly merged 27 commits into
nextfrom
dependency-better

Conversation

@johnnyreilly

@johnnyreilly johnnyreilly commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

First small step in trimming devDependencies.

Removed

  • babel: old v6 placeholder package (it only tells you to install babel-cli). Nothing at the root requires it, and the babel execution tests that use it list it in their own package.json
  • lint-staged: it was configured in package.json, but nothing installs a git hook to run it (no husky or similar), so it never ran. CI already runs npm run format:check and npm run lint
  • semver: only used by test/execution-tests/run-tests.js to skip tests whose folder name starts with a minimum TypeScript/webpack version (e.g. 2.1.4_, 4.7.0_5.0.0_). It's replaced by a small isLowerVersion helper that compares major.minor.patch. One deliberate difference: prerelease tags are ignored, so 7.1.0-beta counts as equal to 7.1.0 (semver calls it lower). No test prefix is anywhere near 7.x, so this changes nothing today
  • glob: only used by test/comparison-tests/create-and-execute-test.js to list every file in a directory. It's replaced by a documented listFiles(dir, { includeDotFiles }) helper that walks directories with fs.readdirSync(dir, { withFileTypes: true }). It returns /-separated paths (also on Windows), keeps glob's sort order, returns an empty list for a missing directory like glob did, and keeps the one call that skipped dotfiles (cleanHashFromOutput) skipping them (without walking into dot-folders). Deliberate differences: symlinked folders aren't followed (Node's recursive readdirSync follows them, so a link back to a parent listed the same files over and over), and a broken symlink is skipped; any other error, such as permissions, is thrown so --save-output can't delete a snapshot it couldn't read. glob is still installed indirectly via karma-webpack; we just no longer depend on it directly
  • markdown-toc: only used by the generate-toc script, which is also removed. The README table of contents was already maintained by hand: Prettier has rewritten its bullets, so re-running the generator would fight Prettier. Also fixed the one stale TOC entry ("Declarations (.d.ts)" -> "Declaration Files (.d.ts)", matching its heading). This removes 573 lines from package-lock.json
  • webpack-cli: only used by test/execution-tests/run-tests.js to run webpack --bail for the one execution test without a karma config (5.6.2_5.0.0_optionsCaching). A new test/execution-tests/run-webpack.js script does the same through webpack's Node API: like webpack-cli, it accepts a config that exports an object, a function or a Promise, or an array of them; config functions get webpack-cli's env and argv (including bail: true), and one that throws is reported through the script's own error handling; it builds once with bail: true, prints the stats using each config's own stats setting (colors only when stdout is a terminal, unless the config says otherwise), and exits with 1 on errors. run-tests.js runs it with execFileSync, so there's no shell quoting. --debug still works via node --inspect-brk. test/comparison-tests/README.md now suggests npx webpack-cli for building a test by hand. This removes 39 packages from node_modules

Also removed their entries from knip.json (ignoreDependencies), the lint-staged config block and the generate-toc script from package.json.

Upgraded

  • mocha 6 → 12. This removes about 1,080 lines from package-lock.json. Only the comparison test runner uses mocha, and it needed three changes:
    • test/comparison-tests/run-tests.js passes --color instead of -c. Mocha 6 restarted itself with --color in process.argv, which is how picocolors decides to enable colors; mocha 12 passes -c through unchanged, so the colored output snapshots stopped matching
    • It also passes --test-to-run=<name> instead of --test-to-run <name>. Mocha 12 parses arguments with util.parseArgs and treated the separate test name as a spec file, printing a warning for every test
    • test/comparison-tests/create-and-execute-test.js now reads its arguments with util.parseArgs (non-strict, since mocha's own arguments are mixed in). It accepts both forms of --test-to-run, and fails with a clear error if the test name is missing, empty, or looks like a flag (meaning parseArgs took the next flag as the value) (before, it silently used the node binary's path)
  • .claude/settings.json: the allowed mocha commands now point at node_modules/mocha/bin/mocha.js, as mocha 12 removed bin/_mocha, and pass the test name as --test-to-run=*

Other cleanup

  • Removed the root Dockerfile and the docker:build / postdocker:build scripts. The image was built on node:18 (ts-loader needs Node 22+, mocha 12 needs 20.19+) with outdated Chrome install steps (apt-key, libgconf-2-4), and nothing else used it. The test-probe workflow and CI already cover running the tests on Linux
  • Also removed .dockerignore (only used by that Dockerfile) and the Dockerfile / .dockerignore entries in .npmignore. The published package is unchanged (npm pack --dry-run still lists 44 files)
  • Removed .devcontainer/ (Dockerfile and devcontainer.json). It was based on an old Node 14 dev container template, well below the Node 22+ that ts-loader needs. Its .npmignore entry is gone too
  • Removed the unused --extra-option argument from test/comparison-tests/create-and-execute-test.js. Nothing passed it: run-tests.js only sends --test-to-run and --save-output
  • var → const/let in test/execution-tests/run-tests.js and run-webpack.js, and dropped a commented-out Karma require
  • Callbacks in test/comparison-tests/create-and-execute-test.js are now arrow functions. Named functions stay as declarations, and the test function createTest returns stays a function because it uses mocha's this.timeout
  • New npm run check:comparison-tests and npm run check:execution-tests scripts type-check the test harness scripts with tsc. The comparison tests' tsconfig.json now also covers link-test-lib.js, and the execution tests get their own tsconfig.json (run-tests.js, run-webpack.js). To pass, run-webpack.js types the webpack callback's stats as Stats or MultiStats and treats missing stats as a failed build. The lint workflow (.github/workflows/lint.yml) now runs both checks after npm run lint, so type errors in the harness scripts fail CI
  • The two harness tsconfigs had identical compiler options, so they now extend a shared test/tsconfig.base.json (with lib: es2022, matching src). The execution tests check now also covers test/run-tests.js and test/karmaConfig.js, which nothing type-checked before
  • Removed the @ts-ignore comments from the comparison test harness. A new test/comparison-tests/globals.d.ts declares the mocha globals it uses (describe, it, and the test's this.timeout and done), since mocha 12 ships no types; this avoids adding @types/mocha. create-and-execute-test.js now checks for webpack() returning null (as its types allow) and fails the test with a clear error, and newline.loader.js types its this as webpack's LoaderContext
  • AGENTS.md lists the three check: scripts, and no longer says lint has no separate type-check script
  • New npm run check:benchmark-tests, also run by the lint workflow, as nothing type-checked the benchmark .mts files. Dropped esModuleInterop from their tsconfig, as TypeScript 7 always enables it
  • src/tsconfig.json now uses "module": "node20" (which implies moduleResolution: node16) instead of module: commonjs + moduleResolution: bundler, so imports are checked with Node's resolution rules, which is where ts-loader runs. Also dropped declarationDir, which matched outDir. The build output is byte-identical: building the old and new configs side by side gives the same 36 files, including .d.ts and source maps

Kept

The first version of this PR also removed babel-polyfill and babel-preset-es2016, which broke CI. The babel execution tests do run (the isNotBabelTest filter is commented out), and some of them load these packages from the root install:

  • 2.1.4_babel-es2016 and 2.1.4_babel-react use the es2016 preset
  • 2.4.1_importCodeSplitting imports babel-polyfill

Both are restored with their original version ranges. babel-core, babel-loader, babel-preset-es2015 and babel-preset-react stay too, because the comparison and execution tests use them.

Testing

  • npm run knip, npm run format:check, npm run check:benchmark-tests, npm run check:comparison-tests and npm run check:execution-tests pass
  • npm run execution-tests: all 27 suites pass locally (webpack 5), including after the var → const change
  • npm run comparison-tests: all 57 suites pass locally (with mocha 12)
  • --save-output on basic runs cleanly and leaves no changed snapshots
  • With mocha 12: the full comparison suite, --save-output, the old --test-to-run <name> form (used by the allowed Claude commands) and a missing test name (clear error) all behave as expected
  • run-webpack.js: 5.6.2_5.0.0_optionsCaching passes. For each config shape (object, array, function, Promise), a working build writes its bundles and exits 0, and a broken build exits 1. The config's stats setting is respected: 'errors-only' prints nothing on success and still shows errors, an array uses each config's own setting, and colors are off when output isn't a terminal unless the config forces them. That test is skipped on webpack 4, so the webpack 4 CI jobs don't run the script
  • run-webpack.js config functions: an array of functions (including async) builds every entry, a function that throws exits 1 through the script's error handling, and argv.bail is true
  • An empty --test-to-run= and --test-to-run --save-output both fail with the clear error message
  • listFiles returns exactly what glob.sync did (same files, same order) for all 729 folders under test/comparison-tests, with and without dotfiles, plus the missing-folder case. A folder with a loop -> . symlink gives each file once; a broken symlink is skipped; a symlink into an unreadable folder throws EACCES. The Windows path handling is only covered by CI's Windows comparison-test job
  • isLowerVersion gives the same answers as semver.lt for every test prefix against the TypeScript and webpack versions CI uses, apart from the prerelease case above
  • Simulated webpack 4: exactly the two _5.0.0_ tests are skipped (4.7.0_5.0.0_node16-file-suffix, 5.6.2_5.0.0_optionsCaching). On webpack 5, none are skipped

🤖 Generated with Claude Code

johnnyreilly and others added 2 commits November 7, 2026 08:07
Remove `babel`, `babel-polyfill` and `babel-preset-es2016`, plus their
knip ignore entries. Nothing at the root uses them: the babel execution
tests that reference them are skipped and install their own deps.

`babel-preset-react` stays because the babel-es6resolveParent comparison
test uses the `react` preset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lint-staged was configured in package.json, but nothing installs a git
hook to run it (no husky or similar), so it never ran. Formatting and
linting are already checked in CI by the format and lint workflows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Benchmark (Ubuntu)

Scenario transpileOnly PR branch median (ms) base branch median (ms) Δ vs base branch
Cold build false 921.7 914.3 +0.8%
Incremental rebuild (leaf touch) false 46.1 47.1 -2.0%
Incremental rebuild (hub touch) false 357.7 355.7 +0.6%
Cold build true 306.9 302.4 +1.5%
Incremental rebuild (leaf touch) true 28.3 27.6 +2.4%
Incremental rebuild (hub touch) true 28.4 28.8 -1.4%
Cold build (all orphan files) false 979.2 968.3 +1.1%
Incremental rebuild (hub touch, all orphan files) false 393.5 382.6 +2.8%

PR branch = /home/runner/work/ts-loader/ts-loader, base branch = /home/runner/work/ts-loader/ts-loader-main. 2 warmup + 10 measured iterations per scenario, median reported. Report-only - no threshold fails this check.

@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Benchmark (macOS)

Scenario transpileOnly PR branch median (ms) base branch median (ms) Δ vs base branch
Cold build false 822.9 821.2 +0.2%
Incremental rebuild (leaf touch) false 50.3 50.3 -0.1%
Incremental rebuild (hub touch) false 331.8 331.1 +0.2%
Cold build true 222.8 225.3 -1.1%
Incremental rebuild (leaf touch) true 50.1 49.3 +1.7%
Incremental rebuild (hub touch) true 49.8 49.8 +0.1%
Cold build (all orphan files) false 944.9 920.7 +2.6%
Incremental rebuild (hub touch, all orphan files) false 387.4 406.7 -4.7%

PR branch = /Users/runner/work/ts-loader/ts-loader, base branch = /Users/runner/work/ts-loader/ts-loader-main. 2 warmup + 10 measured iterations per scenario, median reported. Report-only - no threshold fails this check.

The babel execution tests are not skipped (the isNotBabelTest filter is
commented out), and they resolve these from the root install:
2.1.4_babel-es2016 and 2.1.4_babel-react use the es2016 preset, and
2.4.1_importCodeSplitting imports babel-polyfill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Benchmark (Windows)

Scenario transpileOnly PR branch median (ms) base branch median (ms) Δ vs base branch
Cold build false 1822.5 1822.5 +0.0%
Incremental rebuild (leaf touch) false 64.3 64.1 +0.2%
Incremental rebuild (hub touch) false 751.8 761.8 -1.3%
Cold build true 490.2 494.2 -0.8%
Incremental rebuild (leaf touch) true 37.6 36.6 +2.9%
Incremental rebuild (hub touch) true 44.0 38.6 +13.9%
Cold build (all orphan files) false 1995.4 1980.5 +0.8%
Incremental rebuild (hub touch, all orphan files) false 870.3 882.9 -1.4%

PR branch = C:\source\ts-loader, base branch = C:\source\ts-loader-main. 2 warmup + 10 measured iterations per scenario, median reported. Report-only - no threshold fails this check.

@johnnyreilly johnnyreilly changed the title chore: remove unused babel devDependencies chore: remove babel and lint-staged devDependencies Nov 7, 2026
semver was only used by the execution test runner to skip tests whose
folder name starts with a minimum TypeScript/webpack version. A
major.minor.patch comparison covers that, including skipping the
`_5.0.0_` tests on webpack 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnnyreilly johnnyreilly changed the title chore: remove babel and lint-staged devDependencies chore: remove babel, lint-staged and semver devDependencies Nov 7, 2026
johnnyreilly and others added 2 commits November 7, 2026 08:45
The comparison test harness only used glob.sync to list every file under
a directory. A small `listFiles` helper built on
fs.readdirSync({ recursive: true }) does the same: same results and
order, `/`-separated paths on Windows, dotfiles optional, and an empty
list for a missing directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It was only used by the generate-toc script, and the README's TOC is
already maintained by hand: Prettier has rewritten its bullets, so
re-running the generator would fight Prettier. Also fix the one stale
TOC label ("Declarations" -> "Declaration Files").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnnyreilly johnnyreilly changed the title chore: remove babel, lint-staged and semver devDependencies chore: remove babel, lint-staged, semver and glob devDependencies Nov 7, 2026
@johnnyreilly johnnyreilly changed the title chore: remove babel, lint-staged, semver and glob devDependencies chore: remove babel, lint-staged, semver, glob and markdown-toc devDependencies Nov 7, 2026
Two changes keep the comparison test runner working with mocha 12:

- Pass `--color` instead of `-c`. Mocha 6 restarted itself with the long
  form in process.argv, which picocolors uses to turn colors on; mocha 12
  passes `-c` through as-is, so the colored output snapshots no longer
  matched.
- Pass `--test-to-run=<name>`. Mocha 12 parses arguments with
  util.parseArgs and treats a separate value as a spec file. The test
  file now reads its arguments with util.parseArgs too (non-strict, as
  mocha's own arguments are mixed in), accepts both forms, and fails
  clearly when the test name is missing.

Also point the allowed mocha commands in .claude/settings.json at
bin/mocha.js, as mocha 12 removed bin/_mocha.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnnyreilly johnnyreilly changed the title chore: remove babel, lint-staged, semver, glob and markdown-toc devDependencies chore: remove babel, lint-staged, semver, glob and markdown-toc; upgrade mocha to 12 Nov 7, 2026
It was only used by the execution test runner to run `webpack --bail`
for the one test without a karma config (5.6.2_5.0.0_optionsCaching).
A small run-webpack.js script now does the same through webpack's Node
API: it builds once with `bail: true`, prints the stats, and exits with
1 on errors. `--debug` still works via `node --inspect-brk`.

The comparison tests README now suggests `npx webpack-cli` for building
a test by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnnyreilly johnnyreilly changed the title chore: remove babel, lint-staged, semver, glob and markdown-toc; upgrade mocha to 12 chore: remove babel, lint-staged, semver, glob, markdown-toc and webpack-cli; upgrade mocha to 12 Nov 7, 2026
The Dockerfile was built on node:18 (ts-loader needs Node 22+) with
outdated Chrome install steps, and nothing else used it. The test-probe
workflow and CI already cover running the tests on Linux.

Also remove .dockerignore (only used by this Dockerfile; the
devcontainer builds from its own folder) and their .npmignore entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnnyreilly johnnyreilly changed the title chore: remove babel, lint-staged, semver, glob, markdown-toc and webpack-cli; upgrade mocha to 12 chore: trim unused devDependencies and tooling; upgrade mocha to 12 Nov 7, 2026
johnnyreilly and others added 11 commits November 7, 2026 11:52
- run-webpack.js accepts the same config shapes as webpack-cli: an
  object, an array, a function (called with webpack-cli's env) or a
  Promise.
- run-tests.js runs it with execFileSync, so no shell quoting or doubled
  backslashes on Windows.
- listFiles skips a broken symlink, or a file deleted mid-listing,
  instead of crashing the run, and uses the repo's pathExists helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The folder was removed, so the entry no longer does anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like webpack-cli, print stats using each config's own `stats` setting,
with colors only when stdout is a terminal unless the config says
otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing passed it: run-tests.js only sends --test-to-run and
--save-output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mocha 12 treats a separate value as a spec file, so match the form the
comparison test runner now uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Named functions stay as declarations, and the test function createTest
returns stays a `function` because it uses mocha's `this.timeout`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`npm run check:comparison-tests` and `npm run check:execution-tests`
type-check the harness scripts with tsc. The comparison tests' tsconfig
now also covers link-test-lib.js, and the execution tests get a tsconfig
covering run-tests.js and run-webpack.js.

To pass, run-webpack.js types the webpack callback's stats as Stats or
MultiStats, treats missing stats as a failed build, and uses const; and
an unused catch binding in the comparison run-tests.js is renamed _err.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run `npm run check:comparison-tests` and `npm run check:execution-tests`
after `npm run lint`, so type errors in the harness scripts fail CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ts-loader runs in Node, so check imports with Node's resolution rules
rather than a bundler's. `module: node20` implies `moduleResolution:
node16`, and emits the same CommonJS: the build output (.js, .d.ts and
maps) is byte-identical. Also drop `declarationDir`, which matched
`outDir`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
johnnyreilly and others added 4 commits November 7, 2026 13:31
The comparison and execution test tsconfigs had identical compiler
options, so move them to test/tsconfig.base.json (with lib es2022, to
match src). The execution tests check now also covers test/run-tests.js
and test/karmaConfig.js, which nothing type-checked before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add `npm run check:benchmark-tests` and run it in the lint workflow, as
nothing type-checked the benchmark .mts files. Also drop
`esModuleInterop` from their tsconfig, as TypeScript 7 always enables it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Declare the mocha globals the harness uses (describe, it, and the
  test's `this.timeout` and `done`) in globals.d.ts, as mocha 12 ships
  no types and this avoids a dependency on @types/mocha.
- Check for webpack() returning null (per its types) and fail the test
  with a clear error.
- Type newline.loader.js's `this` as webpack's LoaderContext.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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.

🟡 Changes recommended

The test-name guard permits empty values that clear the entire staging root, alongside unresolved symlink traversal, erroneous snapshot deletion, and missing config-factory bail semantics.

4 open issues
What changed in this PR

This PR reduces ts-loader’s development-tooling footprint, updates its test harnesses for Mocha 12, and expands type checking in CI.

Changes:

  • Trim root dependencies, unused scripts, and obsolete Docker/dev-container setups.
  • Upgrade Mocha and replace glob, semver, and webpack-cli usage with local helpers.
  • Add harness type checks, share compiler options, and use Node-aware resolution for source imports.
File Description
test/​tsconfig.base.json Shares JavaScript harness-check options.
test/​execution-tests/​tsconfig.json Adds execution and shared harness type checking.
test/​execution-tests/​run-webpack.js Adds a one-shot webpack Node API runner.
test/​execution-tests/​run-tests.js Replaces semver and invokes the new webpack runner.
test/​comparison-tests/​tsconfig.json Shares options and expands checked files.
test/​comparison-tests/​run-tests.js Updates Mocha color and test-selection arguments.
test/​comparison-tests/​README.md Documents manual builds using npx webpack-cli.
test/​comparison-tests/​newline.loader.js Types the webpack loader context.
test/​comparison-tests/​globals.d.ts Declares the Mocha globals used by the harness.
test/​comparison-tests/​create-and-execute-test.js Replaces glob and adds argument parsing and compiler checks.
test/​benchmark-tests/​tsconfig.json Removes the redundant interop option.
src/​tsconfig.json Uses Node-aware module resolution.
README.md Corrects the declaration-files TOC label.
package.json Trims dependencies/scripts and adds harness checks.
package-lock.json Prunes packages and refreshes tool resolutions.
knip.json Removes exclusions for deleted dependencies.
Dockerfile Deletes the obsolete root test image.
.npmignore Removes exclusions for deleted container files.
.github/​workflows/​lint.yml Runs all three harness type checks in CI.
.dockerignore Deletes the unused Docker exclusions.
.devcontainer/​Dockerfile Removes the outdated development image.
.devcontainer/​devcontainer.json Removes the obsolete dev-container configuration.
.claude/​settings.json Updates allowed commands for Mocha’s new CLI.

Review effort: Max

Comment thread test/execution-tests/run-webpack.js Resolved
johnnyreilly and others added 3 commits November 7, 2026 14:12
- Reject an empty --test-to-run, which made the whole comparison-tests
  folder "the test" (and cleared the .test staging folder), and a value
  starting with `-`, which means parseArgs took the next flag as the name.
- listFiles walks directories itself instead of using readdirSync's
  recursive mode, which follows symlinked folders: a link back to a parent
  listed the same files over and over. Symlinked folders aren't followed;
  symlinked files still count. Dot-folders are skipped without being
  walked, and only symlinks need a stat call.
- Only a missing target (ENOENT) or a link loop (ELOOP) makes a symlink
  "not a file"; other errors are thrown, so --save-output can't delete
  the snapshot of a file it couldn't read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pass `argv.bail: true` to a config function, as `webpack --bail` did.
- Call function (or Promise) entries in an exported array.
- Start the Promise chain first, so a config function that throws goes
  through the script's own error handling.
- Use arrow functions for callbacks, matching create-and-execute-test.js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md said lint had no separate type-check script, which is no
longer true.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@johnnyreilly
johnnyreilly merged commit 3ad4aa6 into next Nov 8, 2026
45 checks passed
@johnnyreilly
johnnyreilly deleted the dependency-better branch November 8, 2026 13:52
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.

2 participants