-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[module-detection / smart-rename] styled-components
/ Tailwind-Styled-Component
libs
#40
Comments
The webpacked code that includes this (in this app) is in the following chunk: And within this code, it specifically seems to be in Within the original code for that module, I identified a section of code that looks like this: tU = [
"a",
abbr",
"address",
"area",
"article",
// ..snip.. Which at first I manually correlated with the following from const elements = [
'a',
'abbr',
'address',
'area',
'article', But then later found this code: tB = Symbol("isTwElement?"), Which I then searched for on GitHub code search: That seemed to lead me to these 2 repos:
At first glance, both of these repos also appear to have the same
But after accounting for differences in spacing, quotes, etc; and diffing them, it looks like the Based on this, we can compare against the code in our webpack bundled code, and see that it also has |
Right at the top of our webpacked code we see this 34303: function (U, B, G) {
"use strict";
G.d(B, {
Z: function () {
return tq;
},
});
// ..snip.. We find // ..snip..
return (
(J[tB] = !0),
"string" != typeof U
? (J.displayName = U.displayName || U.name || "tw.Component")
: (J.displayName = "tw." + U),
(J.withStyle = (U) => V(Z.concat(U))),
J
);
};
return V();
},
t$ = tU.reduce((U, B) => ({ ...U, [B]: tz(B) }), {}),
tq = Object.assign(tz, t$);
}, We can see some code here that sets
Contrasting the function code that contains the Looking at the end of the code in that file, we can see how it correlates with const intrinsicElementsMap: IntrinsicElementsTemplateFunctionsMap = domElements.reduce(
<K extends IntrinsicElementsKeys>(acc: IntrinsicElementsTemplateFunctionsMap, DomElement: K) => ({
...acc,
[DomElement]: templateFunctionFactory(DomElement)
}),
{} as IntrinsicElementsTemplateFunctionsMap
)
const tw: TailwindInterface = Object.assign(templateFunctionFactory, intrinsicElementsMap)
export default tw A typical webpack module when unminimised has the following basic structure: function(module, exports, require) {
// Module code goes here
} We can see how that maps to our webpacked code: 34303: function (U, B, G) { This means that:
The G.d(B, {
Z: function () {
return tq;
},
}); |
Based on this knowledge, we can now find references to Looking at a different chunk file that imports
We can find a module that uses 46110: function (e, t, n) {
// ..snip..
var r = n(4337),
// ..snip..
d = n(34303),
// ..snip..
var b = d.Z.div(m(), function (e) {
return e.$isMessageRedesign
? "rounded-full h-7 w-7"
: "rounded-sm h-[30px] w-[30px]";
}),
y = d.Z.span(
p(),
function (e) {
return "warning" === e.$type && "bg-orange-500 text-white";
},
function (e) {
return "danger" === e.$type && "bg-red-500 text-white";
}
),
// ..snip.. We can see that the
Looking back at how const intrinsicElementsMap: IntrinsicElementsTemplateFunctionsMap = domElements.reduce(
<K extends IntrinsicElementsKeys>(acc: IntrinsicElementsTemplateFunctionsMap, DomElement: K) => ({
...acc,
[DomElement]: templateFunctionFactory(DomElement)
}),
{} as IntrinsicElementsTemplateFunctionsMap
)
const tw: TailwindInterface = Object.assign(templateFunctionFactory, intrinsicElementsMap)
export default tw We can also see the type definition for export type IntrinsicElementsTemplateFunctionsMap = {
[RTag in keyof JSX.IntrinsicElements]: TemplateFunction<JSX.IntrinsicElements[RTag]>
}
export interface TailwindInterface extends IntrinsicElementsTemplateFunctionsMap {
<C extends TailwindComponent<any, any>>(component: C): TemplateFunction<
TailwindComponentInnerProps<C>,
TailwindComponentInnerOtherProps<C>
>
<C extends React.ComponentType<any>>(component: C): TemplateFunction<
// Prevent functional components without props infering props as `unknown`
C extends (P?: never) => any ? {} : React.ComponentPropsWithoutRef<C>
>
<C extends keyof JSX.IntrinsicElements>(component: C): TemplateFunction<JSX.IntrinsicElements[C]>
} We can read about
Explaining those types with ChatGPT:
Based on that interface, we can now better understand what the // ..snip..
function m() {
var e = (0, r._)([
"relative p-1 ",
" text-white flex items-center justify-center",
]);
return (
(m = function () {
return e;
}),
e
);
}
function p() {
var e = (0, r._)([
"\n absolute w-4 h-4 rounded-full text-[10px] text-white flex justify-center items-center right-0 top-[20px] -mr-2 border border-white\n ",
"\n ",
"\n",
]);
return (
(p = function () {
return e;
}),
e
);
}
// ..snip..
var b = d.Z.div(m(), function (e) {
return e.$isMessageRedesign
? "rounded-full h-7 w-7"
: "rounded-sm h-[30px] w-[30px]";
}),
y = d.Z.span(
p(),
function (e) {
return "warning" === e.$type && "bg-orange-500 text-white";
},
function (e) {
return "danger" === e.$type && "bg-red-500 text-white";
}
), Explained by ChatGPT:
|
Supporting this would be quite challenging to achieve because of the form of it. PRs are welcome if anyone is interested in this. |
Looking back at the main repo/usage docs for
We can see that there are multiple ways of writing a styled component, including: // Basic
const Container = tw.div`
flex
items-center
// ..snip..
`
// Conditional class names
const Button = tw.button`
flex
${(p) => (p.$primary ? "bg-indigo-600" : "bg-indigo-300")}
`
// etc Along with some other potentially relevant notes:
These usage examples are making use of JavaScript's Template Literals 'Tagged templates':
This will essentially end up routing through the const templateFunctionFactory: TailwindInterface = (<C extends React.ElementType>(Element: C): any => {
return (template: TemplateStringsArray, ...templateElements: ((props: any) => string | undefined | null)[]) => {
// ..snip.. We can see that this function is a template literal 'tagged template' function that receives the static strings in the I couldn't find much specifically about Using the above examples from the README in the Babel REPL gives transformed code like this: var _templateObject, _templateObject2;
function _taggedTemplateLiteral(strings, raw) {
if (!raw) {
raw = strings.slice(0);
}
return Object.freeze(
Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })
);
}
// Basic
var Container = tw.div(
_templateObject ||
(_templateObject = _taggedTemplateLiteral([
"\n flex\n items-center\n // ..snip..\n",
]))
);
// Conditional class names
var Button = tw.button(
_templateObject2 ||
(_templateObject2 = _taggedTemplateLiteral(["\n flex\n ", "\n"])),
function (p) {
return p.$primary ? "bg-indigo-600" : "bg-indigo-300";
}
);
// etc We can see how this code looks a lot like the earlier code from our webpacked app, though the babel code implicitly concatenates the template literal strings as part of it's transform, whereas our webpacked code receives them as an array (as per the JS standard), and then passes them to a helper function that seems to concatenate them (potentially something like // ..snip..
function p() {
var e = (0, r._)([
"\n absolute w-4 h-4 rounded-full text-[10px] text-white flex justify-center items-center right-0 top-[20px] -mr-2 border border-white\n ",
"\n ",
"\n",
]);
return (
(p = function () {
return e;
}),
e
);
}
// ..snip..
y = d.Z.span(
p(),
function (e) {
return "warning" === e.$type && "bg-orange-500 text-white";
},
function (e) {
return "danger" === e.$type && "bg-red-500 text-white";
}
), If we were to manually re-write this back to how it would have looked in it's template literal form (ignoring the memoisation it does), it would have been something like this: y = d.Z.span`
absolute
w-4
h-4
rounded-full
text-[10px]
text-white
flex
justify-center
items-center
right-0
top-[20px]
-mr-2
border
border-white
${(e) => (e.$type === "warning" && "bg-orange-500 text-white")}
${(e) => (e.$type === "danger" && "bg-red-500 text-white")}
` Looking at where // ..snip..
return (
<FinalElement
// ..snip..
// set class names
className={cleanTemplate(
mergeArrays(
template,
templateElements.map((t) => t({ ...props, $as }))
),
props.className
)}
// ..snip..
/>
)
// ..snip.. We can see that export const mergeArrays = (template: TemplateStringsArray, templateElements: (string | undefined | null)[]) => {
return template.reduce(
(acc, c, i) => acc.concat(c || [], templateElements[i] || []), // x || [] to remove false values e.g '', null, undefined. as Array.concat() ignores empty arrays i.e []
[] as string[]
)
} We can then see that the result of that is passed to export const cleanTemplate = (template: Array<Interpolation<any>>, inheritedClasses: string = "") => {
const newClasses: string[] = template
.join(" ")
.trim()
.replace(/\n/g, " ") // replace newline with space
.replace(/\s{2,}/g, " ") // replace line return by space
.split(" ")
.filter((c) => c !== ",") // remove comma introduced by template to string
const inheritedClassesArray: string[] = inheritedClasses ? inheritedClasses.split(" ") : []
return twMerge(
...newClasses
.concat(inheritedClassesArray) // add new classes to inherited classes
.filter((c: string) => c !== " ") // remove empty classes
)
} Neither |
Looking at the
Looking at the We can see that the 2 main functions appear to be: function twMerge(
...classLists: Array<string | undefined | null | false | 0 | typeof classLists>
): string
function twJoin(
...classLists: Array<string | undefined | null | false | 0 | typeof classLists>
): string
From these function signatures, and the description text of
We can find the definition of
Looking at export function createTailwindMerge(
createConfigFirst: CreateConfigFirst,
...createConfigRest: CreateConfigSubsequent[]
): TailwindMerge {
let configUtils: ConfigUtils
let cacheGet: ConfigUtils['cache']['get']
let cacheSet: ConfigUtils['cache']['set']
let functionToCall = initTailwindMerge
function initTailwindMerge(classList: string) {
const config = createConfigRest.reduce(
(previousConfig, createConfigCurrent) => createConfigCurrent(previousConfig),
createConfigFirst() as GenericConfig,
)
configUtils = createConfigUtils(config)
cacheGet = configUtils.cache.get
cacheSet = configUtils.cache.set
functionToCall = tailwindMerge
return tailwindMerge(classList)
}
function tailwindMerge(classList: string) {
const cachedResult = cacheGet(classList)
if (cachedResult) {
return cachedResult
}
const result = mergeClassList(classList, configUtils)
cacheSet(classList, result)
return result
}
return function callTailwindMerge() {
return functionToCall(twJoin.apply(null, arguments as any))
}
} This looks quite similar to the memoisation pattern in sections of our webpacked code, for example: function p() {
var e = (0, r._)([
"\n absolute w-4 h-4 rounded-full text-[10px] text-white flex justify-center items-center right-0 top-[20px] -mr-2 border border-white\n ",
"\n ",
"\n",
]);
return (
(p = function () {
return e;
}),
e
);
} Though while it shares a similar sort of memoisation pattern; it doesn't seem to actually be the same code. Here are some references for
Thinking more about the structure of the webpacked code from y = d.Z.span(
p(),
function (e) {
return "warning" === e.$type && "bg-orange-500 text-white";
},
function (e) {
return "danger" === e.$type && "bg-red-500 text-white";
}
), ..it kind of feels like the memoisation could be happening at a higher layer than I wonder if something in the webpack minimisation process is applying a memo to the text passed to the template tags; or perhaps this might even be something that is being done manually in the webpacked app itself. |
@pionxzh Obviously all of the above deep dive research is a LOT, and I wouldn't expect you to read it all in depth right now, but based on what I discovered above, I think it might be possible to make some simple'ish inferences (though without being as robust as perfectly matching the module first (#41)). Here's the first one, and i'll add the other one in a new comment after this. Smart-Rename for 'function replaces self' memoisation patternWe could potentially detect memoisation patterns like the following, and rename the function something more useful: function p() {
var e = (0, r._)([
"\n absolute w-4 h-4 rounded-full text-[10px] text-white flex justify-center items-center right-0 top-[20px] -mr-2 border border-white\n ",
"\n ",
"\n",
]);
return (
(p = function () {
return e;
}),
e
);
} Here's some basic code that ChatGPT generated for this: const jscodeshift = require('jscodeshift').withParser('babylon');
const sourceCode = `TODO` // TODO: include the source code to be processed here
const ast = jscodeshift(sourceCode);
ast.find(jscodeshift.FunctionDeclaration)
.forEach(path => {
// Check if this function reassigns itself
const hasSelfReassignment = jscodeshift(path)
.find(jscodeshift.AssignmentExpression)
.some(assignmentPath => {
const left = assignmentPath.value.left;
return left.type === 'Identifier' && left.name === path.value.id.name;
});
if (hasSelfReassignment) {
const oldName = path.value.id.name
const newName = `${path.value.id.name}Memo`
// Rename the function
path.value.id.name = newName;
console.log(`Function ${oldName} is using a memoization pattern, renamed to ${newName}.`);
} else {
console.log(`Function ${path.value.id.name} is NOT using a memoization pattern.`);
}
});
// Further transformation code and printing the modified source code You can see it in a REPL here: The current output is something like this:
This could use the standard 'rename function' code that |
Smart rename for
|
Nice finding for twMerge and the |
Edit: This comment has been replicated/referenced in the following more specifically relevant issue here:
@pionxzh I just had another idea about this, based on some of my deep diving into Using the If I pass in some code like this: const foo = bar`
staticOne
staticTwo
${dynamicOne}
${dynamicTwo}
staticThree
${dynamicThree}
` It transpiles to this: function _tagged_template_literal(strings, raw) {
if (!raw) {
raw = strings.slice(0);
}
return Object.freeze(Object.defineProperties(strings, {
raw: {
value: Object.freeze(raw)
}
}));
}
function _templateObject() {
var data = _tagged_template_literal([
"\n staticOne\n staticTwo\n ",
"\n ",
"\n staticThree\n ",
"\n"
]);
_templateObject = function _templateObject() {
return data;
};
return data;
}
var foo = bar(_templateObject(), dynamicOne, dynamicTwo, dynamicThree); The Whereas the Looking at the signature for tagged template functions:
We can see that they take a var foo = bar(_templateObject(), dynamicOne, dynamicTwo, dynamicThree); Based on that, we could 're-symbolise' that webpacked code as: function _templateObjectP() {
var data = _tagged_template_literal([
"\n absolute w-4 h-4 rounded-full text-[10px] text-white flex justify-center items-center right-0 top-[20px] -mr-2 border border-white\n ",
"\n ",
"\n",
]);
_templateObjectP = function _templateObject() {
return data;
};
return data;
}
y = d.Z.span(
_templateObjectP(),
function (e) {
return "warning" === e.$type && "bg-orange-500 text-white";
},
function (e) {
return "danger" === e.$type && "bg-red-500 text-white";
}
), Which we could then 'normalise' back to the original tagged template literal syntax as: y = d.Z.span`
absolute w-4 h-4 rounded-full text-[10px] text-white flex justify-center items-center right-0 top-[20px] -mr-2 border border-white
function (e) {
return "warning" === e.$type && "bg-orange-500 text-white";
}
function (e) {
return "danger" === e.$type && "bg-red-500 text-white";
}
`; Or even simplify it further to just: y = d.Z.span`
absolute w-4 h-4 rounded-full text-[10px] text-white flex justify-center items-center right-0 top-[20px] -mr-2 border border-white
(e) => "warning" === e.$type && "bg-orange-500 text-white"
(e) => "danger" === e.$type && "bg-red-500 text-white"
`; ChatGPT Explanation
|
styled-components
/ Tailwind-Styled-Component
libsstyled-components
/ Tailwind-Styled-Component
libs
This relates to the 'module-detection' feature described in the following issue:
While looking through some decompiled code in a rather complex webpack bundled app, I've identified what seems to be the
styled-components
library (or something very similar to it):It would be cool to be able to handle
styled-components
when reversing code.This was originally based on the code I mentioned in the following comment:
A more general high level version of 'module detection' that this feature relates to is described in:
Edit: I've also captured all of my below notes on the following gist for easier future reference:
The text was updated successfully, but these errors were encountered: