Skip to content

Commit

Permalink
🩹✨ fix(patch): importable tailwindcss; fix @roots/sage error logs (#1703
Browse files Browse the repository at this point in the history
)

- moves the tailwind config handlers out of `@roots/sage` into `@roots/bud-tailwindcss`
- generates virtual modules for tailwind config values aliased to `@tailwind`

Generating the imports can be memory intensive and increase build times, so it is opt-in.

Generating an import for every `theme` key:

```ts
app.tailwind.generateImports()
```

Generating an import for specific keys:

```ts
app.tailwind.generateImports([`colors`, `fontFamily`])
```

Generating an import for specific used keys is a lot less intensive than generating imports for everything. In the future we can add a NormalModule plugin to do this automatically.

Anyway, on to an example of using the generated imports in app code:

```ts
import {black} from '@tailwind/colors'
import {sans} from '@tailwind/fontFamily'

export const main = () => {
  document.body.style.backgroundColor = black
  document.body.style.fontFamily = sans
}
```

This is a lot better than trying to import the actual `tailwind.config.js` file to read these values as the values are fully resolved (merged with `defaultTheme`, plugins applied, etc.)

And it's a lot better than importing `tailwindcss/resolveConfig` and doing it in the app code because of the transitive dependencies taken on by that import.

It's also better than pre-compiling a static json file because these values are tree-shakeable. The entire generated json for the default tailwind config is ~100kb.  

The above example adds ~5kb to the overall bundle (and only because tailwind has so many default colors).

If you don't import from `@tailwindcss/*` nothing is added to the bundle (even if the imports are generated)

refers:

- none

## Type of change

**PATCH: backwards compatible change**



This PR includes breaking changes to the following core packages:

- none

This PR includes breaking changes to the follow extensions:

- none

## Dependencies

### Adds

- none

### Removes

- none
  • Loading branch information
kellymears committed Sep 24, 2022
1 parent ca49027 commit 40b1448
Show file tree
Hide file tree
Showing 17 changed files with 1,665 additions and 254 deletions.
2 changes: 2 additions & 0 deletions examples/tailwindcss/bud.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ module.exports = async bud => {
.entry({app: ['app.css', 'app.js']})
.minimize()
.template()

.tailwind.generateImports(['colors'])
}
11 changes: 3 additions & 8 deletions examples/tailwindcss/src/app.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
const body = document.querySelector('body')
import * as colors from '@tailwind/colors'

const changeBg = () => {
body.classList = ''
body.classList.add('bg-indigo-600')
}
document.body.style.backgroundColor = colors.indigo[600]

changeBg()

module?.hot?.accept(err => changeBg())
module?.hot?.accept(console.error)
42 changes: 42 additions & 0 deletions sources/@repo/docs/content/blog/6.4.2.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
slug: '6.4.2'
title: 'Release: 6.4.2'
description: 'Release notes for bud.js 6.4.2'
date: 2022-08-23
author: Kelly Mears
author_title: Lead developer
author_url: https://github.com/kellymears
author_image_url: https://avatars.githubusercontent.com/u/397606?v=4
tags: [release]
---

<!--truncate-->

## 👉🏼 known issue: [@roots/sage] errors logged when not using tailwindcss

If you are not using `@roots/bud-tailwindcss` you will see errors in the logs when running `bud build` or `bud dev`.
This is a known issue and will be resolved in the next release. It isn't a sign that anything is wrong in your project.

## improve: [@roots/sage] `bud.wpjson` now fully resolves tailwind configs

Previously, `bud.wpjson` could only generate a `theme.json` based on a fully static tailwind config file. This excluded tailwind configs that
defined theme properties using functions and also meant that plugins would not be applied.

Now, `bud.wpjson` functions related to tailwindcss will fully resolve the tailwind config file and generate a `theme.json` based on the
processed config. This should mean that _any_ tailwind config can be used in conjunction with `bud.wpjson.useTailwindColors()` and similar functions.

## 🩹 fix: build errors return non-zero exit code

For production builds, build errors will now result in a non-zero exit code. This regression was introduced in 6.4.0. It is now fixed.

## 🩹 fix: eslint.config.js support

`eslint.config.js` configurations are now supported.

## 🩹 fix: bud repl

The `bud repl` command has seen significant improvements since 6.4.0. It fixes a bug in 6.4.1 related to syntax highlighting.

## ℹ️ Release information

For more information [review the diff to see what's changed](https://github.com/roots/bud/compare/v6.4.0...v6.4.2).
6 changes: 4 additions & 2 deletions sources/@roots/bud-tailwindcss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
"@roots/bud-api": "workspace:sources/@roots/bud-api",
"@skypack/package-check": "0.2.2",
"@types/node": "16.11.48",
"@types/tailwindcss": "3.0.11"
"@types/tailwindcss": "3.0.11",
"@types/webpack-virtual-modules": "^0"
},
"dependencies": {
"@roots/bud-framework": "workspace:sources/@roots/bud-framework",
Expand All @@ -81,7 +82,8 @@
"autoprefixer": "^10.4.8",
"chalk": "5.0.1",
"tailwindcss": "^3.1.8",
"tslib": "2.4.0"
"tslib": "2.4.0",
"webpack-virtual-modules": "0.4.5"
},
"peerDependencies": {
"autoprefixer": "*",
Expand Down
10 changes: 0 additions & 10 deletions sources/@roots/bud-tailwindcss/src/env.ts

This file was deleted.

158 changes: 153 additions & 5 deletions sources/@roots/bud-tailwindcss/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,25 @@ import {
dependsOn,
expose,
label,
options,
} from '@roots/bud-framework/extension/decorators'
import {
get,
isFunction,
isObject,
isUndefined,
} from '@roots/bud-support/lodash-es'
import defaultConfig from 'tailwindcss/defaultConfig.js'
import pluginUtils from 'tailwindcss/lib/util/pluginUtils.js'
import resolveConfig from 'tailwindcss/resolveConfig.js'
import type {Config, ThemeConfig} from 'tailwindcss/types/config'
import WebpackVirtualModules from 'webpack-virtual-modules'

type ResolvedConfig = Partial<{
[K in keyof Config['theme'] as `${K & string}`]: ReturnType<
Config['theme'][K]
>
}>

/**
* TailwindCSS support for `@roots/bud`
Expand All @@ -15,8 +33,104 @@ import {
*/
@label(`@roots/bud-tailwindcss`)
@dependsOn([`@roots/bud-postcss`])
@expose(`tailwindcss`)
@expose(`tailwind`)
@options({
generateImports: false,
})
export default class BudTailwindCss extends Extension {
/**
* Resolved tailwind config
*
* @remarks
* 🚨 Any mutations to this object will be applied to the generated tailwindcss!
*
* @public
*/
public theme: ResolvedConfig

/**
* Resolved paths
* @public
*/
public dependencies = {tailwindcss: null, nesting: null}

/**
* Keys that can be imported from `@tailwind` alias
*
* @public
*/
public get importableKeys() {
return Array.isArray(this.options.generateImports)
? this.options.generateImports
: Object.keys(this.theme)
}

/**
* Resolve a tailwind config value
* @public
*/
@bind
public resolveTailwindConfigValue<
K extends `${keyof ThemeConfig & string}`,
>(key: K): Config {
const rawValue = this.theme[key]
return isFunction(rawValue) ? rawValue(pluginUtils) : rawValue
}

/**
* Generate a static module for a tailwind theme key
* @param key - a tailwind confg key
* @returns
*/
@bind
public makeStaticModule(key: keyof Config['theme']) {
const value = get(this.theme, key)

return isObject(value)
? Object.entries(value).reduce(
(acc, [key, value]) =>
`${acc}\nexport const ${key} = ${JSON.stringify(value)}\n`,
``,
)
: `export default ${key} = ${value}`
}

@bind
public async generateImports(
imports?: Array<`${keyof Config['theme'] & string}`>,
) {
this.setOption(
`generateImports`,
!isUndefined(imports) ? imports : true,
)
return this
}

/**
* `init` callback
*
* @public
*/
@bind
public async init() {
this.dependencies.tailwindcss = await this.resolve(`tailwindcss`)
this.dependencies.nesting = await this.resolve(
`tailwindcss/nesting/index.js`,
)

const configDescription =
this.app.context.config[`tailwind.config.js`] ??
this.app.context.config[`tailwind.config.mjs`] ??
this.app.context.config[`tailwind.config.cjs`]

this.theme = Object.assign(
{},
configDescription.module
? resolveConfig(configDescription.module).theme
: resolveConfig(defaultConfig).theme,
)
}

/**
* `configAfter` callback
*
Expand All @@ -26,14 +140,48 @@ export default class BudTailwindCss extends Extension {
@bind
public async configAfter() {
try {
const tailwindcss = await this.resolve(`tailwindcss`)
const nesting = await this.resolve(`tailwindcss/nesting/index.js`)

this.app.postcss.setPlugins({nesting, tailwindcss})
this.app.postcss.setPlugins({
nesting: this.dependencies.nesting,
tailwindcss: this.dependencies.tailwindcss,
})

this.logger.success(`postcss configured for tailwindcss`)
} catch (message) {
this.logger.error(message)
}

try {
if (
this.options.generateImports === true ||
Array.isArray(this.options.generateImports)
) {
await this.app.extensions.add({
label: `bud-tailwindcss-virtual-module`,
make: async () => {
return new WebpackVirtualModules(
this.importableKeys.reduce(
(acc, key) => ({
...acc,
[this.app.path(
`@src`,
`__bud`,
`@tailwind`,
`${key}.mjs`,
)]: this.makeStaticModule(key),
}),
{},
),
)
},
})

this.app.hooks.async(`build.resolve.alias`, async alias => ({
...alias,
[`@tailwind`]: `${this.app.path(`@src`, `__bud`, `@tailwind`)}`,
}))
}
} catch (message) {
this.logger.error(message)
}
}
}
10 changes: 9 additions & 1 deletion sources/@roots/bud-tailwindcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
* @packageDocumentation
*/

import './env.js'
declare module '@roots/bud-framework' {
interface Bud {
tailwind: BudTailwindCss
}

interface Modules {
'@roots/bud-tailwindcss': BudTailwindCss
}
}

import BudTailwindCss from './extension.js'
export default BudTailwindCss
1 change: 1 addition & 0 deletions sources/@roots/sage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
},
"devDependencies": {
"@jest/globals": "29.0.3",
"@roots/bud-tailwindcss": "workspace:sources/@roots/bud-tailwindcss",
"@skypack/package-check": "0.2.2",
"@types/node": "16.11.48",
"tailwindcss": "^3.1.8",
Expand Down
Loading

0 comments on commit 40b1448

Please sign in to comment.