Skip to content

Commit

Permalink
feat: editorSupport option (#784)
Browse files Browse the repository at this point in the history
  • Loading branch information
ineshbose authored Jan 13, 2024
1 parent 8244bc0 commit 9f321d2
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Setup
title: Installation
description: Using Tailwind CSS in your Nuxt project is only one command away.
---

Expand Down Expand Up @@ -106,7 +106,7 @@ npx tailwindcss init
```

::callout{color="blue" icon="i-ph-info-duotone"}
You can configure the paths in the [module options](/getting-started/options).
You can configure the paths in the [module options](/getting-started/configuration).
::

If you're going to create your own Tailwind CSS file, make sure to add the `@tailwind` directives for each of Tailwind’s layer types (base, components, and utilities).
Expand All @@ -131,4 +131,4 @@ export default defineNuxtConfig({
})
```

See the [module options](/getting-started/options).
See the [module options](/getting-started/configuration).
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Options
title: Configuration
description: Configure Nuxt Tailwind with the `tailwindcss` property.
---

Expand Down Expand Up @@ -163,25 +163,17 @@ export default defineNuxtConfig({

You can edit the endpoint by `viewer.endpoint` and if you'd like to export the viewer as a static asset during build, you can set `viewer.exportViewer` to `true` (it will internally run [`npx tailwind-config-viewer export`](https://github.com/rogden/tailwind-config-viewer/blob/master/cli/export.js)).

## `addTwUtil `
## `editorSupport`

- Default: `false`

This option adds the utility function `tw` that will provide IntelliSense suggestions in JS/TS strings if the [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension is installed. For this to work, you will have to add the following VSCode setting:
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).

```json [settings.json]
{
"tailwindCSS.experimental.classRegex": ["tw`([^`]*)", "tw\\('([^'\\)]*)"],
}
```

Once added, the new utility function can be used as follows, providing IntelliSense suggestions when writing Tailwind classes:

```vue [index.vue]
<script setup lang="ts">
const variantClasses = {
primary: tw`bg-red-400`,
secondary: tw('bg-green-400')
}
</script>
```ts [nuxt.config.ts]
export default defineNuxtConfig({
tailwindcss: {
editorSupport: true
// editorSupport: { autocompleteUtil: { as: 'tailwindClasses' }, generateConfig: true }
}
})
```
19 changes: 5 additions & 14 deletions docs/content/2.tailwind/1.config.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ This is an advanced usage section and intended primarily for Nuxt modules author

#### `tailwindcss:loadConfig`

Passes any Tailwind configuration read by the module for each (extended) [layer](https://nuxt.com/docs/getting-started/layers)/[path](/getting-started/options#configpath) before merging all of them.
Passes any Tailwind configuration read by the module for each (extended) [layer](https://nuxt.com/docs/getting-started/layers)/[path](/getting-started/configuration#configpath) before merging all of them.

#### `tailwindcss:config`

Expand All @@ -141,26 +141,17 @@ Passes the _complete_ resolved configuration with all defaults from [the full Ta
You can use a [Nuxt hook](https://nuxtjs.org/guides/directory-structure/modules#run-tasks-on-specific-hooks) to manipulate the Tailwind configuration.

```ts
// ~/modules/nuxt-tailwind-typo/index.ts
// ~/modules/nuxt-tailwind-mod/index.ts
import { defineNuxtModule, addTemplate } from '@nuxt/kit'
import tailwindTypography from '@tailwindcss/typography'

export default defineNuxtModule({
meta: {
name: 'nuxt-tailwind-typo'
},
setup (options, nuxt) {
nuxt.hook('tailwindcss:config', function (tailwindConfig) {
tailwindConfig.plugins.push(tailwindTypography)
tailwindConfig.theme.colors.blue = '#fff'
})

nuxt.hook('tailwindcss:resolvedConfig', function (resolvedConfig) {
// read: https://tailwindcss.nuxtjs.org/tailwind/editor-support
addTemplate({
filename: 'tailwind.config.cjs',
getContents: () => `module.exports = ${JSON.stringify(resolvedConfig)}`,
write: true
})
console.log('This is the resulting config', JSON.stringify(resolvedConfig))
})
}
})
Expand Down Expand Up @@ -259,7 +250,7 @@ module.exports = {

It can often be useful to reference Tailwind configuration values at runtime, e.g. to access some of your theme values when dynamically applying inline styles in a component.

If you need resolved Tailwind config at runtime, you can enable the [exposeConfig](/getting-started/options#exposeconfig) option:
If you need resolved Tailwind config at runtime, you can enable the [exposeConfig](/getting-started/configuration#exposeconfig) option:

```js{}[nuxt.config]
export default {
Expand Down
2 changes: 1 addition & 1 deletion docs/content/2.tailwind/2.viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ When enabled, you will see the Tailwind viewer url in your terminal:

<img src="/tailwind-viewer.png" width="530" height="246" style="margin: 0;" />

Check out the [viewer option](/getting-started/options#viewer) to disable the viewer in development.
Check out the [viewer option](/getting-started/configuration#viewer) to disable the viewer in development.
32 changes: 29 additions & 3 deletions docs/content/2.tailwind/3.editor-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,33 @@ 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.

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 the configuration in CommonJS, you can use the `tailwindcss:resolvedConfig` hook and a custom Nuxt module:
## 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:

```diff [.vscode/settings.json]
// ...
+ "tailwindCSS.experimental.classRegex": ["tw`(.*?)`", "tw\\('(.*?)'\\)"],
"files.associations": {
"*.css": "tailwindcss"
},
// ...
```

Once added, the new utility function can be used as follows, providing IntelliSense suggestions when writing Tailwind classes:

```vue [index.vue]
<script setup lang="ts">
const variantClasses = {
primary: tw`bg-red-400`,
secondary: tw('bg-green-400')
}
</script>
```

## Load Config File

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:

```ts [modules/tw-cjs-config.ts]
import { defineNuxtModule, addTemplate } from '@nuxt/kit'
Expand All @@ -38,6 +64,8 @@ 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",
Expand All @@ -46,5 +74,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.
3 changes: 2 additions & 1 deletion playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export default defineNuxtConfig({
tailwindcss: {
// viewer: false,
exposeConfig: true,
injectPosition: 'last'
injectPosition: 'last',
editorSupport: true
},
content: {
documentDriven: true
Expand Down
5 changes: 4 additions & 1 deletion playground/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div>
<CallToAction />
</div>
<div class="max-w-screen-lg p-4 mx-auto space-y-4">
<div :class="mainDivClass">
<div>
<span class="pr-1 font-medium">This is a HMR test, try changing the color:</span>
<span class="text-blue-500">meow!</span>
Expand Down Expand Up @@ -32,5 +32,8 @@
</template>

<script setup lang="ts">
import { tw } from '#imports'
import tailwindConfig from '#tailwind-config'
const mainDivClass = tw`max-w-screen-lg p-4 mx-auto space-y-4`
</script>
29 changes: 24 additions & 5 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
useNuxt,
addTemplate,
addImportsDir,
createResolver
createResolver,
addImports
} from '@nuxt/kit'

// @ts-expect-error
Expand All @@ -25,7 +26,8 @@ import {
resolveCSSPath,
resolveInjectPosition,
resolveExposeConfig,
resolveViewerConfig
resolveViewerConfig,
resolveEditorSupportConfig
} from './resolvers'
import logger, { LogLevels } from './logger'
import createTemplates from './templates'
Expand All @@ -48,6 +50,7 @@ const defaults = (nuxt = useNuxt()): ModuleOptions => ({
disableHmrHotfix: false,
quiet: nuxt.options.logLevel === 'silent',
addTwUtil: false,
editorSupport: false,
})

export default defineNuxtModule<ModuleOptions>({
Expand Down Expand Up @@ -90,7 +93,6 @@ export default defineNuxtModule<ModuleOptions>({
if (moduleOptions.exposeConfig) {
const exposeConfig = resolveExposeConfig({ level: moduleOptions.exposeLevel, ...(typeof moduleOptions.exposeConfig === 'object' ? moduleOptions.exposeConfig : {})})
createTemplates(resolvedConfig, exposeConfig, nuxt)
isNuxt2() && addTemplate({ filename: 'tailwind.config.cjs', getContents: () => `module.exports = ${JSON.stringify(resolvedConfig, null, 2)}` })
}

// Compute tailwindConfig hash
Expand Down Expand Up @@ -139,8 +141,25 @@ export default defineNuxtModule<ModuleOptions>({
})
}

if (moduleOptions.addTwUtil) {
addImportsDir(resolve('./runtime/utils'))
if (moduleOptions.editorSupport || moduleOptions.addTwUtil || isNuxt2()) {
const editorSupportConfig = resolveEditorSupportConfig(moduleOptions.editorSupport)

if (editorSupportConfig.autocompleteUtil || moduleOptions.addTwUtil) {
addImports({
name: 'autocompleteUtil',
from: resolve('./runtime/utils'),
as: 'tw',
...(typeof editorSupportConfig.autocompleteUtil === 'object' ? editorSupportConfig.autocompleteUtil : {})
})
}

if (editorSupportConfig.generateConfig || isNuxt2()) {
addTemplate({
filename: 'tailwind.config.cjs',
getContents: () => `module.exports = ${JSON.stringify(resolvedConfig, null, 2)}`,
...(typeof editorSupportConfig.generateConfig === 'object' ? editorSupportConfig.generateConfig : {})
})
}
}

// enabled only in development
Expand Down
3 changes: 2 additions & 1 deletion src/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { existsSync } from 'fs'
import { defu } from 'defu'
import { join, relative, resolve } from 'pathe'
import { addTemplate, createResolver, findPath, useNuxt, tryResolveModule, resolveAlias } from '@nuxt/kit'
import type { Arrayable, ExposeConfig, InjectPosition, ModuleOptions, ViewerConfig } from './types'
import type { Arrayable, EditorSupportConfig, ExposeConfig, InjectPosition, ModuleOptions, ViewerConfig } from './types'

/**
* Resolves all configPath values for an application
Expand Down Expand Up @@ -124,6 +124,7 @@ export async function resolveCSSPath (cssPath: ModuleOptions['cssPath'], nuxt =
const resolveBoolObj = <T, U extends Record<string, any>>(config: T, fb: U): U => defu(typeof config === 'object' ? config : {}, fb)
export const resolveViewerConfig = (config: ModuleOptions['viewer']): ViewerConfig => resolveBoolObj(config, { endpoint: '/_tailwind', exportViewer: false })
export const resolveExposeConfig = (config: ModuleOptions['exposeConfig']): ExposeConfig => resolveBoolObj(config, { alias: '#tailwind-config', level: 2 })
export const resolveEditorSupportConfig = (config: ModuleOptions['editorSupport']): EditorSupportConfig => resolveBoolObj(config, { autocompleteUtil: true, generateConfig: false })

/**
* Resolve human-readable inject position specification into absolute index in the array
Expand Down
1 change: 1 addition & 0 deletions src/runtime/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const autocompleteUtil = <T extends TemplateStringsArray | string>(tailwindClasses: T) => tailwindClasses
3 changes: 0 additions & 3 deletions src/runtime/utils/tw.ts

This file was deleted.

58 changes: 52 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
type Import = Exclude<Parameters<typeof import('nuxt/kit')['addImports']>[0], any[]>

export type TWConfig = import('tailwindcss').Config;
export type ResolvedTwConfig = ReturnType<typeof import('tailwindcss/resolveConfig')>
export type ResolvedTwConfig = ReturnType<typeof import('tailwindcss/resolveConfig')>;
export type Arrayable<T> = T | Array<T>;
export type InjectPosition = 'first' | 'last' | number | { after: string };

interface ExtendTailwindConfig {
content?:
| TWConfig['content']
| ((contentDefaults: Array<string>) => TWConfig['content']);
}
| TWConfig['content']
| ((contentDefaults: Array<string>) => TWConfig['content']);
};

type BoolObj<T extends Record<string, any>> = boolean | Partial<T>;

Expand All @@ -17,7 +19,7 @@ export type ViewerConfig = {
*
* @default '/_tailwind'
*/
endpoint: `/${string}`
endpoint: `/${string}`;
/**
* Export the viewer during build
*
Expand All @@ -43,6 +45,41 @@ export type ExposeConfig = {
level: number
};

export type EditorSupportConfig = {
/**
* Enable utility to write Tailwind CSS classes inside strings.
*
* You will need to update `.vscode/settings.json` based on this value.
*
* ```json
* {
* "tailwindCSS.experimental.classRegex": ["tw`(.*?)`", "tw\\('(.*?)'\\)"]
* }
* ```
*
* Read https://tailwindcss.nuxtjs.org/tailwind/editor-support#autocomplete.
*
* @default false // if true, { as: 'tw' }
*/
autocompleteUtil: BoolObj<Pick<Import, 'as'>>;
/**
* Create a flat configuration template for Intellisense plugin.
*
* You will need to update `.vscode/settings.json` based on this value.
*
* ```json
* {
* "tailwindCSS.experimental.configFile": ".nuxt/tailwind.config.cjs"
* }
* ```
*
* Read https://tailwindcss.nuxtjs.org/tailwind/editor-support#load-config-file.
*
* @default false // if true, { filename: 'tailwind.config.cjs', write: true }
*/
generateConfig: BoolObj<Omit<import('nuxt/schema').NuxtTemplate, 'getContents'>>;
};

export interface ModuleOptions {
/**
* 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.
Expand Down Expand Up @@ -101,9 +138,18 @@ export interface ModuleOptions {
* Add util to write Tailwind CSS classes inside strings with `` tw`{classes}` ``
*
* @default false
* @deprecated use `editorSupport.autocompleteUtil` as object
*/
addTwUtil: boolean;
}
/**
* Enable some utilities for better editor support and DX.
*
* Read https://tailwindcss.nuxtjs.org/tailwind/editor-support.
*
* @default false // if true, { autocompleteUtil: true }
*/
editorSupport: BoolObj<EditorSupportConfig>;
};

export interface ModuleHooks {
/**
Expand Down

0 comments on commit 9f321d2

Please sign in to comment.