rollup-plugin-react-scoped-css 0.2.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.github/workflows/pull-request.yml +19 -0
  2. package/.github/workflows/release.yml +30 -0
  3. package/.releaserc +12 -0
  4. package/README.md +40 -6
  5. package/dist/ast-iterator.d.ts +1 -0
  6. package/dist/ast-iterator.js +114 -0
  7. package/dist/ast-transformer.d.ts +1 -0
  8. package/dist/ast-transformer.js +50 -0
  9. package/dist/ast.utils.d.ts +4 -0
  10. package/dist/ast.utils.js +46 -82
  11. package/dist/index.d.ts +27 -0
  12. package/dist/index.js +21 -11
  13. package/dist/parsers/classic-parser.d.ts +7 -0
  14. package/dist/parsers/classic-parser.js +95 -0
  15. package/dist/parsers/jsx-parser.model.d.ts +9 -0
  16. package/dist/parsers/jsx-parser.model.js +8 -0
  17. package/dist/parsers/new-parser.d.ts +7 -0
  18. package/dist/parsers/new-parser.js +94 -0
  19. package/package.json +21 -3
  20. package/src/__mocks__/{compiled-jsx-with-attr.js → classic-compiled-jsx-with-attr.js} +0 -0
  21. package/src/__mocks__/{compiled-jsx.js → classic-compiled-jsx.js} +0 -0
  22. package/src/__mocks__/new-compiled-jsx-with-attr.js +101 -0
  23. package/src/__mocks__/new-compiled-jsx.js +64 -0
  24. package/src/__mocks__/sample-code-file.js +4 -0
  25. package/src/ast-iterator.spec.ts +19 -0
  26. package/src/ast-iterator.ts +27 -0
  27. package/src/ast-transformer.spec.ts +20 -0
  28. package/src/ast-transformer.ts +24 -0
  29. package/src/ast.utils.spec.ts +36 -12
  30. package/src/ast.utils.ts +33 -68
  31. package/src/index.ts +52 -6
  32. package/src/parsers/classic-parser.ts +58 -0
  33. package/src/parsers/jsx-parser.model.ts +10 -0
  34. package/src/parsers/new-parser.ts +58 -0
  35. package/tsconfig.json +2 -1
package/src/ast.utils.ts CHANGED
@@ -1,83 +1,48 @@
1
- function isNode(obj: unknown) {
2
- return typeof obj === 'object' &&
3
- typeof (obj as any)?.end === 'number' &&
4
- typeof (obj as any)?.start === 'number' &&
5
- typeof (obj as any)?.type === 'string'
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
- function isReactCreateElementCallExpression(node: any) {
9
- return node?.type === 'CallExpression' &&
10
- node?.callee?.object?.name === 'React' &&
11
- node?.callee?.property?.name === 'createElement'
12
- }
11
+ global.implementation = null
13
12
 
14
- function isFragmentCallExpression(node: any) {
15
- return node?.arguments?.[0]?.object?.name === "React" && node?.arguments?.[0]?.property?.name === "Fragment"
16
- }
13
+ const classicParser = new ClassicJsxParser()
14
+ const newParser = new NewJsxParser()
17
15
 
18
- function traverse(ast: any, callback: Function) {
19
- for (let key of Object.keys(ast)) {
20
- if (['end', 'start', 'type'].includes(key)) { continue; }
16
+ let parser: JsxParser = classicParser;
21
17
 
22
- if (Array.isArray(ast[key])) {
23
- for (let nodeIndex in ast[key]) {
24
- ast[key][nodeIndex] = traverse(ast[key][nodeIndex], callback)
25
- }
26
- } else if(isNode(ast[key])) {
27
- ast[key] = traverse(ast[key], callback)
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
28
27
  }
29
28
  }
29
+ return implementation
30
+ }
30
31
 
31
- const result = callback(ast);
32
+ export function addHashAttributesToJsxTagsAst(program: any, attr: string) {
32
33
 
33
- return typeof result !== 'undefined' ? result : ast
34
- }
34
+ // Once in the program, we can determine which parser to use
35
+ if (global.implementation === null) {
36
+ global.implementation = findImplementation(program)
35
37
 
36
- function createAttributeNode(attr: string) {
37
- return {
38
- type: 'Property',
39
- method: false,
40
- shorthand: false,
41
- computed: false,
42
- key: {
43
- type: 'Identifier',
44
- name: `"${attr}"`
45
- },
46
- value: {
47
- type: 'Literal',
48
- value: true,
49
- raw: "true"
50
- },
51
- kind: 'init'
38
+ if (global.implementation === ParserImplementations.new) {
39
+ parser = newParser
40
+ }
52
41
  }
53
- }
54
42
 
55
- export function addHashAttributesToJsxTagsAst(program: any, attr: string) {
56
- return traverse(program, (node: any) => {
57
- if (isReactCreateElementCallExpression(node) && !isFragmentCallExpression(node)) {
58
- const arg = node?.arguments?.[1]
59
- return {
60
- ...node,
61
- arguments: node?.arguments.map((v: any, i: number) => {
62
- if (i === 1) {
63
- if (v.type === 'ObjectExpression') {
64
- return {
65
- ...v,
66
- properties: [
67
- ...arg.properties,
68
- createAttributeNode(attr)
69
- ]
70
- }
71
- } else {
72
- return {
73
- type: 'ObjectExpression',
74
- properties: [createAttributeNode(attr)]
75
- }
76
- }
77
- }
78
- return v
79
- })
80
- }
43
+ return astTransformer(program, (node: any) => {
44
+ if (parser.isNodeReactElement(node) && !parser.isNodeReactFragment(node)) {
45
+ return parser.extendNodeWithAttributes(node, attr)
81
46
  }
82
47
  })
83
48
  }
package/src/index.ts CHANGED
@@ -36,27 +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
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[]
43
75
  }
44
76
 
45
77
  export interface VitePartialPlugin extends Plugin {
46
78
  enforce?: "pre" | "post"
47
79
  }
48
80
 
49
- export default function reactScopedCssPlugin(optionsIn:ReactScopedCssPluginOptions = {}): VitePartialPlugin[] {
50
- const options = {
51
- styleFileSuffix: 'scoped',
81
+ export default function reactScopedCssPlugin(optionsIn: ReactScopedCssPluginOptions = {}): VitePartialPlugin[] {
82
+ const options: Partial<ReactScopedCssPluginOptions> = {
52
83
  hashPrefix: 'v',
84
+ styleFileSuffix: 'scoped',
85
+ styleFileExtensions: ['scss', 'css', 'sass', 'less'],
86
+ jsxFileExtensions: ['jsx', 'tsx'],
53
87
  ...optionsIn
54
88
  }
55
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
+
56
98
  const filter = createFilter( options.include, options.exclude );
57
- const scopedCssRegex = new RegExp(`\.${options.styleFileSuffix}\.(scss|css|sass)$`)
58
- const scopedCssInFileRegex = new RegExp(`\.${options.styleFileSuffix}\.(scss|css|sass)(\"|\')`)
59
- const jsxRegex = /\.(tsx|jsx)$/
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('|')})$`)
60
106
 
61
107
  return [
62
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
+ }
package/tsconfig.json CHANGED
@@ -6,7 +6,8 @@
6
6
  "declaration": true,
7
7
  "outDir": "dist",
8
8
  "strict": true,
9
- "esModuleInterop": true
9
+ "esModuleInterop": true,
10
+ "downlevelIteration": true
10
11
  },
11
12
  "exclude": [
12
13
  "node_modules",