Skip to content

Commit a50c379

Browse files
authored
feat: more customisable configuration (#743)
* chore: initial * chore: undo pnpm stuff for now * chore: undo pnpm stuff for now * chore: revert ting * chore: add test * chore: draft viewer test
1 parent f7096aa commit a50c379

File tree

10 files changed

+118
-90
lines changed

10 files changed

+118
-90
lines changed

build.config.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { defineBuildConfig } from 'unbuild'
22

33
export default defineBuildConfig({
44
externals: [
5-
'pathe',
6-
'minimatch'
5+
'pathe'
76
],
87
failOnWarn: false
98
})

docs/content/1.getting-started/2.options.md

+3-29
Original file line numberDiff line numberDiff line change
@@ -83,41 +83,14 @@ If you need to resolve the tailwind config in runtime, you can enable the `expos
8383
export default {
8484
tailwindcss: {
8585
exposeConfig: true
86+
// or, for more customisation
87+
// exposeConfig: { level: 2, alias: '#tailwind-config' }
8688
}
8789
}
8890
```
8991

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

92-
## `exposeLevel`
93-
94-
- Default: `2`
95-
96-
If you want to only import *really* specific parts of your tailwind config, you can enable imports for each property in the config:
97-
98-
```ts [nuxt.config]
99-
export default {
100-
tailwindcss: {
101-
exposeConfig: true,
102-
exposeLevel: 3
103-
}
104-
}
105-
```
106-
107-
This is only relevant when [`exposeConfig`](/getting-started/options#exposeconfig) is set to `true`. Setting `exposeLevel` to ≤ 0 will only expose root properties.
108-
109-
::alert{type="warning"}
110-
111-
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. Refer to the [Nuxt Virtual File System](https://nuxt.com/docs/guide/directory-structure/nuxt#virtual-file-system) to see generated files.
112-
113-
::
114-
115-
::alert{type="info"}
116-
117-
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.
118-
119-
::
120-
12194
## `injectPosition`
12295

12396
- Default: `'first'`
@@ -188,6 +161,7 @@ To disable the viewer during development, set its value to `false`:
188161
export default {
189162
tailwindcss: {
190163
viewer: false
164+
// viewer: { endpoint: '/_tailwind' }
191165
}
192166
}
193167
```

docs/content/2.tailwind/1.config.md

+25-9
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,7 @@ If you need resolved Tailwind config at runtime, you can enable the [exposeConfi
267267
```js{}[nuxt.config]
268268
export default {
269269
tailwindcss: {
270-
exposeConfig: true,
271-
// exposeLevel: 1, // determines tree-shaking (optional)
270+
exposeConfig: true
272271
}
273272
}
274273
```
@@ -281,19 +280,36 @@ import tailwindConfig from '#tailwind-config'
281280

282281
// Import only part which is required to allow tree-shaking
283282
import { theme } from '#tailwind-config'
283+
```
284+
285+
Please be aware this adds `~19.5KB` (`~3.5KB`) to the client bundle size. If you want to only import _really_ specific parts of your tailwind config, you can enable imports for each property in the config:
286+
287+
```js{}[nuxt.config]
288+
export default {
289+
tailwindcss: {
290+
exposeConfig: {
291+
level: 4,
292+
alias: '#twcss' // if you want to change alias
293+
}
294+
}
295+
}
296+
```
284297

285-
// Import within properties for further tree-shaking (based on exposeLevel)
286-
import screens from '#tailwind-config/theme/screens' // default import
287-
import { _neutral } from '#tailwind-config/theme/colors' // named (with _ prefix)
288-
import { _800 as slate800 } from '#tailwind-config/theme/colors/slate' // alias
298+
```js
299+
// Import within properties for further tree-shaking
300+
import screens from '#twcss/theme/screens' // default import
301+
import { _neutral } from '#twcss/theme/colors' // named (with _ prefix)
302+
import { _800 as slate800 } from '#twcss/theme/colors/slate' // alias
289303
```
290304

291305
::alert{type="warning"}
292306

293-
Please be aware this adds `~19.5KB` (`~3.5KB`) to the client bundle size.
307+
It is unlikely for `level` 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. Refer to the [Nuxt Virtual File System](https://nuxt.com/docs/guide/directory-structure/nuxt#virtual-file-system) to see generated files.
294308

295309
::
296310

297-
::alert
298-
Import types are only provided through the `#tailwind-config/*` alias.
311+
::alert{type="info"}
312+
313+
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.
314+
299315
::

docs/content/2.tailwind/2.viewer.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Visualize your Tailwind configuration with the viewer.
44

55
---
66

7-
Nuxt Tailwind exposes a `/_tailwind/` route in development, allowing you to quickly visualize your Tailwind configuration with easy copy-pasting (thanks to the awesome [tailwind-config-viewer](https://github.com/rogden/tailwind-config-viewer) package ✨).
7+
By default, the module will expose a `/_tailwind/` route in development, so that you can quickly visualize your Tailwind configuration with easy copy-pasting (thanks to the awesome [tailwind-config-viewer](https://github.com/rogden/tailwind-config-viewer) package ✨).
88

99
The viewer is available starting from version `v3.4.0` of `@nuxtjs/tailwindcss`.
1010

src/module.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import { configMerger } from './utils'
2222
import {
2323
resolveModulePaths,
2424
resolveCSSPath,
25-
resolveInjectPosition
25+
resolveInjectPosition,
26+
resolveExposeConfig,
27+
resolveViewerConfig
2628
} from './resolvers'
2729
import createTemplates from './templates'
2830
import vitePlugin from './vite-hmr'
@@ -82,7 +84,8 @@ export default defineNuxtModule<ModuleOptions>({
8284
// Expose resolved tailwind config as an alias
8385
// https://tailwindcss.com/docs/configuration/#referencing-in-javascript
8486
if (moduleOptions.exposeConfig) {
85-
createTemplates(resolvedConfig, moduleOptions.exposeLevel, nuxt)
87+
const exposeConfig = resolveExposeConfig({ level: moduleOptions.exposeLevel, ...(typeof moduleOptions.exposeConfig === 'object' ? moduleOptions.exposeConfig : {})})
88+
createTemplates(resolvedConfig, exposeConfig, nuxt)
8689
isNuxt2() && addTemplate({ filename: 'tailwind.config.cjs', getContents: () => `module.exports = ${JSON.stringify(resolvedConfig, null, 2)}` })
8790
}
8891

@@ -154,7 +157,8 @@ export default defineNuxtModule<ModuleOptions>({
154157
// Add _tailwind config viewer endpoint
155158
// TODO: Fix `addServerHandler` on Nuxt 2 w/o Bridge
156159
if (moduleOptions.viewer) {
157-
setupViewer(tailwindConfig, nuxt)
160+
const viewerConfig = resolveViewerConfig(moduleOptions.viewer)
161+
setupViewer(tailwindConfig, viewerConfig, nuxt)
158162

159163
nuxt.hook('devtools:customTabs', (tabs) => {
160164
tabs.push({
@@ -163,7 +167,7 @@ export default defineNuxtModule<ModuleOptions>({
163167
icon: 'logos-tailwindcss-icon',
164168
view: {
165169
type: 'iframe',
166-
src: '/_tailwind/'
170+
src: viewerConfig.endpoint
167171
}
168172
})
169173
})

src/resolvers.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { existsSync } from 'fs'
2+
import { defu } from 'defu'
23
import { join, relative, resolve } from 'pathe'
34
import { addTemplate, createResolver, findPath, useNuxt, tryResolveModule, resolveAlias } from '@nuxt/kit'
4-
import type { Arrayable, InjectPosition, ModuleOptions } from './types'
5+
import type { Arrayable, ExposeConfig, InjectPosition, ModuleOptions, ViewerConfig } from './types'
56

67
/**
78
* Resolves all configPath values for an application
@@ -112,6 +113,17 @@ export async function resolveCSSPath (cssPath: ModuleOptions['cssPath'], nuxt =
112113
}
113114
}
114115

116+
/**
117+
* Resolves a boolean | object as an object with defaults
118+
* @param config
119+
* @param fb
120+
*
121+
* @returns object
122+
*/
123+
const resolveBoolObj = <T, U extends Record<string, any>>(config: T, fb: U): U => defu(typeof config === 'object' ? config : {}, fb)
124+
export const resolveViewerConfig = (config: ModuleOptions['viewer']): ViewerConfig => resolveBoolObj(config, { endpoint: '_tailwind' })
125+
export const resolveExposeConfig = (config: ModuleOptions['exposeConfig']): ExposeConfig => resolveBoolObj(config, { alias: '#tailwind-config', level: 2 })
126+
115127
/**
116128
* Resolve human-readable inject position specification into absolute index in the array
117129
*

src/templates.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { dirname, join } from 'pathe'
22
import { useNuxt, addTemplate } from '@nuxt/kit'
33
import { isJSObject, NON_ALPHANUMERIC_RE } from './utils'
4-
import type { TWConfig } from './types'
4+
import type { ExposeConfig, TWConfig } from './types'
55

66
/**
77
* Creates MJS exports for properties of the config
@@ -10,15 +10,15 @@ import type { TWConfig } from './types'
1010
* @param maxLevel maximum level of depth
1111
* @param nuxt nuxt app
1212
*/
13-
export default function createTemplates (resolvedConfig: Partial<TWConfig>, maxLevel: number, nuxt = useNuxt()) {
13+
export default function createTemplates (resolvedConfig: Partial<TWConfig>, config: ExposeConfig, nuxt = useNuxt()) {
1414
const dtsContent: Array<string> = []
1515

1616
const populateMap = (obj: any, path: string[] = [], level = 1) => {
1717
Object.entries(obj).forEach(([key, value = {} as any]) => {
1818
const subpath = path.concat(key).join('/')
1919

2020
if (
21-
level >= maxLevel || // if recursive call is more than desired
21+
level >= config.level || // if recursive call is more than desired
2222
!isJSObject(value) || // if its not an object, no more recursion required
2323
Object.keys(value).find(k => !k.match(NON_ALPHANUMERIC_RE)) // object has non-alphanumeric property (unsafe var name)
2424
) {
@@ -30,13 +30,13 @@ export default function createTemplates (resolvedConfig: Partial<TWConfig>, maxL
3030
filename: `tailwind.config/${subpath}.mjs`,
3131
getContents: () => `${validKeys.map(i => `const _${i} = ${JSON.stringify(value[i])}`).join('\n')}\nconst config = { ${validKeys.map(i => `"${i}": _${i}, `).join('')}${invalidKeys.map(i => `"${i}": ${JSON.stringify(value[i])}, `).join('')} }\nexport { config as default${validKeys.length > 0 ? ', _' : ''}${validKeys.join(', _')} }`
3232
})
33-
dtsContent.push(`declare module "#tailwind-config/${subpath}" { ${validKeys.map(i => `export const _${i}: ${JSON.stringify(value[i])};`).join('')} const defaultExport: { ${validKeys.map(i => `"${i}": typeof _${i}, `).join('')}${invalidKeys.map(i => `"${i}": ${JSON.stringify(value[i])}, `).join('')} }; export default defaultExport; }`)
33+
dtsContent.push(`declare module "${config.alias}/${subpath}" { ${validKeys.map(i => `export const _${i}: ${JSON.stringify(value[i])};`).join('')} const defaultExport: { ${validKeys.map(i => `"${i}": typeof _${i}, `).join('')}${invalidKeys.map(i => `"${i}": ${JSON.stringify(value[i])}, `).join('')} }; export default defaultExport; }`)
3434
} else {
3535
addTemplate({
3636
filename: `tailwind.config/${subpath}.mjs`,
3737
getContents: () => `export default ${JSON.stringify(value, null, 2)}`
3838
})
39-
dtsContent.push(`declare module "#tailwind-config/${subpath}" { const defaultExport: ${JSON.stringify(value)}; export default defaultExport; }`)
39+
dtsContent.push(`declare module "${config.alias}/${subpath}" { const defaultExport: ${JSON.stringify(value)}; export default defaultExport; }`)
4040
}
4141
} else {
4242
// recurse through nested objects
@@ -47,7 +47,7 @@ export default function createTemplates (resolvedConfig: Partial<TWConfig>, maxL
4747
filename: `tailwind.config/${subpath}.mjs`,
4848
getContents: () => `${values.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(', _')} }`
4949
})
50-
dtsContent.push(`declare module "#tailwind-config/${subpath}" {${Object.keys(value).map(v => ` export const _${v}: typeof import("#tailwind-config/${join(`${key}/${subpath}`, `../${v}`)}")["default"];`).join('')} const defaultExport: { ${values.map(k => `"${k}": typeof _${k}`).join(', ')} }; export default defaultExport; }`)
50+
dtsContent.push(`declare module "${config.alias}/${subpath}" {${Object.keys(value).map(v => ` export const _${v}: typeof import("${config.alias}/${join(`${key}/${subpath}`, `../${v}`)}")["default"];`).join('')} const defaultExport: { ${values.map(k => `"${k}": typeof _${k}`).join(', ')} }; export default defaultExport; }`)
5151
}
5252
})
5353
}
@@ -61,14 +61,14 @@ export default function createTemplates (resolvedConfig: Partial<TWConfig>, maxL
6161
write: true
6262
})
6363

64-
dtsContent.push(`declare module "#tailwind-config" {${configOptions.map(v => ` export const ${v}: typeof import("${join('#tailwind-config', v)}")["default"];`).join('')} const defaultExport: { ${configOptions.map(v => `"${v}": typeof ${v}`)} }; export default defaultExport; }`)
64+
dtsContent.push(`declare module "${config.alias}" {${configOptions.map(v => ` export const ${v}: typeof import("${join(config.alias, v)}")["default"];`).join('')} const defaultExport: { ${configOptions.map(v => `"${v}": typeof ${v}`)} }; export default defaultExport; }`)
6565
const typesTemplate = addTemplate({
6666
filename: 'tailwind.config.d.ts',
6767
getContents: () => dtsContent.join('\n'),
6868
write: true
6969
})
7070

71-
nuxt.options.alias['#tailwind-config'] = dirname(template.dst)
71+
nuxt.options.alias[config.alias] = dirname(template.dst)
7272
nuxt.hook('prepare:types', (opts) => {
7373
opts.references.push({ path: typesTemplate.dst })
7474
})

src/types.ts

+37-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,39 @@
1-
export type TWConfig = import('tailwindcss').Config
2-
export type Arrayable<T> = T | Array<T>
3-
export type InjectPosition = 'first' | 'last' | number | { after: string };
1+
export type TWConfig = import("tailwindcss").Config;
2+
export type Arrayable<T> = T | Array<T>;
3+
export type InjectPosition = "first" | "last" | number | { after: string };
44

55
interface ExtendTailwindConfig {
6-
content?: TWConfig['content'] | ((contentDefaults: Array<string>) => TWConfig['content']);
6+
content?:
7+
| TWConfig["content"]
8+
| ((contentDefaults: Array<string>) => TWConfig["content"]);
79
}
810

11+
type BoolObj<T extends Record<string, any>> = boolean | Partial<T>;
12+
13+
export type ViewerConfig = {
14+
/**
15+
* The endpoint for the viewer
16+
*
17+
* @default '/_tailwind'
18+
*/
19+
endpoint: string
20+
};
21+
22+
export type ExposeConfig = {
23+
/**
24+
* Import name for the configuration
25+
*
26+
* @default '#tailwind-config'
27+
*/
28+
alias: string;
29+
/**
30+
* Deeper references within configuration for optimal tree-shaking.
31+
*
32+
* @default 2
33+
*/
34+
level: number
35+
};
36+
937
export interface ModuleOptions {
1038
/**
1139
* The path of the Tailwind configuration file. The extension can be omitted, in which case it will try to find a `.js`, `.cjs`, `.mjs`, or `.ts` file.
@@ -28,19 +56,20 @@ export interface ModuleOptions {
2856
/**
2957
* [tailwind-config-viewer](https://github.com/rogden/tailwind-config-viewer) usage *in development*
3058
*
31-
* @default true
59+
* @default true // { endpoint: '_tailwind' }
3260
*/
33-
viewer: boolean;
61+
viewer: BoolObj<ViewerConfig>;
3462
/**
3563
* Usage of configuration references in runtime. See https://tailwindcss.nuxtjs.org/tailwind/config#referencing-in-the-application
3664
*
37-
* @default false
65+
* @default false // if true, { alias: '#tailwind-config', level: 2 }
3866
*/
39-
exposeConfig: boolean;
67+
exposeConfig: BoolObj<ExposeConfig>;
4068
/**
4169
* Deeper references within configuration for optimal tree-shaking.
4270
*
4371
* @default 2
72+
* @deprecated use exposeConfig as object
4473
*/
4574
exposeLevel: number;
4675
/**

src/viewer.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { underline, yellow } from 'colorette'
22
import { eventHandler, sendRedirect } from 'h3'
33
import { addDevServerHandler, isNuxt2, isNuxt3, useLogger, useNuxt } from '@nuxt/kit'
44
import { withTrailingSlash, withoutTrailingSlash, joinURL } from 'ufo'
5-
import type { TWConfig } from './types'
5+
import type { TWConfig, ViewerConfig } from './types'
66

7-
export default async function (twConfig: Partial<TWConfig>, nuxt = useNuxt()) {
8-
const route = joinURL(nuxt.options.app?.baseURL, '/_tailwind')
7+
export default async function (twConfig: Partial<TWConfig>, config: ViewerConfig, nuxt = useNuxt()) {
8+
const route = joinURL(nuxt.options.app?.baseURL, config.endpoint)
99
// @ts-ignore
1010
const createServer = await import('tailwind-config-viewer/server/index.js').then(r => r.default || r) as any
1111
const routerPrefix = isNuxt3() ? route : undefined

0 commit comments

Comments
 (0)