ember-scoped-css 0.24.2 → 1.0.0

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.
Files changed (37) hide show
  1. package/README.md +110 -115
  2. package/declarations/lib/path/template-transform-paths.d.ts.map +1 -1
  3. package/declarations/lib/path/utils.d.ts.map +1 -1
  4. package/declarations/lib/path/utils.paths.test.d.ts +0 -3
  5. package/declarations/lib/path/utils.paths.test.d.ts.map +1 -1
  6. package/dist/cjs/babel-plugin.cjs +2 -72
  7. package/dist/cjs/index.cjs +206 -390
  8. package/dist/cjs/template-plugin.cjs +201 -120
  9. package/package.json +5 -18
  10. package/src/build/babel-plugin.js +2 -38
  11. package/src/build/index.js +4 -0
  12. package/src/build/scoped-css-unplugin.js +31 -193
  13. package/src/build/template-plugin.js +124 -9
  14. package/src/lib/{rewriteCss.js → css/rewrite.js} +2 -2
  15. package/src/lib/css/utils.js +74 -3
  16. package/src/lib/path/hash-from-absolute-path.test.ts +2 -24
  17. package/src/lib/path/template-transform-paths.js +0 -15
  18. package/src/lib/path/template-transform-paths.test.ts +8 -69
  19. package/src/lib/path/utils.appPath.test.ts +4 -15
  20. package/src/lib/path/utils.findWorkspacePath.test.ts +16 -22
  21. package/src/lib/path/utils.hashFrom.test.ts +8 -8
  22. package/src/lib/path/utils.isRelevantFile.test.ts +9 -29
  23. package/src/lib/path/utils.js +2 -79
  24. package/src/lib/path/utils.paths.test.ts +1 -4
  25. package/src/lib/request.js +40 -0
  26. package/src/lib/rewriteHbs.js +1 -1
  27. package/dist/cjs/app-css-loader.cjs +0 -531
  28. package/dist/cjs/ember-classic-support.cjs +0 -708
  29. package/src/build/app-css-livereload-loader.js +0 -119
  30. package/src/build/app-css-loader.js +0 -38
  31. package/src/build/ember-classic-support.js +0 -293
  32. package/src/lib/findCssInJs.js +0 -29
  33. package/src/lib/getClassesTagsFromCss.js +0 -48
  34. package/src/lib/getImportedCssFiles.js +0 -19
  35. package/src/lib/isInsideGlobal.js +0 -8
  36. package/src/lib/replaceGlimmerAst.js +0 -63
  37. package/src/lib/replaceHbsInJs.js +0 -113
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember-scoped-css",
3
- "version": "0.24.2",
3
+ "version": "1.0.0",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "ember-addon"
@@ -44,17 +44,6 @@
44
44
  "./helpers/scoped-class": {
45
45
  "default": "./classic-app-support/helpers/scoped-class.js"
46
46
  },
47
- "./build/app-css-loader": {
48
- "import": "./src/build/app-css-loader.js",
49
- "require": "./dist/cjs/app-css-loader.cjs"
50
- },
51
- "./build/app-dependency-loader": {
52
- "import": "./src/build/app-dependency-loader.js",
53
- "require": "./dist/cjs/app-dependency-loader.cjs"
54
- },
55
- "./build/ember-classic-support": {
56
- "require": "./dist/cjs/ember-classic-support.cjs"
57
- },
58
47
  "./addon-main.cjs": "./addon-main.cjs",
59
48
  "./babel-plugin": {
60
49
  "import": "./src/build/babel-plugin.js",
@@ -83,17 +72,15 @@
83
72
  },
84
73
  "devDependencies": {
85
74
  "@babel/eslint-parser": "^7.26.8",
86
- "@nullvoxpopuli/eslint-configs": "^4.3.0",
75
+ "@nullvoxpopuli/eslint-configs": "^5.3.4",
87
76
  "@tsconfig/ember": "^3.0.9",
88
77
  "@tsconfig/strictest": "^2.0.5",
89
- "@typescript-eslint/eslint-plugin": "^8.24.1",
90
- "@typescript-eslint/parser": "^8.24.1",
91
78
  "concurrently": "^9.1.2",
92
79
  "ember-template-lint": "^6.1.0",
93
80
  "esbuild": "^0.25.0",
94
81
  "esbuild-plugin-vitest-cleaner": "^0.5.1",
95
- "eslint": "^8.50.0",
96
- "prettier": "^3.5.1",
82
+ "eslint": "^9.36.0",
83
+ "prettier": "^3.6.2",
97
84
  "typescript": "^5.2.2",
98
85
  "vitest": "^3.0.6",
99
86
  "webpack": "^5.98.0"
@@ -107,7 +94,7 @@
107
94
  }
108
95
  },
109
96
  "engines": {
110
- "node": ">= 18"
97
+ "node": ">= 22.16"
111
98
  },
112
99
  "peerDependencies": {
113
100
  "ember-template-lint": ">= 5.7.2",
@@ -1,8 +1,4 @@
1
- import { ImportUtil } from 'babel-import-util';
2
- import { existsSync } from 'fs';
3
- import nodePath from 'path';
4
-
5
- import { cssPathFor, isRelevantFile } from '../lib/path/utils.js';
1
+ import { isRelevantFile } from '../lib/path/utils.js';
6
2
 
7
3
  function _isRelevantFile(state, cwd) {
8
4
  let fileName = state.file.opts.filename;
@@ -21,11 +17,10 @@ function _isRelevantFile(state, cwd) {
21
17
  */
22
18
  export default (env, options, workingDirectory) => {
23
19
  /**
24
- * This babel plugin does three things:
20
+ * This babel plugin does two things:
25
21
  * - removes the import of scopedClass, if it exists
26
22
  * - if scopedClass was imported, it is removed from any component's "scope bag"
27
23
  * (the scope bag being a low-level object used for passing what is "in scope" for a component)
28
- * - adds an import to the CSS file, if it exists
29
24
  */
30
25
  return {
31
26
  visitor: {
@@ -36,8 +31,6 @@ export default (env, options, workingDirectory) => {
36
31
 
37
32
  return;
38
33
  }
39
-
40
- state.importUtil = new ImportUtil(env, path);
41
34
  },
42
35
  },
43
36
  ImportDeclaration(path, state) {
@@ -76,35 +69,6 @@ export default (env, options, workingDirectory) => {
76
69
  path.remove();
77
70
  }
78
71
  },
79
- /**
80
- * If there is a CSS file, AND a corresponding template,
81
- * we can import the CSS to then defer to the CSS loader
82
- * or other CSS processing to handle the postfixing
83
- */
84
- CallExpression(path, state) {
85
- if (state.canSkip) {
86
- return;
87
- }
88
-
89
- const node = path.node;
90
-
91
- if (
92
- node.callee.name === 'precompileTemplate' ||
93
- node.callee.name === 'hbs' ||
94
- node.callee.name === 'createTemplateFactory'
95
- ) {
96
- const fileName =
97
- state.file.opts.sourceFileName || state.file.opts.filename;
98
-
99
- let cssPath = cssPathFor(fileName);
100
-
101
- if (existsSync(cssPath)) {
102
- let baseCSS = nodePath.basename(cssPath);
103
-
104
- state.importUtil.importForSideEffect(`./${baseCSS}`);
105
- }
106
- }
107
- },
108
72
  },
109
73
  };
110
74
  };
@@ -1,3 +1,7 @@
1
1
  export { default as babelPlugin } from './babel-plugin.js';
2
2
  export { default as scopedCssUnplugin } from './scoped-css-unplugin.js';
3
3
  export { createPlugin as templatePlugin } from './template-plugin.js';
4
+
5
+ import { default as scopedCssUnplugin } from './scoped-css-unplugin.js';
6
+
7
+ export const rollupPlugin = scopedCssUnplugin.rollup;
@@ -1,201 +1,39 @@
1
- import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
3
1
  import path from 'node:path';
4
- import process from 'node:process';
5
2
 
6
3
  import { createUnplugin } from 'unplugin';
7
4
 
8
- import getClassesTagsFromCss from '../lib/getClassesTagsFromCss.js';
9
- import {
10
- cssHasAssociatedComponent,
11
- hashFromAbsolutePath,
12
- isRelevantFile,
13
- } from '../lib/path/utils.js';
14
- import replaceHbsInJs from '../lib/replaceHbsInJs.js';
15
- import rewriteCss from '../lib/rewriteCss.js';
16
- import rewriteHbs from '../lib/rewriteHbs.js';
17
-
18
- function isJsFile(id) {
19
- return (
20
- id.endsWith('.js') ||
21
- id.endsWith('.ts') ||
22
- id.endsWith('.gjs') ||
23
- id.endsWith('.gts')
24
- );
25
- }
26
-
27
- function isHbsFile(id) {
28
- return id.endsWith('.hbs');
29
- }
30
-
31
- function isCssFile(id) {
32
- return id.endsWith('.css');
33
- }
34
-
35
- function asCSSPath(id) {
36
- const cssPath = id.endsWith('.hbs')
37
- ? id.replace(/\.hbs$/, '.css')
38
- : id.replace(/(\.hbs)?\.(js|ts|gjs|gts)$/, '.css');
39
-
40
- return cssPath;
41
- }
42
-
43
- async function transformJsFile(code, id) {
44
- let cssPath = asCSSPath(id);
45
- let cssFileName = path.basename(cssPath);
46
-
47
- let cssExists = existsSync(cssPath);
48
-
49
- // Check for pods (using styles.css)
50
- if (!cssExists) {
51
- let [, ...parts] = id.split('/').reverse();
52
-
53
- cssPath = [...parts.reverse(), 'styles.css'].join('/');
54
- cssFileName = 'styles.css';
55
- cssExists = existsSync(cssPath);
56
- }
57
-
58
- let css;
59
-
60
- if (cssExists) {
61
- css = await readFile(cssPath, 'utf8');
62
- } else {
63
- return {
64
- code,
65
- map: null,
66
- };
67
- }
68
-
69
- // add css import for js and gjs files
70
- code = `import './${cssFileName}';\n\n${code}`;
71
-
72
- // rewrite hbs in js in case it is gjs file (for gjs files hbs is already in js file)
73
-
74
- const rewrittenCode = replaceHbsInJs(code, (hbs, scopedClass) => {
75
- const { classes, tags } = getClassesTagsFromCss(css);
76
- const postfix = hashFromAbsolutePath(cssPath);
77
- const rewritten = rewriteHbs(hbs, classes, tags, postfix, scopedClass);
78
-
79
- return rewritten;
80
- });
81
-
5
+ import { decodeScopedCSSRequest, isScopedCSSRequest } from '../lib/request.js';
6
+ /**
7
+ * The plugin that handles CSS requests from other transform (e.g.: babel)
8
+ *
9
+ * This plugin takes no options because the CSS transform has already happened by the time
10
+ * we handle it here.
11
+ */
12
+ export default createUnplugin(() => {
82
13
  return {
83
- code: rewrittenCode,
84
- map: null,
85
- };
86
- }
87
-
88
- function transformCssFile(code, id, layerName) {
89
- if (cssHasAssociatedComponent(id)) {
90
- const postfix = hashFromAbsolutePath(id);
91
-
92
- code = rewriteCss(code, postfix, path.basename(id), layerName);
93
- }
94
-
95
- return code;
96
- }
97
-
98
- function gatherCSSFiles(bundle) {
99
- let cssFiles = [];
100
-
101
- for (let asset in bundle) {
102
- const cssAsset = asset.replace('js', 'css');
103
-
104
- if (!asset.endsWith('js') || !bundle[cssAsset]) {
105
- continue;
106
- }
107
-
108
- if (process.env.environment === 'development') {
109
- cssFiles.push(bundle[cssAsset].source);
110
- delete bundle[cssAsset];
111
- } else {
112
- const cssImport = path.basename(asset.replace('.js', '.css'));
113
- const importLine = `import './${cssImport}';`;
114
-
115
- const code = bundle[asset].code;
116
-
117
- // add import to js files
118
- if (code && code.indexOf(importLine) < 0) {
119
- bundle[asset].code = `${importLine}\n` + code;
14
+ name: 'ember-scoped-css-unplugin',
15
+
16
+ resolveId(id, importer) {
17
+ if (isScopedCSSRequest(id)) {
18
+ let parsed = decodeScopedCSSRequest(id);
19
+
20
+ return {
21
+ id: path.resolve(path.dirname(importer), parsed.postfix + '.css'),
22
+ meta: {
23
+ 'scoped-css': {
24
+ css: parsed.css,
25
+ },
26
+ },
27
+ };
120
28
  }
121
- }
122
- }
123
-
124
- return cssFiles;
125
- }
126
-
127
- export default createUnplugin(
128
- /**
129
- * @typedef {object} Options
130
- * @property {string} [layerName] the name of the layer to place the generated css. Defaults to "components"
131
- *
132
- * @param {Options} [options]
133
- */
134
- (options) => {
135
- let cwd = process.cwd();
136
- let additionalRoots = options?.additionalRoots || [];
29
+ },
137
30
 
138
- return {
139
- name: 'ember-scoped-css-unplugin',
31
+ load(id) {
32
+ let meta = this.getModuleInfo(id)?.meta?.['scoped-css'];
140
33
 
141
- generateBundle(_, bundle) {
142
- let cssFiles = gatherCSSFiles(bundle);
143
-
144
- if (process.env.environment === 'development') {
145
- this.emitFile({
146
- type: 'asset',
147
- fileName: 'scoped.css',
148
- source: cssFiles.join('\n'),
149
- });
150
- }
151
- },
152
- vite: {
153
- generateBundle() {
154
- /* deliberately do nothing */
155
- },
156
- transform(code, jsPath) {
157
- if (!isRelevantFile(jsPath, { additionalRoots, cwd })) return;
158
-
159
- /**
160
- * HBS files are actually JS files with a call to precompileTemplate
161
- */
162
- if (isHbsFile(jsPath)) {
163
- return transformJsFile(code, jsPath);
164
- } else if (isJsFile(jsPath)) {
165
- return transformJsFile(code, jsPath);
166
- } else if (isCssFile(jsPath)) {
167
- return transformCssFile(code, jsPath, options?.layerName);
168
- }
169
- },
170
- },
171
-
172
- transform(code, jsPath) {
173
- if (!isRelevantFile(jsPath, { additionalRoots, cwd })) return;
174
-
175
- /**
176
- * HBS files are actually JS files with a call to precompileTemplate
177
- */
178
- if (isHbsFile(jsPath)) {
179
- return transformJsFile(code, jsPath);
180
- } else if (isJsFile(jsPath)) {
181
- return transformJsFile(code, jsPath);
182
- } else if (isCssFile(jsPath)) {
183
- let css = transformCssFile(code, jsPath, options?.layerName);
184
-
185
- const emittedFileName = jsPath.replace(
186
- path.join(process.cwd(), 'src/'),
187
- '',
188
- );
189
-
190
- this.emitFile({
191
- type: 'asset',
192
- fileName: emittedFileName,
193
- source: css,
194
- });
195
-
196
- return '';
197
- }
198
- },
199
- };
200
- },
201
- );
34
+ if (meta) {
35
+ return meta.css;
36
+ }
37
+ },
38
+ };
39
+ });
@@ -4,7 +4,8 @@
4
4
  *
5
5
  */
6
6
 
7
- import { getCSSInfo } from '../lib/css/utils.js';
7
+ import { rewriteCss } from '../lib/css/rewrite.js';
8
+ import { getCSSContentInfo, getCSSInfo } from '../lib/css/utils.js';
8
9
  import { fixFilename } from '../lib/path/template-transform-paths.js';
9
10
  import {
10
11
  appPath,
@@ -12,6 +13,7 @@ import {
12
13
  hashFromModulePath,
13
14
  isRelevantFile,
14
15
  } from '../lib/path/utils.js';
16
+ import { makeRequest } from '../lib/request.js';
15
17
  import { templatePlugin } from '../lib/rewriteHbs.js';
16
18
 
17
19
  const noopPlugin = {
@@ -28,9 +30,10 @@ export function createPlugin(config) {
28
30
  * @param {ASTPluginEnvironment} env
29
31
  */
30
32
  return function scopedCss(env) {
33
+ let cwd = process.cwd();
31
34
  let isRelevant = isRelevantFile(env.filename, {
32
35
  additionalRoots: config.additionalRoots,
33
- cwd: process.cwd(),
36
+ cwd,
34
37
  });
35
38
 
36
39
  if (!isRelevant) {
@@ -39,18 +42,64 @@ export function createPlugin(config) {
39
42
 
40
43
  let absolutePath = fixFilename(env.filename);
41
44
  let modulePath = appPath(absolutePath);
45
+ let postfix = hashFromModulePath(modulePath);
46
+
47
+ /**
48
+ * The list of naked tag selectors found in the CSS
49
+ *
50
+ * @type {Set<string>}
51
+ */
52
+ let scopedTags = new Set();
53
+
54
+ /**
55
+ * The list of classes found in the CSS
56
+ *
57
+ * @type {Set<string>}
58
+ */
59
+ let scopedClasses = new Set();
60
+
61
+ /**
62
+ * @param {{ tags: Set<string>; classes: Set<string> }} info
63
+ */
64
+ function addInfo(info) {
65
+ for (let item of info.tags) {
66
+ scopedTags.add(item);
67
+ }
68
+
69
+ for (let item of info.classes) {
70
+ scopedClasses.add(item);
71
+ }
72
+ }
42
73
 
43
74
  let cssPath = cssPathFor(absolutePath);
44
75
  let info = getCSSInfo(cssPath);
45
- let postfix = hashFromModulePath(modulePath);
46
76
 
47
- if (!info) {
48
- return noopPlugin;
77
+ /**
78
+ * This will be falsey if we don't have a co-located CSS file.
79
+ * We'll still want to check for embedded <style scoped> tags though.
80
+ */
81
+ if (info) {
82
+ addInfo(info);
83
+
84
+ let localCssPath = cssPath.replace(cwd + '/', '');
85
+ let scopedCss = rewriteCss(
86
+ info.css,
87
+ postfix,
88
+ localCssPath,
89
+ config.layerName,
90
+ );
91
+
92
+ let cssRequest = makeRequest(postfix, info.id, scopedCss);
93
+
94
+ /**
95
+ * With this we don't need a JS plugin
96
+ */
97
+ env.meta.jsutils.importForSideEffect(cssRequest);
49
98
  }
50
99
 
51
100
  let visitors = templatePlugin({
52
- classes: info.classes,
53
- tags: info.tags,
101
+ classes: scopedClasses,
102
+ tags: scopedTags,
54
103
  postfix,
55
104
  });
56
105
 
@@ -59,13 +108,58 @@ export function createPlugin(config) {
59
108
  visitor: {
60
109
  // Stack Manager
61
110
  ...visitors,
111
+
112
+ /**
113
+ * We have to eagerly get the <style scoped> contents, so we can pre-parse
114
+ * the tags and classes to then pass to the other visitors so that they can
115
+ * appropriately change matching classes / tags.
116
+ */
117
+ Template(node) {
118
+ /**
119
+ * We only allow a scoped <style> at the root
120
+ */
121
+ let styleTag = node.body.find(
122
+ (n) => n.type === 'ElementNode' && n.tag === 'style',
123
+ );
124
+
125
+ if (hasScopedAttribute(styleTag)) {
126
+ let css = textContent(styleTag);
127
+ let info = getCSSContentInfo(css);
128
+ let localCssPath = `<inline>/` + cssPath.replace(cwd + '/', '');
129
+ let scopedCss = rewriteCss(
130
+ info.css,
131
+ postfix,
132
+ localCssPath,
133
+ config.layerName,
134
+ );
135
+
136
+ addInfo(info);
137
+
138
+ let cssRequest = makeRequest(postfix, info.id, scopedCss);
139
+
140
+ env.meta.jsutils.importForSideEffect(cssRequest);
141
+ }
142
+ },
143
+
62
144
  // Visitors broken out like this so we can conditionally
63
145
  // debug based on file path.
64
146
  AttrNode(...args) {
65
147
  return visitors.AttrNode(...args);
66
148
  },
67
- ElementNode(...args) {
68
- return visitors.ElementNode(...args);
149
+ ElementNode(node, walker) {
150
+ // class attribute handling
151
+ visitors.ElementNode(node, walker);
152
+
153
+ if (hasScopedAttribute(node)) {
154
+ if (walker.parent?.node.type !== 'Template') {
155
+ throw new Error(
156
+ '<style scoped> tags must be at the root of the template, they cannot be nested',
157
+ );
158
+ }
159
+
160
+ // Returning null removes the node
161
+ return null;
162
+ }
69
163
  },
70
164
  MustacheStatement(...args) {
71
165
  return visitors.MustacheStatement(...args);
@@ -77,3 +171,24 @@ export function createPlugin(config) {
77
171
  };
78
172
  };
79
173
  }
174
+
175
+ /**
176
+ * Thanks, CardStack and @ef4 for this code.
177
+ */
178
+ const SCOPED_ATTRIBUTE_NAME = 'scoped';
179
+
180
+ function hasScopedAttribute(node) {
181
+ if (!node) return;
182
+ if (node.tag !== 'style') return;
183
+ if (node.type !== 'ElementNode') return;
184
+
185
+ return node.attributes.some(
186
+ (attribute) => attribute.name === SCOPED_ATTRIBUTE_NAME,
187
+ );
188
+ }
189
+
190
+ function textContent(node) {
191
+ let textChildren = node.children.filter((c) => c.type === 'TextNode');
192
+
193
+ return textChildren.map((c) => c.chars).join('');
194
+ }
@@ -1,7 +1,7 @@
1
1
  import postcss from 'postcss';
2
2
  import parser from 'postcss-selector-parser';
3
3
 
4
- import isInsideGlobal from './isInsideGlobal.js';
4
+ import { isInsideGlobal } from './utils.js';
5
5
 
6
6
  function rewriteSelector(sel, postfix) {
7
7
  const transform = (selectors) => {
@@ -37,7 +37,7 @@ function isInsideKeyframes(node) {
37
37
  return isInsideKeyframes(parent);
38
38
  }
39
39
 
40
- export default function rewriteCss(css, postfix, fileName, layerName) {
40
+ export function rewriteCss(css, postfix, fileName, layerName) {
41
41
  const layerNameWithDefault = layerName ?? 'components';
42
42
  const ast = postcss.parse(css);
43
43
 
@@ -1,6 +1,25 @@
1
1
  import { existsSync, readFileSync } from 'fs';
2
+ import postcss from 'postcss';
3
+ import parser from 'postcss-selector-parser';
2
4
 
3
- import getClassesTagsFromCss from '../getClassesTagsFromCss.js';
5
+ import { md5 } from '../path/md5.js';
6
+
7
+ /**
8
+ * @param {string} css
9
+ * @return {string} hashed down version of the CSS for disambiguating
10
+ */
11
+ export function hash(css) {
12
+ return `css-${md5(css)}`;
13
+ }
14
+
15
+ export function isInsideGlobal(node, func) {
16
+ const parent = node.parent;
17
+
18
+ if (!parent) return false;
19
+ if (parent.type === 'pseudo' && parent.value === ':global') return true;
20
+
21
+ return isInsideGlobal(parent, func);
22
+ }
4
23
 
5
24
  /**
6
25
  * @param {string} cssPath path to a CSS file
@@ -11,7 +30,59 @@ export function getCSSInfo(cssPath) {
11
30
  }
12
31
 
13
32
  let css = readFileSync(cssPath, 'utf8');
14
- let result = getClassesTagsFromCss(css);
15
33
 
16
- return result;
34
+ return getCSSContentInfo(css);
35
+ }
36
+
37
+ /**
38
+ * We use this function to check each class used in the template
39
+ * to see if we need to leave it alone or transform it
40
+ *
41
+ * @param {string} css the CSS's contents
42
+ * @return {{ classes: Set<string>, tags: Set<string>, css: string, id: string }}
43
+ */
44
+ export function getCSSContentInfo(css) {
45
+ const classes = new Set();
46
+ const tags = new Set();
47
+
48
+ const ast = postcss.parse(css);
49
+
50
+ ast.walk((node) => {
51
+ if (node.type === 'rule') {
52
+ getClassesAndTags(node.selector, classes, tags);
53
+ }
54
+ });
55
+
56
+ let id = hash(css);
57
+
58
+ return { classes, tags, css, id };
59
+ }
60
+
61
+ function getClassesAndTags(sel, classes, tags) {
62
+ const transform = (sls) => {
63
+ sls.walk((selector) => {
64
+ if (selector.type === 'class' && !isInsideGlobal(selector)) {
65
+ classes.add(selector.value);
66
+ } else if (selector.type === 'tag' && !isInsideGlobal(selector)) {
67
+ tags.add(selector.value);
68
+ }
69
+ });
70
+ };
71
+
72
+ parser(transform).processSync(sel);
73
+ }
74
+
75
+ if (import.meta.vitest) {
76
+ const { it, expect } = import.meta.vitest;
77
+
78
+ it('should return classes and tags that are not in :global', function () {
79
+ const css = '.baz :global(.foo) .bar div :global(p) { color: red; }';
80
+ const { classes, tags } = getCSSContentInfo(css);
81
+
82
+ // classes should be baz and bar
83
+ expect(classes.size).to.equal(2);
84
+ expect([...classes]).to.have.members(['baz', 'bar']);
85
+ expect(tags.size).to.equal(1);
86
+ expect([...tags]).to.have.members(['div']);
87
+ });
17
88
  }
@@ -3,37 +3,15 @@ import path from 'node:path';
3
3
  import { describe, expect, it } from 'vitest';
4
4
 
5
5
  import { hashFromAbsolutePath } from './hash-from-absolute-path.js';
6
- import { hashFromModulePath } from './hash-from-module-path.js';
7
6
  import { paths } from './utils.paths.test.js';
8
7
 
9
8
  describe('hashFromAbsolutePath', () => {
10
9
  describe(`the module: "embroider-app/templates/application"`, () => {
11
- let file = 'templates/application';
12
- let expected = 'ea418816b';
13
-
14
- it('matches the module path', () => {
15
- let postfix = hashFromModulePath(`embroider-app/${file}`);
16
-
17
- expect(postfix).to.equal(expected);
18
- });
19
-
20
- it('works with rewritten app', () => {
21
- let filePath = path.join(
22
- paths.embroiderApp,
23
- '/node_modules/.embroider/rewritten-app',
24
- file,
25
- );
26
-
27
- let postfix = hashFromAbsolutePath(filePath);
28
-
29
- expect(postfix).to.equal(expected);
30
- });
31
-
32
10
  it('works with direct path', () => {
33
- let filePath = path.join(paths.embroiderApp, 'app', file);
11
+ let filePath = path.join(paths.viteApp, 'src/components/third');
34
12
  let postfix = hashFromAbsolutePath(filePath);
35
13
 
36
- expect(postfix).to.equal(expected);
14
+ expect(postfix).to.equal('e4b5bd4dc');
37
15
  });
38
16
  });
39
17
  });