Skip to content

First-class Embroider + GJS/GTS Support#340

Merged
dfreeman merged 29 commits intomasterfrom
standalone-template-transform
Feb 25, 2026
Merged

First-class Embroider + GJS/GTS Support#340
dfreeman merged 29 commits intomasterfrom
standalone-template-transform

Conversation

@dfreeman
Copy link
Member

@dfreeman dfreeman commented Feb 24, 2026

🎺 ember-css-modules is dead; long live Ember CSS Modules.

Background

This PR is the culmination of work that began back with embroider-build/embroider#1142 and ultimately produced the long-lived standalone-template-transform branch in this repo.

The tl;dr is that what was once ember-css-modules is no longer needed: its two jobs were to:

  • process CSS Modules into regular JS + CSS in the build output
  • provide the local-class syntactic sugar in templates

The first bullet is no longer needed in the modern ecosystem, as CSS Modules can be handled directly by the bundler (Vite in a v2 app, and Webpack in a v1 app with Embroider or ember-auto-import).

The second bullet is less needed in a world with FCCTs (.gjs/.gts files), as importing and referencing the classname mapping doesn't require creating a backing class to intermediate any longer. However, for ease of migration (or for those who just like the sugar), the end result here provides lighter-weight options to keep local-class around.

The Packages

This PR breaks the repository into four published packages. Details of each package is in the corresponding READMEs and the excellent migration guides that @deanmarano kindly put together.

ember-css-modules

A v3 version of ember-css-modules is included here as a stepping stone for migration. It keeps the broccoli-css-modules-based processing of CSS in the classic Broccoli pipeline, but delegates local-class handling to the glimmer-local-class-transform package described below.

Notable breaking changes:

  • Minimum Node version is now 22
  • Minimum Ember version is 3.28, though the function helper polyfill is required for versions <4.5
  • The plugin system has been removed

This version of ember-css-modules is now fully compatible with @embroider/webpack in terms of playing nicely with the various static flags, though if you're at that point you likely want to consider moving to ember-local-class (or on to Vite and glimmer-local-class-transform) instead. See compatibility listings below.

glimmer-local-class-transform

This is a @glimmer/syntax AST transform that converts local-class syntax to a reference to an import from the corresponding styles module. V2 apps and addons can use this transform directly by setting it up as part of the template processing in their Babel configuration.

ember-local-class

This is a very lightweight v1 addon that just sets up glimmer-local-class-transform on behalf of its parent. This is necessary for v1 apps, as they aren't able to add template transforms directly in their own config. (An in-repo addon is an option, but at that point you've essentially just vendored ember-local-class into your repo.)

ember-local-class has a few notable differences to ember-css-modules:

  • It does not do any CSS preprocessing
  • It defaults to only supporting colocated CSS (no classic/pods layout)
  • For consistency with bundlers, it defaults to .module.css as the module extension rather than .css

rollup-plugin-preprocess-css-modules

This is a Rollup plugin that enables v2 addons (or other component libraries) to preprocess CSS Modules into standards-compliant JS + CSS. This ensures that their use of CSS Modules is just an implementation detail and avoids imposing any requirements on host applications' bundlers processing CSS Modules on their behalf.

The notable difference between this package and using e.g. rollup-plugin-postcss to process CSS Modules is that rollup-plugin-preprocess-css-modules leaves the resulting CSS as standalone .css files that are referenced as needed by ES import statements. This adheres to the Embroider v2 package format RFC's expectations for CSS distribution and ensures that the host application can bundle styles in the most effective way.

Usage Patterns

The packages here have varying levels of compatibility with v1/v2 addons/apps. Each of the setups described below has a corresponding test case in /test-packages.

V1 App

Choose one of the following combinations:

  • ember-css-modules (classic build or @embroider/webpack)
  • ember-local-class + ember-auto-import with allowAppImports and modules: { auto: true }
  • ember-local-class + @embroider/webpack with modules: { auto: true }

V1 Addon

Use ember-css-modules in dependencies.

ember-css-modules is still the only solution available to v1 addons for CSS Modules, as they have no equivalent of allowAppImports config to opt certain subpaths into being handled directly by the host bundler.

V2 App

Use glimmer-local-class-transform and Vite's built-in CSS Modules support. Add glimmer-local-class-transform to the transforms array for babel-plugin-ember-template-compilation in your babel.config.cjs.

V2 Addon

Use glimmer-local-class-transform and rollup-plugin-preprocess-css-modules.

  • Add glimmer-local-class-transform to the transforms array for babel-plugin-ember-template-compilation in your babel.publish.config.cjs.
  • In rollup.config.mjs:
    • Add .hbs to the extensions array you pass to @rollup/plugin-babel
    • Add rollup-plugin-preprocess-css-modules above the call to addon.keepAssets

Closes #318, closes #285

dfreeman and others added 28 commits June 17, 2025 20:46
Fix classNames runtime helper for undefined and unmapped classes
Add setup instructions for v2 apps and addons, document config
options (pathMapping, runtimeModule), usage examples for static,
dynamic, and mixed class names, and a brief explanation of how
the compile-time transform works.
Add explanation of what the plugin does (with before/after example),
setup instructions for v2 addon rollup configs, and documentation
for all three config options (include, generateScopedName,
getOutputFilename).
Replace the HTML comment hiding the full documentation with a
<details> element so it's still visible but collapsed by default,
given the package is in maintenance mode.
Describe compatibility at each stage: ember-css-modules works with
classic builds and Embroider+Webpack but not Vite, so the build
pipeline migration and CSS Modules swap can be done separately.
Break out steps that can be done incrementally while still on
ember-css-modules: consolidate to colocated layout, rename to
.module.css, remove legacy config options, migrate PostCSS config.
Break out steps that can be done while still a v1 addon: consolidate
to colocated layout and rename to .module.css. Converting-to-v2 steps
now account for whether prep was already done.
Cover the common case of apps using SCSS via PostCSS plugins with
ember-css-modules, and how to migrate to Vite's native Sass support.
Co-authored-by: Dan Freeman <dfreeman@salsify.com>
Also archive CHANGELOG.md to opt into generated release notes
Copy link

@mnoble01 mnoble01 left a comment

Choose a reason for hiding this comment

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

Looks good to me! The guides are very helpful and clear. I have one comment about some potential additional test coverage.

@dfreeman dfreeman merged commit 53509cc into master Feb 25, 2026
6 checks passed
@dfreeman dfreeman deleted the standalone-template-transform branch February 25, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for ember-template-imports? Question: How to use it with v2 addon?

3 participants