Skip to content

refactor: make the loader an async function (speculative) - #1724

Closed
johnnyreilly wants to merge 1 commit into
nextfrom
async-function-loader
Closed

johnnyreilly wants to merge 1 commit into
nextfrom
async-function-loader

Conversation

@johnnyreilly

Copy link
Copy Markdown
Member

Speculative: converts ts-loader to the async function loader style, as far as webpack allows.

The catch: this.async() can't go away entirely

When a loader returns a promise, webpack's loader-runner does:

result.then(function(r) { callback(null, r); }, callback);

Only content is forwarded, so a loader that just returns its output loses its source map. ts-loader always produces one, so a "pure" async loader isn't an option without dropping source maps. You also can't mix the two: if an await happens before calling this.callback, loader-runner has already taken the returned promise as the result.

So loader is now an async function that calls this.async() once at the top and hands the result to it once at the end:

async function loader(this, contents, inputSourceMap) {
  this.cacheable && this.cacheable();
  const callback = this.async();
  let result;
  try {
    result = await compile(this, contents, inputSourceMap);
  } catch (error) {
    callback(error instanceof Error ? error : new Error(String(error)));
    return;
  }
  callback(null, result.output, result.sourceMap);
}

What changed

  • successLoader + makeSourceMapAndFinish (which passed callback around) are replaced by one async compile() that returns { output, sourceMap } or throws.
  • mapToInputSourceMap uses async/await with try/finally for the destroy() calls, instead of a hand-built new Promise.
  • Output and behaviour are unchanged. The only snapshot changes are stack-frame names in three error tests (nodeModulesMeaningfulErrorWhenImportingTs, tsconfigInvalid, validateLoaderOptionNames): successLoader / makeSourceMapAndFinish → compile.
    • Note: the tsconfig-parse error used to be created in loader's own frame on purpose, to match classic ts-loader's single at Object.loader frame. It now shows at compile then at Object.loader. Easy to restore if we care.

Testing

npm run lint, npm run comparison-tests, npm run execution-tests all pass locally (macOS).

🤖 Generated with Claude Code

Replace the callback-threading (`successLoader` / `makeSourceMapAndFinish`)
with a single `async compile()` that returns `{ output, sourceMap }` or
throws, and convert `mapToInputSourceMap` to async/await.

`this.async()` is still called once: webpack only forwards `content` from
a loader's returned promise, so returning would drop the source map.

Comparison snapshots updated for the renamed stack frames only.

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

Copy link
Copy Markdown

Benchmark (Ubuntu)

Scenario transpileOnly PR branch median (ms) base branch median (ms) Δ vs base branch
Cold build false 1267.9 1256.5 +0.9%
Incremental rebuild (leaf touch) false 58.0 58.3 -0.4%
Incremental rebuild (hub touch) false 476.9 485.6 -1.8%
Cold build true 385.8 385.1 +0.2%
Incremental rebuild (leaf touch) true 34.4 34.5 -0.2%
Incremental rebuild (hub touch) true 33.4 34.3 -2.5%
Cold build (all orphan files) false 1289.7 1288.7 +0.1%
Incremental rebuild (hub touch, all orphan files) false 498.5 507.4 -1.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

Copy link
Copy Markdown

Benchmark (macOS)

Scenario transpileOnly PR branch median (ms) base branch median (ms) Δ vs base branch
Cold build false 786.2 772.4 +1.8%
Incremental rebuild (leaf touch) false 50.3 50.3 -0.1%
Incremental rebuild (hub touch) false 294.9 313.1 -5.8%
Cold build true 252.3 251.7 +0.3%
Incremental rebuild (leaf touch) true 47.9 48.5 -1.2%
Incremental rebuild (hub touch) true 50.7 49.5 +2.4%
Cold build (all orphan files) false 756.8 764.2 -1.0%
Incremental rebuild (hub touch, all orphan files) false 300.4 296.3 +1.4%

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.

@github-actions

Copy link
Copy Markdown

Benchmark (Windows)

Scenario transpileOnly PR branch median (ms) base branch median (ms) Δ vs base branch
Cold build false 1640.9 1633.3 +0.5%
Incremental rebuild (leaf touch) false 55.8 56.8 -1.8%
Incremental rebuild (hub touch) false 677.7 691.2 -1.9%
Cold build true 445.9 438.7 +1.6%
Incremental rebuild (leaf touch) true 31.9 31.5 +1.4%
Incremental rebuild (hub touch) true 32.6 32.1 +1.5%
Cold build (all orphan files) false 1787.0 1798.6 -0.6%
Incremental rebuild (hub touch, all orphan files) false 745.6 756.6 -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.

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