Skip to content

Commit

Permalink
Add preset for SolidJS (#817)
Browse files Browse the repository at this point in the history
Twin now has support for [SolidJS](https://www.solidjs.com/)
When we define the solid preset like this:

```js
// babel-plugin-macros.config.js
module.exports = {
  twin: {
    preset: 'solid',
  },
}
```

Or in `package.json`:

```js
// package.json
"babelMacros": {
  "twin": {
    "preset": "solid"
  }
},
```

Twin will use the imports from
[solid-styled-components](https://github.com/solidjs/solid-styled-components)
- the official css-in-js library provided by the SolidJS team.

Here are the conversions that happen during common styling patterns:

### `tw` prop

```js
function Component() {
  return <div tw="block" />
}
// ↓ ↓ ↓ ↓ ↓ ↓
import { styled } from "solid-styled-components";
const TwComponent = styled("div")({ "display": "block" });
function Component() {
  return <TwComponent data-tw="block" />;
}
```

### `css` prop

```js
const Component = ({ children }) => (
  <div css={[]}>{children}</div>
)
// ↓ ↓ ↓ ↓ ↓ ↓
import { styled } from "solid-styled-components";
const TwComponent = styled("div")([]);
const Component = ({ children }) => (
  <TwComponent>{children}</TwComponent>
)
```

### `tw.div`

```js
import tw from "twin.macro"
const Component = tw.div`block`
// ↓ ↓ ↓ ↓ ↓ ↓
import { styled } from "solid-styled-components";
const Component = styled("div")({ "display": "block" });
```

### `styled.div`

```js
import { styled } from "twin.macro"
const Component = styled.div([])
// ↓ ↓ ↓ ↓ ↓ ↓
import { styled } from "solid-styled-components";
const Component = styled("div")([]);
```

### More

- [StackBlitz
example](https://stackblitz.com/github/ben-rogerson/twin.examples/tree/master/vite-solid-typescript?file=src/App.tsx)
- [SolidJS
example](https://github.com/ben-rogerson/twin.examples/blob/master/vite-solid-typescript/README.md)
-
[solid-styled-components](https://github.com/solidjs/solid-styled-components)

Related #398
  • Loading branch information
ben-rogerson committed Jul 23, 2023
1 parent d8bd0c5 commit e438582
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 50 deletions.
27 changes: 13 additions & 14 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@

These options are available in your [twin config](#twin-config-location):

| Name | Default | Description |
| --------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| config | `"tailwind.config.js"` | The path to your Tailwind config. Also takes a config object. |
| preset | `"emotion"` | The css-in-js library behind the scenes.<br>Also supported: `"styled-components"` `"goober"` `"stitches"` |
| dataTwProp | `true` | Add a prop to jsx components in development showing the original tailwind classes.<br/> Use `"all"` to keep the prop in production. |
| debug | `false` | Display information in your terminal about the Tailwind class conversions. |
| disableShortCss | `true` | Disable converting short css within the tw import/prop. |
| hasLogColors | `true` | Disable log colors to remove the glyphs when the color display is not supported |
| includeClassNames | `false` | Look in className props for tailwind classes to convert. |
| dataCsProp | `true` | Add a prop to your elements in development so you can see the original cs prop classes, eg: `<div data-cs="maxWidth[1em]" />`. |
| disableCsProp | `true` | Disable twin from reading values specified in the cs prop. |
| sassyPseudo | `false` | Some css-in-js frameworks require the `&` in selectors like `&:hover`, this option ensures it’s added. |
| moveKeyframesToGlobalStyles | `false` | `@keyframes` are added next to the `animation-x` classes - this option can move them to global styles instead. |
| autoCssProp | _deprecated in v2.8.2_ | `styled-components` only: Used to add an import of 'styled-components/macro' which automatically adds the styled-components css prop. Here’s how to [setup the new styled-components css prop](https://twinredirect.page.link/auto-css-prop). |
| Name | Default | Description |
| --------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| config | `"tailwind.config.js"` | The path to your Tailwind config. Also takes a config object. |
| preset | `"emotion"` | The css-in-js library behind the scenes.<br>Also supported: `"styled-components"` `"goober"` `"stitches"` `"solid"` |
| dataTwProp | `true` | Add a prop to jsx components in development showing the original tailwind classes.<br/> Use `"all"` to keep the prop in production. |
| debug | `false` | Display information in your terminal about the Tailwind class conversions. |
| disableShortCss | `true` | Disable converting short css within the tw import/prop. |
| hasLogColors | `true` | Disable log colors to remove the glyphs when the color display is not supported |
| includeClassNames | `false` | Check className attributes for tailwind classes to convert. |
| dataCsProp | `true` | Add a prop to your elements in development so you can see the original cs prop classes, eg: `<div data-cs="maxWidth[1em]" />`. |
| disableCsProp | `true` | Disable twin from reading values specified in the cs prop. |
| sassyPseudo | `false` | Some css-in-js frameworks require the `&` in selectors like `&:hover`, this option ensures it’s added. |
| moveKeyframesToGlobalStyles | `false` | `@keyframes` are added next to the `animation-x` classes - this option can move them to global styles instead. |

### Options

Expand Down
3 changes: 2 additions & 1 deletion docs/styled-component-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ interface ContainerProps {
hasBg?: string
}

const Container = styled.div(({ hasBg }: ContainerProps) => [
const Container = styled.div<ContainerProps>(({ hasBg }) => [
tw`flex w-full`, // Add base styles first
hasBg && tw`bg-black`, // Then add conditional styles
])
Expand Down Expand Up @@ -342,6 +342,7 @@ Use a theme value to grab a value from your tailwind.config:

```js
tw.div`[color:theme('colors.gray.300')]`
tw.div`[margin:theme('spacing[2.5]')]`
tw.div`[box-shadow: 5px 10px theme('colors.black')]`
```

Expand Down
11 changes: 8 additions & 3 deletions src/core/createCoreContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import type {

function packageCheck(
packageToCheck: PossiblePresets,
params: GetPackageConfig
params: GetPackageConfig,
hasNoFallback?: boolean
): boolean {
if (params.config && params.config.preset === packageToCheck) return true

if (hasNoFallback) return false

return (
(params.config && params.config.preset === packageToCheck) ||
params.styledImport.from.includes(packageToCheck) ||
params.cssImport.from.includes(packageToCheck)
)
Expand All @@ -36,8 +40,9 @@ type GetPackageConfig = {
function getPackageUsed(params: GetPackageConfig): GetPackageUsed {
return {
isEmotion: packageCheck('emotion', params),
isStyledComponents: packageCheck('styled-components', params),
isStyledComponents: packageCheck('styled-components', params, true),
isGoober: packageCheck('goober', params),
isSolid: packageCheck('solid', params),
isStitches: packageCheck('stitches', params),
}
}
Expand Down
45 changes: 36 additions & 9 deletions src/core/lib/twinConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const TWIN_CONFIG_DEFAULTS = {
autoCssProp: false,
config: undefined,
convertHtmlElementToStyled: false,
convertStyledDot: false,
convertStyledDotToParam: false,
convertStyledDotToFunction: false,
css: { import: '', from: '' },
dataCsProp: false,
dataTwProp: false,
Expand All @@ -21,26 +22,38 @@ const TWIN_CONFIG_DEFAULTS = {
sassyPseudo: false,
stitchesConfig: undefined,
styled: { import: '', from: '' },
}
} as const

// Defaults for different css-in-js libraries
const configDefaultsGoober = { sassyPseudo: true } // Sets selectors like hover to &:hover
const configDefaultsGoober = {
sassyPseudo: true, // Sets selectors like hover to &:hover
} as const

const configDefaultsSolid = {
sassyPseudo: true, // Sets selectors like hover to &:hover
moveTwPropToStyled: true, // Move the tw prop to a styled definition
convertHtmlElementToStyled: true, // Add a styled definition on css prop elements
convertStyledDotToFunction: true, // Convert styled.[element] to a default syntax
} as const

const configDefaultsStitches = {
sassyPseudo: true, // Sets selectors like hover to &:hover
convertStyledDot: true, // Convert styled.[element] to a default syntax
convertStyledDotToParam: true, // Convert styled.[element] to a default syntax
moveTwPropToStyled: true, // Move the tw prop to a styled definition
convertHtmlElementToStyled: true, // For packages like stitches, add a styled definition on css prop elements
convertHtmlElementToStyled: true, // Add a styled definition on css prop elements
stitchesConfig: undefined, // Set the path to the stitches config
moveKeyframesToGlobalStyles: true, // Stitches doesn't support inline @keyframes
}
} as const

function configDefaultsTwin({
isSolid,
isGoober,
isStitches,
isDev,
}: GetPackageUsed & { isDev: boolean }): TwinConfigAll {
return {
...TWIN_CONFIG_DEFAULTS,
...(isSolid && configDefaultsSolid),
...(isGoober && configDefaultsGoober),
...(isStitches && configDefaultsStitches),
dataTwProp: isDev,
Expand All @@ -52,7 +65,13 @@ function isBoolean(value: unknown): boolean {
return typeof value === 'boolean'
}

const allowedPresets = ['styled-components', 'emotion', 'goober', 'stitches']
const allowedPresets = [
'styled-components',
'emotion',
'goober',
'stitches',
'solid',
]

type ConfigTwinValidators = Record<
keyof typeof TWIN_CONFIG_DEFAULTS & 'disableColorVariables',
Expand All @@ -76,6 +95,10 @@ const configTwinValidators: ConfigTwinValidators = {
(value: unknown): boolean => !value,
'The “autoCssProp” feature has been removed from twin.macro@2.8.2+\nThis means the css prop must be added by styled-components instead.\nSetup info at https://twinredirect.page.link/auto-css-prop\n\nRemove the “autoCssProp” item from your config to avoid this message.',
],
convertStyledDot: [
(value: unknown): boolean => !value,
'The “convertStyledDot” feature was changed to “convertStyledDotParam”.',
],
disableColorVariables: [
(value: unknown): boolean => !value,
'The disableColorVariables feature has been removed from twin.macro@3+\n\nRemove the disableColorVariables item from your config to avoid this message.',
Expand All @@ -97,9 +120,13 @@ const configTwinValidators: ConfigTwinValidators = {
isBoolean,
'The config “disableCsProp” can only be a boolean',
],
convertStyledDot: [
convertStyledDotToParam: [
isBoolean,
'The config “convertStyledDotToParam” can only be a boolean',
],
convertStyledDotToFunction: [
isBoolean,
'The config “convertStyledDot” can only be a boolean',
'The config “convertStyledDotToFunction” can only be a boolean',
],
moveTwPropToStyled: [
isBoolean,
Expand Down
5 changes: 5 additions & 0 deletions src/core/lib/userPresets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ const userPresets = {
css: { import: 'css', from: 'stitches.config' },
global: { import: 'global', from: 'stitches.config' },
},
solid: {
styled: { import: 'styled', from: 'solid-styled-components' },
css: { import: 'css', from: 'solid-styled-components' },
global: { import: 'createGlobalStyles', from: 'solid-styled-components' },
},
}

export default userPresets
4 changes: 3 additions & 1 deletion src/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export type TwinConfigAll = {
disableCsProp: boolean
disableShortCss: boolean
config?: string | Partial<TailwindConfig>
convertStyledDot?: boolean
convertStyledDotToParam?: boolean
convertStyledDotToFunction?: boolean
moveTwPropToStyled?: boolean
moveKeyframesToGlobalStyles?: boolean
convertHtmlElementToStyled?: boolean
Expand Down Expand Up @@ -133,6 +134,7 @@ export type GetPackageUsed = {
isStyledComponents: boolean
isGoober: boolean
isStitches: boolean
isSolid: boolean
}

export type TailwindMatchOptions = {
Expand Down
1 change: 1 addition & 0 deletions src/macro/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function convertHtmlElementToStyled(
...params,
jsxPath,
secondArg: t.objectExpression([]),
fromProp: 'css',
})
}

Expand Down
2 changes: 1 addition & 1 deletion src/macro/globalStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ function handleGlobalStylesJsx(params: AdditionalHandlerParameters): void {
state.isImportingCss = !state.existingCssIdentifier
}

if (coreContext.packageUsed.isGoober) {
if (coreContext.packageUsed.isGoober || coreContext.packageUsed.isSolid) {
const declaration = getGlobalDeclarationTte(declarationData)
program.unshiftContainer('body', declaration)
path.replaceWith(t.jSXIdentifier(globalUid.name))
Expand Down
72 changes: 67 additions & 5 deletions src/macro/lib/astHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ function parseTte(

function replaceWithLocation<EmptyArray>(
path: NodePath,
replacement: NodePath | T.Expression
replacement: NodePath | T.Expression | T.ExpressionStatement
): [NodePath] | EmptyArray[] {
const { loc } = path.node
const newPaths = replacement ? path.replaceWith(replacement) : []
Expand Down Expand Up @@ -418,6 +418,55 @@ type MakeStyledComponent = {
program: NodePath<T.Program>
state: State
coreContext: CoreContext
fromProp: 'tw' | 'css'
}

type CreateStyledProps = Pick<
MakeStyledComponent,
'jsxPath' | 't' | 'secondArg'
> & {
stateStyled: T.Identifier
constName: T.Identifier
firstArg: T.MemberExpression | T.Identifier | T.StringLiteral
}

function createStyledPropsForTw({
t,
stateStyled,
firstArg,
secondArg,
constName,
}: CreateStyledProps): T.VariableDeclaration {
const callee = t.callExpression(stateStyled, [firstArg])
const declarations = [
t.variableDeclarator(constName, t.callExpression(callee, [secondArg])),
]

return t.variableDeclaration('const', declarations)
}

function createStyledPropsForCss(
args: CreateStyledProps
): T.VariableDeclaration | undefined {
const cssPropAttribute = args.jsxPath
.get('attributes')
.find(
p =>
p.isJSXAttribute() &&
p.get('name').isJSXIdentifier() &&
p.get('name')?.node.name === 'css'
)

const cssPropValue = cssPropAttribute?.get(
'value'
) as NodePath<T.JSXExpressionContainer>

const expression = cssPropValue?.node?.expression
if (!expression || expression.type === 'JSXEmptyExpression') return

cssPropAttribute?.remove()

return createStyledPropsForTw({ ...args, secondArg: expression })
}

function makeStyledComponent({
Expand All @@ -427,6 +476,7 @@ function makeStyledComponent({
program,
state,
coreContext,
fromProp,
}: MakeStyledComponent): void {
const constName = program.scope.generateUidIdentifier('TwComponent')

Expand All @@ -436,11 +486,23 @@ function makeStyledComponent({
}

const firstArg = getFirstStyledArgument(jsxPath, t, coreContext.assert)
let styledDefinition = null
const stateStyled: T.Identifier = state.styledIdentifier

if (coreContext.packageUsed.isSolid) {
const params = { jsxPath, t, stateStyled, firstArg, secondArg, constName }
styledDefinition =
fromProp === 'tw'
? createStyledPropsForTw(params)
: createStyledPropsForCss(params)
} else {
const args = [firstArg, secondArg].filter(Boolean)
const init = t.callExpression(stateStyled, args)
const declarations = [t.variableDeclarator(constName, init)]
styledDefinition = t.variableDeclaration('const', declarations)
}

const args = [firstArg, secondArg].filter(Boolean)
const identifier = t.callExpression(state.styledIdentifier, args)
const styledProps = [t.variableDeclarator(constName, identifier)]
const styledDefinition = t.variableDeclaration('const', styledProps)
if (!styledDefinition) return

const rootParentPath = jsxPath.findParent(p =>
p.parentPath ? p.parentPath.isProgram() : false
Expand Down
43 changes: 31 additions & 12 deletions src/macro/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ function addStyledImport({
})
}

function moveDotElementToParam({
function moveDotElement({
path,
t,
moveToParam = true,
}: {
path: NodePath
t: typeof T
moveToParam: boolean
}): void {
if (path.parent.type !== 'MemberExpression') return

Expand All @@ -62,11 +64,20 @@ function moveDotElementToParam({
| T.SpreadElement
| T.JSXNamespacedName
| T.ArgumentPlaceholder
const args = [t.stringLiteral(styledName), styledArgs].filter(Boolean)
const replacement = t.callExpression(
(path as NodePath<T.Expression>).node,
args
)
| T.ArrowFunctionExpression

let replacement
if (moveToParam) {
// `styled('div', {})`
const args = [t.stringLiteral(styledName), styledArgs].filter(Boolean)
replacement = t.callExpression((path as NodePath<T.Expression>).node, args)
} else {
// `styled('div')({})`
const callee = t.callExpression((path as NodePath<T.Expression>).node, [
t.stringLiteral(styledName),
])
replacement = t.expressionStatement(t.callExpression(callee, [styledArgs]))
}

replaceWithLocation(parentCallExpression, replacement)
}
Expand All @@ -76,18 +87,26 @@ function handleStyledFunction({
t,
coreContext,
}: AdditionalHandlerParameters): void {
if (!coreContext.twinConfig.convertStyledDot) return
if (
!coreContext.twinConfig.convertStyledDotToParam &&
!coreContext.twinConfig.convertStyledDotToFunction
)
return
if (isEmpty(references)) return

const defaultRefs = references.default || []
const styledRefs = references.styled || []

;[...defaultRefs, ...styledRefs]
.filter(Boolean)
.forEach((path: NodePath): void => {
// convert tw.div`` & styled.div`` to styled('div', {})
moveDotElementToParam({ path, t })
const refs = [...defaultRefs, ...styledRefs].filter(Boolean)

refs.forEach((path: NodePath): void => {
// convert tw.div`` & styled.div`` to styled('div', {}) / styled('div')({})
moveDotElement({
path,
t,
moveToParam: coreContext.twinConfig.convertStyledDotToParam ?? true,
})
})
}

export { updateStyledReferences, addStyledImport, handleStyledFunction }

0 comments on commit e438582

Please sign in to comment.