ember-scoped-css 2.0.4 → 2.2.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.
- package/dist/cjs/all-CFsaG5pM.cjs +1416 -0
- package/dist/cjs/all.cjs +1 -1
- package/dist/cjs/babel.cjs +1 -1
- package/dist/cjs/rollup.cjs +1 -1
- package/dist/cjs/vite.cjs +1 -1
- package/package.json +4 -3
- package/src/build/template-plugin.js +53 -9
- package/src/build/template-plugin.test.ts +140 -1
- package/src/build/unplugin-colocated.js +56 -6
- package/src/build/unplugin-inline.js +53 -13
- package/src/lib/css/rewrite.js +18 -16
- package/src/lib/css/rewrite.test.ts +68 -52
- package/src/lib/css/utils.js +107 -3
- package/src/lib/path/const.js +1 -0
- package/src/lib/path/utils.isRelevantFile.test.ts +7 -0
- package/src/lib/path/utils.js +1 -0
- package/src/lib/request.js +10 -2
- package/dist/cjs/all-DIcjm1uz.cjs +0 -1319
package/dist/cjs/all.cjs
CHANGED
package/dist/cjs/babel.cjs
CHANGED
package/dist/cjs/rollup.cjs
CHANGED
package/dist/cjs/vite.cjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember-scoped-css",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ember-addon"
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"ember-template-recast": "^6.1.5",
|
|
63
63
|
"glob": "^8.1.0",
|
|
64
64
|
"postcss": "^8.5.3",
|
|
65
|
+
"postcss-scss": "^4.0.9",
|
|
65
66
|
"postcss-selector-parser": "^6.0.16",
|
|
66
67
|
"recast": "^0.23.7",
|
|
67
68
|
"unplugin": "^2.3.10"
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
"@types/babel__core": "^7.20.5",
|
|
76
77
|
"@types/common-tags": "^1.8.4",
|
|
77
78
|
"@types/jscodeshift": "^17.3.0",
|
|
78
|
-
"@vitest/coverage-v8": "^
|
|
79
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
79
80
|
"babel-plugin-ember-template-compilation": "^3.0.1",
|
|
80
81
|
"common-tags": "^1.8.2",
|
|
81
82
|
"concurrently": "^9.2.1",
|
|
@@ -86,7 +87,7 @@
|
|
|
86
87
|
"prettier": "^3.6.2",
|
|
87
88
|
"tsdown": "^0.15.12",
|
|
88
89
|
"typescript": "^5.2.2",
|
|
89
|
-
"vitest": "^
|
|
90
|
+
"vitest": "^4.1.0",
|
|
90
91
|
"webpack": "^5.98.0"
|
|
91
92
|
},
|
|
92
93
|
"ember-addon": {
|
|
@@ -122,18 +122,32 @@ export function createPlugin(config) {
|
|
|
122
122
|
|
|
123
123
|
if (hasScopedAttribute(styleTag)) {
|
|
124
124
|
let css = textContent(styleTag);
|
|
125
|
-
let
|
|
125
|
+
let lang = getLangAttribute(styleTag);
|
|
126
|
+
let info = getCSSContentInfo(css, lang);
|
|
126
127
|
|
|
127
128
|
addInfo(info);
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
if (hasInlineAttributeWithoutLang(styleTag)) {
|
|
131
|
+
/**
|
|
132
|
+
* This will be handled in ElementNode traversal
|
|
133
|
+
*/
|
|
133
134
|
return;
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
if (lang) {
|
|
138
|
+
/**
|
|
139
|
+
* For <style scoped inline lang="..."> we cannot preprocess at Babel-time
|
|
140
|
+
* (preprocessing is async and requires Vite's ResolvedConfig).
|
|
141
|
+
* Remove the tag and inject via virtual CSS module and warn user.
|
|
142
|
+
*/
|
|
143
|
+
console.warn(
|
|
144
|
+
`[ember-scoped-css] <style scoped inline lang="${lang}"> is not supported ` +
|
|
145
|
+
`(preprocessing is async and cannot run at Babel-time). ` +
|
|
146
|
+
`Downgrading to non-inline: the style tag will be removed and injected as a virtual CSS module.`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let cssRequest = request.inline.create(info.id, postfix, css, lang);
|
|
137
151
|
|
|
138
152
|
env.meta.jsutils.importForSideEffect(cssRequest);
|
|
139
153
|
}
|
|
@@ -155,7 +169,7 @@ export function createPlugin(config) {
|
|
|
155
169
|
);
|
|
156
170
|
}
|
|
157
171
|
|
|
158
|
-
if (
|
|
172
|
+
if (hasInlineAttributeWithoutLang(node)) {
|
|
159
173
|
let text = textContent(node);
|
|
160
174
|
let scopedText = rewriteCss(
|
|
161
175
|
text,
|
|
@@ -176,7 +190,7 @@ export function createPlugin(config) {
|
|
|
176
190
|
return null;
|
|
177
191
|
}
|
|
178
192
|
|
|
179
|
-
if (
|
|
193
|
+
if (hasInlineAttributeWithoutLang(node)) {
|
|
180
194
|
throw new Error(
|
|
181
195
|
`<style inline> is not valid. Please add the scoped attribute: <style scoped inline>`,
|
|
182
196
|
);
|
|
@@ -198,6 +212,7 @@ export function createPlugin(config) {
|
|
|
198
212
|
*/
|
|
199
213
|
const SCOPED_ATTRIBUTE_NAME = 'scoped';
|
|
200
214
|
const INLINE_ATTRIBUTE_NAME = 'inline';
|
|
215
|
+
const LANG_ATTRIBUTE_NAME = 'lang';
|
|
201
216
|
|
|
202
217
|
function hasScopedAttribute(node) {
|
|
203
218
|
if (!node) return;
|
|
@@ -209,16 +224,45 @@ function hasScopedAttribute(node) {
|
|
|
209
224
|
);
|
|
210
225
|
}
|
|
211
226
|
|
|
212
|
-
function
|
|
227
|
+
function hasInlineAttributeWithoutLang(node) {
|
|
213
228
|
if (!node) return;
|
|
214
229
|
if (node.tag !== 'style') return;
|
|
215
230
|
if (node.type !== 'ElementNode') return;
|
|
216
231
|
|
|
232
|
+
if (getLangAttribute(node)) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
217
236
|
return node.attributes.some(
|
|
218
237
|
(attribute) => attribute.name === INLINE_ATTRIBUTE_NAME,
|
|
219
238
|
);
|
|
220
239
|
}
|
|
221
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Returns the value of the `lang` attribute on a `<style>` node, or null if absent.
|
|
243
|
+
*
|
|
244
|
+
* @param {object} node
|
|
245
|
+
* @returns {string | null}
|
|
246
|
+
*/
|
|
247
|
+
function getLangAttribute(node) {
|
|
248
|
+
if (!node) return null;
|
|
249
|
+
if (node.tag !== 'style') return null;
|
|
250
|
+
if (node.type !== 'ElementNode') return null;
|
|
251
|
+
|
|
252
|
+
const attr = node.attributes.find(
|
|
253
|
+
(attribute) => attribute.name === LANG_ATTRIBUTE_NAME,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
if (!attr) return null;
|
|
257
|
+
|
|
258
|
+
// The attribute value is a TextNode child of the AttrNode's value
|
|
259
|
+
const value = attr.value;
|
|
260
|
+
|
|
261
|
+
if (value?.type === 'TextNode') return value.chars || null;
|
|
262
|
+
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
222
266
|
function textContent(node) {
|
|
223
267
|
let textChildren = node.children.filter((c) => c.type === 'TextNode');
|
|
224
268
|
|
|
@@ -2,7 +2,7 @@ import * as babel from '@babel/core';
|
|
|
2
2
|
import { stripIndent } from 'common-tags';
|
|
3
3
|
import { Preprocessor } from 'content-tag';
|
|
4
4
|
import jscodeshift from 'jscodeshift';
|
|
5
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { createPlugin } from './template-plugin.js';
|
|
8
8
|
|
|
@@ -28,6 +28,30 @@ async function transform(file: string, config = {}) {
|
|
|
28
28
|
return result?.code;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function virtualImportUrlsOf(file: string | null | undefined) {
|
|
32
|
+
if (!file) return [];
|
|
33
|
+
|
|
34
|
+
let j = jscodeshift;
|
|
35
|
+
|
|
36
|
+
let result: string[] = [];
|
|
37
|
+
|
|
38
|
+
j(file)
|
|
39
|
+
.find(j.ImportDeclaration, {
|
|
40
|
+
source: {
|
|
41
|
+
value: (value: string) => value.includes('.ember-scoped.css?css='),
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
.forEach((path) => {
|
|
45
|
+
let source = path.node.source.value;
|
|
46
|
+
|
|
47
|
+
if (typeof source === 'string') {
|
|
48
|
+
result.push(source);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
31
55
|
function templateContentsOf(file: string | null | undefined) {
|
|
32
56
|
if (!file) return [];
|
|
33
57
|
|
|
@@ -194,3 +218,118 @@ it('scoped inline transforms correctly', async () => {
|
|
|
194
218
|
]
|
|
195
219
|
`);
|
|
196
220
|
});
|
|
221
|
+
|
|
222
|
+
describe('lang attribute (SCSS preprocessor)', () => {
|
|
223
|
+
it('scoped lang="scss" emits virtual import with lang param and rewrites classes', async () => {
|
|
224
|
+
let output = await transform(`
|
|
225
|
+
export const Foo = <template>
|
|
226
|
+
<div class="foo">hi</div>
|
|
227
|
+
<style scoped lang="scss">
|
|
228
|
+
.foo {
|
|
229
|
+
&:hover { color: blue; }
|
|
230
|
+
color: red;
|
|
231
|
+
}
|
|
232
|
+
</style>
|
|
233
|
+
</template>;
|
|
234
|
+
`);
|
|
235
|
+
|
|
236
|
+
// The style tag should be removed (it's a virtual module, not inline)
|
|
237
|
+
expect(templateContentsOf(output)).toMatchInlineSnapshot(`
|
|
238
|
+
[
|
|
239
|
+
"<div class="foo_e65d154a1">hi</div>",
|
|
240
|
+
]
|
|
241
|
+
`);
|
|
242
|
+
|
|
243
|
+
// The virtual module import should include &lang=scss
|
|
244
|
+
expect(virtualImportUrlsOf(output)).toMatchInlineSnapshot(`
|
|
245
|
+
[
|
|
246
|
+
"./e65d154a1___css-3fbbf8c13a5ef6f5c5395268df4e8f37.ember-scoped.css?css=%0A%20%20%20%20%20%20%20%20%20%20.foo%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%26%3Ahover%20%7B%20color%3A%20blue%3B%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3A%20red%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20&lang=scss",
|
|
247
|
+
]
|
|
248
|
+
`);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('scoped inline lang="scss" is downgraded to non-inline (warning emitted, style tag removed)', async () => {
|
|
252
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
253
|
+
|
|
254
|
+
let output = await transform(`
|
|
255
|
+
export const Foo = <template>
|
|
256
|
+
<div class="foo">hi</div>
|
|
257
|
+
<style scoped inline lang="scss">
|
|
258
|
+
.foo {
|
|
259
|
+
&:hover { color: blue; }
|
|
260
|
+
color: red;
|
|
261
|
+
}
|
|
262
|
+
</style>
|
|
263
|
+
</template>;
|
|
264
|
+
`);
|
|
265
|
+
|
|
266
|
+
// The style tag should be removed (downgraded to non-inline)
|
|
267
|
+
expect(templateContentsOf(output)).toMatchInlineSnapshot(`
|
|
268
|
+
[
|
|
269
|
+
"<div class="foo_e65d154a1">hi</div>",
|
|
270
|
+
]
|
|
271
|
+
`);
|
|
272
|
+
|
|
273
|
+
// A virtual module import should have been emitted with lang=scss
|
|
274
|
+
expect(virtualImportUrlsOf(output)).toMatchInlineSnapshot(`
|
|
275
|
+
[
|
|
276
|
+
"./e65d154a1___css-3fbbf8c13a5ef6f5c5395268df4e8f37.ember-scoped.css?css=%0A%20%20%20%20%20%20%20%20%20%20.foo%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%26%3Ahover%20%7B%20color%3A%20blue%3B%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3A%20red%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20&lang=scss",
|
|
277
|
+
]
|
|
278
|
+
`);
|
|
279
|
+
|
|
280
|
+
// A warning should have been logged
|
|
281
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
282
|
+
expect.stringContaining(
|
|
283
|
+
'<style scoped inline lang="scss"> is not supported',
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
warnSpy.mockRestore();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('handles scoped lang="scss" BEM constructs', async () => {
|
|
291
|
+
let output = await transform(`
|
|
292
|
+
export const Foo = <template>
|
|
293
|
+
<div class="block block--modifier">hi</div>
|
|
294
|
+
<style scoped lang="scss">
|
|
295
|
+
.block {
|
|
296
|
+
&--modifier { color: green; }
|
|
297
|
+
}
|
|
298
|
+
</style>
|
|
299
|
+
</template>;
|
|
300
|
+
`);
|
|
301
|
+
|
|
302
|
+
expect(templateContentsOf(output)).toMatchInlineSnapshot(`
|
|
303
|
+
[
|
|
304
|
+
"<div class="block_e65d154a1 block--modifier_e65d154a1">hi</div>",
|
|
305
|
+
]
|
|
306
|
+
`);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('handles deeply nested BEM constructs', async () => {
|
|
310
|
+
let output = await transform(`
|
|
311
|
+
export const Foo = <template>
|
|
312
|
+
<div class="block block--modifier block--modifier--modifier block--modifier--modifier--modifier">hi</div>
|
|
313
|
+
<style scoped lang="scss">
|
|
314
|
+
.block {
|
|
315
|
+
&--modifier {
|
|
316
|
+
color: green;
|
|
317
|
+
&--modifier {
|
|
318
|
+
color: green;
|
|
319
|
+
&--modifier {
|
|
320
|
+
color: green;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
</style>
|
|
326
|
+
</template>;
|
|
327
|
+
`);
|
|
328
|
+
|
|
329
|
+
expect(templateContentsOf(output)).toMatchInlineSnapshot(`
|
|
330
|
+
[
|
|
331
|
+
"<div class="block_e65d154a1 block--modifier_e65d154a1 block--modifier--modifier_e65d154a1 block--modifier--modifier--modifier_e65d154a1">hi</div>",
|
|
332
|
+
]
|
|
333
|
+
`);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
import { rewriteCss } from '../lib/css/rewrite.js';
|
|
@@ -6,6 +7,15 @@ import { request } from '../lib/request.js';
|
|
|
6
7
|
|
|
7
8
|
const META = 'scoped-css:colocated';
|
|
8
9
|
|
|
10
|
+
/** File extensions that Vite can preprocess via its CSS preprocessor pipeline */
|
|
11
|
+
const PREPROCESSED_EXTENSIONS = new Set([
|
|
12
|
+
'.scss',
|
|
13
|
+
'.sass',
|
|
14
|
+
'.less',
|
|
15
|
+
'.styl',
|
|
16
|
+
'.stylus',
|
|
17
|
+
]);
|
|
18
|
+
|
|
9
19
|
/**
|
|
10
20
|
* Plugin for supporting colocated styles
|
|
11
21
|
*
|
|
@@ -16,6 +26,12 @@ const META = 'scoped-css:colocated';
|
|
|
16
26
|
export function colocated(options = {}) {
|
|
17
27
|
const CWD = process.cwd();
|
|
18
28
|
|
|
29
|
+
/** @type {import('vite').ResolvedConfig | undefined} */
|
|
30
|
+
let viteConfig;
|
|
31
|
+
|
|
32
|
+
/** @type {((code: string, filename: string, config: unknown) => Promise<{ code: string }>) | undefined} */
|
|
33
|
+
let preprocessCSS;
|
|
34
|
+
|
|
19
35
|
/**
|
|
20
36
|
*
|
|
21
37
|
* @param {string} id the request id / what was imported
|
|
@@ -51,11 +67,6 @@ export function colocated(options = {}) {
|
|
|
51
67
|
path.basename(parsed.fileName),
|
|
52
68
|
);
|
|
53
69
|
|
|
54
|
-
/**
|
|
55
|
-
* Rollup doesn't normally watch CSS files
|
|
56
|
-
*/
|
|
57
|
-
this.addWatchFile(filePath);
|
|
58
|
-
|
|
59
70
|
return buildResponse(id, filePath);
|
|
60
71
|
}
|
|
61
72
|
},
|
|
@@ -63,6 +74,8 @@ export function colocated(options = {}) {
|
|
|
63
74
|
const meta = this.getModuleInfo(id)?.meta?.[META];
|
|
64
75
|
|
|
65
76
|
if (meta) {
|
|
77
|
+
this.addWatchFile(meta.fullPath);
|
|
78
|
+
|
|
66
79
|
let code = readFileSync(meta.fullPath, 'utf-8');
|
|
67
80
|
|
|
68
81
|
let css = rewriteCss(
|
|
@@ -76,18 +89,55 @@ export function colocated(options = {}) {
|
|
|
76
89
|
}
|
|
77
90
|
},
|
|
78
91
|
vite: {
|
|
92
|
+
async configResolved(config) {
|
|
93
|
+
viteConfig = config;
|
|
94
|
+
|
|
95
|
+
// Resolve Vite's preprocessCSS from the app root to ensure we find
|
|
96
|
+
// the correct Vite installation (not a stale or missing one).
|
|
97
|
+
try {
|
|
98
|
+
const require = createRequire(config.root);
|
|
99
|
+
const vitePath = require.resolve('vite');
|
|
100
|
+
const viteModule = await import(vitePath);
|
|
101
|
+
|
|
102
|
+
preprocessCSS = viteModule.preprocessCSS;
|
|
103
|
+
} catch {
|
|
104
|
+
// Vite may not be resolvable from the config root in some setups;
|
|
105
|
+
// preprocessor support for colocated .scss files will throw a clear
|
|
106
|
+
// error at load time if used.
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
79
110
|
/**
|
|
80
111
|
* There may not be meta for this request yet.
|
|
81
112
|
*
|
|
82
113
|
* @param {*} id
|
|
83
114
|
*/
|
|
84
|
-
load(id) {
|
|
115
|
+
async load(id) {
|
|
85
116
|
if (request.is.colocated(id)) {
|
|
86
117
|
const parsed = request.colocated.decode(id);
|
|
87
118
|
|
|
88
119
|
let code = readFileSync(parsed.fileName, 'utf-8');
|
|
89
120
|
let relativeFilePath = path.relative(CWD, parsed.fileName);
|
|
90
121
|
|
|
122
|
+
const ext = path.extname(parsed.fileName).toLowerCase();
|
|
123
|
+
|
|
124
|
+
if (PREPROCESSED_EXTENSIONS.has(ext)) {
|
|
125
|
+
if (!viteConfig || !preprocessCSS) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`[ember-scoped-css] Colocated CSS file with extension '${ext}' requires Vite. ` +
|
|
128
|
+
`CSS preprocessing is only supported in Vite builds.`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const result = await preprocessCSS(
|
|
133
|
+
code,
|
|
134
|
+
parsed.fileName,
|
|
135
|
+
viteConfig,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
code = result.code;
|
|
139
|
+
}
|
|
140
|
+
|
|
91
141
|
let css = rewriteCss(
|
|
92
142
|
code,
|
|
93
143
|
parsed.postfix,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
|
|
3
4
|
import { rewriteCss } from '../lib/css/rewrite.js';
|
|
@@ -17,6 +18,12 @@ const META = 'scoped-css:inline';
|
|
|
17
18
|
export function inline(options = {}) {
|
|
18
19
|
const CWD = process.cwd();
|
|
19
20
|
|
|
21
|
+
/** @type {import('vite').ResolvedConfig | undefined} */
|
|
22
|
+
let viteConfig;
|
|
23
|
+
|
|
24
|
+
/** @type {((code: string, filename: string, config: unknown) => Promise<{ code: string }>) | undefined} */
|
|
25
|
+
let preprocessCSS;
|
|
26
|
+
|
|
20
27
|
/**
|
|
21
28
|
* @param {string} id the request id / what was imported
|
|
22
29
|
*/
|
|
@@ -25,22 +32,14 @@ export function inline(options = {}) {
|
|
|
25
32
|
|
|
26
33
|
const relativeFilePath = path.relative(CWD, filePath);
|
|
27
34
|
|
|
28
|
-
const css = rewriteCss(
|
|
29
|
-
parsed.css,
|
|
30
|
-
parsed.postfix,
|
|
31
|
-
`<inline>/${relativeFilePath}`,
|
|
32
|
-
options.layerName,
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const nextId = filePath.split('?')[0];
|
|
36
|
-
|
|
37
35
|
return {
|
|
38
|
-
id:
|
|
36
|
+
id: filePath.split('?')[0],
|
|
39
37
|
meta: {
|
|
40
38
|
[META]: {
|
|
41
|
-
css,
|
|
39
|
+
rawCss: parsed.css,
|
|
42
40
|
postfix: parsed.postfix,
|
|
43
41
|
fileName: relativeFilePath,
|
|
42
|
+
lang: parsed.lang,
|
|
44
43
|
},
|
|
45
44
|
},
|
|
46
45
|
};
|
|
@@ -60,12 +59,53 @@ export function inline(options = {}) {
|
|
|
60
59
|
return buildResponse(id, filePath);
|
|
61
60
|
}
|
|
62
61
|
},
|
|
63
|
-
load(id) {
|
|
62
|
+
async load(id) {
|
|
64
63
|
const meta = this.getModuleInfo(id)?.meta?.[META];
|
|
65
64
|
|
|
66
65
|
if (meta) {
|
|
67
|
-
|
|
66
|
+
let rawCss = meta.rawCss;
|
|
67
|
+
|
|
68
|
+
if (meta.lang) {
|
|
69
|
+
if (!viteConfig || !preprocessCSS) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`[ember-scoped-css] <style scoped lang="${meta.lang}"> requires Vite. ` +
|
|
72
|
+
`CSS preprocessing via the 'lang' attribute is only supported in Vite builds.`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const fakeFilename = `${meta.fileName}.${meta.lang}`;
|
|
77
|
+
const result = await preprocessCSS(rawCss, fakeFilename, viteConfig);
|
|
78
|
+
|
|
79
|
+
rawCss = result.code;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const css = rewriteCss(
|
|
83
|
+
rawCss,
|
|
84
|
+
meta.postfix,
|
|
85
|
+
`<inline>/${meta.fileName}`,
|
|
86
|
+
options.layerName,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return css;
|
|
68
90
|
}
|
|
69
91
|
},
|
|
92
|
+
vite: {
|
|
93
|
+
async configResolved(config) {
|
|
94
|
+
viteConfig = config;
|
|
95
|
+
|
|
96
|
+
// Resolve Vite's preprocessCSS from the app root to ensure we find
|
|
97
|
+
// the correct Vite installation (not a stale or missing one).
|
|
98
|
+
try {
|
|
99
|
+
const require = createRequire(config.root);
|
|
100
|
+
const vitePath = require.resolve('vite');
|
|
101
|
+
const viteModule = await import(vitePath);
|
|
102
|
+
|
|
103
|
+
preprocessCSS = viteModule.preprocessCSS;
|
|
104
|
+
} catch {
|
|
105
|
+
// Vite may not be resolvable from the config root in some setups;
|
|
106
|
+
// lang= support will throw a clear error at load time if used.
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
},
|
|
70
110
|
};
|
|
71
111
|
}
|
package/src/lib/css/rewrite.js
CHANGED
|
@@ -21,7 +21,7 @@ function isDeclaration(node) {
|
|
|
21
21
|
* NOTE: "keyframes" is a singular definition, in that it's a block containing keyframes
|
|
22
22
|
* using `@keyframes {}` with only one thing on the inside doesn't make sense.
|
|
23
23
|
*/
|
|
24
|
-
function
|
|
24
|
+
function rewriteReferenceable(node, postfix) {
|
|
25
25
|
let originalName = node.params;
|
|
26
26
|
let postfixedName = node.params + SEP + postfix;
|
|
27
27
|
|
|
@@ -90,25 +90,25 @@ export function rewriteCss(css, postfix, fileName, layerName) {
|
|
|
90
90
|
* kind => originalName => postfixedName
|
|
91
91
|
* @type {{ [kind: string]: { [originalName: string]: string }}}
|
|
92
92
|
*/
|
|
93
|
-
const
|
|
93
|
+
const referenceables = {
|
|
94
94
|
keyframes: {},
|
|
95
95
|
'counter-style': {},
|
|
96
96
|
'position-try': {},
|
|
97
97
|
property: {},
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
const
|
|
100
|
+
const availableReferenceables = new Set(Object.keys(referenceables));
|
|
101
101
|
|
|
102
|
-
function
|
|
102
|
+
function isReferenceable(node) {
|
|
103
103
|
if (node.type !== 'atrule') return;
|
|
104
104
|
|
|
105
|
-
return
|
|
105
|
+
return availableReferenceables.has(node.name);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
function updateDirectReferences(node) {
|
|
109
109
|
if (!node.value) return;
|
|
110
110
|
|
|
111
|
-
for (let [, map] of Object.entries(
|
|
111
|
+
for (let [, map] of Object.entries(referenceables)) {
|
|
112
112
|
if (map[node.value]) {
|
|
113
113
|
node.value = map[node.value];
|
|
114
114
|
}
|
|
@@ -118,11 +118,11 @@ export function rewriteCss(css, postfix, fileName, layerName) {
|
|
|
118
118
|
function updateShorthandContents(node) {
|
|
119
119
|
if (node.prop === 'animation') {
|
|
120
120
|
let parts = node.value.split(' ');
|
|
121
|
-
let match = parts.filter((x) =>
|
|
121
|
+
let match = parts.filter((x) => referenceables.keyframes[x]);
|
|
122
122
|
|
|
123
123
|
if (match.length) {
|
|
124
124
|
match.forEach((x) => {
|
|
125
|
-
let replacement =
|
|
125
|
+
let replacement = referenceables.keyframes[x];
|
|
126
126
|
|
|
127
127
|
if (!replacement) return;
|
|
128
128
|
|
|
@@ -131,7 +131,9 @@ export function rewriteCss(css, postfix, fileName, layerName) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
for (let [lookFor, replaceWith] of Object.entries(
|
|
134
|
+
for (let [lookFor, replaceWith] of Object.entries(
|
|
135
|
+
referenceables.property,
|
|
136
|
+
)) {
|
|
135
137
|
let lookForVar = `var(${lookFor})`;
|
|
136
138
|
let replaceWithVar = `var(${replaceWith})`;
|
|
137
139
|
|
|
@@ -141,27 +143,27 @@ export function rewriteCss(css, postfix, fileName, layerName) {
|
|
|
141
143
|
|
|
142
144
|
/**
|
|
143
145
|
* We have to do two passes:
|
|
144
|
-
* 1. postfix all the
|
|
146
|
+
* 1. postfix all the referenceable syntax
|
|
145
147
|
* 2. postfix as normal, but also checking values of CSS properties
|
|
146
|
-
* that could match postfixed
|
|
148
|
+
* that could match postfixed referenceables from step 1
|
|
147
149
|
*/
|
|
148
150
|
|
|
149
|
-
// Step 1: find
|
|
151
|
+
// Step 1: find referenceables
|
|
150
152
|
ast.walk((node) => {
|
|
151
153
|
/**
|
|
152
154
|
* @keyframes, @counter-style, etc
|
|
153
155
|
*/
|
|
154
|
-
if (
|
|
156
|
+
if (isReferenceable(node)) {
|
|
155
157
|
let name = node.name;
|
|
156
|
-
let { originalName, postfixedName } =
|
|
158
|
+
let { originalName, postfixedName } = rewriteReferenceable(node, postfix);
|
|
157
159
|
|
|
158
|
-
|
|
160
|
+
referenceables[name][originalName] = postfixedName;
|
|
159
161
|
|
|
160
162
|
return;
|
|
161
163
|
}
|
|
162
164
|
});
|
|
163
165
|
|
|
164
|
-
// Step 2: postfix and update
|
|
166
|
+
// Step 2: postfix and update referenced referenceables
|
|
165
167
|
ast.walk((node) => {
|
|
166
168
|
if (isDeclaration(node)) {
|
|
167
169
|
updateDirectReferences(node);
|