ember-scoped-css 0.0.0 → 0.1.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/index.js CHANGED
@@ -1,5 +1,23 @@
1
- 'use strict';
1
+ const rollupEmberTemplateImportsPlugin = require('./src/rollup-ember-template-imports-plugin');
2
+ const addonJsUnplugin = require('./src/addon-js-unplugin');
3
+ const addonCssRollup = require('./src/addon-css-rollup');
4
+ const addonHbsRollup = require('./src/addon-hbs-rollup');
5
+ const appJsUnplugin = require('./src/app-js-unplugin');
6
+ const appCssUnplugin = require('./src/app-css-unplugin');
7
+ const appCssLoader = require('./src/app-css-loader');
8
+ const appDependencyLoader = require('./src/app-dependency-loader');
9
+ const appScopedcssWebpack = require('./src/app-scopedcss-webpack');
10
+ const addonRewritecssRollup = require('./src/addon-rewritecss-rollup');
2
11
 
3
12
  module.exports = {
4
- name: require('./package').name,
13
+ rollupEmberTemplateImportsPlugin,
14
+ addonJsUnplugin,
15
+ addonCssRollup,
16
+ addonHbsRollup,
17
+ appJsUnplugin,
18
+ appCssUnplugin,
19
+ appCssLoader,
20
+ appDependencyLoader,
21
+ appScopedcssWebpack,
22
+ addonRewritecssRollup,
5
23
  };
package/package.json CHANGED
@@ -1,80 +1,33 @@
1
1
  {
2
2
  "name": "ember-scoped-css",
3
- "version": "0.0.0",
4
- "description": "The default blueprint for ember-cli addons.",
5
- "keywords": [
6
- "ember-addon"
7
- ],
8
- "repository": "",
9
- "license": "MIT",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "type": "commonjs",
6
+ "main": "index.js",
7
+ "keywords": [],
10
8
  "author": "",
11
- "directories": {
12
- "doc": "doc",
13
- "test": "tests"
14
- },
15
- "scripts": {
16
- "build": "ember build --environment=production",
17
- "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
18
- "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
19
- "lint:hbs": "ember-template-lint .",
20
- "lint:hbs:fix": "ember-template-lint . --fix",
21
- "lint:js": "eslint . --cache",
22
- "lint:js:fix": "eslint . --fix",
23
- "start": "ember serve",
24
- "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
25
- "test:ember": "ember test",
26
- "test:ember-compatibility": "ember try:each"
27
- },
9
+ "license": "ISC",
28
10
  "dependencies": {
29
- "ember-cli-babel": "^7.26.11",
30
- "ember-cli-htmlbars": "^6.2.0"
11
+ "blueimp-md5": "^2.19.0",
12
+ "cheerio": "1.0.0-rc.12",
13
+ "ember-source": "^4.10.0",
14
+ "ember-template-imports": "github:candunaj/ember-template-imports#embed-styles-in-gjs",
15
+ "ember-template-recast": "^6.1.3",
16
+ "glob": "^8.1.0",
17
+ "postcss": "^8.4.21",
18
+ "postcss-selector-parser": "^6.0.11",
19
+ "recast": "^0.22.0",
20
+ "rollup": "^2.67.0",
21
+ "unplugin": "^1.0.1"
31
22
  },
32
23
  "devDependencies": {
33
- "@ember/optional-features": "^2.0.0",
34
- "@ember/string": "^3.0.1",
35
- "@ember/test-helpers": "^2.9.3",
36
- "@embroider/test-setup": "^2.0.2",
37
- "@glimmer/component": "^1.1.2",
38
- "@glimmer/tracking": "^1.1.2",
39
- "babel-eslint": "^10.1.0",
40
- "broccoli-asset-rev": "^3.0.0",
41
- "concurrently": "^7.6.0",
42
- "ember-auto-import": "^2.5.0",
43
- "ember-cli": "~4.10.0",
44
- "ember-cli-dependency-checker": "^3.3.1",
45
- "ember-cli-inject-live-reload": "^2.1.0",
46
- "ember-cli-sri": "^2.1.1",
47
- "ember-cli-terser": "^4.0.2",
48
- "ember-load-initializers": "^2.1.2",
49
- "ember-page-title": "^7.0.0",
50
- "ember-qunit": "^6.1.1",
51
- "ember-resolver": "^10.0.0",
52
- "ember-source": "~4.10.0",
53
- "ember-source-channel-url": "^3.0.0",
54
- "ember-template-lint": "^5.3.1",
55
- "ember-try": "^2.0.0",
56
- "eslint": "^7.32.0",
57
- "eslint-config-prettier": "^8.6.0",
58
- "eslint-plugin-ember": "^11.4.3",
59
- "eslint-plugin-n": "^15.6.1",
60
- "eslint-plugin-prettier": "^4.2.1",
61
- "eslint-plugin-qunit": "^7.3.4",
62
- "loader.js": "^4.7.0",
63
- "prettier": "^2.8.3",
64
- "qunit": "^2.19.3",
65
- "qunit-dom": "^2.0.0",
24
+ "chai": "^4.3.7",
25
+ "mocha": "^10.2.0",
26
+ "sinon": "^15.0.1",
66
27
  "webpack": "^5.75.0"
67
28
  },
68
- "peerDependencies": {
69
- "ember-source": "^3.28.0 || ^4.0.0"
70
- },
71
- "engines": {
72
- "node": "14.* || 16.* || >= 18"
73
- },
74
- "ember": {
75
- "edition": "octane"
76
- },
77
- "ember-addon": {
78
- "configPath": "tests/dummy/config"
29
+ "scripts": {
30
+ "test": "mocha",
31
+ "lint": ""
79
32
  }
80
- }
33
+ }
@@ -0,0 +1,92 @@
1
+ const path = require('path');
2
+ const getPostfix = require('./getPostfix');
3
+ const rewriteCss = require('./rewriteCss');
4
+ const { readFile } = require('fs').promises;
5
+ const fsExists = require('./fsExists');
6
+ const findCssInJs = require('./findCssInJs');
7
+ const getImportedCssFiles = require('./getImportedCssFiles');
8
+ const recast = require('recast');
9
+
10
+ module.exports = function () {
11
+ return {
12
+ name: 'addon-css-rollup',
13
+
14
+ async resolveId(source, importer, options) {
15
+ // catch emited css files
16
+ if (source.endsWith('.css')) {
17
+ const resolution = await this.resolve(source, importer, {
18
+ ...options,
19
+ skipSelf: true,
20
+ });
21
+
22
+ if (resolution) {
23
+ return resolution;
24
+ } else {
25
+ const gjsCss = this.getModuleInfo(importer)?.meta?.gjsCss;
26
+ if (gjsCss) {
27
+ return {
28
+ external: false,
29
+ id: importer.replace(/\.js$/, '.css'),
30
+ meta: {
31
+ importer,
32
+ gjsCss,
33
+ },
34
+ };
35
+ }
36
+ }
37
+ }
38
+ return null;
39
+ },
40
+
41
+ async load(id) {
42
+ if (id.endsWith('.css')) {
43
+ const gjsCss = this.getModuleInfo(id).meta.gjsCss;
44
+ let css = gjsCss;
45
+ if (!css) {
46
+ css = await readFile(id, 'utf-8');
47
+ }
48
+
49
+ return css;
50
+ }
51
+ },
52
+
53
+ async transform(code, id) {
54
+ if (id.endsWith('.css')) {
55
+ this.emitFile({
56
+ type: 'asset',
57
+ fileName: id.replace(path.join(process.cwd(), 'src/'), ''),
58
+ source: code,
59
+ });
60
+ return '';
61
+ }
62
+ },
63
+
64
+ generateBundle(a, bundle) {
65
+ let cssFiles = [];
66
+ for (let asset in bundle) {
67
+ const cssAsset = asset.replace('js', 'css');
68
+ if (!asset.endsWith('js') || !bundle[cssAsset]) {
69
+ continue;
70
+ }
71
+
72
+ if (process.env.environment === 'development') {
73
+ cssFiles.push(bundle[cssAsset].source);
74
+ delete bundle[cssAsset];
75
+ } else {
76
+ // add import to js files
77
+ bundle[asset].code =
78
+ `import './${path.basename(asset.replace('.js', '.css'))}';\n` +
79
+ bundle[asset].code;
80
+ }
81
+ }
82
+
83
+ if (process.env.environment === 'development') {
84
+ this.emitFile({
85
+ type: 'asset',
86
+ fileName: 'scoped.css',
87
+ source: cssFiles.join('\n'),
88
+ });
89
+ }
90
+ },
91
+ };
92
+ };
@@ -0,0 +1,42 @@
1
+ const { readFile } = require('fs').promises;
2
+ const getPostfix = require('./getPostfix');
3
+ const replaceHbsInJs = require('./replaceHbsInJs');
4
+ const getClassesTagsFromCss = require('./getClassesTagsFromCss');
5
+ const rewriteHbs = require('./rewriteHbs');
6
+ const fsExists = require('./fsExists');
7
+
8
+ module.exports = function rollupCssColocation(options = {}) {
9
+ return {
10
+ name: 'addon-hbs-rollup',
11
+
12
+ async transform(code, id) {
13
+ if (id.endsWith('.hbs.js')) {
14
+ const hbsPath = id.replace('.js', '');
15
+ const cssPath = hbsPath.replace('.hbs', '.css');
16
+
17
+ const cssExists = await fsExists(cssPath);
18
+ if (cssExists) {
19
+ // read the css file
20
+ // TODO: get css from loader, because there are classes in imported css files; css can be stored in meta!!!!!
21
+ // const resolution = await this.resolve(importPath, id);
22
+ // resolution.meta.internalImport = true;
23
+ // const importedCss = await this.load(resolution);
24
+ const css = await readFile(cssPath, 'utf-8');
25
+ const { classes, tags } = getClassesTagsFromCss(css);
26
+
27
+ // generate unique postfix
28
+ const postfix = getPostfix(cssPath);
29
+
30
+ // rewrite the template
31
+ const rewrittenHbsJs = replaceHbsInJs(code, (hbs) => {
32
+ // add dependency to the css file
33
+ this.addWatchFile(cssPath);
34
+ return rewriteHbs(hbs, classes, tags, postfix);
35
+ });
36
+
37
+ return rewrittenHbsJs;
38
+ }
39
+ }
40
+ },
41
+ };
42
+ };
@@ -0,0 +1,55 @@
1
+ const { createUnplugin } = require('unplugin');
2
+ const { readFile } = require('fs').promises;
3
+ const path = require('path');
4
+ const getClassesTagsFromCss = require('./getClassesTagsFromCss');
5
+ const getPostfix = require('./getPostfix');
6
+ const replaceHbsInJs = require('./replaceHbsInJs');
7
+ const rewriteHbs = require('./rewriteHbs');
8
+ const fsExists = require('./fsExists');
9
+ const findCssInJs = require('./findCssInJs');
10
+ const recast = require('recast');
11
+
12
+ module.exports = createUnplugin((options) => {
13
+ return {
14
+ name: 'addon-js-unplugin',
15
+
16
+ transformInclude(id) {
17
+ return id.endsWith('.js');
18
+ },
19
+
20
+ async transform(code, jsPath) {
21
+ const cssPath = jsPath.replace(/(\.hbs)?\.js$/, '.css');
22
+ const cssFileName = path.basename(cssPath);
23
+
24
+ const cssExists = await fsExists(cssPath);
25
+ let css;
26
+ if (cssExists) {
27
+ css = await readFile(cssPath, 'utf8');
28
+ } else {
29
+ if (code.includes('__GLIMMER_STYLES')) {
30
+ const result = findCssInJs(code, true);
31
+ css = result.css;
32
+ code = recast.print(result.ast).code;
33
+ this.getModuleInfo(jsPath).meta.gjsCss = result.css;
34
+ }
35
+ }
36
+
37
+ if (!css) {
38
+ return code;
39
+ }
40
+
41
+ // add css import for js and gjs files
42
+ code = `import './${cssFileName}';\n\n${code}`;
43
+
44
+ // rewrite hbs in js in case it is gjs file (for gjs files hbs is already in js file)
45
+ // for js components "@embroider/addon-dev/template-colocation-plugin", will add hbs to js later. So there is hbs plugin to rewrite hbs
46
+
47
+ return replaceHbsInJs(code, (hbs) => {
48
+ const { classes, tags } = getClassesTagsFromCss(css);
49
+ const postfix = getPostfix(cssPath);
50
+ const rewritten = rewriteHbs(hbs, classes, tags, postfix);
51
+ return rewritten;
52
+ });
53
+ },
54
+ };
55
+ });
@@ -0,0 +1,29 @@
1
+ const path = require('path');
2
+ const getPostfix = require('./getPostfix');
3
+ const rewriteCss = require('./rewriteCss');
4
+ const fsExists = require('./fsExists');
5
+
6
+ module.exports = function () {
7
+ return {
8
+ name: 'addon-rewritecss-rollup',
9
+
10
+ async transform(code, id) {
11
+ if (!id.endsWith('.css')) {
12
+ return;
13
+ }
14
+ const postfix = getPostfix(id);
15
+ const jsPath = id.replace(/\.css$/, '.gjs');
16
+ const hbsPath = id.replace(/\.css$/, '.hbs');
17
+
18
+ const [jsExists, hbsExists] = await Promise.all([
19
+ fsExists(jsPath),
20
+ fsExists(hbsPath),
21
+ ]);
22
+
23
+ if (jsExists || hbsExists) {
24
+ const rewritten = rewriteCss(code, postfix, path.basename(id));
25
+ return rewritten;
26
+ }
27
+ },
28
+ };
29
+ };
@@ -0,0 +1,111 @@
1
+ const { createUnplugin } = require('unplugin');
2
+ const path = require('path');
3
+ const { readFile, writeFile } = require('fs').promises;
4
+ const { Compilation } = require('webpack');
5
+ const getPostfix = require('./getPostfix');
6
+ const cheerio = require('cheerio');
7
+
8
+ module.exports = createUnplugin(({ appDir, loaders, htmlEntrypointInfo }) => {
9
+ return {
10
+ name: 'app-css-livereload-loader',
11
+
12
+ transformInclude(id) {
13
+ return id.endsWith('.js') || id.endsWith('.hbs');
14
+ },
15
+
16
+ async transform(code, jsPath) {
17
+ if (process.env.EMBER_ENV === 'production') {
18
+ return code;
19
+ }
20
+
21
+ const importRegex = /import\s+['"]([^'"]+\.css)['"]\s*;$/gm;
22
+ let cssPaths = [];
23
+ let match;
24
+ while ((match = importRegex.exec(code))) {
25
+ const importPath = match[1];
26
+ const directory = path.dirname(jsPath);
27
+ const cssPath = path.resolve(directory, importPath);
28
+ cssPaths.push(cssPath);
29
+
30
+ // replace import with empty string
31
+ code = code.replace(match[0], '');
32
+ }
33
+
34
+ if (!cssPaths.length) {
35
+ return code;
36
+ }
37
+
38
+ const promises = cssPaths.map(async (cssPath) => {
39
+ let css = await readFile(cssPath, 'utf8');
40
+ for (let i = loaders.length - 1; i >= 0; i--) {
41
+ const loader = loaders[i];
42
+ css = await loader.bind({ resourcePath: cssPath })(css);
43
+ }
44
+ // random string; lenght is 8
45
+ const postfix = getPostfix(path.basename(cssPath));
46
+
47
+ this.emitFile({
48
+ type: 'asset',
49
+ fileName:
50
+ 'assets/includedscripts/' + postfix + '_' + path.basename(cssPath),
51
+ source: css,
52
+ });
53
+ });
54
+
55
+ await Promise.all(promises);
56
+
57
+ return code;
58
+ },
59
+
60
+ webpack(compiler) {
61
+ if (process.env.EMBER_ENV === 'production') {
62
+ return;
63
+ }
64
+
65
+ compiler.hooks.thisCompilation.tap('Replace', (compilation) => {
66
+ compilation.hooks.processAssets.tapAsync(
67
+ {
68
+ name: 'Replace',
69
+ stage: Compilation.PROCESS_ASSETS_STAGE_DERIVED,
70
+ },
71
+ async (assets, callback) => {
72
+ try {
73
+ const cssAssets = Object.keys(assets).filter((asset) =>
74
+ asset.startsWith('assets/includedscripts/')
75
+ );
76
+
77
+ if (!cssAssets.length) {
78
+ return;
79
+ }
80
+ let linkAdded = false;
81
+ const document =
82
+ htmlEntrypointInfo.htmlEntryPoint.dom.window.document;
83
+
84
+ for (let asset of cssAssets) {
85
+ const head = document.getElementsByTagName('head')[0];
86
+ const linkExists = head.querySelector(
87
+ `link[rel="stylesheet"][href="/${asset}"]`
88
+ );
89
+
90
+ if (!linkExists) {
91
+ const link = document.createElement('link');
92
+ link.rel = 'stylesheet';
93
+ link.href = '/' + asset;
94
+ head.appendChild(link);
95
+ linkAdded = true;
96
+ }
97
+ }
98
+
99
+ // if (linkAdded) {
100
+ // const indexHtmlWithLinks = dom.serialize();
101
+ // await writeFile(indexHtmlPath, indexHtmlWithLinks, 'utf8');
102
+ // }
103
+ } finally {
104
+ callback();
105
+ }
106
+ }
107
+ );
108
+ });
109
+ },
110
+ };
111
+ });
@@ -0,0 +1,28 @@
1
+ // const { createUnplugin } = require('unplugin');
2
+ const { basename, join } = require('path');
3
+ const fsExists = require('./fsExists');
4
+ const getPostfix = require('./getPostfix');
5
+ const rewriteCss = require('./rewriteCss');
6
+ // const path = require('path');
7
+
8
+ module.exports = async function (code) {
9
+ const cssPath = this.resourcePath;
10
+ const cssFileName = basename(cssPath);
11
+ const postfix = getPostfix(cssPath);
12
+
13
+ const hbsPath = cssPath.replace('.css', '.hbs');
14
+ const gjsPath = cssPath.replace('.css', '.js');
15
+ const [hbsExists, gjsExists] = await Promise.all([
16
+ fsExists(hbsPath),
17
+ fsExists(gjsPath),
18
+ ]);
19
+
20
+ let rewrittenCss;
21
+ if (hbsExists || gjsExists && cssPath.startsWith(this.rootContext)) {
22
+ rewrittenCss = rewriteCss(code, postfix, cssFileName);
23
+ } else {
24
+ rewrittenCss = code;
25
+ }
26
+
27
+ return rewrittenCss;
28
+ };
@@ -0,0 +1,35 @@
1
+ const { createUnplugin } = require('unplugin');
2
+ const replaceGlimmerAst = require('./replaceGlimmerAst');
3
+ const path = require('path');
4
+ const getPostfix = require('./getPostfix');
5
+ const getClassesTagsFromCss = require('./getClassesTagsFromCss');
6
+ const { readFile } = require('fs').promises;
7
+ const fsExists = require('./fsExists');
8
+
9
+
10
+ module.exports = createUnplugin(() => {
11
+ return {
12
+ name: 'app-css-unplugin',
13
+
14
+ // loadInclude(id){
15
+ // if(id.endsWith('.css')){
16
+ // return true;
17
+ // }
18
+ // },
19
+
20
+ // load(id){
21
+ // // this.addWatchFile(id);
22
+ // return 'console.log(' + JSON.stringify(id) + ');';
23
+ // },
24
+
25
+ // transformInclude(id) {
26
+ // if(id.endsWith('.css')){
27
+ // return true;
28
+ // }
29
+ // },
30
+
31
+ // async transform(code, id) {
32
+ // return code;
33
+ // },
34
+ };
35
+ });
@@ -0,0 +1,19 @@
1
+ const fsExists = require('./fsExists');
2
+
3
+ module.exports = async function (source) {
4
+ if (this.resourcePath.endsWith('.js')) {
5
+ const hbsExists = await fsExists(this.resourcePath.replace(/\.js/, '.hbs'));
6
+ if (hbsExists) {
7
+ return source;
8
+ }
9
+ }
10
+
11
+ const cssPath = this.resourcePath.replace(/(\.js)|(\.hbs)/, '.css');
12
+ const cssExists = await fsExists(cssPath);
13
+
14
+ if (cssExists) {
15
+ this.addDependency(cssPath);
16
+ }
17
+
18
+ return source;
19
+ };
@@ -0,0 +1,130 @@
1
+ const { createUnplugin } = require('unplugin');
2
+ const replaceGlimmerAst = require('./replaceGlimmerAst');
3
+ const path = require('path');
4
+ const getPostfix = require('./getPostfix');
5
+ const getClassesTagsFromCss = require('./getClassesTagsFromCss');
6
+ const { readFile } = require('fs').promises;
7
+ const fsExists = require('./fsExists');
8
+
9
+ function* iterateOpcodes(opcodes) {
10
+ for (let instruction of opcodes) {
11
+ if (!Array.isArray(instruction)) {
12
+ continue;
13
+ }
14
+
15
+ yield instruction;
16
+
17
+ for (let subInstruction of iterateOpcodes(instruction)) {
18
+ yield subInstruction;
19
+ }
20
+ }
21
+ }
22
+
23
+ function inflateTagName(tag) {
24
+ if (typeof tag === 'string') {
25
+ return tag;
26
+ } else {
27
+ if (tag === 0) {
28
+ return 'div';
29
+ } else if (tag === 1) {
30
+ return 'span';
31
+ } else if (tag === 2) {
32
+ return 'p';
33
+ } else if (tag === 3) {
34
+ return 'a';
35
+ }
36
+ }
37
+ throw new Error('Unknown tag');
38
+ }
39
+
40
+ module.exports = createUnplugin(({ appDir }) => {
41
+ return {
42
+ name: 'app-js-unplugin',
43
+
44
+ transformInclude(id) {
45
+ return (
46
+ id.includes(path.basename(appDir)) &&
47
+ (id.endsWith('.js') || id.endsWith('.hbs'))
48
+ );
49
+ },
50
+
51
+ async transform(code, id) {
52
+ const cssPath = id.replace(/(\.js)|(\.hbs)/, '.css');
53
+ const cssFileName = path.basename(cssPath);
54
+ const postfix = getPostfix(cssPath);
55
+
56
+ return await replaceGlimmerAst(code, id, (opcodes, css) => {
57
+ const { classes, tags } = getClassesTagsFromCss(css);
58
+ const a = code;
59
+ // this.addWatchFile(cssPath);
60
+ const tmp = this;
61
+ const insertions = [];
62
+
63
+ for (let instruction of iterateOpcodes(opcodes[0])) {
64
+ // replace classes
65
+ if (
66
+ instruction[0] === 14 &&
67
+ instruction[1] === 0 &&
68
+ instruction[2] &&
69
+ instruction[2].split(' ').find((i) => classes.has(i.trim()))
70
+ ) {
71
+ // 14 - css attribute, 0 - class
72
+ instruction[2] = instruction[2]
73
+ .split(' ')
74
+ .map((className) => {
75
+ if (className.trim() && classes.has(className.trim())) {
76
+ return className.trim() + '_' + postfix;
77
+ } else {
78
+ return className;
79
+ }
80
+ })
81
+ .join(' ');
82
+ }
83
+
84
+ // add postfix to tags
85
+ if (
86
+ instruction[0] === 10 &&
87
+ tags.has(inflateTagName(instruction[1]))
88
+ ) {
89
+ // 10 - open element
90
+ let existingClassInstruction;
91
+ for (
92
+ let i = opcodes[0].indexOf(instruction);
93
+ i <= opcodes[0].length;
94
+ i++
95
+ ) {
96
+ if (opcodes[0][i][0] === 14 && opcodes[0][i][1] === 0) {
97
+ // 14 - css attribute, 0 - class
98
+ existingClassInstruction = opcodes[0][i];
99
+ break;
100
+ }
101
+ if (opcodes[0][i][0] === 12) {
102
+ // 12 - flush element
103
+ break;
104
+ }
105
+ }
106
+
107
+ if (existingClassInstruction) {
108
+ existingClassInstruction[2] += ' ' + postfix;
109
+ } else {
110
+ const classInstruction = [14, 0, postfix, undefined];
111
+ insertions.push([instruction, classInstruction]);
112
+ }
113
+ }
114
+ }
115
+
116
+ // insert new instructions
117
+ for (let [instruction, classInstruction] of insertions) {
118
+ const index = opcodes[0].indexOf(instruction);
119
+ opcodes[0].splice(index + 1, 0, classInstruction);
120
+ }
121
+
122
+ // rewrite opcodes
123
+ // const dbg = new WireFormatDebugger(opcodes);
124
+ // const wfd = dbg.format(opcodes);
125
+
126
+ return opcodes;
127
+ });
128
+ },
129
+ };
130
+ });
@@ -0,0 +1,68 @@
1
+ // const { RawSource } = require('webpack-sources');
2
+ const rewriteCss = require('./rewriteCss');
3
+ const { readFile, writeFile } = require('fs').promises;
4
+ const path = require('path');
5
+ const getPostfix = require('./getPostfix');
6
+ const fsExists = require('./fsExists');
7
+ const getFiles = require('./getFiles');
8
+
9
+ module.exports = class {
10
+ apply(compiler) {
11
+ if (process.env.EMBER_ENV === 'production') {
12
+ return;
13
+ }
14
+
15
+ compiler.hooks.emit.tapAsync(
16
+ 'scoped-webpack-plugin',
17
+ async (compilation, callback) => {
18
+ try {
19
+ const cssFiles = await getFiles(
20
+ path.resolve(compiler.context, '**/*.css')
21
+ );
22
+
23
+ // Rewrite the CSS files
24
+ const rewrittenFiles = [];
25
+ // const rewritenFiles = cssFiles.map((file) => {
26
+ for (let file of cssFiles) {
27
+ if (file.endsWith(`/${path.basename(compiler.context)}.css`)) {
28
+ // import scoped.css into app.css
29
+ let appCss = await readFile(file, 'utf-8');
30
+ await writeFile(file, `@import "scoped.css";\n${appCss}`);
31
+ }
32
+
33
+ if (
34
+ [
35
+ path.basename(compiler.context) + '.css',
36
+ 'test-support.css',
37
+ ].some((f) => file.endsWith(f)) ||
38
+ file.includes('/vendor/') ||
39
+ !(
40
+ (await fsExists(file.replace('.css', '.js'))) ||
41
+ (await fsExists(file.replace('.css', '.hbs')))
42
+ )
43
+ ) {
44
+ continue;
45
+ }
46
+ const fileName = path.basename(file);
47
+ const postfix = getPostfix(fileName);
48
+ const css = await readFile(file, 'utf-8');
49
+ const rewrittenCss = rewriteCss(css, postfix, fileName);
50
+
51
+ rewrittenFiles.push(rewrittenCss);
52
+ }
53
+
54
+ // Concatenate the rewritten CSS files
55
+ let concatenatedCSS = rewrittenFiles.join('\n\n');
56
+
57
+ // Store the concatenated CSS in the assets/scoped-components.css
58
+ compilation.assets['assets/scoped.css'] = {
59
+ source: () => concatenatedCSS,
60
+ size: () => concatenatedCSS.length,
61
+ };
62
+ } finally {
63
+ callback();
64
+ }
65
+ }
66
+ );
67
+ }
68
+ };
@@ -0,0 +1,27 @@
1
+ const recast = require('recast');
2
+
3
+ module.exports = 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
+ if (removeCallExpression) {
17
+ nodePath.prune();
18
+ return false;
19
+ }
20
+ }
21
+
22
+ this.traverse(nodePath);
23
+ },
24
+ });
25
+
26
+ return { css, ast };
27
+ };
@@ -0,0 +1,10 @@
1
+ const { stat } = require('fs').promises;
2
+
3
+ module.exports = async function (path) {
4
+ try {
5
+ await stat(path);
6
+ return true;
7
+ } catch (e) {
8
+ return false;
9
+ }
10
+ };
@@ -0,0 +1,31 @@
1
+ const postcss = require('postcss');
2
+ const parser = require('postcss-selector-parser');
3
+ const isInsideGlobal = require('./isInsideGlobal');
4
+
5
+ function getClassesAndTags(sel, classes, tags) {
6
+ const transform = (sls) => {
7
+ sls.walk((selector) => {
8
+ if (selector.type === 'class' && !isInsideGlobal(selector)) {
9
+ classes.add(selector.value);
10
+ } else if (selector.type === 'tag' && !isInsideGlobal(selector)) {
11
+ tags.add(selector.value);
12
+ }
13
+ });
14
+ };
15
+
16
+ parser(transform).processSync(sel);
17
+ }
18
+
19
+ module.exports = function getClassesTagsFromCss(css) {
20
+ const classes = new Set();
21
+ const tags = new Set();
22
+
23
+ const ast = postcss.parse(css);
24
+ ast.walk((node) => {
25
+ if (node.type === 'rule') {
26
+ getClassesAndTags(node.selector, classes, tags);
27
+ }
28
+ });
29
+
30
+ return { classes, tags };
31
+ };
@@ -0,0 +1,12 @@
1
+ const glob = require('glob');
2
+
3
+ module.exports = function (globPath) {
4
+ return new Promise((resolve, reject) => {
5
+ glob(globPath, (err, files) => {
6
+ if (err) {
7
+ reject(err);
8
+ }
9
+ resolve(files);
10
+ });
11
+ });
12
+ };
@@ -0,0 +1,18 @@
1
+ module.exports = 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
+ if (!importPath.includes('http') && !importPath.startsWith('url(')) {
9
+ css = css.replace(match[0], '');
10
+ importedCssPaths.unshift(importPath);
11
+ }
12
+ }
13
+
14
+ return {
15
+ css,
16
+ importedCssPaths,
17
+ };
18
+ };
@@ -0,0 +1,9 @@
1
+ const md5 = require('blueimp-md5');
2
+
3
+ module.exports = function (cssFileName) {
4
+ // if (cssFileName.includes('/')) {
5
+ // throw new Error('cssFileName should not contain /');
6
+ // }
7
+
8
+ return 'e' + md5(cssFileName).substring(0, 8);
9
+ };
@@ -0,0 +1,6 @@
1
+ module.exports = function isInsideGlobal(node, func) {
2
+ const parent = node.parent;
3
+ if (!parent) return false;
4
+ if (parent.type === 'pseudo' && parent.value === ':global') return true;
5
+ return isInsideGlobal(parent, func);
6
+ };
@@ -0,0 +1,56 @@
1
+ const recast = require('recast');
2
+ const babelParser = require('recast/parsers/babel');
3
+ const path = require('path');
4
+ const { readFile } = require('fs').promises;
5
+ const fsExists = require('./fsExists');
6
+
7
+ const parseOptions = {
8
+ parser: babelParser,
9
+ };
10
+
11
+ module.exports = async function (script, id, replaceFunction) {
12
+ const ast = recast.parse(script, parseOptions);
13
+ const cssPath = id.replace(/(\.js)|(\.hbs)/, '.css');
14
+ let css;
15
+
16
+ const cssExists = await fsExists(cssPath);
17
+ if (cssExists) {
18
+ css = await readFile(cssPath, 'utf-8');
19
+ }
20
+
21
+ if (!css) {
22
+ return script;
23
+ }
24
+
25
+ recast.visit(ast, {
26
+ visitCallExpression(nodePath) {
27
+ const node = nodePath.node;
28
+ if (
29
+ node.callee.name === 'createTemplateFactory' &&
30
+ node.arguments.length === 1
31
+ ) {
32
+ const blockProp = node.arguments[0].properties.find(
33
+ (prop) => prop.key.value === 'block'
34
+ );
35
+ const opcodes = JSON.parse(blockProp.value.value);
36
+ const newOpcodes = replaceFunction(opcodes, css);
37
+ blockProp.value.value = JSON.stringify(newOpcodes);
38
+
39
+ const fileName = path.basename(cssPath);
40
+ // if (!importPath) {
41
+ // unplugin.addWatchFile(cssPath);
42
+ // }
43
+ const importCss = recast.parse(
44
+ `import './${fileName}';\n`,
45
+ parseOptions
46
+ );
47
+ const importCssNode = importCss.program.body[0];
48
+ ast.program.body.unshift(importCssNode);
49
+ }
50
+
51
+ this.traverse(nodePath);
52
+ },
53
+ });
54
+ const resultScript = recast.print(ast).code;
55
+ return resultScript;
56
+ };
@@ -0,0 +1,32 @@
1
+ const recast = require('recast');
2
+ const babelParser = require('recast/parsers/babel');
3
+
4
+ const parseOptions = {
5
+ parser: babelParser,
6
+ };
7
+
8
+ module.exports = function (script, replaceFunction) {
9
+ const ast = recast.parse(script, parseOptions);
10
+ recast.visit(ast, {
11
+ visitCallExpression(path) {
12
+ const node = path.node;
13
+ if (
14
+ node.callee.name === '__GLIMMER_TEMPLATE' ||
15
+ node.callee.name === 'precompileTemplate'
16
+ ) {
17
+ if (node.arguments[0].type === 'TemplateLiteral') {
18
+ node.arguments[0].quasis[0].value.raw = replaceFunction(
19
+ node.arguments[0].quasis[0].value.raw
20
+ );
21
+ } else if (
22
+ node.arguments[0].type === 'StringLiteral' ||
23
+ node.arguments[0].type === 'Literal'
24
+ ) {
25
+ node.arguments[0].value = replaceFunction(node.arguments[0].value);
26
+ }
27
+ }
28
+ this.traverse(path);
29
+ },
30
+ });
31
+ return recast.print(ast).code;
32
+ };
@@ -0,0 +1,40 @@
1
+ const parser = require('postcss-selector-parser');
2
+ const postcss = require('postcss');
3
+ const isInsideGlobal = require('./isInsideGlobal');
4
+
5
+ function rewriteSelector(sel, postfix) {
6
+ const transform = (selectors) => {
7
+ selectors.walk((selector) => {
8
+ if (selector.type === 'class' && !isInsideGlobal(selector)) {
9
+ selector.value += '_' + postfix;
10
+ } else if (selector.type === 'tag' && !isInsideGlobal(selector)) {
11
+ selector.replaceWith(
12
+ parser.tag({ value: selector.value }),
13
+ parser.className({ value: postfix })
14
+ );
15
+ }
16
+ });
17
+
18
+ // remove :global
19
+ selectors.walk((selector) => {
20
+ if (selector.type === 'pseudo' && selector.value === ':global') {
21
+ selector.replaceWith(...selector.nodes);
22
+ }
23
+ });
24
+ };
25
+ const transformed = parser(transform).processSync(sel);
26
+ return transformed;
27
+ }
28
+
29
+ module.exports = function rewriteCss(css, postfix, fileName) {
30
+ const ast = postcss.parse(css);
31
+ ast.walk((node) => {
32
+ if (node.type === 'rule') {
33
+ node.selector = rewriteSelector(node.selector, postfix);
34
+ }
35
+ });
36
+
37
+ const rewrittenCss = ast.toString();
38
+ return `/* ${fileName} */\n@layer components {\n\n` + rewrittenCss + '\n}\n';
39
+ // return `/* ${fileName} */\n ${rewrittenCss}`;
40
+ };
@@ -0,0 +1,39 @@
1
+ const recast = require('ember-template-recast');
2
+
3
+ module.exports = function rewriteHbs(hbs, classes, tags, postfix) {
4
+ let ast = recast.parse(hbs);
5
+
6
+ recast.traverse(ast, {
7
+ AttrNode(node) {
8
+ if (node.name === 'class' && node.value.chars) {
9
+ const newClasses = node.value.chars.split(' ').map((c) => {
10
+ if (c.trim() && classes.has(c.trim())) {
11
+ return c.trim() + '_' + postfix;
12
+ } else {
13
+ return c;
14
+ }
15
+ });
16
+
17
+ node.value.chars = newClasses.join(' ');
18
+ }
19
+ },
20
+
21
+ ElementNode(node) {
22
+ if (tags.has(node.tag)) {
23
+ // check if class attribute already exists
24
+ const classAttr = node.attributes.find((attr) => attr.name === 'class');
25
+ if (classAttr) {
26
+ classAttr.value.chars += ' ' + postfix;
27
+ } else {
28
+ // push class attribute
29
+ node.attributes.push(
30
+ recast.builders.attr('class', recast.builders.text(postfix))
31
+ );
32
+ }
33
+ }
34
+ },
35
+ });
36
+
37
+ let result = recast.print(ast);
38
+ return result;
39
+ };
@@ -0,0 +1,77 @@
1
+ const fs = require('fs/promises');
2
+ const path = require('path');
3
+ const {
4
+ preprocessEmbeddedTemplates,
5
+ } = require('ember-template-imports/lib/preprocess-embedded-templates.js');
6
+ const {
7
+ TEMPLATE_TAG_NAME,
8
+ TEMPLATE_TAG_PLACEHOLDER,
9
+ } = require('ember-template-imports/lib/util.js');
10
+
11
+ module.exports = function firstClassComponentTemplates() {
12
+ return {
13
+ name: 'preprocess-fccts',
14
+ async resolveId(source, importer, options) {
15
+ if (source.endsWith('.hbs')) return;
16
+
17
+ for (let ext of ['', '.gjs', '.gts']) {
18
+ let result = await this.resolve(source + ext, importer, {
19
+ ...options,
20
+ skipSelf: true,
21
+ });
22
+
23
+ if (result?.external) {
24
+ return;
25
+ }
26
+
27
+ if (FCCT_EXTENSION.test(result?.id)) {
28
+ return resolutionFor(result.id);
29
+ }
30
+ }
31
+ },
32
+
33
+ async load(id) {
34
+ let originalId = this.getModuleInfo(id)?.meta?.fccts?.originalId ?? id;
35
+
36
+ if (originalId !== id) {
37
+ this.addWatchFile(originalId);
38
+ }
39
+
40
+ if (FCCT_EXTENSION.test(originalId)) {
41
+ return await preprocessTemplates(originalId);
42
+ }
43
+ },
44
+ };
45
+ };
46
+
47
+ const FCCT_EXTENSION = /\.g([jt]s)$/;
48
+
49
+ function resolutionFor(originalId) {
50
+ return {
51
+ id: originalId.replace(FCCT_EXTENSION, '.$1'),
52
+ meta: {
53
+ fccts: { originalId },
54
+ },
55
+ };
56
+ }
57
+
58
+ async function preprocessTemplates(id) {
59
+ let ember = (await import('ember-source')).default;
60
+ let contents = await fs.readFile(id, 'utf-8');
61
+
62
+ // This is basically taken directly from `ember-template-imports`
63
+ let result = preprocessEmbeddedTemplates(contents, {
64
+ relativePath: path.relative('.', id),
65
+
66
+ getTemplateLocalsRequirePath: ember.absolutePaths.templateCompiler,
67
+ getTemplateLocalsExportPath: '_GlimmerSyntax.getTemplateLocals',
68
+
69
+ templateTag: TEMPLATE_TAG_NAME,
70
+ templateTagReplacement: TEMPLATE_TAG_PLACEHOLDER,
71
+
72
+ includeSourceMaps: true,
73
+ includeTemplateTokens: true,
74
+ });
75
+
76
+ return result.output;
77
+ }
@@ -0,0 +1,15 @@
1
+ const expect = require('chai').expect;
2
+ const getClassesTagsFromCss = require('../src/getClassesTagsFromCss');
3
+
4
+ describe('rewriteCss', function () {
5
+ it('should return classes and tags that are not in :global', function () {
6
+ const css = '.baz :global(.foo) .bar div :global(p) { color: red; }';
7
+ const { classes, tags } = getClassesTagsFromCss(css);
8
+
9
+ // classes should be baz and bar
10
+ expect(classes.size).to.equal(2);
11
+ expect([...classes]).to.have.members(['baz', 'bar']);
12
+ expect(tags.size).to.equal(1);
13
+ expect([...tags]).to.have.members(['div']);
14
+ });
15
+ });
@@ -0,0 +1,23 @@
1
+ const expect = require('chai').expect;
2
+ const { stub } = require('sinon');
3
+ const getPostfix = require('../src/getPostfix');
4
+
5
+ describe('getPostfix', function () {
6
+ it('should return a string', function () {
7
+ const postfix = getPostfix('foo.css');
8
+
9
+ expect(postfix).to.be.a('string');
10
+ });
11
+
12
+ it('should return a string starting with "e"', function () {
13
+ const postfix = getPostfix('foo.css');
14
+
15
+ expect(postfix).to.match(/^e/);
16
+ });
17
+
18
+ it('should return a string of length 9', function () {
19
+ const postfix = getPostfix('foo.css');
20
+
21
+ expect(postfix).to.have.lengthOf(9);
22
+ });
23
+ });
@@ -0,0 +1,27 @@
1
+ const expect = require('chai').expect;
2
+ const { stub } = require('sinon');
3
+ const rewriteCss = require('../src/rewriteCss');
4
+
5
+ describe('rewriteCss', function () {
6
+ it('should rewrite css', function () {
7
+ const css = '.foo { color: red; }';
8
+ const postfix = 'postfix';
9
+ const fileName = 'foo.css';
10
+ const rewritten = rewriteCss(css, postfix, fileName);
11
+
12
+ expect(rewritten).to.equal(
13
+ `/* foo.css */\n@layer components {\n\n.foo_postfix { color: red; }\n}\n`
14
+ );
15
+ });
16
+
17
+ it('shouldnt rewrite global', function () {
18
+ const css = '.baz :global(.foo p) .bar { color: red; }';
19
+ const postfix = 'postfix';
20
+ const fileName = 'foo.css';
21
+ const rewritten = rewriteCss(css, postfix, fileName);
22
+
23
+ expect(rewritten).to.equal(
24
+ `/* foo.css */\n@layer components {\n\n.baz_postfix .foo p .bar_postfix { color: red; }\n}\n`
25
+ );
26
+ });
27
+ });
package/README.md DELETED
@@ -1,32 +0,0 @@
1
- # ember-scoped-css
2
-
3
- [Short description of the addon.]
4
-
5
-
6
- ## Compatibility
7
-
8
- * Ember.js v3.28 or above
9
- * Ember CLI v3.28 or above
10
- * Node.js v14 or above
11
-
12
-
13
- ## Installation
14
-
15
- ```
16
- ember install ember-scoped-css
17
- ```
18
-
19
-
20
- ## Usage
21
-
22
- [Longer description of how to use the addon in apps.]
23
-
24
-
25
- ## Contributing
26
-
27
- See the [Contributing](CONTRIBUTING.md) guide for details.
28
-
29
-
30
- ## License
31
-
32
- This project is licensed under the [MIT License](LICENSE.md).