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
@@ -1,119 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import path from 'node:path';
3
-
4
- import { createUnplugin } from 'unplugin';
5
- import { Compilation } from 'webpack';
6
-
7
- import generateHash from '../lib/generateAbsolutePathHash.js';
8
-
9
- export default createUnplugin(({ loaders, htmlEntrypointInfo }) => {
10
- return {
11
- name: 'app-css-livereload-loader',
12
-
13
- transformInclude(id) {
14
- return id.endsWith('.js') || id.endsWith('.hbs');
15
- },
16
-
17
- async transform(code, jsPath) {
18
- if (process.env.EMBER_ENV === 'production') {
19
- return code;
20
- }
21
-
22
- const importRegex = /import\s+['"]([^'"]+\.css)['"]\s*;$/gm;
23
- let cssPaths = [];
24
- let match;
25
-
26
- while ((match = importRegex.exec(code))) {
27
- const importPath = match[1];
28
- const directory = path.dirname(jsPath);
29
- const cssPath = path.resolve(directory, importPath);
30
-
31
- cssPaths.push(cssPath);
32
-
33
- // replace import with empty string
34
- code = code.replace(match[0], '');
35
- }
36
-
37
- if (!cssPaths.length) {
38
- return code;
39
- }
40
-
41
- const promises = cssPaths.map(async (cssPath) => {
42
- let css = await readFile(cssPath, 'utf8');
43
-
44
- for (let i = loaders.length - 1; i >= 0; i--) {
45
- const loader = loaders[i];
46
-
47
- css = await loader.bind({ resourcePath: cssPath })(css);
48
- }
49
-
50
- // random string; lenght is 8
51
- const postfix = generateHash(path.basename(cssPath));
52
-
53
- this.emitFile({
54
- type: 'asset',
55
- fileName:
56
- 'assets/includedscripts/' + postfix + '_' + path.basename(cssPath),
57
- source: css,
58
- });
59
- });
60
-
61
- await Promise.all(promises);
62
-
63
- return code;
64
- },
65
-
66
- webpack(compiler) {
67
- if (process.env.EMBER_ENV === 'production') {
68
- return;
69
- }
70
-
71
- compiler.hooks.thisCompilation.tap('Replace', (compilation) => {
72
- compilation.hooks.processAssets.tapAsync(
73
- {
74
- name: 'Replace',
75
- stage: Compilation.PROCESS_ASSETS_STAGE_DERIVED,
76
- },
77
- async (assets, callback) => {
78
- try {
79
- const cssAssets = Object.keys(assets).filter((asset) =>
80
- asset.startsWith('assets/includedscripts/'),
81
- );
82
-
83
- if (!cssAssets.length) {
84
- return;
85
- }
86
-
87
- // let linkAdded = false;
88
- const document =
89
- htmlEntrypointInfo.htmlEntryPoint.dom.window.document;
90
-
91
- for (let asset of cssAssets) {
92
- const head = document.getElementsByTagName('head')[0];
93
- const linkExists = head.querySelector(
94
- `link[rel="stylesheet"][href="/${asset}"]`,
95
- );
96
-
97
- if (!linkExists) {
98
- const link = document.createElement('link');
99
-
100
- link.rel = 'stylesheet';
101
- link.href = '/' + asset;
102
- head.appendChild(link);
103
- // linkAdded = true;
104
- }
105
- }
106
-
107
- // if (linkAdded) {
108
- // const indexHtmlWithLinks = dom.serialize();
109
- // await writeFile(indexHtmlPath, indexHtmlWithLinks, 'utf8');
110
- // }
111
- } finally {
112
- callback();
113
- }
114
- },
115
- );
116
- });
117
- },
118
- };
119
- });
@@ -1,38 +0,0 @@
1
- // import { createUnplugin } from 'unplugin';
2
- import path from 'node:path';
3
-
4
- import {
5
- cssHasAssociatedComponent,
6
- hashFrom,
7
- isRelevantFile,
8
- } from '../lib/path/utils.js';
9
- import rewriteCss from '../lib/rewriteCss.js';
10
-
11
- export default async function (code) {
12
- const options = this.getOptions();
13
- const cssPath = this.resourcePath;
14
-
15
- if (!isRelevantFile(cssPath, { ...options, cwd: this.rootContext })) {
16
- return code;
17
- }
18
-
19
- if (!cssPath.startsWith(this.rootContext)) {
20
- return code;
21
- }
22
-
23
- const cssFileName = path.basename(cssPath);
24
-
25
- if (cssHasAssociatedComponent(cssPath)) {
26
- const postfix = hashFrom(cssPath);
27
- const rewrittenCss = rewriteCss(
28
- code,
29
- postfix,
30
- cssFileName,
31
- options.layerName,
32
- );
33
-
34
- return rewrittenCss;
35
- }
36
-
37
- return code;
38
- }
@@ -1,293 +0,0 @@
1
- 'use strict';
2
-
3
- import { existsSync } from 'node:fs';
4
- import { readFile, writeFile } from 'node:fs/promises';
5
- import path from 'node:path';
6
-
7
- import Concat from 'broccoli-concat';
8
- import { Funnel } from 'broccoli-funnel';
9
- import MergeTrees from 'broccoli-merge-trees';
10
- import Filter from 'broccoli-persistent-filter';
11
- import { Preprocessor } from 'content-tag';
12
-
13
- import getClassesTagsFromCss from '../lib/getClassesTagsFromCss.js';
14
- import {
15
- cssHasAssociatedComponent,
16
- hashFromModulePath,
17
- packageScopedPathToModulePath,
18
- } from '../lib/path/utils.js';
19
- import rewriteCss from '../lib/rewriteCss.js';
20
- import rewriteHbs from '../lib/rewriteHbs.js';
21
-
22
- const p = new Preprocessor();
23
- const COMPONENT_EXTENSIONS = ['hbs', 'js', 'ts', 'gjs', 'gts'];
24
- const TEMPLATE_EXTENSIONS = ['hbs', 'gjs', 'gts'];
25
-
26
- class ScopedFilter extends Filter {
27
- constructor(componentsNode, options = {}) {
28
- super(componentsNode, options);
29
- this.warningStream = process.stderr;
30
- this.options = options;
31
- }
32
-
33
- get extensions() {
34
- return ['css'];
35
- }
36
-
37
- get targetExtension() {
38
- return 'css';
39
- }
40
-
41
- async processString(content, relativePath) {
42
- // ignore css files for ember-css-modules
43
- if (relativePath.endsWith('.module.css')) {
44
- return '';
45
- }
46
-
47
- // check if corresponding js file exists
48
- let hasRelevantFile = false;
49
-
50
- for (let inputPath of this.inputPaths) {
51
- if (hasRelevantFile) break;
52
-
53
- for (let ext of COMPONENT_EXTENSIONS) {
54
- if (hasRelevantFile) break;
55
-
56
- const relativeComponentPath = relativePath.replace(/\.css$/, '.' + ext);
57
- const componentPath = path.join(inputPath, relativeComponentPath);
58
-
59
- if (existsSync(componentPath)) {
60
- hasRelevantFile = true;
61
- }
62
- }
63
-
64
- /**
65
- * Pods support
66
- */
67
- if (relativePath.endsWith('styles.css')) {
68
- const absoluteCssPath = path.join(inputPath, relativePath);
69
-
70
- if (cssHasAssociatedComponent(absoluteCssPath)) {
71
- hasRelevantFile = true;
72
- }
73
- }
74
- }
75
-
76
- // rewrite css file
77
- if (hasRelevantFile) {
78
- let localPackagerStylePath = packageScopedPathToModulePath(relativePath);
79
-
80
- const hash = hashFromModulePath(localPackagerStylePath);
81
-
82
- content = rewriteCss(
83
- content,
84
- hash,
85
- relativePath,
86
- this.options.getUserOptions?.()?.layerName,
87
- );
88
-
89
- return content;
90
- } else {
91
- return '';
92
- }
93
- }
94
-
95
- async postProcess(results, relativePath) {
96
- if (process.env.CI || relativePath.endsWith('.module.css')) {
97
- return results;
98
- }
99
-
100
- for (let inputPath of this.inputPaths) {
101
- const templateFile = {};
102
-
103
- eachExtension: for (let ext of TEMPLATE_EXTENSIONS) {
104
- const templatePath = relativePath.replace(/\.css/, '.' + ext);
105
- let templateFilePath = path.join(inputPath, templatePath);
106
- let exists = existsSync(templateFilePath);
107
-
108
- /**
109
- * Pods support
110
- */
111
- if (ext === 'hbs' && !exists && relativePath.endsWith('styles.css')) {
112
- let podsName = relativePath.replace(/styles\.css$/, 'template.hbs');
113
-
114
- templateFilePath = path.join(inputPath, podsName);
115
- exists = existsSync(templateFilePath);
116
- }
117
-
118
- if (exists) {
119
- templateFile.path = templateFilePath;
120
- templateFile.ext = ext;
121
-
122
- break eachExtension;
123
- }
124
- }
125
-
126
- // if the template exists we check the css for changes
127
- if (templateFile.path) {
128
- const cssFilePath = path.join(inputPath, relativePath);
129
- const cssContents = await readFile(cssFilePath, 'utf-8');
130
- const { classes, tags } = getClassesTagsFromCss(cssContents);
131
- const previousClasses = this.options.previousClasses.get(relativePath);
132
-
133
- // if we have previous classes, and they are different, build templates to compare
134
- if (previousClasses && classes !== previousClasses) {
135
- const localPackagerStylePath =
136
- packageScopedPathToModulePath(relativePath);
137
- const postfix = hashFromModulePath(localPackagerStylePath);
138
- const templateRaw = await readFile(templateFile.path, 'utf-8');
139
- const templateComparison = [];
140
- let templateContents = templateRaw;
141
-
142
- if (templateFile.ext === 'hbs') {
143
- templateComparison.push(
144
- didTemplateChange(
145
- templateContents,
146
- previousClasses,
147
- classes,
148
- tags,
149
- postfix,
150
- ),
151
- );
152
- } else {
153
- // find all template tags, and extract the contents to compare
154
- const results = p.parse(templateRaw, '');
155
- const templates = results.map((x) => x.contents);
156
-
157
- for (let template of templates) {
158
- templateComparison.push(
159
- didTemplateChange(
160
- template,
161
- previousClasses,
162
- classes,
163
- tags,
164
- postfix,
165
- ),
166
- );
167
- }
168
- }
169
-
170
- const templateChanged = templateComparison.some((v) => v);
171
-
172
- // if the rewrite doesn't match the original output, we want to rebuild the template
173
- if (templateChanged) {
174
- // this is an awful hack because we don't know how to invalidate broccoli-persistent-filter cache
175
- // ideally we'd invalidate the broccoli-peristent-filter cache here
176
- await writeFile(templateFile.path, templateContents + ' ');
177
- }
178
- }
179
- // assign for next run comparison
180
-
181
- this.options.previousClasses.set(relativePath, classes);
182
- }
183
- }
184
-
185
- return results;
186
- }
187
- }
188
-
189
- /**
190
- * Compares a template with the context of different sets of extraced css classes
191
- *
192
- * @param {string} contents
193
- * @param {Set} previousClasses
194
- * @param {Set} currentClasses
195
- * @param {Set} tags
196
- * @param {string} postfix
197
- * @returns {Boolean}
198
- */
199
- function didTemplateChange(
200
- contents,
201
- previousClasses,
202
- currentClasses,
203
- tags,
204
- postfix,
205
- ) {
206
- const original = rewriteHbs(contents, previousClasses, tags, postfix);
207
- const current = rewriteHbs(contents, currentClasses, tags, postfix);
208
-
209
- return original !== current;
210
- }
211
-
212
- export default class ScopedCssPreprocessor {
213
- constructor(options) {
214
- this.owner = options.owner;
215
- this.appName = options.owner.parent.pkg.name;
216
- this.name = 'scoped-css-preprocessor';
217
- this.previousClasses = new Map();
218
- }
219
-
220
- /**
221
- * Sets the options from `setupPreprocessorRegistry`, the v1-Addon Hook
222
- * @param {{ layerName?: string }} options
223
- */
224
- configureOptions(options) {
225
- this.userOptions = options || { ['not configured']: true };
226
- }
227
-
228
- toTree(inputNode, inputPath, outputDirectory, options) {
229
- const otherPreprocessors = this.preprocessors.filter((p) => p !== this);
230
- const otherTrees = otherPreprocessors.map((p) => p.toTree(...arguments));
231
- let mergedOtherTrees = new MergeTrees(otherTrees);
232
-
233
- let roots = [
234
- this.appName + '/components/',
235
- ...(this.userOptions.additionalRoots || []).map(
236
- (root) => `${this.appName}/${root}`,
237
- ),
238
- ];
239
- let include = roots
240
- .map((root) => {
241
- let withSlash = root.endsWith('/') ? root : `${root}/`;
242
-
243
- return [
244
- `${withSlash}**/*.css`,
245
- ...COMPONENT_EXTENSIONS.map((ext) => `${withSlash}**/*.${ext}`),
246
- ];
247
- })
248
- .flat();
249
-
250
- let componentsNode = new Funnel(inputNode, { include });
251
-
252
- componentsNode = new ScopedFilter(componentsNode, {
253
- inputPath,
254
- getUserOptions: () => this.userOptions,
255
- owner: this.owner,
256
- previousClasses: this.previousClasses,
257
- });
258
-
259
- const componentStyles = new Funnel(componentsNode, {
260
- include: roots.map((root) => {
261
- let withSlash = root.endsWith('/') ? root : `${root}/`;
262
-
263
- return `${withSlash}**/*.css`;
264
- }),
265
- });
266
-
267
- const appCss = new Funnel(inputNode, {
268
- include: [this.appName + '/styles/app.css'],
269
- });
270
-
271
- let mergedStyles = new MergeTrees(
272
- [appCss, mergedOtherTrees, componentStyles],
273
- { overwrite: true },
274
- );
275
-
276
- let newOutput = new Concat(mergedStyles, {
277
- outputFile: options.outputPaths['app'],
278
- allowNone: true,
279
- sourceMapConfig: { enabled: true },
280
- });
281
-
282
- if (this.userOptions?.passthrough) {
283
- let passedThrough = new Funnel(mergedStyles, {
284
- include: this.userOptions.passthrough,
285
- destDir: this.userOptions.passthroughDestination,
286
- });
287
-
288
- return new MergeTrees([passedThrough, newOutput], { overwrite: true });
289
- } else {
290
- return newOutput;
291
- }
292
- }
293
- }
@@ -1,29 +0,0 @@
1
- import recast from 'recast';
2
-
3
- export default function (script, removeCallExpression = false) {
4
- const ast = typeof script === 'string' ? recast.parse(script) : script;
5
- let css = '';
6
-
7
- recast.visit(ast, {
8
- visitCallExpression(nodePath) {
9
- const node = nodePath.node;
10
-
11
- if (
12
- node.callee.name === '__GLIMMER_STYLES' &&
13
- node.arguments.length === 1
14
- ) {
15
- css = node.arguments[0].quasis[0].value.raw;
16
-
17
- if (removeCallExpression) {
18
- nodePath.prune();
19
-
20
- return false;
21
- }
22
- }
23
-
24
- this.traverse(nodePath);
25
- },
26
- });
27
-
28
- return { css, ast };
29
- }
@@ -1,48 +0,0 @@
1
- import postcss from 'postcss';
2
- import parser from 'postcss-selector-parser';
3
-
4
- import isInsideGlobal from './isInsideGlobal.js';
5
-
6
- function getClassesAndTags(sel, classes, tags) {
7
- const transform = (sls) => {
8
- sls.walk((selector) => {
9
- if (selector.type === 'class' && !isInsideGlobal(selector)) {
10
- classes.add(selector.value);
11
- } else if (selector.type === 'tag' && !isInsideGlobal(selector)) {
12
- tags.add(selector.value);
13
- }
14
- });
15
- };
16
-
17
- parser(transform).processSync(sel);
18
- }
19
-
20
- export default function getClassesTagsFromCss(css) {
21
- const classes = new Set();
22
- const tags = new Set();
23
-
24
- const ast = postcss.parse(css);
25
-
26
- ast.walk((node) => {
27
- if (node.type === 'rule') {
28
- getClassesAndTags(node.selector, classes, tags);
29
- }
30
- });
31
-
32
- return { classes, tags };
33
- }
34
-
35
- if (import.meta.vitest) {
36
- const { it, expect } = import.meta.vitest;
37
-
38
- it('should return classes and tags that are not in :global', function () {
39
- const css = '.baz :global(.foo) .bar div :global(p) { color: red; }';
40
- const { classes, tags } = getClassesTagsFromCss(css);
41
-
42
- // classes should be baz and bar
43
- expect(classes.size).to.equal(2);
44
- expect([...classes]).to.have.members(['baz', 'bar']);
45
- expect(tags.size).to.equal(1);
46
- expect([...tags]).to.have.members(['div']);
47
- });
48
- }
@@ -1,19 +0,0 @@
1
- export default function (css) {
2
- const regex = /@import\s+["']?([^"')]+)["']?;/g;
3
- const importedCssPaths = [];
4
- let match;
5
-
6
- while ((match = regex.exec(css))) {
7
- const importPath = match[1];
8
-
9
- if (!importPath.includes('http') && !importPath.startsWith('url(')) {
10
- css = css.replace(match[0], '');
11
- importedCssPaths.unshift(importPath);
12
- }
13
- }
14
-
15
- return {
16
- css,
17
- importedCssPaths,
18
- };
19
- }
@@ -1,8 +0,0 @@
1
- export default function isInsideGlobal(node, func) {
2
- const parent = node.parent;
3
-
4
- if (!parent) return false;
5
- if (parent.type === 'pseudo' && parent.value === ':global') return true;
6
-
7
- return isInsideGlobal(parent, func);
8
- }
@@ -1,63 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
3
- import path from 'node:path';
4
-
5
- import babelParser from '@babel/parser';
6
- import recast from 'recast';
7
-
8
- const parseOptions = {
9
- parser: babelParser,
10
- };
11
-
12
- export default async function replaceGlimmerAst(script, id, replaceFunction) {
13
- const ast = recast.parse(script, parseOptions);
14
- const cssPath = id.replace(/(\.js)|(\.hbs)/, '.css');
15
- let css;
16
-
17
- const cssExists = existsSync(cssPath);
18
-
19
- if (cssExists) {
20
- css = await readFile(cssPath, 'utf-8');
21
- }
22
-
23
- if (!css) {
24
- return script;
25
- }
26
-
27
- recast.visit(ast, {
28
- visitCallExpression(nodePath) {
29
- const node = nodePath.node;
30
-
31
- if (
32
- node.callee.name === 'createTemplateFactory' &&
33
- node.arguments.length === 1
34
- ) {
35
- const blockProp = node.arguments[0].properties.find(
36
- (prop) => prop.key.value === 'block',
37
- );
38
- const opcodes = JSON.parse(blockProp.value.value);
39
- const newOpcodes = replaceFunction(opcodes, css);
40
-
41
- blockProp.value.value = JSON.stringify(newOpcodes);
42
-
43
- const fileName = path.basename(cssPath);
44
- // if (!importPath) {
45
- // unplugin.addWatchFile(cssPath);
46
- // }
47
- const importCss = recast.parse(
48
- `import './${fileName}';\n`,
49
- parseOptions,
50
- );
51
- const importCssNode = importCss.program.body[0];
52
-
53
- ast.program.body.unshift(importCssNode);
54
- }
55
-
56
- this.traverse(nodePath);
57
- },
58
- });
59
-
60
- const resultScript = recast.print(ast).code;
61
-
62
- return resultScript;
63
- }