Skip to content

Commit

Permalink
chore: finishing up
Browse files Browse the repository at this point in the history
  • Loading branch information
ineshbose committed Feb 14, 2024
1 parent 8567875 commit 2c0d664
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 116 deletions.
4 changes: 2 additions & 2 deletions docs/content/1.getting-started/2.configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,13 @@ You can edit the endpoint by `viewer.endpoint` and if you'd like to export the v

- Default: `false`

You can take advantage of some DX utilities this modules provide to you as you develop your Nuxt applicatio with Tailwind. Read more in [Editor Support](/tailwind/editor-support).
You can take advantage of some DX utilities this modules provide to you as you develop your Nuxt application with Tailwind. Read more in [Editor Support](/tailwind/editor-support).

```ts [nuxt.config.ts]
export default defineNuxtConfig({
tailwindcss: {
editorSupport: true
// editorSupport: { autocompleteUtil: { as: 'tailwindClasses' }, generateConfig: true }
// editorSupport: { autocompleteUtil: { as: 'tailwindClasses' } }
}
})
```
33 changes: 19 additions & 14 deletions docs/content/2.tailwind/3.editor-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Add the following configuration to your `.vscode/settings.json` file, so that Ta

If you use pnpm, ensure that tailwindcss is installed in your top-level node_modules folder.

## Autocomplete
## String Classes Autocomplete

When using strings of Tailwind classes, you can enable IntelliSense suggestions using the [`editorSupport.autocompleteUtil`](/getting-started/configuration#editorsupport) option. You will have to add the following VSCode setting:

Expand All @@ -44,9 +44,20 @@ const variantClasses = {
</script>
```

## Load Config File
## Configuration IntelliSense

Since Tailwind CSS v3.3, [ESM/TS configuration has been supported](https://tailwindcss.com/blog/tailwindcss-v3-3#esm-and-type-script-support) so your editor should automatically configure autocomplete based on your `tailwind.config`. If you happen to use a lower version and/or require to generate a flat configuration, you can do so using [`editorSupport.generateConfig`](/getting-started/configuration#editorsupport) option, or you can use the `tailwindcss:resolvedConfig` hook and a custom Nuxt module:
Since Tailwind CSS v3.3, [ESM/TS configuration has been supported](https://tailwindcss.com/blog/tailwindcss-v3-3#esm-and-type-script-support) so your editor should automatically configure autocomplete based on your `tailwind.config`. If you have a complex Nuxt project with multiple Tailwind configurations that are within layers, passed from hooks or inline `nuxt.config` and want to use a merged configuration, the module generates it in `.nuxt/tailwind.config.cjs` that you can use by adding the following VSCode setting:

```diff [.vscode/settings.json]
// ...
+ "tailwindCSS.experimental.configFile": ".nuxt/tailwind.config.cjs",
"files.associations": {
"*.css": "tailwindcss"
},
// ...
```

If you require more customisation to what configuration the IntelliSense extension reads, you can take advantage of hooks, especially the `tailwindcss:resolvedConfig` hook that runs the configuration through [`tailwindcss/resolveConfig`](https://github.com/tailwindlabs/tailwindcss/blob/master/resolveConfig.js) to provide the complete config object.

```ts [modules/tw-cjs-config.ts]
import { defineNuxtModule, addTemplate } from '@nuxt/kit'
Expand All @@ -55,8 +66,11 @@ export default defineNuxtModule({
setup (options, nuxt) {
nuxt.hook('tailwindcss:resolvedConfig', (config) => {
addTemplate({
filename: 'tailwind.config.cjs', // gets prepended by .nuxt/
getContents: () => `module.exports = ${JSON.stringify(config)}`,
filename: 'intellisense-tw.cjs', // gets prepended by .nuxt/
getContents: () => `
/* my-comment */
module.exports = ${JSON.stringify(config)}
`,
write: true
})
})
Expand All @@ -65,12 +79,3 @@ export default defineNuxtModule({
```

This hook allows you to customize your generated template in different ways (e.g., different filename, contents, etc.) through a module. Please be aware that using `JSON.stringify` will remove plugins from your configuration.

```diff [.vscode/settings.json]
// ...
+ "tailwindCSS.experimental.configFile": ".nuxt/tailwind.config.cjs",
"files.associations": {
"*.css": "tailwindcss"
},
// ...
```
55 changes: 23 additions & 32 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { relative } from 'pathe'
import { addTemplate, createResolver, useNuxt } from '@nuxt/kit'
import loadConfig from 'tailwindcss/loadConfig.js'
import resolveConfig from 'tailwindcss/resolveConfig.js'
import logger from './logger'
import { resolveModulePaths } from './resolvers'
import type { ModuleHooks, ModuleOptions, TWConfig } from './types'
import configMerger from './runtime/merger.mjs'

Expand All @@ -22,9 +22,8 @@ JSON.stringify(
*
* @returns template with all configs handled
*/
export default async function loadTwConfig(moduleOptions: ModuleOptions, nuxt = useNuxt()) {
export default async function loadTwConfig(configPaths: string[], contentPaths: string[], inlineConfig: ModuleOptions['config'], nuxt = useNuxt()) {
const { resolve } = createResolver(import.meta.url)
const [configPaths, contentPaths] = await resolveModulePaths(moduleOptions.configPath, nuxt)
const configUpdatedHook = Object.fromEntries(configPaths.map((p) => [p, '']))

const makeHandler = (configPath: string, path: (string | symbol)[] = []): ProxyHandler<PartialTWConfig> => ({
Expand All @@ -35,6 +34,10 @@ export default async function loadTwConfig(moduleOptions: ModuleOptions, nuxt =
},

set(target, key, value) {
if (JSON.stringify(target[key as string]) === JSON.stringify(value)) {
return Reflect.set(target, key, value)
}

if (key === 'plugins' && typeof value === 'function') {
logger.warn(
'You have injected a functional plugin into your Tailwind Config which cannot be serialized.',
Expand All @@ -43,10 +46,6 @@ export default async function loadTwConfig(moduleOptions: ModuleOptions, nuxt =
return false
}

if (JSON.stringify(target[key as string]) === JSON.stringify(value)) {
return Reflect.set(target, key, value)
}

configUpdatedHook[configPath] += `cfg${path.concat(key).map((k) => `[${JSON.stringify(k)}]`).join('')} = ${JSON.stringify(value)};`
return Reflect.set(target, key, value)
},
Expand All @@ -64,7 +63,7 @@ export default async function loadTwConfig(moduleOptions: ModuleOptions, nuxt =
try {
_tailwindConfig = loadConfig(configPath)
} catch (e) {
configUpdatedHook[configPath] = 'skip'
configUpdatedHook[configPath] = 'return {};'
logger.warn(`Failed to load Tailwind config at: \`./${relative(nuxt.options.rootDir, configPath)}\``, e)
}

Expand All @@ -79,7 +78,7 @@ export default async function loadTwConfig(moduleOptions: ModuleOptions, nuxt =
).then((configs) => configs.reduce(
(prev, curr) => configMerger(curr, prev),
// internal default tailwind config
configMerger(moduleOptions.config, { content: contentPaths })
configMerger(inlineConfig, { content: contentPaths })
)) as TWConfig

// Allow extending tailwindcss config by other modules
Expand All @@ -91,13 +90,14 @@ export default async function loadTwConfig(moduleOptions: ModuleOptions, nuxt =
write: true,
getContents: () => {
const layerConfigs = configPaths.map((configPath) => {
const configImport = `require(${JSON.stringify(/[/\\]node_modules[/\\]/.test(configPath) /* || configPath.startsWith(nuxt.options.buildDir) // but this may use updateTemplate */ ? configPath : './' + relative(nuxt.options.buildDir, configPath))})`
return configUpdatedHook[configPath] ? configUpdatedHook[configPath] === 'skip' ? '' : `(() => {const cfg=${configImport};${configUpdatedHook[configPath]};return cfg;})()` : configImport
const configImport = `require(${JSON.stringify(/[/\\]node_modules[/\\]/.test(configPath) ? configPath : './' + relative(nuxt.options.buildDir, configPath))})`
return configUpdatedHook[configPath] ? configUpdatedHook[configPath].startsWith('return {};') ? '' : `(() => {const cfg=${configImport};${configUpdatedHook[configPath]};return cfg;})()` : configImport
}).filter(Boolean)

return [
`const configMerger = require(${JSON.stringify(resolve('./runtime/merger.mjs'))});`,
`\nconst inlineConfig = ${serializeConfig(moduleOptions.config as PartialTWConfig)};\n`,
// `const configMerger = require(${JSON.stringify(createResolver(import.meta.url).resolve('./runtime/merger.mjs'))});`,
`\nconst inlineConfig = ${serializeConfig(inlineConfig as PartialTWConfig)};\n`,
'const config = [',
layerConfigs.join(',\n'),
`].reduce((prev, curr) => configMerger(curr, prev), configMerger(inlineConfig, { content: ${JSON.stringify(contentPaths)} }));\n`,
Expand All @@ -106,26 +106,17 @@ export default async function loadTwConfig(moduleOptions: ModuleOptions, nuxt =
}
})

return {
template,
tailwindConfig,
configPaths,
}
// new Proxy(tailwindConfig, {
// get: (target, key: string) => {
// if (key in template) {
// return template[key as keyof typeof template]
// }
// switch (key) {
// case 'configPaths':
// return configPaths
// case 'load':
// return () => loadConfig(template.dst)
// default:
// return target[key]
// }
// }
// })
const resolvedConfig = resolveConfig(tailwindConfig)
await nuxt.callHook('tailwindcss:resolvedConfig', resolvedConfig)

return new Proxy(resolvedConfig, {
get: (target, key: string) => {
if (key in template) {
return template[key as keyof typeof template]
}
return target[key]
}
}) as typeof resolvedConfig & typeof template
}

declare module 'nuxt/schema' {
Expand Down
15 changes: 7 additions & 8 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import {
updateTemplates
} from '@nuxt/kit'

// @ts-expect-error
// @ts-expect-error no declaration file
import defaultTailwindConfig from 'tailwindcss/stubs/config.simple.js'
import resolveConfig from 'tailwindcss/resolveConfig.js'

import * as resolvers from './resolvers'
import logger, { LogLevels } from './logger'
Expand Down Expand Up @@ -63,15 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
}

const { resolve } = createResolver(import.meta.url)
const twConfig = await loadTwConfig(moduleOptions, nuxt)
await nuxt.callHook('tailwindcss:resolvedConfig', resolveConfig(twConfig.tailwindConfig))
const [configPaths, contentPaths] = await resolvers.resolveModulePaths(moduleOptions.configPath, nuxt)
const twConfig = await loadTwConfig(configPaths, contentPaths, moduleOptions.config, nuxt)

// Expose resolved tailwind config as an alias
if (moduleOptions.exposeConfig) {
const exposeConfig = resolvers.resolveExposeConfig({ level: moduleOptions.exposeLevel, ...(typeof moduleOptions.exposeConfig === 'object' ? moduleOptions.exposeConfig : {})})
const configTemplates = createConfigTemplates(twConfig, exposeConfig, nuxt)
nuxt.hook('builder:watch', (_, path) => {
twConfig.configPaths.includes(join(nuxt.options.rootDir, path)) && updateTemplates({ filter: template => configTemplates.includes(template.dst) })
configPaths.includes(join(nuxt.options.rootDir, path)) && updateTemplates({ filter: template => configTemplates.includes(template.dst) })
})
}

Expand Down Expand Up @@ -108,7 +107,7 @@ export default defineNuxtModule<ModuleOptions>({
...(postcssOptions.plugins || {}),
'tailwindcss/nesting': postcssOptions.plugins?.['tailwindcss/nesting'] ?? {},
'postcss-custom-properties': postcssOptions.plugins?.['postcss-custom-properties'] ?? {},
tailwindcss: twConfig.template.dst
tailwindcss: twConfig.dst
}

// install postcss8 module on nuxt < 2.16
Expand Down Expand Up @@ -138,7 +137,7 @@ export default defineNuxtModule<ModuleOptions>({
// TODO: Fix `addServerHandler` on Nuxt 2 w/o Bridge
if (moduleOptions.viewer) {
const viewerConfig = resolvers.resolveViewerConfig(moduleOptions.viewer)
setupViewer(twConfig.template.dst, viewerConfig, nuxt)
setupViewer(twConfig, viewerConfig, nuxt)

// @ts-ignore
nuxt.hook('devtools:customTabs', (tabs) => {
Expand All @@ -157,7 +156,7 @@ export default defineNuxtModule<ModuleOptions>({
} else {
// production only
if (moduleOptions.viewer) {
exportViewer(twConfig.template.dst, resolvers.resolveViewerConfig(moduleOptions.viewer))
exportViewer(twConfig, resolvers.resolveViewerConfig(moduleOptions.viewer))
}
}
}
Expand Down
30 changes: 10 additions & 20 deletions src/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { dirname, join } from 'pathe'
import { useNuxt, addTemplate, addTypeTemplate } from '@nuxt/kit'
import type { ResolvedNuxtTemplate } from 'nuxt/schema'
import type { ExposeConfig, TWConfig } from './types'
import type loadTwConfig from './config'
import resolveConfig from 'tailwindcss/resolveConfig.js'
import loadConfig from 'tailwindcss/loadConfig.js'

const NON_ALPHANUMERIC_RE = /^[0-9a-z]+$/i
const isJSObject = (value: any) => typeof value === 'object' && !Array.isArray(value)
Expand All @@ -18,13 +16,11 @@ const isJSObject = (value: any) => typeof value === 'object' && !Array.isArray(v
*
* @returns array of templates
*/
export default function createConfigTemplates(twConfig: Awaited<ReturnType<typeof loadTwConfig>>, config: ExposeConfig, nuxt = useNuxt()) {
export default function createConfigTemplates(twConfig: Awaited<ReturnType<typeof import('./config')['default']>>, config: ExposeConfig, nuxt = useNuxt()) {
const templates: ResolvedNuxtTemplate<any>[] = []
const resolvedConfig = resolveConfig(twConfig.tailwindConfig)
const getTWConfig = () =>
new Promise<TWConfig>((resolve) => resolve(loadConfig(twConfig.template.dst)))
.then((config) => resolveConfig(config))
.catch(() => resolvedConfig)
const getTWConfig = (objPath: string[] = []) =>
import(twConfig.dst).then((config: TWConfig) => resolveConfig(config))
.catch(() => twConfig).then((c) => objPath.reduce((prev, curr) => prev[curr], c))

const populateMap = (obj: any, path: string[] = [], level = 1) => {
Object.entries(obj).forEach(([key, value = {} as any]) => {
Expand All @@ -38,11 +34,8 @@ export default function createConfigTemplates(twConfig: Awaited<ReturnType<typeo
) {
templates.push(addTemplate({
filename: `tailwind.config/${subpath}.mjs`,
options: { subpathComponents },
getContents: async ({ options }) => {
const _tailwindConfig = await getTWConfig()
let _value = _tailwindConfig
options.subpathComponents.forEach((c) => _value = _value[c])
getContents: async () => {
const _value = await getTWConfig(subpathComponents)

if (isJSObject(_value)) {
const [validKeys, invalidKeys]: [string[], string[]] = [[], []]
Expand All @@ -64,11 +57,8 @@ export default function createConfigTemplates(twConfig: Awaited<ReturnType<typeo

templates.push(addTemplate({
filename: `tailwind.config/${subpath}.mjs`,
options: { subpathComponents },
getContents: async ({ options }) => {
const _tailwindConfig = await getTWConfig()
let _value = _tailwindConfig
options.subpathComponents.forEach((c) => _value = _value[c])
getContents: async () => {
const _value = await getTWConfig(subpathComponents)
const values = Object.keys(_value)

return [
Expand All @@ -83,7 +73,7 @@ export default function createConfigTemplates(twConfig: Awaited<ReturnType<typeo
})
}

populateMap(resolvedConfig)
populateMap(twConfig)

const template = addTemplate({
filename: 'tailwind.config/index.mjs',
Expand Down Expand Up @@ -127,7 +117,7 @@ export default function createConfigTemplates(twConfig: Awaited<ReturnType<typeo
return declareModule(value, path.concat(key), level + 1).join('') + `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; }\n`
})

const configOptions = Object.keys(resolvedConfig)
const configOptions = Object.keys(_tailwindConfig)
return declareModule(_tailwindConfig).join('') + `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; }`
}
}))
Expand Down
Loading

0 comments on commit 2c0d664

Please sign in to comment.