Skip to content

Commit d2cbd3f

Browse files
committed
updated rollup_class_prefixer-plug to handle transpiled code, enabled type creation again in lib build system
1 parent c27f770 commit d2cbd3f

File tree

5 files changed

+289
-169
lines changed

5 files changed

+289
-169
lines changed

bundler_plugins/rollup_class_prefixer-plugin.ts

Lines changed: 175 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import generate from "@babel/generator"
2-
import { default as traverse } from "@babel/traverse"
32
import { parse } from "@babel/parser"
3+
import { default as traverse } from "@babel/traverse"
44

5-
import * as t from "@babel/types"
6-
import type { OutputAsset, Plugin } from "rollup"
7-
import postcss from "postcss"
85
import { existsSync } from "node:fs"
96
import fs from "node:fs/promises"
7+
import * as t from "@babel/types"
8+
import postcss from "postcss"
9+
import type { OutputAsset, Plugin } from "rollup"
1010

1111
let classPrefix = ""
1212
let classesToBePrefix: string[] = []
@@ -18,6 +18,9 @@ let classesToPrefixRegexJS: RegExp
1818
//CSS:
1919
let classesToPrefixRegexCSS: RegExp
2020

21+
const bgenerate = generate.default
22+
const btraverse = traverse.default
23+
2124
// the css prefixing is done by postcss
2225
async function prefixCSS(code: string, fileName: string) {
2326
const pcssPrefixerPlugin: postcss.Plugin = {
@@ -36,72 +39,109 @@ async function prefixCSS(code: string, fileName: string) {
3639
return result
3740
}
3841

39-
/**
40-
* Uses a regex to prefix the classes inside className or class strings
41-
*/
42-
async function prefixJSX(code: string, fileName: string) {
43-
let replacements = 0
44-
45-
const prefixFunc = (stringVal: string, shouldCount = true) => {
46-
let _replacements = 0
47-
const replaced = stringVal.replace(classesToPrefixRegexJS, (match) => {
48-
_replacements++
49-
return `${classPrefix}${match}`
50-
})
51-
if (_replacements) {
52-
if (shouldCount) {
53-
replacements += _replacements
42+
// Helper function to handle both JSX and plain class attributes
43+
function handleClassNameValue(classNameValueNode, prefixFunc) {
44+
if (t.isStringLiteral(classNameValueNode)) {
45+
const stringVal = classNameValueNode.value
46+
const prefixedValue = prefixFunc(stringVal)
47+
classNameValueNode.value = prefixedValue
48+
} else if (t.isJSXExpressionContainer(classNameValueNode)) {
49+
const expressionContainer = classNameValueNode.expression
50+
if (t.isStringLiteral(expressionContainer)) {
51+
const stringVal = expressionContainer.value
52+
const prefixedValue = prefixFunc(stringVal)
53+
expressionContainer.value = prefixedValue
54+
} else if (t.isTemplateLiteral(expressionContainer)) {
55+
for (const expression of expressionContainer.expressions) {
56+
if (t.isStringLiteral(expression)) {
57+
const stringVal = expression.value
58+
const prefixedValue = prefixFunc(stringVal)
59+
expression.value = prefixedValue
60+
}
61+
}
62+
for (const element of expressionContainer.quasis) {
63+
const stringVal = element.value.raw
64+
const prefixedValue = prefixFunc(stringVal)
65+
element.value.raw = prefixedValue
66+
67+
const cstringVal = element.value.cooked
68+
if (cstringVal) {
69+
const cprefixedValue = prefixFunc(cstringVal, false)
70+
element.value.cooked = cprefixedValue
71+
}
5472
}
55-
return replaced
5673
}
57-
return stringVal
5874
}
75+
}
76+
77+
// Does the actual prefixing
78+
let replacements = 0
79+
const prefixFunc = (stringVal: string, shouldCount = true) => {
80+
classesToPrefixRegexJS.lastIndex = 0
81+
let _replacements = 0
82+
const replaced = stringVal.replace(classesToPrefixRegexJS, (match) => {
83+
_replacements++
84+
return `${classPrefix}${match}`
85+
})
86+
if (_replacements) {
87+
if (shouldCount) {
88+
replacements += _replacements
89+
}
90+
return replaced
91+
}
92+
return stringVal
93+
}
94+
95+
// Helper function to handle TemplateLiteral nodes
96+
function handleTemplateLiteral(templateLiteralNode) {
97+
for (const expression of templateLiteralNode.expressions) {
98+
if (t.isStringLiteral(expression)) {
99+
const stringVal = expression.value
100+
const prefixedValue = prefixFunc(stringVal)
101+
expression.value = prefixedValue
102+
}
103+
}
104+
for (const element of templateLiteralNode.quasis) {
105+
const stringVal = element.value.raw
106+
const prefixedValue = prefixFunc(stringVal)
107+
element.value.raw = prefixedValue
108+
109+
const cstringVal = element.value.cooked
110+
if (cstringVal) {
111+
const cprefixedValue = prefixFunc(cstringVal, false)
112+
element.value.cooked = cprefixedValue
113+
}
114+
}
115+
}
59116

117+
/**
118+
* Uses a regex to prefix the classes inside className or class strings
119+
*/
120+
async function prefixJSX(code: string, fileName: string) {
60121
const ast = parse(code, {
61122
sourceType: "module",
62123
plugins: ["jsx", "typescript"],
63124
})
64125

65-
traverse.default(ast, {
126+
btraverse(ast, {
66127
JSXAttribute(path) {
67128
if (t.isJSXIdentifier(path.node.name, { name: "className" })) {
68-
const classNameValueNode = path.node.value
69-
if (t.isStringLiteral(classNameValueNode)) {
70-
const stringVal = classNameValueNode.value
71-
const prefixedValue = prefixFunc(stringVal)
129+
const classNameValueNode: t.Node = path.node.value
130+
handleClassNameValue(classNameValueNode, prefixFunc)
131+
}
132+
},
133+
ObjectProperty(path) {
134+
if (
135+
t.isIdentifier(path.node.key, { name: "className" }) ||
136+
t.isIdentifier(path.node.key, { name: "class" })
137+
) {
138+
if (t.isStringLiteral(path.node.value)) {
139+
const prefixedValue = prefixFunc(path.node.value.value)
72140
path.node.value = t.stringLiteral(prefixedValue)
73-
} else if (t.isJSXExpressionContainer(classNameValueNode)) {
74-
const expressionContainer = classNameValueNode.expression
75-
if (t.isStringLiteral(expressionContainer)) {
76-
const stringVal = expressionContainer.value
77-
const prefixedValue = prefixFunc(stringVal)
78-
expressionContainer.value = prefixedValue
79-
} else if (t.isTemplateLiteral(expressionContainer)) {
80-
for (const expression of expressionContainer.expressions) {
81-
if (t.isStringLiteral(expression)) {
82-
const stringVal = expression.value
83-
const prefixedValue = prefixFunc(stringVal)
84-
expression.value = prefixedValue
85-
}
86-
}
87-
for (const element of expressionContainer.quasis) {
88-
const stringVal = element.value.raw
89-
const prefixedValue = prefixFunc(stringVal)
90-
element.value.raw = prefixedValue
91-
92-
const cstringVal = element.value.cooked
93-
if (cstringVal) {
94-
const cprefixedValue = prefixFunc(
95-
cstringVal,
96-
false,
97-
)
98-
element.value.cooked = cprefixedValue
99-
}
100-
}
101-
}
102141
}
103142
}
104143
},
144+
105145
// Handle TypeScript properties like `this.className = 'some-class'`
106146
ClassProperty(path) {
107147
if (t.isIdentifier(path.node.key, { name: "className" })) {
@@ -117,9 +157,84 @@ async function prefixJSX(code: string, fileName: string) {
117157
}
118158
}
119159
},
160+
161+
// Handle JSX attributes in the transpiled React.createElement calls
162+
CallExpression(path) {
163+
const callee = path.get("callee")
164+
// Check if it's a call to jsxRuntimeExports.jsx or jsxRuntimeExports.jsxs
165+
if (
166+
callee.isMemberExpression() &&
167+
t.isIdentifier(callee.node.object, {
168+
name: "jsxRuntimeExports",
169+
}) &&
170+
(t.isIdentifier(callee.node.property, { name: "jsx" }) ||
171+
t.isIdentifier(callee.node.property, { name: "jsxs" }))
172+
) {
173+
const args = path.get("arguments")
174+
if (args.length > 1) {
175+
const props = args[1]
176+
if (props.isObjectExpression()) {
177+
// biome-ignore lint/complexity/noForEach: <explanation>
178+
props.get("properties").forEach((propPath) => {
179+
if (t.isObjectProperty(propPath.node)) {
180+
const key = propPath.get("key")
181+
if (key.isIdentifier({ name: "className" })) {
182+
const value = propPath.get("value")
183+
if (value.isStringLiteral()) {
184+
const stringVal = value.node.value
185+
const prefixedValue =
186+
prefixFunc(stringVal)
187+
value.replaceWith(
188+
t.stringLiteral(prefixedValue),
189+
)
190+
} else if (value.isTemplateLiteral()) {
191+
handleTemplateLiteral(value.node)
192+
}
193+
}
194+
}
195+
})
196+
}
197+
}
198+
}
199+
200+
// handle React.createElement calls - untested
201+
if (
202+
callee.isIdentifier({ name: "React" }) ||
203+
callee.isIdentifier({ name: "createElement" })
204+
) {
205+
const args = path.get("arguments")
206+
if (args.length > 1) {
207+
const props = args[1] // props is the second argument
208+
if (props.isObjectExpression()) {
209+
// biome-ignore lint/complexity/noForEach: <explanation>
210+
props.get("properties").forEach((propPath) => {
211+
if (t.isObjectProperty(propPath.node)) {
212+
const key = propPath.get("key")
213+
if (
214+
key.isIdentifier({ name: "className" }) ||
215+
key.isIdentifier({ name: "class" })
216+
) {
217+
const value = propPath.get("value")
218+
if (value.isStringLiteral()) {
219+
const stringVal = value.node.value
220+
const prefixedValue =
221+
prefixFunc(stringVal)
222+
value.replaceWith(
223+
t.stringLiteral(prefixedValue),
224+
)
225+
} else if (value.isTemplateLiteral()) {
226+
handleTemplateLiteral(value.node)
227+
}
228+
}
229+
}
230+
})
231+
}
232+
}
233+
}
234+
},
120235
})
121236

122-
const updatedCode = generate.default(ast).code as string
237+
const updatedCode = bgenerate(ast).code as string
123238
if (replacements) {
124239
console.log(
125240
"\x1b[33m%s\x1b[0m",
@@ -133,7 +248,7 @@ async function prefixJSX(code: string, fileName: string) {
133248
/**
134249
* Rollup plugin that prefixes the classes in the JS(X) and TS(X) files.
135250
*/
136-
export function classPrefixerPlugin({
251+
export default function classPrefixerPlugin({
137252
prefix,
138253
classes,
139254
jsFiles = ["js", "jsx", "ts", "tsx"],
@@ -148,7 +263,7 @@ export function classPrefixerPlugin({
148263
classesToBePrefix = classes
149264
jsFilePostfixes = jsFiles
150265
classesToPrefixRegexJS = new RegExp(
151-
`\b(${classesToBePrefix.join("|")})\b`,
266+
`\\b(${classesToBePrefix.join("|")})\\b`,
152267
"g",
153268
)
154269
classesToPrefixRegexCSS = new RegExp(
@@ -163,6 +278,8 @@ export function classPrefixerPlugin({
163278
return null
164279
}
165280

281+
replacements = 0
282+
166283
const ext = id.split(".").pop()
167284
if (ext && jsFilePostfixes.includes(ext)) {
168285
const data = await fs.readFile(id, "utf-8")

library/src/components/ToastFlag.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useRef, type CSSProperties } from "react"
1+
import { useRef, type CSSProperties } from "react"
22
import { createPortal } from "react-dom"
33

44
import { ToastContainer, toast } from "react-toastify"
@@ -141,7 +141,7 @@ export function showFlagExtended({
141141
appearance={appearance}
142142
type={flagType}
143143
style={flagStyle}
144-
className="bottom-0 bg-transparent p-0 shadow-none"
144+
className="bottom-0 bg-transparent p-0"
145145
{...props}
146146
/>,
147147
{

0 commit comments

Comments
 (0)