ember-scoped-css 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -0
- package/dist/build/app-css-loader.cjs +16 -10
- package/dist/lib/scoped-css-preprocessor.cjs +1441 -1372
- package/dist/runtime/test-support.cjs +2 -1
- package/dist/scoped-babel-plugin.cjs +218 -151
- package/package.json +3 -3
- package/src/build/scoped-css-unplugin.js +3 -1
- package/src/lib/generateAbsolutePathHash.js +44 -3
- package/src/lib/scoped-css-preprocessor.js +5 -2
- package/src/runtime/test-support.js +3 -1
- package/src/scoped-babel-plugin.js +4 -2
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# ember-scoped-css
|
|
2
|
+
|
|
3
|
+
`ember-scoped-css` is a modern addon that allows you to isolate your CSS in a modular way with co-located scoped CSS. This is a build-time-only addon and therefore is fully supported if your app is built with Embroider.
|
|
4
|
+
|
|
5
|
+
With `ember-scoped-css` you can write your component styles in a co-located `.css` file next to your `.hbs` or `.gjs/.gts` files. Every selector you write in your styles is automatically scoped to the component. So you can develop your component with styles isolated from the rest of the application and you don't have to worry about CSS selectors collisions or issues with the CSS cascade.
|
|
6
|
+
|
|
7
|
+
If you want to read more specifics on how this addon achieves isolation with CSS you can read more in the [detailed CSS isolation documentation](docs/css-isolation.md)
|
|
8
|
+
|
|
9
|
+
As selectors are scoped/renamed during the build process. So there is no performance hit when running the app.
|
|
10
|
+
|
|
11
|
+
The philosophy of `ember-scoped-css` is to stick as close to CSS and HTML as possible and not introduce new syntax or concepts unless it is absolutely necessary.
|
|
12
|
+
|
|
13
|
+
## Compatibility
|
|
14
|
+
|
|
15
|
+
- V2 addons
|
|
16
|
+
|
|
17
|
+
We only support using `ember-scoped-css` as a rollup plugin with a V2 addon right now but we are actively working to support embroider apps and classic ember apps.
|
|
18
|
+
|
|
19
|
+
## Installation for an ember app
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
npm install --save-dev ember-scoped-css
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Installation for a V2 Addon
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
npm install --save-dev ember-scoped-css
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
1. If you want to use `.gjs/gts` components, then follow the instructions from [rollup-plugin-glimmer-template-tag](https://github.com/NullVoxPopuli/rollup-plugin-glimmer-template-tag) addon.
|
|
32
|
+
|
|
33
|
+
2. Add the following to your `rollup.config.mjs`:
|
|
34
|
+
|
|
35
|
+
```diff
|
|
36
|
+
+ import { scopedCssUnplugin } from 'ember-scoped-css/build';
|
|
37
|
+
|
|
38
|
+
// if you want to have some global styles in your addon then
|
|
39
|
+
// put them in the styles folder and change the path to the styles folder
|
|
40
|
+
// if there are no global styles then you can remove addon.keepAssets
|
|
41
|
+
- addon.keepAssets(['**/*.css']),
|
|
42
|
+
+ addon.keepAssets(['**/styles/*.css']),
|
|
43
|
+
|
|
44
|
+
// add the following to the rollup config
|
|
45
|
+
+ scopedCssUnplugin.rollup(),
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
With `ember-scoped-css` you define styles in `.css` files that are colocated with your components
|
|
51
|
+
|
|
52
|
+
```hbs
|
|
53
|
+
{{! src/components/my-component.hbs }}
|
|
54
|
+
<div data-test-my-component class='hello-class header'><b>Hello</b>, world!</div>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```css
|
|
58
|
+
/* src/components/my-component.css */
|
|
59
|
+
.hello-class {
|
|
60
|
+
color: red;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* the :global() pseudo-class is used to define a global class. It mean that header class wont be scoped to that component */
|
|
64
|
+
.hello-class:global(.header) {
|
|
65
|
+
font-size: 20px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
b {
|
|
69
|
+
color: blue;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Passing classes as arguments to a component
|
|
74
|
+
|
|
75
|
+
There is a `scoped-class` helper that you can use to pass a class name as an argument to a component. The helper takes a class name and returns a scoped class name. `scoped-class` helper is replaced at build time so there is no performance hit when running the app.
|
|
76
|
+
|
|
77
|
+
```hbs
|
|
78
|
+
{{! src/components/my-component.hbs }}
|
|
79
|
+
<OtherComponent @internalClass={{scoped-class 'hello-class'}} />
|
|
80
|
+
<OtherComponent @internalClass={{(scoped-class 'hello-class')}} />
|
|
81
|
+
<OtherComponent
|
|
82
|
+
@internalClass={{concat (scoped-class 'hello-class') ' other-class'}}
|
|
83
|
+
/>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Testing
|
|
87
|
+
|
|
88
|
+
As classes are renamed during the build process you can't directly verify if classes are present in your tests. To solve this problem you can use the `scopedClass` function from the `ember-scoped-css/test-support` module. The function takes the class names and path to the CSS file where are the classes defined and returns the scoped class names.
|
|
89
|
+
|
|
90
|
+
The path to the CSS file is always relative to the V2 addon root no matter where the test is located.
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import { scopedClass } from 'ember-scoped-css/test-support';
|
|
94
|
+
|
|
95
|
+
test('MyComponent has hello-class', async function (assert) {
|
|
96
|
+
assert.expect(1);
|
|
97
|
+
|
|
98
|
+
await render(hbs`
|
|
99
|
+
<MyComponent />
|
|
100
|
+
`);
|
|
101
|
+
|
|
102
|
+
const rewrittenClass = scopedClass(
|
|
103
|
+
'hello-class',
|
|
104
|
+
'<module-name>/components/my-component'
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
assert.dom('[data-test-my-component]').hasClass(rewrittenClass);
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Linting
|
|
112
|
+
|
|
113
|
+
`ember-scoped-css` exports a ember-template-lint plugin with one rule `scoped-class-helper`. This lint rule is intended to help you prevent improper use of the `scoped-class` helper which might not be immediately obvious during regular development. You can read more information in the [lint rules documentation](docs/lint-rules.md)
|
|
114
|
+
|
|
115
|
+
### Steps for adding the rule to the project
|
|
116
|
+
|
|
117
|
+
1. Add `ember-scoped-css` plugin to `.template-lintrc.js`
|
|
118
|
+
|
|
119
|
+
```diff
|
|
120
|
+
'use strict';
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
plugins: [
|
|
124
|
+
+ 'ember-scoped-css/src/template-lint/plugin'
|
|
125
|
+
],
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
2. Add `scoped-class-helper` rule to `.template-lintrc.js`
|
|
130
|
+
|
|
131
|
+
```diff
|
|
132
|
+
'use strict';
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
plugins: [
|
|
136
|
+
'ember-scoped-css/src/template-lint/plugin'
|
|
137
|
+
],
|
|
138
|
+
rules: {
|
|
139
|
+
+ 'scoped-class-helper': 'error',
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
This project is licensed under the [MIT License](LICENSE.md).
|
|
@@ -9606,7 +9606,7 @@ __export(app_css_loader_exports, {
|
|
|
9606
9606
|
default: () => app_css_loader_default
|
|
9607
9607
|
});
|
|
9608
9608
|
module.exports = __toCommonJS(app_css_loader_exports);
|
|
9609
|
-
var
|
|
9609
|
+
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
9610
9610
|
|
|
9611
9611
|
// src/lib/fsExists.js
|
|
9612
9612
|
var import_promises = require("fs/promises");
|
|
@@ -9621,6 +9621,7 @@ async function fsExists_default(path5) {
|
|
|
9621
9621
|
|
|
9622
9622
|
// src/lib/generateAbsolutePathHash.js
|
|
9623
9623
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
9624
|
+
var import_node_path3 = __toESM(require("node:path"), 1);
|
|
9624
9625
|
|
|
9625
9626
|
// ../node_modules/.pnpm/find-up@6.3.0/node_modules/find-up/index.js
|
|
9626
9627
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
@@ -9761,9 +9762,6 @@ function findUpSync(name, options = {}) {
|
|
|
9761
9762
|
return matches[0];
|
|
9762
9763
|
}
|
|
9763
9764
|
|
|
9764
|
-
// src/lib/generateAbsolutePathHash.js
|
|
9765
|
-
var import_path = __toESM(require("path"), 1);
|
|
9766
|
-
|
|
9767
9765
|
// src/lib/generateRelativePathHash.js
|
|
9768
9766
|
var import_blueimp_md5 = __toESM(require_md5(), 1);
|
|
9769
9767
|
function generateRelativePathHash(relativePath) {
|
|
@@ -9773,13 +9771,21 @@ function generateRelativePathHash(relativePath) {
|
|
|
9773
9771
|
// src/lib/generateAbsolutePathHash.js
|
|
9774
9772
|
function generateHashFromAbsolutePath(absolutePath) {
|
|
9775
9773
|
const modulePath = appPath(absolutePath);
|
|
9776
|
-
|
|
9774
|
+
const hash = generateRelativePathHash(modulePath);
|
|
9775
|
+
return hash;
|
|
9776
|
+
}
|
|
9777
|
+
function packageScopedPathToModulePath(packageScopedPath) {
|
|
9778
|
+
let packageRelative = packageScopedPath.replace(/^\/src\//, "/");
|
|
9779
|
+
let parsed = import_node_path3.default.parse(packageRelative);
|
|
9780
|
+
let localPackagerStylePath = import_node_path3.default.join(parsed.dir, parsed.name);
|
|
9781
|
+
return localPackagerStylePath;
|
|
9777
9782
|
}
|
|
9778
9783
|
function appPath(sourcePath) {
|
|
9779
9784
|
let workspacePath = findWorkspacePath(sourcePath);
|
|
9780
9785
|
let name = workspacePackageName(sourcePath);
|
|
9781
9786
|
let packageRelative = sourcePath.replace(workspacePath, "");
|
|
9782
|
-
|
|
9787
|
+
let localPackagerStylePath = packageScopedPathToModulePath(packageRelative);
|
|
9788
|
+
return `${name}${localPackagerStylePath}`;
|
|
9783
9789
|
}
|
|
9784
9790
|
var CACHE = /* @__PURE__ */ new Set();
|
|
9785
9791
|
function hasSeen(sourcePath) {
|
|
@@ -9796,9 +9802,9 @@ function findWorkspacePath(sourcePath) {
|
|
|
9796
9802
|
return seen;
|
|
9797
9803
|
}
|
|
9798
9804
|
const packageJsonPath = findUpSync("package.json", {
|
|
9799
|
-
cwd:
|
|
9805
|
+
cwd: import_node_path3.default.dirname(sourcePath)
|
|
9800
9806
|
});
|
|
9801
|
-
const workspacePath =
|
|
9807
|
+
const workspacePath = import_node_path3.default.dirname(packageJsonPath);
|
|
9802
9808
|
CACHE.add(workspacePath);
|
|
9803
9809
|
return workspacePath;
|
|
9804
9810
|
}
|
|
@@ -9809,7 +9815,7 @@ function workspacePackageName(sourcePath) {
|
|
|
9809
9815
|
if (existing) {
|
|
9810
9816
|
return existing.name;
|
|
9811
9817
|
}
|
|
9812
|
-
let buffer = import_node_fs2.default.readFileSync(
|
|
9818
|
+
let buffer = import_node_fs2.default.readFileSync(import_node_path3.default.join(workspace, "package.json"));
|
|
9813
9819
|
let content = buffer.toString();
|
|
9814
9820
|
let json = JSON.parse(content);
|
|
9815
9821
|
MANIFEST_CACHE.set(workspace, json);
|
|
@@ -9904,7 +9910,7 @@ function rewriteCss(css, postfix, fileName) {
|
|
|
9904
9910
|
// src/build/app-css-loader.js
|
|
9905
9911
|
async function app_css_loader_default(code) {
|
|
9906
9912
|
const cssPath = this.resourcePath;
|
|
9907
|
-
const cssFileName =
|
|
9913
|
+
const cssFileName = import_node_path4.default.basename(cssPath);
|
|
9908
9914
|
const postfix = generateHashFromAbsolutePath(cssPath);
|
|
9909
9915
|
const hbsPath = cssPath.replace(".css", ".hbs");
|
|
9910
9916
|
const gjsPath = cssPath.replace(".css", ".js");
|