rollup-plugin-react-scoped-css 0.4.1 → 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 +20 -0
- package/.github/workflows/release.yml +30 -0
- package/.releaserc +13 -0
- package/.vscode/settings.json +8 -0
- package/README.md +55 -7
- package/babel.config.js +6 -2
- package/dist/ast-iterator.d.ts +1 -0
- package/dist/ast-iterator.js +114 -0
- package/dist/ast-transformer.d.ts +1 -0
- package/dist/ast-transformer.js +50 -0
- package/dist/ast.utils.d.ts +4 -0
- package/dist/ast.utils.js +49 -108
- package/dist/index.d.ts +2 -2
- package/dist/index.js +24 -23
- package/dist/parsers/classic-parser.d.ts +3 -2
- package/dist/parsers/classic-parser.js +87 -24
- package/dist/parsers/jsx-parser.model.d.ts +0 -3
- package/dist/parsers/new-parser.d.ts +3 -2
- package/dist/parsers/new-parser.js +86 -23
- package/package.json +18 -3
- package/src/__mocks__/{compiled-jsx-with-attr.js → classic-compiled-jsx-with-attr.js} +0 -0
- package/src/__mocks__/{compiled-jsx.js → classic-compiled-jsx.js} +0 -0
- package/src/__mocks__/new-compiled-jsx-with-attr.js +101 -0
- package/src/__mocks__/new-compiled-jsx.js +64 -0
- package/src/__mocks__/sample-code-file.js +4 -0
- package/src/ast-iterator.spec.ts +22 -0
- package/src/ast-iterator.ts +32 -0
- package/src/ast-transformer.spec.ts +23 -0
- package/src/ast-transformer.ts +28 -0
- package/src/ast.utils.spec.ts +59 -20
- package/src/ast.utils.ts +41 -99
- package/src/index.ts +77 -57
- package/src/parsers/classic-parser.ts +72 -0
- package/src/parsers/jsx-parser.model.ts +10 -0
- package/src/parsers/new-parser.ts +74 -0
- package/tsconfig.json +2 -1
- package/dist/parsers/base-parser.d.ts +0 -15
- package/dist/parsers/base-parser.js +0 -109
- package/dist/parsers/jsx-parser.interface.d.ts +0 -6
- package/dist/parsers/jsx-parser.interface.js +0 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const isNode = (obj: any) => {
|
|
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
|
+
};
|
|
9
|
+
|
|
10
|
+
export const astIterator = function* (ast: any): IterableIterator<any> {
|
|
11
|
+
if (!ast) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const key of Object.keys(ast)) {
|
|
16
|
+
// Skip useless keys
|
|
17
|
+
if (["end", "start", "type"].includes(key)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// If key value is an array, iterate over it
|
|
22
|
+
if (Array.isArray(ast[key])) {
|
|
23
|
+
for (const nodeIndex in ast[key]) {
|
|
24
|
+
yield* astIterator(ast[key][nodeIndex]);
|
|
25
|
+
}
|
|
26
|
+
} else if (isNode(ast[key])) {
|
|
27
|
+
yield* astIterator(ast[key]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
yield ast;
|
|
32
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { parse } from "acorn";
|
|
4
|
+
import { astTransformer } from "./ast-transformer";
|
|
5
|
+
|
|
6
|
+
describe("astTransformer", () => {
|
|
7
|
+
it("should iterate through all nodes", async () => {
|
|
8
|
+
let iterationCount = 0;
|
|
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 });
|
|
14
|
+
|
|
15
|
+
const newAst = astTransformer(ast, (node) => {
|
|
16
|
+
iterationCount++;
|
|
17
|
+
expect(node).toBeTruthy();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(newAst).toBeDefined();
|
|
21
|
+
expect(iterationCount).toBe(28);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const isNode = (obj: any) => {
|
|
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
|
+
};
|
|
9
|
+
|
|
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
|
+
}
|
|
15
|
+
|
|
16
|
+
if (Array.isArray(ast[key])) {
|
|
17
|
+
for (const nodeIndex in ast[key]) {
|
|
18
|
+
ast[key][nodeIndex] = astTransformer(ast[key][nodeIndex], callback);
|
|
19
|
+
}
|
|
20
|
+
} else if (isNode(ast[key])) {
|
|
21
|
+
ast[key] = astTransformer(ast[key], callback);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const result = callback(ast);
|
|
26
|
+
|
|
27
|
+
return typeof result !== "undefined" ? result : ast;
|
|
28
|
+
};
|
package/src/ast.utils.spec.ts
CHANGED
|
@@ -1,22 +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(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const ast = parse(compiled, { ecmaVersion: 2021, sourceType: "module" })
|
|
12
|
-
const modifiedAst = addHashAttributesToJsxTagsAst(ast, 'data-v-123456')
|
|
13
|
-
const modifiedCompiled = generate(modifiedAst, { format: {
|
|
14
|
-
indent: {
|
|
15
|
-
style: ' ',
|
|
16
|
-
},
|
|
17
|
-
quotes: 'double',
|
|
18
|
-
} })
|
|
7
|
+
describe("AST Utils", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
global.implementation = null;
|
|
10
|
+
});
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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",
|
|
30
|
+
},
|
|
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",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(modifiedCompiled).toEqual(compiledWithAttr);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
package/src/ast.utils.ts
CHANGED
|
@@ -1,111 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* @returns
|
|
7
|
-
*/
|
|
8
|
-
function traverse(ast: any, callback: Function) {
|
|
9
|
-
for (let key of Object.keys(ast)) {
|
|
10
|
-
if (['end', 'start', 'type'].includes(key)) { continue; }
|
|
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";
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
} else if(isNode(ast[key])) {
|
|
17
|
-
ast[key] = traverse(ast[key], callback)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const result = callback(ast);
|
|
22
|
-
|
|
23
|
-
return typeof result !== 'undefined' ? result : ast
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function isNode(obj: unknown) {
|
|
27
|
-
return typeof obj === 'object' &&
|
|
28
|
-
typeof (obj as any)?.end === 'number' &&
|
|
29
|
-
typeof (obj as any)?.start === 'number' &&
|
|
30
|
-
typeof (obj as any)?.type === 'string'
|
|
7
|
+
declare global {
|
|
8
|
+
// eslint-disable-next-line
|
|
9
|
+
var implementation: null | ParserImplementations;
|
|
31
10
|
}
|
|
32
11
|
|
|
33
|
-
|
|
34
|
-
return node?.type === 'CallExpression' &&
|
|
35
|
-
node?.callee?.object?.name === 'React' &&
|
|
36
|
-
node?.callee?.property?.name === 'createElement'
|
|
37
|
-
}
|
|
12
|
+
global.implementation = null;
|
|
38
13
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
['_jsxDEV', '_jsx', '_jsxs'].includes(node?.callee?.name)
|
|
42
|
-
}
|
|
14
|
+
const classicParser = new ClassicJsxParser();
|
|
15
|
+
const newParser = new NewJsxParser();
|
|
43
16
|
|
|
44
|
-
|
|
45
|
-
return node?.arguments?.[0]?.object?.name === "React" &&
|
|
46
|
-
node?.arguments?.[0]?.property?.name === "Fragment"
|
|
47
|
-
}
|
|
17
|
+
let parser: JsxParser = classicParser;
|
|
48
18
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
19
|
+
const findImplementation = (program: any) => {
|
|
20
|
+
const implementation: ParserImplementations = ParserImplementations.classic;
|
|
21
|
+
for (const node of astIterator(program)) {
|
|
22
|
+
if (node.type !== "ImportDeclaration") {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
53
25
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
26
|
+
if (
|
|
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)
|
|
31
|
+
) {
|
|
32
|
+
return ParserImplementations.new;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return implementation;
|
|
36
|
+
};
|
|
58
37
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
38
|
+
export function addHashAttributesToJsxTagsAst(program: any, attr: string) {
|
|
39
|
+
// Once in the program, we can determine which parser to use
|
|
40
|
+
if (global.implementation === null) {
|
|
41
|
+
global.implementation = findImplementation(program);
|
|
63
42
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
method: false,
|
|
68
|
-
shorthand: false,
|
|
69
|
-
computed: false,
|
|
70
|
-
key: {
|
|
71
|
-
type: 'Identifier',
|
|
72
|
-
name: `"${attr}"`
|
|
73
|
-
},
|
|
74
|
-
value: {
|
|
75
|
-
type: 'Literal',
|
|
76
|
-
value: true,
|
|
77
|
-
raw: "true"
|
|
78
|
-
},
|
|
79
|
-
kind: 'init'
|
|
43
|
+
if (global.implementation === ParserImplementations.new) {
|
|
44
|
+
parser = newParser;
|
|
45
|
+
}
|
|
80
46
|
}
|
|
81
|
-
}
|
|
82
47
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const arg = node?.arguments?.[1]
|
|
87
|
-
return {
|
|
88
|
-
...node,
|
|
89
|
-
arguments: node?.arguments.map((v: any, i: number) => {
|
|
90
|
-
if (i === 1) {
|
|
91
|
-
if (v.type === 'ObjectExpression') {
|
|
92
|
-
return {
|
|
93
|
-
...v,
|
|
94
|
-
properties: [
|
|
95
|
-
...arg.properties,
|
|
96
|
-
createAttributeNode(attr)
|
|
97
|
-
]
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
return {
|
|
101
|
-
type: 'ObjectExpression',
|
|
102
|
-
properties: [createAttributeNode(attr)]
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return v
|
|
107
|
-
})
|
|
108
|
-
}
|
|
48
|
+
return astTransformer(program, (node: any) => {
|
|
49
|
+
if (parser.isNodeReactElement(node) && !parser.isNodeReactFragment(node)) {
|
|
50
|
+
return parser.extendNodeWithAttributes(node, attr);
|
|
109
51
|
}
|
|
110
|
-
})
|
|
111
|
-
}
|
|
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,31 +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
|
-
|
|
130
143
|
if (scopedCssInFileRegex.test(code)) {
|
|
131
144
|
const importerHash = generateHash(id);
|
|
132
|
-
const program = this.parse(code)
|
|
133
|
-
const newAst = addHashAttributesToJsxTagsAst(
|
|
134
|
-
|
|
145
|
+
const program = this.parse(code);
|
|
146
|
+
const newAst = addHashAttributesToJsxTagsAst(
|
|
147
|
+
program,
|
|
148
|
+
`data-${options.hashPrefix}-${importerHash}`
|
|
149
|
+
);
|
|
150
|
+
return generate(newAst);
|
|
135
151
|
}
|
|
136
152
|
|
|
137
153
|
if (scopedCssRegex.test(getFilenameFromPath(id))) {
|
|
138
154
|
const importerHash = getHashFromPath(id);
|
|
139
|
-
return addAttributesToCss(
|
|
155
|
+
return addAttributesToCss(
|
|
156
|
+
code,
|
|
157
|
+
getFilenameFromPath(id),
|
|
158
|
+
`data-${options.hashPrefix}-${importerHash}`
|
|
159
|
+
);
|
|
140
160
|
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
]
|
|
144
|
-
}
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { JsxParser } from "./jsx-parser.model";
|
|
2
|
+
|
|
3
|
+
export class ClassicJsxParser implements JsxParser {
|
|
4
|
+
isNodeReactFragment(node: any) {
|
|
5
|
+
return (
|
|
6
|
+
node?.arguments?.[0]?.object?.name === "React" &&
|
|
7
|
+
node?.arguments?.[0]?.property?.name === "Fragment"
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
isNodeReactElement(node: any) {
|
|
11
|
+
return (
|
|
12
|
+
node?.type === "CallExpression" &&
|
|
13
|
+
node?.callee?.object?.name === "React" &&
|
|
14
|
+
node?.callee?.property?.name === "createElement"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public extendNodeWithAttributes(node: any, attr: string) {
|
|
19
|
+
const arg = node?.arguments?.[1];
|
|
20
|
+
return {
|
|
21
|
+
...node,
|
|
22
|
+
arguments: node?.arguments.map((v: any, i: number) => {
|
|
23
|
+
if (i === 1) {
|
|
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
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return v;
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private createAttributeNode(attr: string) {
|
|
55
|
+
return {
|
|
56
|
+
type: "Property",
|
|
57
|
+
method: false,
|
|
58
|
+
shorthand: false,
|
|
59
|
+
computed: false,
|
|
60
|
+
key: {
|
|
61
|
+
type: "Identifier",
|
|
62
|
+
name: `"${attr}"`,
|
|
63
|
+
},
|
|
64
|
+
value: {
|
|
65
|
+
type: "Literal",
|
|
66
|
+
value: true,
|
|
67
|
+
raw: "true",
|
|
68
|
+
},
|
|
69
|
+
kind: "init",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface JsxParser {
|
|
2
|
+
isNodeReactElement: (node: any) => boolean;
|
|
3
|
+
isNodeReactFragment: (node: any) => boolean;
|
|
4
|
+
extendNodeWithAttributes: (node: any, attr: string) => any;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum ParserImplementations {
|
|
8
|
+
new = "new",
|
|
9
|
+
classic = "classic",
|
|
10
|
+
}
|