Skip to content

Commit

Permalink
feat(exposeConfig): nested properties (#583)
Browse files Browse the repository at this point in the history
* refactor(expose-config): recursively iterating over config

* chore: type definitions and a bugfix

* bugfix: escaping quotes and special characters

* refactor: using templates now

* feat: exports depending on exposeLevel

* chore: set start level to 1

* chore: updated docs and added test

* chore: more docs, and reverting d.ts file

* fix: passing write prop to test setup

Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
  • Loading branch information
ineshbose and atinux authored Jan 25, 2023
1 parent 7da555c commit e88c23a
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 6 deletions.
29 changes: 29 additions & 0 deletions docs/content/1.getting-started/2.options.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,35 @@ export default {

Learn more about it in the [Referencing in the application](/tailwind/config#referencing-in-the-application) section.

## `exposeLevel`

- Default: `2`

If you want to only import *really* specific parts of your tailwind config, you can enable imports for each property in the config:

```ts [nuxt.config]
export default {
tailwindcss: {
exposeConfig: true,
exposeLevel: 3
}
}
```

This is only relevant when [`exposeConfig`](/getting-started/options#exposeconfig) is `true`. Using `exposeLevel` to be ≤ 0 will only expose root properties.

::alert{type="warning"}

It is unlikely for `exposeLevel` to ever be over 4 - the usual depth of a Tailwind config. A higher value is also likely to increase boot-time and disk space in dev.

::

::alert{type="info"}

Named exports for properties below [root options](https://tailwindcss.com/docs/configuration#configuration-options) are prefixed with `_` (`_colors`, `_900`, `_2xl`) to ensure safe variable names. You can use default imports to provide any identifier or rename named imports using `as`. Properties with unsafe variable names (`spacing['1.5']`, `height['1/2']`, `keyframes.ping['75%, 100%']`) do not get exported individually.

::

## `injectPosition`

- Default: `'first'`
Expand Down
16 changes: 11 additions & 5 deletions docs/content/2.tailwind/1.config.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,15 @@ This merging strategy of with a function only applies to `plugins` and `content`

::

### Whitelisting classes
### Safelisting classes

If you need to whitelist classes and avoid the content purge system, you need to specify the `safelist` option:
If you need to safelist classes and avoid the content purge system, you need to specify the `safelist` option:

```js{}[tailwind.config.js]
module.exports = {
// Whitelisting some classes to avoid content purge
// Safelisting some classes to avoid content purge
safelist: [
'whitelisted',
'safelisted',
{
pattern: /bg-(red|green|blue)-(100|200|300)/,
},
Expand All @@ -224,7 +224,8 @@ If you need resolved Tailwind config at runtime, you can enable the [exposeConfi
```js{}[nuxt.config]
export default {
tailwindcss: {
exposeConfig: true
exposeConfig: true,
// exposeLevel: 1, // determines tree-shaking (optional)
}
}
```
Expand All @@ -237,6 +238,11 @@ import tailwindConfig from '#tailwind-config'

// Import only part which is required to allow tree-shaking
import { theme } from '#tailwind-config'

// Import within properties for further tree-shaking (based on exposeLevel)
import screens from '#tailwind-config/theme/screens' // default import
import { _neutral } from '#tailwind-config/theme/colors' // named (with _ prefix)
import { _800 as slate800 } from '#tailwind-config/theme/colors/slate' // alias
```

::alert{type="warning"}
Expand Down
39 changes: 38 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface ModuleOptions {
config: Config;
viewer: boolean;
exposeConfig: boolean;
exposeLevel: number;
injectPosition: InjectPosition;
disableHmrHotfix: boolean;
}
Expand All @@ -63,6 +64,7 @@ export default defineNuxtModule<ModuleOptions>({
config: defaultTailwindConfig(),
viewer: true,
exposeConfig: false,
exposeLevel: 2,
injectPosition: 'first',
disableHmrHotfix: false
}),
Expand Down Expand Up @@ -149,10 +151,45 @@ export default defineNuxtModule<ModuleOptions>({
// Expose resolved tailwind config as an alias
// https://tailwindcss.com/docs/configuration/#referencing-in-javascript
if (moduleOptions.exposeConfig) {
/**
* Creates MJS exports for properties of the config
*
* @param obj config
* @param path parent properties trace
* @param level level of object depth
* @param maxLevel maximum level of depth
*/
const populateMap = (obj: any, path: string[] = [], level = 1, maxLevel = moduleOptions.exposeLevel) => {
Object.entries(obj).forEach(([key, value = {}]) => {
if (
level >= maxLevel || // if recursive call is more than desired
typeof value !== 'object' || // if its not an object, no more recursion required
Array.isArray(value) || // arrays are objects in JS, but we can't break it down
Object.keys(value).find(k => !k.match(/^[0-9a-z]+$/i)) // object has non-alphanumeric property (unsafe var name)
) {
addTemplate({
filename: `tailwind.config/${path.concat(key).join('/')}.mjs`,
getContents: () => `export default ${JSON.stringify(value, null, 2)}`
})
} else {
// recurse through nested objects
populateMap(value, path.concat(key), level + 1, maxLevel)

const values = Object.keys(value)
addTemplate({
filename: `tailwind.config/${path.concat(key).join('/')}.mjs`,
getContents: () => `${Object.keys(value).map(v => `import _${v} from "./${key}/${v}.mjs"`).join('\n')}\nconst config = { ${values.map(k => `"${k}": _${k}`).join(', ')} }\nexport { config as default${values.length > 0 ? ', _' : ''}${values.join(', _')} }`
})
}
})
}

populateMap(resolvedConfig)

const configOptions = Object.keys(resolvedConfig)
const template = addTemplate({
filename: 'tailwind.config.mjs',
getContents: () => `${Object.entries(resolvedConfig).map(([k, v]) => `export const ${k} = ${JSON.stringify(v, null, 2)}`).join('\n')}\nexport default { ${configOptions.join(', ')} }`
getContents: () => `${configOptions.map(v => `import ${v} from "./tailwind.config/${v}.mjs"`).join('\n')}\nconst config = { ${configOptions.join(', ')} }\nexport { config as default, ${configOptions.join(', ')} }`
})
addTemplate({
filename: 'tailwind.config.d.ts',
Expand Down
8 changes: 8 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('tailwindcss module', async () => {

await setupNuxtTailwind({
exposeConfig: true,
exposeLevel: 2,
cssPath: r('tailwind.css')
})

Expand Down Expand Up @@ -53,4 +54,11 @@ describe('tailwindcss module', async () => {
// expect(logger.info).toHaveBeenNthCalledWith(2, `Merging Tailwind config from ~/${relative(rootDir, nuxt.options.tailwindcss.configPath)}`)
// })
//

test('expose config', () => {
const nuxt = useTestContext().nuxt
const vfsKey = Object.keys(nuxt.vfs).find(k => k.includes('tailwind.config/theme/animation.'))
// check default tailwind default animation exists
expect(nuxt.vfs[vfsKey]).contains('"pulse": "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"')
})
})

0 comments on commit e88c23a

Please sign in to comment.