Skip to content

feat: migrate to RSPack - #2131

Merged
jaylinski merged 6 commits into
handlebars-lang:masterfrom
kibertoad:feat/rspack
Mar 14, 2026
Merged

feat: migrate to RSPack#2131
jaylinski merged 6 commits into
handlebars-lang:masterfrom
kibertoad:feat/rspack

Conversation

@kibertoad

@kibertoad kibertoad commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replaced webpack v1 + grunt-babel + grunt-uglify with rspack (builtin:swc-loader, SwcJsMinimizerRspackPlugin,
    BannerPlugin) and @swc/cli for CJS compilation
  • Removed all grunt dependencies from the build pipeline; Gruntfile retained only for legacy metrics/version tasks
  • Rewrote publish-to-aws task as standalone Node.js script (no grunt dependency)
  • Added 29 rspack build validation tests covering bundle output, UMD format, license banners, minification, functional
    correctness, and browser compatibility targeting
  • Re-enabled webpack/rollup integration tests in CI and upgraded to modern versions (webpack 5.99 /
    webpack-cli 6 / rollup 4.40)
  • Updated CONTRIBUTING.md and PR template with current tooling and branch info

Breaking changes

  • import * as Handlebars from 'handlebars' no longer works. Instead, import Handlebars from 'handlebars' should be used (default import).
    This was always the semantically correct form, as the package only has a default export. The previous behavior was an
    accidental side-effect of Babel's loose module compilation, which assigned module.exports = exports.default.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Migrates the build pipeline from legacy Grunt + webpack v1 tooling to rspack (bundling) and SWC (CJS compilation), updates integration tooling to modern webpack/rollup versions, and adjusts docs/CI accordingly.

Changes:

  • Replace Grunt-based build steps with swc (CJS) + rspack (UMD bundles) and add a new rspack validation test project in Vitest.
  • Rework publish-to-aws into a standalone Node script and update CI/scripts to use the new build.
  • Update integration tests/dependencies and documentation to reflect current branch/tooling expectations.

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
vitest.config.js Adds a new rspack Vitest project and relaxes statement coverage threshold slightly for SWC helpers.
tests/rspack/rspack.test.js Adds build-output validation tests for rspack bundles, minification, banners, and basic runtime behavior.
tests/integration/webpack-test/src/handlebars-wildcard-import-test.js Removes wildcard import integration test (no longer supported).
tests/integration/webpack-test/src/handlebars-wildcard-import-pre-4.2-test.js Removes pre-4.2 wildcard import integration test.
tests/integration/webpack-test/src/handlebars-runtime-test.js Switches runtime import to default import style.
tests/integration/webpack-test/src/handlebars-require-vs-import-test.js Updates import usage to default import to match new interop behavior.
tests/integration/webpack-test/src/handlebars-default-import-pre-4.2-test.js Removes pre-4.2 default import test.
tests/integration/webpack-test/package.json Upgrades webpack + webpack-cli versions in integration fixture.
tests/integration/webpack-babel-test/src/handlebars-inline-precompile-test.js Switches runtime import to default import style.
tests/integration/webpack-babel-test/package.json Upgrades Babel/webpack-related dependencies in integration fixture.
tests/integration/rollup-test/package.json Upgrades rollup and node-resolve plugin in integration fixture.
tasks/publish-to-aws.js Converts AWS publish task from a Grunt task to a standalone Node script.
rspack.config.js Introduces rspack configuration to produce UMD bundles (minified and unminified) with license banners.
package.json Updates scripts/devDependencies for new build/test pipeline; removes Grunt build invocation from main scripts.
lib/precompiler.js Adjusts Handlebars import to default import.
lib/handlebars.runtime.js Copies runtime namespace into a plain object to allow VM monkey-patching.
Gruntfile.js Removes build pipeline tasks, leaving Gruntfile as “legacy” loader for remaining tasks.
CONTRIBUTING.md Updates contributor docs to new build/test/lint commands and branch targeting guidance.
.swcrc Adds SWC config for CommonJS compilation and inline sourcemaps.
.github/workflows/ci.yml Re-enables integration tests on Ubuntu and switches browser job build step to npm run build.
.github/PULL_REQUEST_TEMPLATE.md Updates PR template branch targeting to master.

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

Comment thread tasks/publish-to-aws.js Outdated
Comment thread Gruntfile.js
Comment thread .github/workflows/ci.yml Outdated
Comment thread rspack.config.js Outdated
Comment on lines +4 to +5
import { execSync } from 'child_process';

Copilot AI Mar 10, 2026

Copy link

Choose a reason for hiding this comment

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

This test file mixes ESM import syntax with CommonJS globals like __dirname and require(...). Under Vitest/Vite the presence of import typically makes the file run as an ES module, where __dirname and require are undefined, causing the suite to fail to even load. Consider converting this file to the same CommonJS style used by the other test suites (no import of vitest, use require), or compute __dirname via import.meta.url and use createRequire for CommonJS imports.

Suggested change
import { execSync } from 'child_process';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is fine as-is. Vitest runs test files through its own transform pipeline (powered by Vite/esbuild), which injects CJS compat shims (__dirname, require) regardless of whether the file uses import syntax. The globals: true setting also auto-injects describe/it/expect without needing explicit imports.

The mix of import and __dirname/require() is standard practice in Vitest projects - it's not actually running as a raw Node ESM module. No change needed here.

Comment thread tests/rspack/rspack.test.js Outdated
@kibertoad

Copy link
Copy Markdown
Contributor Author

@jaylinski ready for rereview!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 22 out of 23 changed files in this pull request and generated 2 comments.


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

You can also share your feedback on Copilot code review. Take the survey.

Comment thread tests/rspack/rspack.test.js
@kibertoad

Copy link
Copy Markdown
Contributor Author

@jaylinski second batch addressed

@kibertoad

Copy link
Copy Markdown
Contributor Author

@jaylinski ping?..

@jaylinski

Copy link
Copy Markdown
Member

Took a look at the output, there are some differences in file-size:

user@vm-ubuntu-dev:~/Code/handlebars.js$ du -hs dist.new/*
424K    dist.new/cjs
472K    dist.new/handlebars.js
96K     dist.new/handlebars.min.js
312K    dist.new/handlebars.runtime.js
48K     dist.new/handlebars.runtime.min.js
user@vm-ubuntu-dev:~/Code/handlebars.js$ du -hs dist/cjs/*
356K    dist/cjs/handlebars
8,0K    dist/cjs/handlebars.js
8,0K    dist/cjs/handlebars.runtime.js
32K     dist/cjs/precompiler.js

Analyzed it with GPT-5.3-Codex, which says this is expected because of tooling changes:

High-level result

  • Same artifact set: 29 files in each tree, no added/removed files.
  • Content changed in 27/29 files.
  • Byte-identical files: handlebars.min.js == handlebars.min.js, and handlebars.runtime.min.js == handlebars.runtime.min.js.

Size deltas (key outputs)

  • handlebars.js: 576,896 -> 482,009 (-94,887)
  • handlebars.runtime.js: 387,907 -> 316,480 (-71,427)
  • cjs/handlebars.js: 4,604 -> 4,259 (-345)
  • cjs/handlebars.runtime.js: 4,519 -> 6,819 (+2,300)
  • cjs/precompiler.js: 31,809 -> 31,926 (+117)
  • Minified browser bundles unchanged in size and hash.

What changed in output shape

  1. Browser UMD wrapper/runtime codegen changed
  • Old unminified bundles start with classic webpack bootstrap in dist.old/handlebars.js:1 and dist.old/handlebars.runtime.js:1.
  • New unminified bundles include a license banner and rspack-style module runtime shape, visible from dist.new/handlebars.js:1 and dist.new/handlebars.runtime.js:1.
  • This looks like toolchain codegen differences, not API differences for UMD consumers.
  1. CJS entry exports changed (behavior-impacting)
  • Old CJS entrypoints explicitly set module.exports = exports['default']:
    • dist.old/cjs/handlebars.js:61
    • dist.old/cjs/handlebars.runtime.js:63
  • New CJS entrypoints expose only ESModule-style default export metadata (Object.defineProperty(exports, "default", ...)) and do not assign module.exports to default:
    • dist.new/cjs/handlebars.js:2
    • dist.new/cjs/handlebars.runtime.js:2
  • Runtime check confirms behavior difference:
    • require('./dist.old/cjs/handlebars.js').compile exists.
    • require('./dist.new/cjs/handlebars.js').compile is missing; you need .default.compile.
    • Same pattern for runtime .template.
  • This is the biggest compatibility difference in output.
  1. SWC-style transpilation in CJS internals
  • New CJS files use SWC helper conventions (_interop_require_default, _interop_require_wildcard, _define_property, etc.) and Object.defineProperty export setup.
  • Old CJS files use babel-style helper forms and direct default export reassignment.
  1. Source map payload format differences
  • Inline sourceMappingURL blocks differ across changed unminified/CJS files (encoding format/content changed).
  • Example pattern shift visible in the ends of changed files like handlebars.runtime.js vs handlebars.runtime.js.
  1. Runtime semantic code in generated output
  • New runtime outputs include the VM spread change comment and behavior:
    • dist.new/handlebars.runtime.js:2994
    • dist.new/cjs/handlebars.runtime.js:99
  • hb.VM is copied into a plain object in new output, whereas old output referenced runtime module object directly.

If you want, I can also produce a machine-readable report (JSON/Markdown table) listing all 27 changed files with per-file size and hash deltas.

@jaylinski
jaylinski merged commit 70b8f11 into handlebars-lang:master Mar 14, 2026
9 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.

3 participants