glimmer-scoped-css
is an Ember addon that lets you embed <style>
tags in component templates that will be scoped to only apply within those components:
If you have app/components/something.hbs
:
<style scoped>
p {
color: blue;
}
</style>
<h1>An h1</h1>
<p>A p.</p>
you get this generated HTML:
<h1 data-scopedcss-58ccb4dfe0-e9125e9996>An h1</h1>
<p data-scopedcss-58ccb4dfe0-e9125e9996>A p.</p>
and this generated CSS:
p[data-scopedcss-58ccb4dfe0-e9125e9996] {
color: blue;
}
Nested components only have the parent component’s styles on elements with ...attributes
. You can see this in action in test-app
.
The implementation is adapted from a Vue PostCSS plugin. It also supports these pseudo-elements:
If you want to use CSS in your component but want a selector to not be scoped, you can use :global
:
:global(.red) {
color: red;
}
The generated CSS will look like this:
.red {
color: red;
}
Using :deep
on a selector will attach the scoping attribute to the element selector before it.
.a :deep(.b) {
color: pink;
}
The generated CSS will look like this:
.a[data-scopedcss-3afb00313e] .b {
color: pink;
}
This is a pre-1.0 release with several limitations:
- it assumes a Webpack build
- it hardcodes Webpack CSS loaders
- this works fine when you have the default loaders configured by
@embroider/webpack
- this works fine when you have the default loaders configured by
- the styles are in a
style
element inindex.html
, not linked - scoped styles cannot use interpolation:
{{whatever}}
will be duplicated unprocessed in the stylesheet - there is log noise about source maps like this:
unexpectedly found "<style>\n p { color: blue" when slicing source, but expected "data-scopedcss-53259f1da9-58ccb4dfe0"
- An open prettier issue means that if you apply prettier to your handlebars, it will format your CSS in a silly way.
- Ember.js v3.28 or above
- Embroider v3.0.0 or above
ember install glimmer-scoped-css
-
Include in
ember-cli-build.js
:const { Webpack } = require('@embroider/webpack'); +const { GlimmerScopedCSSWebpackPlugin } = require('glimmer-scoped-css/webpack'); return require('@embroider/compat').compatBuild(app, Webpack, { + packagerOptions: { + webpackConfig: { + plugins: [new GlimmerScopedCSSWebpackPlugin()], + }, + }, });
-
Add an in-repo addon to install the Handlebars preprocessor:
In
package.json
:"ember": { "edition": "octane" +}, +"ember-addon": { + "paths": [ + "lib/setup-ast-transforms" + ] }
Add
lib/setup-ast-transforms/package.json
:{ "name": "setup-ast-transforms", "keywords": [ "ember-addon" ], "dependencies": { "glimmer-scoped-css": "*" } }
Add
lib/setup-ast-transforms/index.js
:'use strict'; const { installScopedCSS } = require('glimmer-scoped-css'); module.exports = { name: require('./package').name, setupPreprocessorRegistry(type, registry) { if (type === 'parent') { installScopedCSS(registry); } }, };
-
Install the plugin in the addon’s Rollup config
rollup.config.mjs
:import { Addon } from '@embroider/addon-dev/rollup'; import { babel } from '@rollup/plugin-babel'; import copy from 'rollup-plugin-copy'; +import { scopedCSS } from 'glimmer-scoped-css/rollup'; const addon = new Addon({ srcDir: 'src', destDir: 'dist', }); export default { output: addon.output(), plugins: [ + scopedCSS('src'), …
-
Add the AST transform in
babel.config.json
:["babel-plugin-ember-template-compilation", { "targetFormat": "hbs", - "transforms": [] + "transforms": ["glimmer-scoped-css/ast-transform"] }],
-
Install the preprocessor directly in the addon’s
index.js
:'use strict' +const { installScopedCSS } = require('glimmer-scoped-css'); module.exports = { name: require('./package').name, isDevelopingAddon() { return true; }, + setupPreprocessorRegistry(type, registry) { + if (type === 'self') { + installScopedCSS(registry); + } + }, };
Add a top-level <style scoped>
element in your component .hbs
file and it will be scoped to elements in that component only. It also works in <template>
in .gjs
/.gts
files.
Nested <style scoped>
elements cannot be processed for scoping. Use <style>
directly if you need a nested element, it will not receive scoping attributes and will be passed through to output.
glimmer-scoped-css consists of two parts. The first part is an AST transform that takes as input your <style>
tags inside handlebars and emits as output specially crafted import statements that account for that scoped CSS.
The second part is a plugin for your current environment (by default, webpack) that satisfies the specially-crafted import statements by turning them into CSS. To implement a new plugin, you should use import { isScopedCSSRequest, decodeScopedCSSRequest } from 'glimmer-scoped-css'
to identify these imports and turn them back into CSS, respectively.
Error: 6:51 error "glimmer-scoped-css/webpack" is not found node/no-missing-require
The eslint-plugin-node
package that produces this error doesn’t understand the exports
structure supported by newer Node versions and is unmaintained. Ember CLI has moved to using eslint-plugin-n
as a drop-in replacement as of 4.10.
Changing to eslint-plugin-n
and updating the lint configuration fixes these errors.
See the Contributing guide for details.
This project is licensed under the MIT License.