rollup-plugin-react-scoped-css 0.5.2 → 0.6.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/.eslintrc.yml +29 -0
- package/.github/workflows/pull-request.yml +2 -1
- package/.vscode/settings.json +8 -0
- package/README.md +31 -6
- package/babel.config.js +6 -2
- package/dist/ast-iterator.js +5 -5
- package/dist/ast-transformer.d.ts +1 -1
- package/dist/ast-transformer.js +6 -6
- package/dist/ast.utils.js +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -17
- package/dist/parsers/classic-parser.js +23 -17
- package/dist/parsers/new-parser.js +22 -16
- package/package.json +10 -1
- package/src/ast-iterator.spec.ts +8 -5
- package/src/ast-iterator.ts +20 -15
- package/src/ast-transformer.spec.ts +9 -6
- package/src/ast-transformer.ts +18 -14
- package/src/ast.utils.spec.ts +56 -41
- package/src/ast.utils.ts +25 -24
- package/src/index.ts +77 -56
- package/src/parsers/classic-parser.ts +44 -30
- package/src/parsers/jsx-parser.model.ts +6 -6
- package/src/parsers/new-parser.ts +47 -31
package/.eslintrc.yml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
env:
|
|
2
|
+
browser: true
|
|
3
|
+
es2021: true
|
|
4
|
+
extends:
|
|
5
|
+
- eslint:recommended
|
|
6
|
+
- plugin:@typescript-eslint/eslint-recommended
|
|
7
|
+
- plugin:@typescript-eslint/recommended
|
|
8
|
+
- plugin:react/recommended
|
|
9
|
+
- plugin:prettier/recommended
|
|
10
|
+
- plugin:react/jsx-runtime
|
|
11
|
+
parser: '@typescript-eslint/parser'
|
|
12
|
+
parserOptions:
|
|
13
|
+
ecmaFeatures:
|
|
14
|
+
jsx: true
|
|
15
|
+
ecmaVersion: "latest"
|
|
16
|
+
sourceType: module
|
|
17
|
+
plugins:
|
|
18
|
+
- react
|
|
19
|
+
- '@typescript-eslint'
|
|
20
|
+
settings:
|
|
21
|
+
react:
|
|
22
|
+
version: "detect"
|
|
23
|
+
ignorePatterns:
|
|
24
|
+
- "**/node_modules/**"
|
|
25
|
+
- "**/__mocks__/**/*.js"
|
|
26
|
+
- "**/dist/**"
|
|
27
|
+
rules: {
|
|
28
|
+
"react/jsx-filename-extension": [1, { "extensions": [".jsx", ".tsx"] }]
|
|
29
|
+
}
|
package/README.md
CHANGED
|
@@ -25,19 +25,44 @@ export default function Sub() {
|
|
|
25
25
|
h1 { color: red; }
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
|
-
|
|
29
28
|
And just like that the styles will be scoped to the component.
|
|
30
29
|
|
|
30
|
+
## Technicality
|
|
31
|
+
since this plugin uses the engine of vue to scope the css to the components, the following sections come straight from the [vue documentation](https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors).
|
|
32
|
+
|
|
33
|
+
### Deep selector
|
|
34
|
+
If you want a selector in scoped styles to be "deep", i.e. affecting child components, you can use the >>> combinator:
|
|
35
|
+
```css
|
|
36
|
+
.a >>> .b { /* ... */ }
|
|
37
|
+
```
|
|
38
|
+
The above will be compiled into:
|
|
39
|
+
```css
|
|
40
|
+
.a[data-v-f3f3eg9] .b { /* ... */ }
|
|
41
|
+
```
|
|
42
|
+
Some pre-processors, such as Sass, may not be able to parse >>> properly. In those cases you can use the /deep/ or ::v-deep combinator instead - both are aliases for >>> and work exactly the same. Based on the example above these two expressions will be compiled to the same output:
|
|
43
|
+
```scss
|
|
44
|
+
.a::v-deep .b { /* ... */ }
|
|
45
|
+
/* or */
|
|
46
|
+
.a /deep/ .b { /* ... */ }
|
|
47
|
+
```
|
|
48
|
+
### Dynamically Generated Content
|
|
49
|
+
DOM content created with dangerouslySetInnerHTML are not affected by scoped styles, but you can still style them using deep selectors.
|
|
50
|
+
|
|
51
|
+
### Also Keep in Mind
|
|
52
|
+
Scoped styles do not eliminate the need for classes. Due to the way browsers render various CSS selectors, p { color: red } will be many times slower when scoped (i.e. when combined with an attribute selector). If you use classes or ids instead, such as in .example { color: red }, then you virtually eliminate that performance hit.
|
|
53
|
+
|
|
54
|
+
Be careful with descendant selectors in recursive components! For a CSS rule with the selector .a .b, if the element that matches .a contains a recursive child component, then all .b in that child component will be matched by the rule.
|
|
55
|
+
|
|
31
56
|
## How to install
|
|
32
57
|
|
|
33
58
|
```sh
|
|
34
|
-
npm i rollup-plugin-react-scoped-css
|
|
59
|
+
$ npm i rollup-plugin-react-scoped-css
|
|
35
60
|
```
|
|
36
61
|
|
|
37
62
|
### Simple Configuration
|
|
38
63
|
|
|
39
64
|
in vite:
|
|
40
|
-
```
|
|
65
|
+
```js
|
|
41
66
|
// vite.config.js
|
|
42
67
|
import { defineConfig } from 'vite'
|
|
43
68
|
import reactRefresh from '@vitejs/plugin-react-refresh'
|
|
@@ -50,7 +75,7 @@ export default defineConfig({
|
|
|
50
75
|
```
|
|
51
76
|
|
|
52
77
|
in rollup:
|
|
53
|
-
```
|
|
78
|
+
```js
|
|
54
79
|
// rollup.config.js
|
|
55
80
|
import reactScopedCssPlugin from 'rollup-plugin-react-scoped-css';
|
|
56
81
|
|
|
@@ -63,7 +88,7 @@ export default {
|
|
|
63
88
|
|
|
64
89
|
### Customizing the plugin
|
|
65
90
|
There are a few options available to customize how the plugin works
|
|
66
|
-
```
|
|
91
|
+
```ts
|
|
67
92
|
{
|
|
68
93
|
/**
|
|
69
94
|
* Which files should be included and parsed by the plugin
|
|
@@ -107,7 +132,7 @@ There are a few options available to customize how the plugin works
|
|
|
107
132
|
### With other rollup plugins
|
|
108
133
|
Since this plugin works in two parts, you might need to expose the first part, then add any other plugin, and then expose the second part of the plugin. This part is automatically handled with vite thanks to the enforce attribute.
|
|
109
134
|
|
|
110
|
-
```
|
|
135
|
+
```js
|
|
111
136
|
const reactScopedPlugins = reactScopedCssPlugin()
|
|
112
137
|
export default {
|
|
113
138
|
//...
|
package/babel.config.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// eslint-disable-next-line no-undef
|
|
1
2
|
module.exports = {
|
|
2
|
-
presets: [
|
|
3
|
-
}
|
|
3
|
+
presets: [
|
|
4
|
+
["@babel/preset-env", { targets: { node: "current" } }],
|
|
5
|
+
"@babel/preset-typescript",
|
|
6
|
+
],
|
|
7
|
+
};
|
package/dist/ast-iterator.js
CHANGED
|
@@ -41,10 +41,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
41
41
|
exports.astIterator = void 0;
|
|
42
42
|
var isNode = function (obj) {
|
|
43
43
|
var _a, _b, _c;
|
|
44
|
-
return typeof obj ===
|
|
45
|
-
typeof ((_a = obj) === null || _a === void 0 ? void 0 : _a.end) ===
|
|
46
|
-
typeof ((_b = obj) === null || _b === void 0 ? void 0 : _b.start) ===
|
|
47
|
-
typeof ((_c = obj) === null || _c === void 0 ? void 0 : _c.type) ===
|
|
44
|
+
return (typeof obj === "object" &&
|
|
45
|
+
typeof ((_a = obj) === null || _a === void 0 ? void 0 : _a.end) === "number" &&
|
|
46
|
+
typeof ((_b = obj) === null || _b === void 0 ? void 0 : _b.start) === "number" &&
|
|
47
|
+
typeof ((_c = obj) === null || _c === void 0 ? void 0 : _c.type) === "string");
|
|
48
48
|
};
|
|
49
49
|
var astIterator = function (ast) {
|
|
50
50
|
var _a, _b, key, _c, _d, _i, nodeIndex, e_1_1;
|
|
@@ -64,7 +64,7 @@ var astIterator = function (ast) {
|
|
|
64
64
|
if (!!_b.done) return [3 /*break*/, 10];
|
|
65
65
|
key = _b.value;
|
|
66
66
|
// Skip useless keys
|
|
67
|
-
if ([
|
|
67
|
+
if (["end", "start", "type"].includes(key)) {
|
|
68
68
|
return [3 /*break*/, 9];
|
|
69
69
|
}
|
|
70
70
|
if (!Array.isArray(ast[key])) return [3 /*break*/, 7];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const astTransformer: (ast: any, callback:
|
|
1
|
+
export declare const astTransformer: (ast: any, callback: (ast: any) => any) => any;
|
package/dist/ast-transformer.js
CHANGED
|
@@ -14,17 +14,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.astTransformer = void 0;
|
|
15
15
|
var isNode = function (obj) {
|
|
16
16
|
var _a, _b, _c;
|
|
17
|
-
return typeof obj ===
|
|
18
|
-
typeof ((_a = obj) === null || _a === void 0 ? void 0 : _a.end) ===
|
|
19
|
-
typeof ((_b = obj) === null || _b === void 0 ? void 0 : _b.start) ===
|
|
20
|
-
typeof ((_c = obj) === null || _c === void 0 ? void 0 : _c.type) ===
|
|
17
|
+
return (typeof obj === "object" &&
|
|
18
|
+
typeof ((_a = obj) === null || _a === void 0 ? void 0 : _a.end) === "number" &&
|
|
19
|
+
typeof ((_b = obj) === null || _b === void 0 ? void 0 : _b.start) === "number" &&
|
|
20
|
+
typeof ((_c = obj) === null || _c === void 0 ? void 0 : _c.type) === "string");
|
|
21
21
|
};
|
|
22
22
|
var astTransformer = function (ast, callback) {
|
|
23
23
|
var e_1, _a;
|
|
24
24
|
try {
|
|
25
25
|
for (var _b = __values(Object.keys(ast)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
26
26
|
var key = _c.value;
|
|
27
|
-
if ([
|
|
27
|
+
if (["end", "start", "type"].includes(key)) {
|
|
28
28
|
continue;
|
|
29
29
|
}
|
|
30
30
|
if (Array.isArray(ast[key])) {
|
|
@@ -45,6 +45,6 @@ var astTransformer = function (ast, callback) {
|
|
|
45
45
|
finally { if (e_1) throw e_1.error; }
|
|
46
46
|
}
|
|
47
47
|
var result = callback(ast);
|
|
48
|
-
return typeof result !==
|
|
48
|
+
return typeof result !== "undefined" ? result : ast;
|
|
49
49
|
};
|
|
50
50
|
exports.astTransformer = astTransformer;
|
package/dist/ast.utils.js
CHANGED
|
@@ -27,13 +27,13 @@ var findImplementation = function (program) {
|
|
|
27
27
|
try {
|
|
28
28
|
for (var _b = __values((0, ast_iterator_1.astIterator)(program)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
29
29
|
var node = _c.value;
|
|
30
|
-
if (node.type !==
|
|
30
|
+
if (node.type !== "ImportDeclaration") {
|
|
31
31
|
continue;
|
|
32
32
|
}
|
|
33
33
|
if (node.specifiers.some(function (n) {
|
|
34
|
-
return [
|
|
35
|
-
})
|
|
36
|
-
|
|
34
|
+
return ["_jsxDEV", "jsxRuntime"].includes(n.local.name);
|
|
35
|
+
}) ||
|
|
36
|
+
["react/jsx-dev-runtime", "react/jsx-runtime"].includes(node.source.value)) {
|
|
37
37
|
return jsx_parser_model_1.ParserImplementations.new;
|
|
38
38
|
}
|
|
39
39
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FilterPattern } from
|
|
2
|
-
import type { Plugin } from
|
|
1
|
+
import { FilterPattern } from "@rollup/pluginutils";
|
|
2
|
+
import type { Plugin } from "rollup";
|
|
3
3
|
export interface ReactScopedCssPluginOptions {
|
|
4
4
|
/**
|
|
5
5
|
* Which files should be included and parsed by the plugin
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
30
30
|
return result;
|
|
31
31
|
};
|
|
32
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
/* eslint-disable no-useless-escape */
|
|
33
34
|
var path = __importStar(require("path"));
|
|
34
35
|
var js_xxhash_1 = require("js-xxhash");
|
|
35
36
|
var pluginutils_1 = require("@rollup/pluginutils");
|
|
@@ -37,17 +38,17 @@ var component_compiler_utils_1 = require("@vue/component-compiler-utils");
|
|
|
37
38
|
var escodegen_1 = require("escodegen");
|
|
38
39
|
var ast_utils_1 = require("./ast.utils");
|
|
39
40
|
var getFilenameFromPath = function (filePath) {
|
|
40
|
-
var parts = filePath.split(
|
|
41
|
-
return parts[parts.length - 1].split(
|
|
41
|
+
var parts = filePath.split("/");
|
|
42
|
+
return parts[parts.length - 1].split("?")[0];
|
|
42
43
|
};
|
|
43
44
|
var getHashFromPath = function (filePath) {
|
|
44
|
-
var search =
|
|
45
|
+
var search = "?scope=";
|
|
45
46
|
var hash = filePath.slice(filePath.indexOf(search) + search.length);
|
|
46
47
|
return hash;
|
|
47
48
|
};
|
|
48
49
|
var generateHash = function (input, seed) {
|
|
49
50
|
if (seed === void 0) { seed = 0; }
|
|
50
|
-
var hashNum = (0, js_xxhash_1.xxHash32)(Buffer.from(input,
|
|
51
|
+
var hashNum = (0, js_xxhash_1.xxHash32)(Buffer.from(input, "utf8"), seed);
|
|
51
52
|
return hashNum.toString(16);
|
|
52
53
|
};
|
|
53
54
|
var addAttributesToCss = function (src, fileName, hash) {
|
|
@@ -62,24 +63,24 @@ var addAttributesToCss = function (src, fileName, hash) {
|
|
|
62
63
|
};
|
|
63
64
|
function reactScopedCssPlugin(optionsIn) {
|
|
64
65
|
if (optionsIn === void 0) { optionsIn = {}; }
|
|
65
|
-
var options = __assign({ hashPrefix:
|
|
66
|
+
var options = __assign({ hashPrefix: "v", styleFileSuffix: "scoped", styleFileExtensions: ["scss", "css", "sass", "less"], jsxFileExtensions: ["jsx", "tsx"] }, optionsIn);
|
|
66
67
|
if (!options.styleFileExtensions || !options.styleFileExtensions.length) {
|
|
67
|
-
throw new Error(
|
|
68
|
+
throw new Error("You need to provide at least one style file extension");
|
|
68
69
|
}
|
|
69
70
|
if (!options.jsxFileExtensions || !options.jsxFileExtensions.length) {
|
|
70
|
-
throw new Error(
|
|
71
|
+
throw new Error("You need to provide at least one jsx file extension");
|
|
71
72
|
}
|
|
72
73
|
var filter = (0, pluginutils_1.createFilter)(options.include, options.exclude);
|
|
73
74
|
var scopedCssRegex = options.styleFileSuffix
|
|
74
|
-
? new RegExp(".".concat(options.styleFileSuffix, ".(").concat(options.styleFileExtensions.join(
|
|
75
|
-
: new RegExp(".(".concat(options.styleFileExtensions.join(
|
|
75
|
+
? new RegExp(".".concat(options.styleFileSuffix, ".(").concat(options.styleFileExtensions.join("|"), ")$"))
|
|
76
|
+
: new RegExp(".(".concat(options.styleFileExtensions.join("|"), ")$"));
|
|
76
77
|
var scopedCssInFileRegex = options.styleFileSuffix
|
|
77
|
-
? new RegExp(".".concat(options.styleFileSuffix, ".(").concat(options.styleFileExtensions.join(
|
|
78
|
-
: new RegExp(".(".concat(options.styleFileExtensions.join(
|
|
79
|
-
var jsxRegex = new RegExp(".(".concat(options.jsxFileExtensions.join(
|
|
78
|
+
? new RegExp(".".concat(options.styleFileSuffix, ".(").concat(options.styleFileExtensions.join("|"), ")(\"|')"))
|
|
79
|
+
: new RegExp(".(".concat(options.styleFileExtensions.join("|"), ")(\"|')"));
|
|
80
|
+
var jsxRegex = new RegExp(".(".concat(options.jsxFileExtensions.join("|"), ")$"));
|
|
80
81
|
return [
|
|
81
82
|
{
|
|
82
|
-
name:
|
|
83
|
+
name: "rollup-plugin-react-scoped-css:pre",
|
|
83
84
|
resolveId: function (source, importer) {
|
|
84
85
|
if (!importer) {
|
|
85
86
|
return;
|
|
@@ -90,10 +91,10 @@ function reactScopedCssPlugin(optionsIn) {
|
|
|
90
91
|
return url;
|
|
91
92
|
}
|
|
92
93
|
},
|
|
93
|
-
enforce:
|
|
94
|
+
enforce: "pre",
|
|
94
95
|
},
|
|
95
96
|
{
|
|
96
|
-
name:
|
|
97
|
+
name: "rollup-plugin-react-scoped-css:post",
|
|
97
98
|
transform: function (code, id) {
|
|
98
99
|
if (!filter(id)) {
|
|
99
100
|
return;
|
|
@@ -108,8 +109,8 @@ function reactScopedCssPlugin(optionsIn) {
|
|
|
108
109
|
var importerHash = getHashFromPath(id);
|
|
109
110
|
return addAttributesToCss(code, getFilenameFromPath(id), "data-".concat(options.hashPrefix, "-").concat(importerHash));
|
|
110
111
|
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
112
|
+
},
|
|
113
|
+
},
|
|
113
114
|
];
|
|
114
115
|
}
|
|
115
116
|
exports.default = reactScopedCssPlugin;
|
|
@@ -42,14 +42,14 @@ var ClassicJsxParser = /** @class */ (function () {
|
|
|
42
42
|
}
|
|
43
43
|
ClassicJsxParser.prototype.isNodeReactFragment = function (node) {
|
|
44
44
|
var _a, _b, _c, _d, _e, _f;
|
|
45
|
-
return ((_c = (_b = (_a = node === null || node === void 0 ? void 0 : node.arguments) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.object) === null || _c === void 0 ? void 0 : _c.name) === "React" &&
|
|
46
|
-
((_f = (_e = (_d = node === null || node === void 0 ? void 0 : node.arguments) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.property) === null || _f === void 0 ? void 0 : _f.name) === "Fragment";
|
|
45
|
+
return (((_c = (_b = (_a = node === null || node === void 0 ? void 0 : node.arguments) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.object) === null || _c === void 0 ? void 0 : _c.name) === "React" &&
|
|
46
|
+
((_f = (_e = (_d = node === null || node === void 0 ? void 0 : node.arguments) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.property) === null || _f === void 0 ? void 0 : _f.name) === "Fragment");
|
|
47
47
|
};
|
|
48
48
|
ClassicJsxParser.prototype.isNodeReactElement = function (node) {
|
|
49
49
|
var _a, _b, _c, _d;
|
|
50
|
-
return (node === null || node === void 0 ? void 0 : node.type) ===
|
|
51
|
-
((_b = (_a = node === null || node === void 0 ? void 0 : node.callee) === null || _a === void 0 ? void 0 : _a.object) === null || _b === void 0 ? void 0 : _b.name) ===
|
|
52
|
-
((_d = (_c = node === null || node === void 0 ? void 0 : node.callee) === null || _c === void 0 ? void 0 : _c.property) === null || _d === void 0 ? void 0 : _d.name) ===
|
|
50
|
+
return ((node === null || node === void 0 ? void 0 : node.type) === "CallExpression" &&
|
|
51
|
+
((_b = (_a = node === null || node === void 0 ? void 0 : node.callee) === null || _a === void 0 ? void 0 : _a.object) === null || _b === void 0 ? void 0 : _b.name) === "React" &&
|
|
52
|
+
((_d = (_c = node === null || node === void 0 ? void 0 : node.callee) === null || _c === void 0 ? void 0 : _c.property) === null || _d === void 0 ? void 0 : _d.name) === "createElement");
|
|
53
53
|
};
|
|
54
54
|
ClassicJsxParser.prototype.extendNodeWithAttributes = function (node, attr) {
|
|
55
55
|
var _this = this;
|
|
@@ -57,15 +57,21 @@ var ClassicJsxParser = /** @class */ (function () {
|
|
|
57
57
|
var arg = (_a = node === null || node === void 0 ? void 0 : node.arguments) === null || _a === void 0 ? void 0 : _a[1];
|
|
58
58
|
return __assign(__assign({}, node), { arguments: node === null || node === void 0 ? void 0 : node.arguments.map(function (v, i) {
|
|
59
59
|
if (i === 1) {
|
|
60
|
-
if (v.type ===
|
|
61
|
-
return __assign(__assign({}, v), { properties: __spreadArray(__spreadArray([], __read(arg.properties), false), [
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
if (v.type === "ObjectExpression") {
|
|
61
|
+
return __assign(__assign({}, v), { properties: __spreadArray(__spreadArray([], __read(arg.properties), false), [_this.createAttributeNode(attr)], false) });
|
|
62
|
+
}
|
|
63
|
+
else if (v.type === "CallExpression" &&
|
|
64
|
+
v.callee.name === "_objectSpread") {
|
|
65
|
+
var args = __spreadArray([], __read(v.arguments), false);
|
|
66
|
+
args[0].properties = __spreadArray(__spreadArray([], __read(args[0].properties), false), [
|
|
67
|
+
_this.createAttributeNode(attr),
|
|
68
|
+
], false);
|
|
69
|
+
return __assign(__assign({}, v), { arguments: args });
|
|
64
70
|
}
|
|
65
71
|
else {
|
|
66
72
|
return {
|
|
67
|
-
type:
|
|
68
|
-
properties: [_this.createAttributeNode(attr)]
|
|
73
|
+
type: "ObjectExpression",
|
|
74
|
+
properties: [_this.createAttributeNode(attr)],
|
|
69
75
|
};
|
|
70
76
|
}
|
|
71
77
|
}
|
|
@@ -74,20 +80,20 @@ var ClassicJsxParser = /** @class */ (function () {
|
|
|
74
80
|
};
|
|
75
81
|
ClassicJsxParser.prototype.createAttributeNode = function (attr) {
|
|
76
82
|
return {
|
|
77
|
-
type:
|
|
83
|
+
type: "Property",
|
|
78
84
|
method: false,
|
|
79
85
|
shorthand: false,
|
|
80
86
|
computed: false,
|
|
81
87
|
key: {
|
|
82
|
-
type:
|
|
83
|
-
name: "\"".concat(attr, "\"")
|
|
88
|
+
type: "Identifier",
|
|
89
|
+
name: "\"".concat(attr, "\""),
|
|
84
90
|
},
|
|
85
91
|
value: {
|
|
86
|
-
type:
|
|
92
|
+
type: "Literal",
|
|
87
93
|
value: true,
|
|
88
|
-
raw: "true"
|
|
94
|
+
raw: "true",
|
|
89
95
|
},
|
|
90
|
-
kind:
|
|
96
|
+
kind: "init",
|
|
91
97
|
};
|
|
92
98
|
};
|
|
93
99
|
return ClassicJsxParser;
|
|
@@ -42,13 +42,13 @@ var NewJsxParser = /** @class */ (function () {
|
|
|
42
42
|
}
|
|
43
43
|
NewJsxParser.prototype.isNodeReactFragment = function (node) {
|
|
44
44
|
var _a, _b, _c, _d;
|
|
45
|
-
return ((_b = (_a = node.arguments) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.type) === "Identifier" &&
|
|
46
|
-
[
|
|
45
|
+
return (((_b = (_a = node.arguments) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.type) === "Identifier" &&
|
|
46
|
+
["_Fragment", "Fragment"].includes((_d = (_c = node.arguments) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.name));
|
|
47
47
|
};
|
|
48
48
|
NewJsxParser.prototype.isNodeReactElement = function (node) {
|
|
49
49
|
var _a;
|
|
50
|
-
return (node === null || node === void 0 ? void 0 : node.type) ===
|
|
51
|
-
[
|
|
50
|
+
return ((node === null || node === void 0 ? void 0 : node.type) === "CallExpression" &&
|
|
51
|
+
["_jsxDEV", "_jsx", "_jsxs", "jsxDev", "jsx", "jsxs"].includes((_a = node === null || node === void 0 ? void 0 : node.callee) === null || _a === void 0 ? void 0 : _a.name));
|
|
52
52
|
};
|
|
53
53
|
NewJsxParser.prototype.extendNodeWithAttributes = function (node, attr) {
|
|
54
54
|
var _this = this;
|
|
@@ -56,15 +56,21 @@ var NewJsxParser = /** @class */ (function () {
|
|
|
56
56
|
var arg = (_a = node === null || node === void 0 ? void 0 : node.arguments) === null || _a === void 0 ? void 0 : _a[1];
|
|
57
57
|
return __assign(__assign({}, node), { arguments: node === null || node === void 0 ? void 0 : node.arguments.map(function (v, i) {
|
|
58
58
|
if (i === 1) {
|
|
59
|
-
if (v.type ===
|
|
60
|
-
return __assign(__assign({}, v), { properties: __spreadArray(__spreadArray([], __read(arg.properties), false), [
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
if (v.type === "ObjectExpression") {
|
|
60
|
+
return __assign(__assign({}, v), { properties: __spreadArray(__spreadArray([], __read(arg.properties), false), [_this.createAttributeNode(attr)], false) });
|
|
61
|
+
}
|
|
62
|
+
else if (v.type === "CallExpression" &&
|
|
63
|
+
v.callee.name === "_objectSpread") {
|
|
64
|
+
var args = __spreadArray([], __read(v.arguments), false);
|
|
65
|
+
args[0].properties = __spreadArray(__spreadArray([], __read(args[0].properties), false), [
|
|
66
|
+
_this.createAttributeNode(attr),
|
|
67
|
+
], false);
|
|
68
|
+
return __assign(__assign({}, v), { arguments: args });
|
|
63
69
|
}
|
|
64
70
|
else {
|
|
65
71
|
return {
|
|
66
|
-
type:
|
|
67
|
-
properties: [_this.createAttributeNode(attr)]
|
|
72
|
+
type: "ObjectExpression",
|
|
73
|
+
properties: [_this.createAttributeNode(attr)],
|
|
68
74
|
};
|
|
69
75
|
}
|
|
70
76
|
}
|
|
@@ -73,20 +79,20 @@ var NewJsxParser = /** @class */ (function () {
|
|
|
73
79
|
};
|
|
74
80
|
NewJsxParser.prototype.createAttributeNode = function (attr) {
|
|
75
81
|
return {
|
|
76
|
-
type:
|
|
82
|
+
type: "Property",
|
|
77
83
|
method: false,
|
|
78
84
|
shorthand: false,
|
|
79
85
|
computed: false,
|
|
80
86
|
key: {
|
|
81
|
-
type:
|
|
82
|
-
name: "\"".concat(attr, "\"")
|
|
87
|
+
type: "Identifier",
|
|
88
|
+
name: "\"".concat(attr, "\""),
|
|
83
89
|
},
|
|
84
90
|
value: {
|
|
85
|
-
type:
|
|
91
|
+
type: "Literal",
|
|
86
92
|
value: true,
|
|
87
|
-
raw: "true"
|
|
93
|
+
raw: "true",
|
|
88
94
|
},
|
|
89
|
-
kind:
|
|
95
|
+
kind: "init",
|
|
90
96
|
};
|
|
91
97
|
};
|
|
92
98
|
return NewJsxParser;
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rollup-plugin-react-scoped-css",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A rollup plugin designed to allow scoped css to be run in react (Compatible with vite and rollup)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"build:watch": "tsc --watch",
|
|
10
|
+
"lint": "eslint '*/**/*.{js,jsx,ts,tsx}'",
|
|
10
11
|
"test": "jest"
|
|
11
12
|
},
|
|
12
13
|
"repository": {
|
|
@@ -49,7 +50,15 @@
|
|
|
49
50
|
"@types/escodegen": "0.0.7",
|
|
50
51
|
"@types/estree": "0.0.50",
|
|
51
52
|
"@types/jest": "^27.0.1",
|
|
53
|
+
"@typescript-eslint/eslint-plugin": "^5.5.0",
|
|
54
|
+
"@typescript-eslint/parser": "^5.5.0",
|
|
52
55
|
"acorn": "^8.6.0",
|
|
56
|
+
"eslint": "^8.3.0",
|
|
57
|
+
"eslint-config-prettier": "^8.3.0",
|
|
58
|
+
"eslint-plugin-import": "^2.25.3",
|
|
59
|
+
"eslint-plugin-node": "^11.1.0",
|
|
60
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
61
|
+
"eslint-plugin-react": "^7.27.1",
|
|
53
62
|
"jest": "^27.0.6",
|
|
54
63
|
"semantic-release": "^18.0.1"
|
|
55
64
|
}
|
package/src/ast-iterator.spec.ts
CHANGED
|
@@ -3,11 +3,14 @@ import { resolve } from "path";
|
|
|
3
3
|
import { parse } from "acorn";
|
|
4
4
|
import { astIterator } from "./ast-iterator";
|
|
5
5
|
|
|
6
|
-
describe(
|
|
7
|
-
it(
|
|
6
|
+
describe("astIterator", () => {
|
|
7
|
+
it("should iterate through all nodes", async () => {
|
|
8
8
|
let iterationCount = 0;
|
|
9
|
-
const file = await readFile(
|
|
10
|
-
|
|
9
|
+
const file = await readFile(
|
|
10
|
+
resolve(__dirname, "./__mocks__/sample-code-file.js"),
|
|
11
|
+
{ encoding: "utf-8" }
|
|
12
|
+
);
|
|
13
|
+
const ast = parse(file, { sourceType: "module", ecmaVersion: 2022 });
|
|
11
14
|
|
|
12
15
|
for (const node of astIterator(ast)) {
|
|
13
16
|
iterationCount++;
|
|
@@ -16,4 +19,4 @@ describe('astIterator', () => {
|
|
|
16
19
|
|
|
17
20
|
expect(iterationCount).toBe(28);
|
|
18
21
|
});
|
|
19
|
-
})
|
|
22
|
+
});
|
package/src/ast-iterator.ts
CHANGED
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
|
|
2
1
|
const isNode = (obj: any) => {
|
|
3
|
-
return
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
return (
|
|
3
|
+
typeof obj === "object" &&
|
|
4
|
+
typeof (obj as any)?.end === "number" &&
|
|
5
|
+
typeof (obj as any)?.start === "number" &&
|
|
6
|
+
typeof (obj as any)?.type === "string"
|
|
7
|
+
);
|
|
8
|
+
};
|
|
8
9
|
|
|
9
10
|
export const astIterator = function* (ast: any): IterableIterator<any> {
|
|
10
|
-
if (!ast) {
|
|
11
|
+
if (!ast) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
11
14
|
|
|
12
|
-
for (
|
|
15
|
+
for (const key of Object.keys(ast)) {
|
|
13
16
|
// Skip useless keys
|
|
14
|
-
if ([
|
|
17
|
+
if (["end", "start", "type"].includes(key)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
15
20
|
|
|
16
21
|
// If key value is an array, iterate over it
|
|
17
22
|
if (Array.isArray(ast[key])) {
|
|
18
|
-
for (
|
|
19
|
-
yield
|
|
23
|
+
for (const nodeIndex in ast[key]) {
|
|
24
|
+
yield* astIterator(ast[key][nodeIndex]);
|
|
20
25
|
}
|
|
21
|
-
} else if(isNode(ast[key])) {
|
|
22
|
-
yield
|
|
26
|
+
} else if (isNode(ast[key])) {
|
|
27
|
+
yield* astIterator(ast[key]);
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
yield ast
|
|
27
|
-
}
|
|
31
|
+
yield ast;
|
|
32
|
+
};
|
|
@@ -3,18 +3,21 @@ import { resolve } from "path";
|
|
|
3
3
|
import { parse } from "acorn";
|
|
4
4
|
import { astTransformer } from "./ast-transformer";
|
|
5
5
|
|
|
6
|
-
describe(
|
|
7
|
-
it(
|
|
6
|
+
describe("astTransformer", () => {
|
|
7
|
+
it("should iterate through all nodes", async () => {
|
|
8
8
|
let iterationCount = 0;
|
|
9
|
-
const file = await readFile(
|
|
10
|
-
|
|
9
|
+
const file = await readFile(
|
|
10
|
+
resolve(__dirname, "./__mocks__/sample-code-file.js"),
|
|
11
|
+
{ encoding: "utf-8" }
|
|
12
|
+
);
|
|
13
|
+
const ast = parse(file, { sourceType: "module", ecmaVersion: 2022 });
|
|
11
14
|
|
|
12
15
|
const newAst = astTransformer(ast, (node) => {
|
|
13
16
|
iterationCount++;
|
|
14
17
|
expect(node).toBeTruthy();
|
|
15
|
-
})
|
|
18
|
+
});
|
|
16
19
|
|
|
17
20
|
expect(newAst).toBeDefined();
|
|
18
21
|
expect(iterationCount).toBe(28);
|
|
19
22
|
});
|
|
20
|
-
})
|
|
23
|
+
});
|
package/src/ast-transformer.ts
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
const isNode = (obj: any) => {
|
|
2
|
-
return
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
return (
|
|
3
|
+
typeof obj === "object" &&
|
|
4
|
+
typeof (obj as any)?.end === "number" &&
|
|
5
|
+
typeof (obj as any)?.start === "number" &&
|
|
6
|
+
typeof (obj as any)?.type === "string"
|
|
7
|
+
);
|
|
8
|
+
};
|
|
7
9
|
|
|
8
|
-
export const astTransformer = (ast: any, callback:
|
|
9
|
-
for (
|
|
10
|
-
if ([
|
|
10
|
+
export const astTransformer = (ast: any, callback: (ast: any) => any) => {
|
|
11
|
+
for (const key of Object.keys(ast)) {
|
|
12
|
+
if (["end", "start", "type"].includes(key)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
11
15
|
|
|
12
16
|
if (Array.isArray(ast[key])) {
|
|
13
|
-
for (
|
|
14
|
-
ast[key][nodeIndex] = astTransformer(ast[key][nodeIndex], callback)
|
|
17
|
+
for (const nodeIndex in ast[key]) {
|
|
18
|
+
ast[key][nodeIndex] = astTransformer(ast[key][nodeIndex], callback);
|
|
15
19
|
}
|
|
16
|
-
} else if(isNode(ast[key])) {
|
|
17
|
-
ast[key] = astTransformer(ast[key], callback)
|
|
20
|
+
} else if (isNode(ast[key])) {
|
|
21
|
+
ast[key] = astTransformer(ast[key], callback);
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
const result = callback(ast);
|
|
22
26
|
|
|
23
|
-
return typeof result !==
|
|
24
|
-
}
|
|
27
|
+
return typeof result !== "undefined" ? result : ast;
|
|
28
|
+
};
|
package/src/ast.utils.spec.ts
CHANGED
|
@@ -1,46 +1,61 @@
|
|
|
1
|
-
import { resolve } from
|
|
2
|
-
import { readFile } from
|
|
3
|
-
import { parse } from
|
|
4
|
-
import { generate } from
|
|
5
|
-
import { addHashAttributesToJsxTagsAst } from
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { parse } from "acorn";
|
|
4
|
+
import { generate } from "escodegen";
|
|
5
|
+
import { addHashAttributesToJsxTagsAst } from "./ast.utils";
|
|
6
6
|
|
|
7
|
-
describe(
|
|
7
|
+
describe("AST Utils", () => {
|
|
8
8
|
beforeEach(() => {
|
|
9
|
-
global.implementation = null
|
|
10
|
-
})
|
|
9
|
+
global.implementation = null;
|
|
10
|
+
});
|
|
11
11
|
|
|
12
|
-
describe(
|
|
13
|
-
it(
|
|
14
|
-
const compiled = await readFile(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
describe("classic implementation", () => {
|
|
13
|
+
it("Should add the attr to the right elements", async () => {
|
|
14
|
+
const compiled = await readFile(
|
|
15
|
+
resolve(__dirname, "./__mocks__/classic-compiled-jsx.js"),
|
|
16
|
+
{ encoding: "utf-8" }
|
|
17
|
+
);
|
|
18
|
+
const compiledWithAttr = await readFile(
|
|
19
|
+
resolve(__dirname, "./__mocks__/classic-compiled-jsx-with-attr.js"),
|
|
20
|
+
{ encoding: "utf-8" }
|
|
21
|
+
);
|
|
22
|
+
const ast = parse(compiled, { ecmaVersion: 2021, sourceType: "module" });
|
|
23
|
+
const modifiedAst = addHashAttributesToJsxTagsAst(ast, "data-v-123456");
|
|
24
|
+
const modifiedCompiled = generate(modifiedAst, {
|
|
25
|
+
format: {
|
|
26
|
+
indent: {
|
|
27
|
+
style: " ",
|
|
28
|
+
},
|
|
29
|
+
quotes: "double",
|
|
21
30
|
},
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(modifiedCompiled).toEqual(compiledWithAttr);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("new implementation", () => {
|
|
38
|
+
it("Should add the attr to the right elements", async () => {
|
|
39
|
+
const compiled = await readFile(
|
|
40
|
+
resolve(__dirname, "./__mocks__/new-compiled-jsx.js"),
|
|
41
|
+
{ encoding: "utf-8" }
|
|
42
|
+
);
|
|
43
|
+
const compiledWithAttr = await readFile(
|
|
44
|
+
resolve(__dirname, "./__mocks__/new-compiled-jsx-with-attr.js"),
|
|
45
|
+
{ encoding: "utf-8" }
|
|
46
|
+
);
|
|
47
|
+
const ast = parse(compiled, { ecmaVersion: 2021, sourceType: "module" });
|
|
48
|
+
const modifiedAst = addHashAttributesToJsxTagsAst(ast, "data-v-123456");
|
|
49
|
+
const modifiedCompiled = generate(modifiedAst, {
|
|
50
|
+
format: {
|
|
51
|
+
indent: {
|
|
52
|
+
style: " ",
|
|
53
|
+
},
|
|
54
|
+
quotes: "double",
|
|
38
55
|
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
})
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(modifiedCompiled).toEqual(compiledWithAttr);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
package/src/ast.utils.ts
CHANGED
|
@@ -1,52 +1,53 @@
|
|
|
1
|
-
import { astIterator } from "./ast-iterator"
|
|
2
|
-
import { astTransformer } from "./ast-transformer"
|
|
3
|
-
import { ClassicJsxParser } from "./parsers/classic-parser"
|
|
4
|
-
import { JsxParser, ParserImplementations } from "./parsers/jsx-parser.model"
|
|
5
|
-
import { NewJsxParser } from "./parsers/new-parser"
|
|
1
|
+
import { astIterator } from "./ast-iterator";
|
|
2
|
+
import { astTransformer } from "./ast-transformer";
|
|
3
|
+
import { ClassicJsxParser } from "./parsers/classic-parser";
|
|
4
|
+
import { JsxParser, ParserImplementations } from "./parsers/jsx-parser.model";
|
|
5
|
+
import { NewJsxParser } from "./parsers/new-parser";
|
|
6
6
|
|
|
7
7
|
declare global {
|
|
8
|
-
|
|
8
|
+
// eslint-disable-next-line
|
|
9
|
+
var implementation: null | ParserImplementations;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
global.implementation = null
|
|
12
|
+
global.implementation = null;
|
|
12
13
|
|
|
13
|
-
const classicParser = new ClassicJsxParser()
|
|
14
|
-
const newParser = new NewJsxParser()
|
|
14
|
+
const classicParser = new ClassicJsxParser();
|
|
15
|
+
const newParser = new NewJsxParser();
|
|
15
16
|
|
|
16
17
|
let parser: JsxParser = classicParser;
|
|
17
18
|
|
|
18
19
|
const findImplementation = (program: any) => {
|
|
19
|
-
|
|
20
|
-
for (
|
|
21
|
-
if (node.type !==
|
|
20
|
+
const implementation: ParserImplementations = ParserImplementations.classic;
|
|
21
|
+
for (const node of astIterator(program)) {
|
|
22
|
+
if (node.type !== "ImportDeclaration") {
|
|
22
23
|
continue;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
if (
|
|
26
|
-
node.specifiers.some((n: any) =>
|
|
27
|
-
[
|
|
28
|
-
||
|
|
27
|
+
node.specifiers.some((n: any) =>
|
|
28
|
+
["_jsxDEV", "jsxRuntime"].includes(n.local.name)
|
|
29
|
+
) ||
|
|
30
|
+
["react/jsx-dev-runtime", "react/jsx-runtime"].includes(node.source.value)
|
|
29
31
|
) {
|
|
30
|
-
return ParserImplementations.new
|
|
32
|
+
return ParserImplementations.new;
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
|
-
return implementation
|
|
34
|
-
}
|
|
35
|
+
return implementation;
|
|
36
|
+
};
|
|
35
37
|
|
|
36
38
|
export function addHashAttributesToJsxTagsAst(program: any, attr: string) {
|
|
37
|
-
|
|
38
39
|
// Once in the program, we can determine which parser to use
|
|
39
40
|
if (global.implementation === null) {
|
|
40
|
-
global.implementation = findImplementation(program)
|
|
41
|
+
global.implementation = findImplementation(program);
|
|
41
42
|
|
|
42
43
|
if (global.implementation === ParserImplementations.new) {
|
|
43
|
-
parser = newParser
|
|
44
|
+
parser = newParser;
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
return astTransformer(program, (node: any) => {
|
|
48
49
|
if (parser.isNodeReactElement(node) && !parser.isNodeReactFragment(node)) {
|
|
49
|
-
return parser.extendNodeWithAttributes(node, attr)
|
|
50
|
+
return parser.extendNodeWithAttributes(node, attr);
|
|
50
51
|
}
|
|
51
|
-
})
|
|
52
|
-
}
|
|
52
|
+
});
|
|
53
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
/* eslint-disable no-useless-escape */
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { xxHash32 } from "js-xxhash";
|
|
4
|
+
import { createFilter, FilterPattern } from "@rollup/pluginutils";
|
|
5
|
+
import { compileStyle } from "@vue/component-compiler-utils";
|
|
6
|
+
import { generate } from "escodegen";
|
|
6
7
|
|
|
7
|
-
import type { Plugin } from
|
|
8
|
-
import { addHashAttributesToJsxTagsAst } from
|
|
8
|
+
import type { Plugin } from "rollup";
|
|
9
|
+
import { addHashAttributesToJsxTagsAst } from "./ast.utils";
|
|
9
10
|
|
|
10
11
|
const getFilenameFromPath = (filePath: string) => {
|
|
11
|
-
const parts = filePath.split(
|
|
12
|
-
return parts[parts.length - 1].split(
|
|
13
|
-
}
|
|
12
|
+
const parts = filePath.split("/");
|
|
13
|
+
return parts[parts.length - 1].split("?")[0];
|
|
14
|
+
};
|
|
14
15
|
|
|
15
16
|
const getHashFromPath = (filePath: string) => {
|
|
16
|
-
const search =
|
|
17
|
-
const hash = filePath.slice(filePath.indexOf(search) + search.length)
|
|
18
|
-
return hash
|
|
19
|
-
}
|
|
17
|
+
const search = "?scope=";
|
|
18
|
+
const hash = filePath.slice(filePath.indexOf(search) + search.length);
|
|
19
|
+
return hash;
|
|
20
|
+
};
|
|
20
21
|
|
|
21
|
-
const generateHash = (input: string, seed
|
|
22
|
-
|
|
22
|
+
const generateHash = (input: string, seed = 0) => {
|
|
23
|
+
const hashNum = xxHash32(Buffer.from(input, "utf8"), seed);
|
|
23
24
|
return hashNum.toString(16);
|
|
24
|
-
}
|
|
25
|
+
};
|
|
25
26
|
|
|
26
27
|
const addAttributesToCss = (src: string, fileName: string, hash: string) => {
|
|
27
28
|
const { code } = compileStyle({
|
|
@@ -30,83 +31,93 @@ const addAttributesToCss = (src: string, fileName: string, hash: string) => {
|
|
|
30
31
|
id: hash,
|
|
31
32
|
scoped: true,
|
|
32
33
|
trim: true,
|
|
33
|
-
})
|
|
34
|
+
});
|
|
34
35
|
|
|
35
|
-
return code
|
|
36
|
-
}
|
|
36
|
+
return code;
|
|
37
|
+
};
|
|
37
38
|
|
|
38
39
|
export interface ReactScopedCssPluginOptions {
|
|
39
40
|
/**
|
|
40
41
|
* Which files should be included and parsed by the plugin
|
|
41
42
|
* Default: undefined
|
|
42
43
|
*/
|
|
43
|
-
include?: FilterPattern
|
|
44
|
+
include?: FilterPattern;
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* Which files should be exluded and that should not be parsed by the plugin
|
|
47
48
|
* Default: undefined
|
|
48
49
|
*/
|
|
49
|
-
exclude?: FilterPattern
|
|
50
|
+
exclude?: FilterPattern;
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
53
|
* If you want to customize the stylesheet file pattern
|
|
53
54
|
* if undefined or '' is passed, all files will be evaluated
|
|
54
55
|
* Default: 'scoped'
|
|
55
56
|
*/
|
|
56
|
-
styleFileSuffix?: string
|
|
57
|
+
styleFileSuffix?: string;
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
60
|
* If you want to customize the attribute prefix that is added to the jsx elements
|
|
60
61
|
* Default: 'v'
|
|
61
62
|
*/
|
|
62
|
-
hashPrefix?: string
|
|
63
|
+
hashPrefix?: string;
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* If you want to customize the stylesheet extensions
|
|
66
67
|
* Default: ['scss', 'css', 'sass', 'less']
|
|
67
68
|
*/
|
|
68
|
-
|
|
69
|
+
styleFileExtensions?: string[];
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
72
|
* If you have jsx in other file extensions
|
|
72
73
|
* Default: ['jsx', 'tsx']
|
|
73
74
|
*/
|
|
74
|
-
jsxFileExtensions?: string[]
|
|
75
|
+
jsxFileExtensions?: string[];
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
export interface VitePartialPlugin extends Plugin {
|
|
78
|
-
enforce?: "pre" | "post"
|
|
79
|
+
enforce?: "pre" | "post";
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
export default function reactScopedCssPlugin(
|
|
82
|
+
export default function reactScopedCssPlugin(
|
|
83
|
+
optionsIn: ReactScopedCssPluginOptions = {}
|
|
84
|
+
): VitePartialPlugin[] {
|
|
82
85
|
const options: Partial<ReactScopedCssPluginOptions> = {
|
|
83
|
-
hashPrefix:
|
|
84
|
-
styleFileSuffix:
|
|
85
|
-
styleFileExtensions: [
|
|
86
|
-
jsxFileExtensions: [
|
|
87
|
-
...optionsIn
|
|
88
|
-
}
|
|
86
|
+
hashPrefix: "v",
|
|
87
|
+
styleFileSuffix: "scoped",
|
|
88
|
+
styleFileExtensions: ["scss", "css", "sass", "less"],
|
|
89
|
+
jsxFileExtensions: ["jsx", "tsx"],
|
|
90
|
+
...optionsIn,
|
|
91
|
+
};
|
|
89
92
|
|
|
90
93
|
if (!options.styleFileExtensions || !options.styleFileExtensions.length) {
|
|
91
|
-
throw new Error(
|
|
94
|
+
throw new Error("You need to provide at least one style file extension");
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
if (!options.jsxFileExtensions || !options.jsxFileExtensions.length) {
|
|
95
|
-
throw new Error(
|
|
98
|
+
throw new Error("You need to provide at least one jsx file extension");
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
const filter = createFilter(
|
|
99
|
-
const scopedCssRegex = options.styleFileSuffix
|
|
100
|
-
? new RegExp(
|
|
101
|
-
|
|
101
|
+
const filter = createFilter(options.include, options.exclude);
|
|
102
|
+
const scopedCssRegex = options.styleFileSuffix
|
|
103
|
+
? new RegExp(
|
|
104
|
+
`\.${options.styleFileSuffix}\.(${options.styleFileExtensions.join(
|
|
105
|
+
"|"
|
|
106
|
+
)})$`
|
|
107
|
+
)
|
|
108
|
+
: new RegExp(`\.(${options.styleFileExtensions.join("|")})$`);
|
|
102
109
|
const scopedCssInFileRegex = options.styleFileSuffix
|
|
103
|
-
? new RegExp(
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
? new RegExp(
|
|
111
|
+
`\.${options.styleFileSuffix}\.(${options.styleFileExtensions.join(
|
|
112
|
+
"|"
|
|
113
|
+
)})(\"|\')`
|
|
114
|
+
)
|
|
115
|
+
: new RegExp(`\.(${options.styleFileExtensions.join("|")})(\"|\')`);
|
|
116
|
+
const jsxRegex = new RegExp(`\.(${options.jsxFileExtensions.join("|")})$`);
|
|
106
117
|
|
|
107
118
|
return [
|
|
108
119
|
{
|
|
109
|
-
name:
|
|
120
|
+
name: "rollup-plugin-react-scoped-css:pre",
|
|
110
121
|
resolveId(source, importer) {
|
|
111
122
|
if (!importer) {
|
|
112
123
|
return;
|
|
@@ -114,30 +125,40 @@ export default function reactScopedCssPlugin(optionsIn: ReactScopedCssPluginOpti
|
|
|
114
125
|
|
|
115
126
|
if (scopedCssRegex.test(source) && jsxRegex.test(importer)) {
|
|
116
127
|
const importerHash = generateHash(importer);
|
|
117
|
-
const url = path.resolve(
|
|
118
|
-
|
|
128
|
+
const url = path.resolve(
|
|
129
|
+
path.dirname(importer),
|
|
130
|
+
`${source}?scope=${importerHash}`
|
|
131
|
+
);
|
|
132
|
+
return url;
|
|
119
133
|
}
|
|
120
134
|
},
|
|
121
|
-
enforce:
|
|
135
|
+
enforce: "pre",
|
|
122
136
|
},
|
|
123
137
|
{
|
|
124
|
-
name:
|
|
138
|
+
name: "rollup-plugin-react-scoped-css:post",
|
|
125
139
|
transform(code, id) {
|
|
126
140
|
if (!filter(id)) {
|
|
127
141
|
return;
|
|
128
142
|
}
|
|
129
143
|
if (scopedCssInFileRegex.test(code)) {
|
|
130
144
|
const importerHash = generateHash(id);
|
|
131
|
-
const program = this.parse(code)
|
|
132
|
-
const newAst = addHashAttributesToJsxTagsAst(
|
|
133
|
-
|
|
145
|
+
const program = this.parse(code);
|
|
146
|
+
const newAst = addHashAttributesToJsxTagsAst(
|
|
147
|
+
program,
|
|
148
|
+
`data-${options.hashPrefix}-${importerHash}`
|
|
149
|
+
);
|
|
150
|
+
return generate(newAst);
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
if (scopedCssRegex.test(getFilenameFromPath(id))) {
|
|
137
154
|
const importerHash = getHashFromPath(id);
|
|
138
|
-
return addAttributesToCss(
|
|
155
|
+
return addAttributesToCss(
|
|
156
|
+
code,
|
|
157
|
+
getFilenameFromPath(id),
|
|
158
|
+
`data-${options.hashPrefix}-${importerHash}`
|
|
159
|
+
);
|
|
139
160
|
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
]
|
|
143
|
-
}
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
}
|
|
@@ -2,57 +2,71 @@ import { JsxParser } from "./jsx-parser.model";
|
|
|
2
2
|
|
|
3
3
|
export class ClassicJsxParser implements JsxParser {
|
|
4
4
|
isNodeReactFragment(node: any) {
|
|
5
|
-
return
|
|
5
|
+
return (
|
|
6
|
+
node?.arguments?.[0]?.object?.name === "React" &&
|
|
6
7
|
node?.arguments?.[0]?.property?.name === "Fragment"
|
|
8
|
+
);
|
|
7
9
|
}
|
|
8
10
|
isNodeReactElement(node: any) {
|
|
9
|
-
return
|
|
10
|
-
node?.
|
|
11
|
-
node?.callee?.
|
|
11
|
+
return (
|
|
12
|
+
node?.type === "CallExpression" &&
|
|
13
|
+
node?.callee?.object?.name === "React" &&
|
|
14
|
+
node?.callee?.property?.name === "createElement"
|
|
15
|
+
);
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
public extendNodeWithAttributes(node: any, attr: string) {
|
|
15
|
-
const arg = node?.arguments?.[1]
|
|
19
|
+
const arg = node?.arguments?.[1];
|
|
16
20
|
return {
|
|
17
21
|
...node,
|
|
18
22
|
arguments: node?.arguments.map((v: any, i: number) => {
|
|
19
23
|
if (i === 1) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
if (v.type === "ObjectExpression") {
|
|
25
|
+
return {
|
|
26
|
+
...v,
|
|
27
|
+
properties: [...arg.properties, this.createAttributeNode(attr)],
|
|
28
|
+
};
|
|
29
|
+
} else if (
|
|
30
|
+
v.type === "CallExpression" &&
|
|
31
|
+
v.callee.name === "_objectSpread"
|
|
32
|
+
) {
|
|
33
|
+
const args = [...v.arguments];
|
|
34
|
+
args[0].properties = [
|
|
35
|
+
...args[0].properties,
|
|
36
|
+
this.createAttributeNode(attr),
|
|
37
|
+
];
|
|
38
|
+
return {
|
|
39
|
+
...v,
|
|
40
|
+
arguments: args,
|
|
41
|
+
};
|
|
42
|
+
} else {
|
|
43
|
+
return {
|
|
44
|
+
type: "ObjectExpression",
|
|
45
|
+
properties: [this.createAttributeNode(attr)],
|
|
46
|
+
};
|
|
34
47
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
48
|
+
}
|
|
49
|
+
return v;
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
private createAttributeNode(attr: string) {
|
|
41
55
|
return {
|
|
42
|
-
type:
|
|
56
|
+
type: "Property",
|
|
43
57
|
method: false,
|
|
44
58
|
shorthand: false,
|
|
45
59
|
computed: false,
|
|
46
60
|
key: {
|
|
47
|
-
type:
|
|
48
|
-
name: `"${attr}"
|
|
61
|
+
type: "Identifier",
|
|
62
|
+
name: `"${attr}"`,
|
|
49
63
|
},
|
|
50
64
|
value: {
|
|
51
|
-
type:
|
|
65
|
+
type: "Literal",
|
|
52
66
|
value: true,
|
|
53
|
-
raw: "true"
|
|
67
|
+
raw: "true",
|
|
54
68
|
},
|
|
55
|
-
kind:
|
|
56
|
-
}
|
|
69
|
+
kind: "init",
|
|
70
|
+
};
|
|
57
71
|
}
|
|
58
|
-
}
|
|
72
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export interface JsxParser {
|
|
2
|
-
isNodeReactElement: (node: any) => boolean
|
|
3
|
-
isNodeReactFragment: (node: any) => boolean
|
|
4
|
-
extendNodeWithAttributes: (node: any, attr: string) => any
|
|
2
|
+
isNodeReactElement: (node: any) => boolean;
|
|
3
|
+
isNodeReactFragment: (node: any) => boolean;
|
|
4
|
+
extendNodeWithAttributes: (node: any, attr: string) => any;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export enum ParserImplementations {
|
|
8
|
-
new =
|
|
9
|
-
classic =
|
|
10
|
-
}
|
|
8
|
+
new = "new",
|
|
9
|
+
classic = "classic",
|
|
10
|
+
}
|
|
@@ -2,57 +2,73 @@ import { JsxParser } from "./jsx-parser.model";
|
|
|
2
2
|
|
|
3
3
|
export class NewJsxParser implements JsxParser {
|
|
4
4
|
isNodeReactFragment(node: any) {
|
|
5
|
-
return
|
|
6
|
-
|
|
5
|
+
return (
|
|
6
|
+
node.arguments?.[0]?.type === "Identifier" &&
|
|
7
|
+
["_Fragment", "Fragment"].includes(node.arguments?.[0]?.name)
|
|
8
|
+
);
|
|
7
9
|
}
|
|
8
|
-
|
|
10
|
+
|
|
9
11
|
isNodeReactElement(node: any) {
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
+
return (
|
|
13
|
+
node?.type === "CallExpression" &&
|
|
14
|
+
["_jsxDEV", "_jsx", "_jsxs", "jsxDev", "jsx", "jsxs"].includes(
|
|
15
|
+
node?.callee?.name
|
|
16
|
+
)
|
|
17
|
+
);
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
public extendNodeWithAttributes(node: any, attr: string) {
|
|
15
|
-
const arg = node?.arguments?.[1]
|
|
21
|
+
const arg = node?.arguments?.[1];
|
|
16
22
|
return {
|
|
17
23
|
...node,
|
|
18
24
|
arguments: node?.arguments.map((v: any, i: number) => {
|
|
19
25
|
if (i === 1) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
if (v.type === "ObjectExpression") {
|
|
27
|
+
return {
|
|
28
|
+
...v,
|
|
29
|
+
properties: [...arg.properties, this.createAttributeNode(attr)],
|
|
30
|
+
};
|
|
31
|
+
} else if (
|
|
32
|
+
v.type === "CallExpression" &&
|
|
33
|
+
v.callee.name === "_objectSpread"
|
|
34
|
+
) {
|
|
35
|
+
const args = [...v.arguments];
|
|
36
|
+
args[0].properties = [
|
|
37
|
+
...args[0].properties,
|
|
38
|
+
this.createAttributeNode(attr),
|
|
39
|
+
];
|
|
40
|
+
return {
|
|
41
|
+
...v,
|
|
42
|
+
arguments: args,
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
return {
|
|
46
|
+
type: "ObjectExpression",
|
|
47
|
+
properties: [this.createAttributeNode(attr)],
|
|
48
|
+
};
|
|
34
49
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
50
|
+
}
|
|
51
|
+
return v;
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
private createAttributeNode(attr: string) {
|
|
41
57
|
return {
|
|
42
|
-
type:
|
|
58
|
+
type: "Property",
|
|
43
59
|
method: false,
|
|
44
60
|
shorthand: false,
|
|
45
61
|
computed: false,
|
|
46
62
|
key: {
|
|
47
|
-
type:
|
|
48
|
-
name: `"${attr}"
|
|
63
|
+
type: "Identifier",
|
|
64
|
+
name: `"${attr}"`,
|
|
49
65
|
},
|
|
50
66
|
value: {
|
|
51
|
-
type:
|
|
67
|
+
type: "Literal",
|
|
52
68
|
value: true,
|
|
53
|
-
raw: "true"
|
|
69
|
+
raw: "true",
|
|
54
70
|
},
|
|
55
|
-
kind:
|
|
56
|
-
}
|
|
71
|
+
kind: "init",
|
|
72
|
+
};
|
|
57
73
|
}
|
|
58
|
-
}
|
|
74
|
+
}
|