rollup-plugin-react-scoped-css 0.3.0 → 0.5.1
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/.github/workflows/pull-request.yml +19 -0
- package/.github/workflows/release.yml +30 -0
- package/.releaserc +13 -0
- package/README.md +35 -7
- 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 +46 -89
- package/dist/index.d.ts +27 -1
- package/dist/index.js +21 -11
- package/dist/parsers/classic-parser.d.ts +7 -0
- package/dist/parsers/classic-parser.js +95 -0
- package/dist/parsers/jsx-parser.model.d.ts +9 -0
- package/dist/parsers/jsx-parser.model.js +8 -0
- package/dist/parsers/new-parser.d.ts +7 -0
- package/dist/parsers/new-parser.js +94 -0
- package/package.json +14 -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 +19 -0
- package/src/ast-iterator.ts +27 -0
- package/src/ast-transformer.spec.ts +20 -0
- package/src/ast-transformer.ts +24 -0
- package/src/ast.utils.spec.ts +36 -12
- package/src/ast.utils.ts +33 -79
- package/src/index.ts +52 -8
- package/src/parsers/classic-parser.ts +58 -0
- package/src/parsers/jsx-parser.model.ts +10 -0
- package/src/parsers/new-parser.ts +58 -0
- package/tsconfig.json +2 -1
package/src/ast.utils.ts
CHANGED
|
@@ -1,94 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
var implementation: null | ParserImplementations
|
|
6
9
|
}
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
return ( // with React.createElement
|
|
10
|
-
node?.type === 'CallExpression' &&
|
|
11
|
-
node?.callee?.object?.name === 'React' &&
|
|
12
|
-
node?.callee?.property?.name === 'createElement'
|
|
13
|
-
) || ( // with babel jsx runtime
|
|
14
|
-
node?.type === 'CallExpression' &&
|
|
15
|
-
node?.callee?.name === '_jsxDEV'
|
|
16
|
-
)
|
|
17
|
-
}
|
|
11
|
+
global.implementation = null
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
node?.arguments?.[0]?.object?.name === "React" &&
|
|
22
|
-
node?.arguments?.[0]?.property?.name === "Fragment"
|
|
23
|
-
) || ( // with babel jsx runtime
|
|
24
|
-
node.arguments?.[0]?.type === "Identifier" &&
|
|
25
|
-
node.arguments?.[0]?.name === "_Fragment"
|
|
26
|
-
)
|
|
27
|
-
}
|
|
13
|
+
const classicParser = new ClassicJsxParser()
|
|
14
|
+
const newParser = new NewJsxParser()
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
for (let key of Object.keys(ast)) {
|
|
31
|
-
if (['end', 'start', 'type'].includes(key)) { continue; }
|
|
16
|
+
let parser: JsxParser = classicParser;
|
|
32
17
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
const findImplementation = (program: any) => {
|
|
19
|
+
let implementation: ParserImplementations = ParserImplementations.classic;
|
|
20
|
+
for (let node of astIterator(program)) {
|
|
21
|
+
if (node.type !== 'ImportDeclaration') {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (node.specifiers.some((n: any) => n.local.name === 'jsxRuntime') || node.source.value === 'react/jsx-runtime') {
|
|
26
|
+
return ParserImplementations.new
|
|
39
27
|
}
|
|
40
28
|
}
|
|
29
|
+
return implementation
|
|
30
|
+
}
|
|
41
31
|
|
|
42
|
-
|
|
32
|
+
export function addHashAttributesToJsxTagsAst(program: any, attr: string) {
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
// Once in the program, we can determine which parser to use
|
|
35
|
+
if (global.implementation === null) {
|
|
36
|
+
global.implementation = findImplementation(program)
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
method: false,
|
|
51
|
-
shorthand: false,
|
|
52
|
-
computed: false,
|
|
53
|
-
key: {
|
|
54
|
-
type: 'Identifier',
|
|
55
|
-
name: `"${attr}"`
|
|
56
|
-
},
|
|
57
|
-
value: {
|
|
58
|
-
type: 'Literal',
|
|
59
|
-
value: true,
|
|
60
|
-
raw: "true"
|
|
61
|
-
},
|
|
62
|
-
kind: 'init'
|
|
38
|
+
if (global.implementation === ParserImplementations.new) {
|
|
39
|
+
parser = newParser
|
|
40
|
+
}
|
|
63
41
|
}
|
|
64
|
-
}
|
|
65
42
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const arg = node?.arguments?.[1]
|
|
70
|
-
return {
|
|
71
|
-
...node,
|
|
72
|
-
arguments: node?.arguments.map((v: any, i: number) => {
|
|
73
|
-
if (i === 1) {
|
|
74
|
-
if (v.type === 'ObjectExpression') {
|
|
75
|
-
return {
|
|
76
|
-
...v,
|
|
77
|
-
properties: [
|
|
78
|
-
...arg.properties,
|
|
79
|
-
createAttributeNode(attr)
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
return {
|
|
84
|
-
type: 'ObjectExpression',
|
|
85
|
-
properties: [createAttributeNode(attr)]
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return v
|
|
90
|
-
})
|
|
91
|
-
}
|
|
43
|
+
return astTransformer(program, (node: any) => {
|
|
44
|
+
if (parser.isNodeReactElement(node) && !parser.isNodeReactFragment(node)) {
|
|
45
|
+
return parser.extendNodeWithAttributes(node, attr)
|
|
92
46
|
}
|
|
93
47
|
})
|
|
94
48
|
}
|
package/src/index.ts
CHANGED
|
@@ -36,29 +36,73 @@ const addAttributesToCss = (src: string, fileName: string, hash: string) => {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export interface ReactScopedCssPluginOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Which files should be included and parsed by the plugin
|
|
41
|
+
* Default: undefined
|
|
42
|
+
*/
|
|
39
43
|
include?: FilterPattern
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Which files should be exluded and that should not be parsed by the plugin
|
|
47
|
+
* Default: undefined
|
|
48
|
+
*/
|
|
40
49
|
exclude?: FilterPattern
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* If you want to customize the stylesheet file pattern
|
|
53
|
+
* if undefined or '' is passed, all files will be evaluated
|
|
54
|
+
* Default: 'scoped'
|
|
55
|
+
*/
|
|
41
56
|
styleFileSuffix?: string
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* If you want to customize the attribute prefix that is added to the jsx elements
|
|
60
|
+
* Default: 'v'
|
|
61
|
+
*/
|
|
42
62
|
hashPrefix?: string
|
|
43
|
-
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* If you want to customize the stylesheet extensions
|
|
66
|
+
* Default: ['scss', 'css', 'sass', 'less']
|
|
67
|
+
*/
|
|
68
|
+
styleFileExtensions?: string[]
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* If you have jsx in other file extensions
|
|
72
|
+
* Default: ['jsx', 'tsx']
|
|
73
|
+
*/
|
|
74
|
+
jsxFileExtensions?: string[]
|
|
44
75
|
}
|
|
45
76
|
|
|
46
77
|
export interface VitePartialPlugin extends Plugin {
|
|
47
78
|
enforce?: "pre" | "post"
|
|
48
79
|
}
|
|
49
80
|
|
|
50
|
-
export default function reactScopedCssPlugin(optionsIn:ReactScopedCssPluginOptions = {}): VitePartialPlugin[] {
|
|
51
|
-
const options = {
|
|
52
|
-
styleFileSuffix: 'scoped',
|
|
81
|
+
export default function reactScopedCssPlugin(optionsIn: ReactScopedCssPluginOptions = {}): VitePartialPlugin[] {
|
|
82
|
+
const options: Partial<ReactScopedCssPluginOptions> = {
|
|
53
83
|
hashPrefix: 'v',
|
|
54
|
-
|
|
84
|
+
styleFileSuffix: 'scoped',
|
|
85
|
+
styleFileExtensions: ['scss', 'css', 'sass', 'less'],
|
|
86
|
+
jsxFileExtensions: ['jsx', 'tsx'],
|
|
55
87
|
...optionsIn
|
|
56
88
|
}
|
|
57
89
|
|
|
90
|
+
if (!options.styleFileExtensions || !options.styleFileExtensions.length) {
|
|
91
|
+
throw new Error('You need to provide at least one style file extension')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!options.jsxFileExtensions || !options.jsxFileExtensions.length) {
|
|
95
|
+
throw new Error('You need to provide at least one jsx file extension')
|
|
96
|
+
}
|
|
97
|
+
|
|
58
98
|
const filter = createFilter( options.include, options.exclude );
|
|
59
|
-
const scopedCssRegex =
|
|
60
|
-
|
|
61
|
-
|
|
99
|
+
const scopedCssRegex = options.styleFileSuffix
|
|
100
|
+
? new RegExp(`\.${options.styleFileSuffix}\.(${options.styleFileExtensions.join('|')})$`)
|
|
101
|
+
: new RegExp(`\.(${options.styleFileExtensions.join('|')})$`)
|
|
102
|
+
const scopedCssInFileRegex = options.styleFileSuffix
|
|
103
|
+
? new RegExp(`\.${options.styleFileSuffix}\.(${options.styleFileExtensions.join('|')})(\"|\')`)
|
|
104
|
+
: new RegExp(`\.(${options.styleFileExtensions.join('|')})(\"|\')`)
|
|
105
|
+
const jsxRegex = new RegExp(`\.(${options.jsxFileExtensions.join('|')})$`)
|
|
62
106
|
|
|
63
107
|
return [
|
|
64
108
|
{
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { JsxParser } from "./jsx-parser.model";
|
|
2
|
+
|
|
3
|
+
export class ClassicJsxParser implements JsxParser {
|
|
4
|
+
isNodeReactFragment(node: any) {
|
|
5
|
+
return node?.arguments?.[0]?.object?.name === "React" &&
|
|
6
|
+
node?.arguments?.[0]?.property?.name === "Fragment"
|
|
7
|
+
}
|
|
8
|
+
isNodeReactElement(node: any) {
|
|
9
|
+
return node?.type === 'CallExpression' &&
|
|
10
|
+
node?.callee?.object?.name === 'React' &&
|
|
11
|
+
node?.callee?.property?.name === 'createElement'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public extendNodeWithAttributes(node: any, attr: string) {
|
|
15
|
+
const arg = node?.arguments?.[1]
|
|
16
|
+
return {
|
|
17
|
+
...node,
|
|
18
|
+
arguments: node?.arguments.map((v: any, i: number) => {
|
|
19
|
+
if (i === 1) {
|
|
20
|
+
if (v.type === 'ObjectExpression') {
|
|
21
|
+
return {
|
|
22
|
+
...v,
|
|
23
|
+
properties: [
|
|
24
|
+
...arg.properties,
|
|
25
|
+
this.createAttributeNode(attr)
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
return {
|
|
30
|
+
type: 'ObjectExpression',
|
|
31
|
+
properties: [this.createAttributeNode(attr)]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return v
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private createAttributeNode(attr: string) {
|
|
41
|
+
return {
|
|
42
|
+
type: 'Property',
|
|
43
|
+
method: false,
|
|
44
|
+
shorthand: false,
|
|
45
|
+
computed: false,
|
|
46
|
+
key: {
|
|
47
|
+
type: 'Identifier',
|
|
48
|
+
name: `"${attr}"`
|
|
49
|
+
},
|
|
50
|
+
value: {
|
|
51
|
+
type: 'Literal',
|
|
52
|
+
value: true,
|
|
53
|
+
raw: "true"
|
|
54
|
+
},
|
|
55
|
+
kind: 'init'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { JsxParser } from "./jsx-parser.model";
|
|
2
|
+
|
|
3
|
+
export class NewJsxParser implements JsxParser {
|
|
4
|
+
isNodeReactFragment(node: any) {
|
|
5
|
+
return node.arguments?.[0]?.type === "Identifier" &&
|
|
6
|
+
['_Fragment', 'Fragment'].includes(node.arguments?.[0]?.name)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
isNodeReactElement(node: any) {
|
|
10
|
+
return node?.type === 'CallExpression' &&
|
|
11
|
+
['_jsxDEV', '_jsx', '_jsxs', 'jsxDev', 'jsx', 'jsxs'].includes(node?.callee?.name)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public extendNodeWithAttributes(node: any, attr: string) {
|
|
15
|
+
const arg = node?.arguments?.[1]
|
|
16
|
+
return {
|
|
17
|
+
...node,
|
|
18
|
+
arguments: node?.arguments.map((v: any, i: number) => {
|
|
19
|
+
if (i === 1) {
|
|
20
|
+
if (v.type === 'ObjectExpression') {
|
|
21
|
+
return {
|
|
22
|
+
...v,
|
|
23
|
+
properties: [
|
|
24
|
+
...arg.properties,
|
|
25
|
+
this.createAttributeNode(attr)
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
return {
|
|
30
|
+
type: 'ObjectExpression',
|
|
31
|
+
properties: [this.createAttributeNode(attr)]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return v
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private createAttributeNode(attr: string) {
|
|
41
|
+
return {
|
|
42
|
+
type: 'Property',
|
|
43
|
+
method: false,
|
|
44
|
+
shorthand: false,
|
|
45
|
+
computed: false,
|
|
46
|
+
key: {
|
|
47
|
+
type: 'Identifier',
|
|
48
|
+
name: `"${attr}"`
|
|
49
|
+
},
|
|
50
|
+
value: {
|
|
51
|
+
type: 'Literal',
|
|
52
|
+
value: true,
|
|
53
|
+
raw: "true"
|
|
54
|
+
},
|
|
55
|
+
kind: 'init'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|