Skip to content

Commit f90b80b

Browse files
Tahulpi0atinux
authoredJul 13, 2022
feat: extends support (#499)
Co-authored-by: pooya parsa <pyapar@gmail.com> Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
1 parent a626d6c commit f90b80b

File tree

13 files changed

+1386
-183
lines changed

13 files changed

+1386
-183
lines changed
 

‎build.config.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineBuildConfig } from 'unbuild'
2+
3+
export default defineBuildConfig({
4+
externals: [
5+
'pathe',
6+
'minimatch'
7+
]
8+
})

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ You can learn more about the [Tailwind configuration](https://tailwindcss.com/do
2828
## Overwriting the configuration
2929

3030
You can extend the default configuration:
31+
3132
- with a [tailwind.config.js](#tailwindconfigjs) file
3233
- using the [config option](#config-option)
3334
- with the `tailwindcss:config` Nuxt hook
@@ -89,7 +90,6 @@ This config has less priority over the [tailwind.config.js](#tailwindconfigjs) f
8990

9091
::
9192

92-
9393
### `tailwindcss:config` hook
9494

9595
::alert{type="warning"}
@@ -188,7 +188,6 @@ module.exports = {
188188
}
189189
```
190190

191-
192191
## Referencing in the application
193192

194193
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.

‎docs/content/3.examples/6.content.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
title: '@nuxt/content'
3+
description: 'Discover how powerful it is to use TailwindCSS in @nuxt/content Markdown files!'
4+
---
5+
6+
The module supports [@nuxt/content](https://content.nuxtjs.org), meaning you can use Tailwind classes inside your content files with MDC syntax!
7+
8+
```md
9+
Hello, this is a [Markdown]{.px-1 .bg-blue-200 .rounded-lg} file!
10+
11+
::div{.w-full .bg-green-200 .p-4}
12+
Another way to use it!
13+
::
14+
```
15+
16+
Learn more about the [Span Syntax](https://content.nuxtjs.org/guide/writing/mdc#span-text) and how to use classes in Markdown files!

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"codecov": "latest",
4949
"eslint": "latest",
5050
"nuxt": "npm:nuxt3@latest",
51+
"@nuxt/content": "npm:@nuxt/content-edge@latest",
5152
"standard-version": "latest"
5253
}
5354
}

‎playground/content/content.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
::div{.my-4}
2+
[@nuxt/content support]{.px-4 .text-xl .font-bold}
3+
::
4+
5+
---
6+
7+
::div{.my-4 .px-4}
8+
Hello World! :wave:
9+
10+
This is an integration test with [@nuxt/content](https://content.nuxtjs.org){.font-bold .hover:text-blue-500}! :smile:
11+
12+
It feels pretty smooth!
13+
::

‎playground/nuxt.config.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import { defineNuxtConfig } from 'nuxt'
2-
import tailwindModule from '..'
2+
import tailwindModule from '../src/module'
33

44
export default defineNuxtConfig({
5-
buildModules: [
5+
extends: ['./theme'],
6+
modules: [
7+
'@nuxt/content',
68
tailwindModule
79
],
810
tailwindcss: {
9-
exposeConfig: true
11+
exposeConfig: true,
12+
configPath: './tailwind.config.js'
13+
},
14+
content: {
15+
documentDriven: true
1016
},
1117
css: [
1218
// Including Inter CSS is the first component to reproduce HMR issue. It also causes playground to look better,

‎playground/pages/index.vue

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
<div class="max-w-screen-lg p-4 mx-auto space-y-4">
77
<div>
88
<span class="pr-1 font-medium">This is a HMR test, try changing the color:</span>
9-
<span class="text-indigo-500">meow!</span>
9+
<span class="text-brand">meow!</span>
10+
</div>
11+
<div>
12+
<NuxtLink to="/content" class="underline hover:text-indigo-500">
13+
Visit the /content page to check out the @nuxt/content integration!
14+
</NuxtLink>
1015
</div>
1116
<div>
1217
<span class="text-sm font-semibold text-gray-700">Resolved tailwind config:</span>

‎playground/theme/nuxt.config.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { fileURLToPath } from 'url'
2+
import { defineNuxtConfig } from 'nuxt'
3+
import { resolve } from 'pathe'
4+
import tailwindModule from '../../src/module'
5+
6+
const themeDir = fileURLToPath(new URL('./', import.meta.url))
7+
const resolveThemeDir = (path: string) => resolve(themeDir, path)
8+
9+
export default defineNuxtConfig({
10+
modules: [tailwindModule],
11+
12+
tailwindcss: {
13+
configPath: resolveThemeDir('./tailwind.config.js')
14+
}
15+
})

‎playground/theme/tailwind.config.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default {
2+
theme: {
3+
extend: {
4+
colors: {
5+
brand: '#0070f3'
6+
}
7+
}
8+
}
9+
}

‎src/hmr.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { isAbsolute, resolve } from 'path'
22
import { HmrContext, Plugin } from 'vite'
33
import minimatch from 'minimatch'
44

5-
export default function (tailwindConfig, rootDir: string, cssPath: string): Plugin {
6-
const resolvedContent: string[] = tailwindConfig.content.map(f => !isAbsolute(f) ? resolve(rootDir, f) : f)
5+
export default function (tailwindConfig: any = {}, rootDir: string, cssPath: string): Plugin {
6+
const resolvedContent: string[] = (tailwindConfig.content || []).map(f => !isAbsolute(f) ? resolve(rootDir, f) : f)
77

88
return {
99
name: 'nuxt:tailwindcss',

‎src/module.ts

+107-32
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
1-
import { join, relative } from 'path'
21
import { existsSync } from 'fs'
3-
import { defuArrayFn } from 'defu'
2+
import { join, relative } from 'pathe'
3+
import defu, { defuArrayFn } from 'defu'
44
import chalk from 'chalk'
55
import consola from 'consola'
66
import {
77
defineNuxtModule,
88
installModule,
99
addTemplate,
1010
addDevServerHandler,
11-
requireModule,
1211
isNuxt2,
1312
createResolver,
1413
resolvePath,
15-
addVitePlugin
14+
addVitePlugin,
15+
tryRequireModule
1616
} from '@nuxt/kit'
1717
import { name, version } from '../package.json'
1818
import vitePlugin from './hmr'
1919
import defaultTailwindConfig from './tailwind.config'
2020

2121
const logger = consola.withScope('nuxt:tailwindcss')
2222

23+
const layerPaths = (srcDir: string) => ([
24+
`${srcDir}/components/**/*.{vue,js,ts}`,
25+
`${srcDir}/layouts/**/*.vue`,
26+
`${srcDir}/pages/**/*.vue`,
27+
`${srcDir}/composables/**/*.{js,ts}`,
28+
`${srcDir}/plugins/**/*.{js,ts}`,
29+
`${srcDir}/App.{js,ts,vue}`,
30+
`${srcDir}/app.{js,ts,vue}`,
31+
`${srcDir}/Error.{js,ts,vue}`,
32+
`${srcDir}/error.{js,ts,vue}`
33+
])
34+
2335
export interface ModuleHooks {
2436
'tailwindcss:config': (tailwindConfig: any) => void
2537
}
@@ -33,46 +45,76 @@ export default defineNuxtModule({
3345
defaults: nuxt => ({
3446
configPath: 'tailwind.config',
3547
cssPath: join(nuxt.options.dir.assets, 'css/tailwind.css'),
36-
config: defaultTailwindConfig(nuxt.options),
48+
config: defaultTailwindConfig(),
3749
viewer: true,
3850
exposeConfig: false,
3951
injectPosition: 0,
4052
disableHmrHotfix: false
4153
}),
4254
async setup (moduleOptions, nuxt) {
43-
const configPath = await resolvePath(moduleOptions.configPath)
44-
const cssPath = await resolvePath(moduleOptions.cssPath, { extensions: ['.css', '.sass', '.scss', '.less', '.styl'] })
45-
const injectPosition = ~~Math.min(moduleOptions.injectPosition, (nuxt.options.css || []).length + 1)
55+
/**
56+
* Config file handling
57+
*/
4658

47-
// Include CSS file in project css
48-
let resolvedCss: string
49-
if (typeof cssPath === 'string') {
50-
if (existsSync(cssPath)) {
51-
logger.info(`Using Tailwind CSS from ~/${relative(nuxt.options.srcDir, cssPath)}`)
52-
resolvedCss = cssPath
53-
} else {
54-
logger.info('Using default Tailwind CSS file from runtime/tailwind.css')
55-
resolvedCss = createResolver(import.meta.url).resolve('runtime/tailwind.css')
59+
const configPaths = []
60+
const contentPaths = []
61+
62+
/**
63+
* Push config paths into `configPaths` without extension.
64+
* Allows next steps of processing to try both .js / .ts when resolving the config.
65+
*/
66+
const addConfigPath = async (path: string | string[]) => {
67+
if (typeof path === 'string') {
68+
path = (await resolvePath(path)).split('.').slice(0, -1).join('.')
69+
configPaths.push(path)
70+
return
71+
}
72+
73+
if (Array.isArray(path)) {
74+
for (let _path of path) {
75+
_path = (await resolvePath(_path)).split('.').slice(0, -1).join('.')
76+
configPaths.push()
77+
}
5678
}
5779
}
5880

59-
// Inject only if this file isn't listed already by user (e.g. user may put custom path both here and in css):
60-
const resolvedNuxtCss = await Promise.all(nuxt.options.css.map(p => resolvePath(p)))
61-
if (!resolvedNuxtCss.includes(resolvedCss)) {
62-
nuxt.options.css.splice(injectPosition, 0, resolvedCss)
81+
// Support `extends` directories
82+
if (nuxt.options._layers && nuxt.options._layers.length > 1) {
83+
interface NuxtLayer {
84+
config: any
85+
configFile: string
86+
cwd: string
87+
}
88+
89+
for (const layer of (nuxt.options._layers as NuxtLayer[])) {
90+
await addConfigPath(layer?.config?.tailwindcss?.configPath || join(layer.cwd, 'tailwind.config'))
91+
contentPaths.push(...layerPaths(layer.cwd))
92+
}
93+
} else {
94+
await addConfigPath(moduleOptions.configPath)
95+
contentPaths.push(...layerPaths(nuxt.options.srcDir))
6396
}
6497

65-
// Extend the Tailwind config
98+
// Watch the Tailwind config file to restart the server
99+
if (nuxt.options.dev) {
100+
configPaths.forEach(path => nuxt.options.watch.push(path))
101+
}
102+
103+
// Recursively resolve each config and merge tailwind configs together.
66104
let tailwindConfig: any = {}
67-
if (existsSync(configPath)) {
68-
tailwindConfig = requireModule(configPath, { clearCache: true })
69-
logger.info(`Merging Tailwind config from ~/${relative(nuxt.options.srcDir, configPath)}`)
105+
for (const configPath of configPaths) {
106+
const _tailwindConfig = tryRequireModule(configPath, { clearCache: true })
107+
70108
// Transform purge option from Array to object with { content }
71-
if (Array.isArray(tailwindConfig.purge)) {
72-
tailwindConfig.content = tailwindConfig.purge
109+
if (_tailwindConfig && Array.isArray(_tailwindConfig.purge)) {
110+
_tailwindConfig.content = _tailwindConfig.purge
73111
}
112+
113+
tailwindConfig = defu(_tailwindConfig || {}, tailwindConfig)
74114
}
75115

116+
tailwindConfig.content = [...(tailwindConfig.content || []), ...contentPaths]
117+
76118
// Merge with our default purgecss default
77119
tailwindConfig = defuArrayFn(tailwindConfig, moduleOptions.config)
78120

@@ -93,18 +135,43 @@ export default defineNuxtModule({
93135
nuxt.options.alias['#tailwind-config'] = template.dst
94136
}
95137

96-
// Watch the Tailwind config file to restart the server
97-
if (nuxt.options.dev) {
98-
nuxt.options.watch.push(configPath)
99-
}
100-
101138
// Allow extending tailwindcss config by other modules
102139
// @ts-ignore
103140
await nuxt.callHook('tailwindcss:config', tailwindConfig)
104141

105142
// Compute tailwindConfig hash
106143
tailwindConfig._hash = String(Date.now())
107144

145+
/**
146+
* CSS file handling
147+
*/
148+
149+
const cssPath = await resolvePath(moduleOptions.cssPath, { extensions: ['.css', '.sass', '.scss', '.less', '.styl'] })
150+
const injectPosition = ~~Math.min(moduleOptions.injectPosition, (nuxt.options.css || []).length + 1)
151+
152+
// Include CSS file in project css
153+
let resolvedCss: string
154+
if (typeof cssPath === 'string') {
155+
if (existsSync(cssPath)) {
156+
logger.info(`Using Tailwind CSS from ~/${relative(nuxt.options.srcDir, cssPath)}`)
157+
resolvedCss = cssPath
158+
} else {
159+
logger.info('Using default Tailwind CSS file from runtime/tailwind.css')
160+
// @ts-ignore
161+
resolvedCss = createResolver(import.meta.url).resolve('runtime/tailwind.css')
162+
}
163+
}
164+
165+
// Inject only if this file isn't listed already by user (e.g. user may put custom path both here and in css):
166+
const resolvedNuxtCss = await Promise.all(nuxt.options.css.map(p => resolvePath(p)))
167+
if (!resolvedNuxtCss.includes(resolvedCss)) {
168+
nuxt.options.css.splice(injectPosition, 0, resolvedCss)
169+
}
170+
171+
/**
172+
* PostCSS 8 support for Nuxt 2
173+
*/
174+
108175
// Setup postcss plugins
109176
// https://tailwindcss.com/docs/using-with-preprocessors#future-css-features
110177
const postcssOptions =
@@ -120,11 +187,19 @@ export default defineNuxtModule({
120187
await installModule('@nuxt/postcss8')
121188
}
122189

190+
/**
191+
* Vite HMR support
192+
*/
193+
123194
if (nuxt.options.dev && !moduleOptions.disableHmrHotfix) {
124195
// Insert Vite plugin to work around HMR issue
125196
addVitePlugin(vitePlugin(tailwindConfig, nuxt.options.rootDir, resolvedCss))
126197
}
127198

199+
/**
200+
* Viewer
201+
*/
202+
128203
// Add _tailwind config viewer endpoint
129204
if (nuxt.options.dev && moduleOptions.viewer) {
130205
const route = '/_tailwind'

‎src/tailwind.config.ts

+1-10
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
11
// Learn more at https://tailwindcss.com/docs/configuration
22
import { Config } from 'tailwindcss'
33

4-
export default ({ srcDir }): Config => ({
4+
export default (): Config => ({
55
theme: {
66
extend: {}
77
},
88
plugins: [],
99
content: [
10-
`${srcDir}/components/**/*.{vue,js,ts}`,
11-
`${srcDir}/layouts/**/*.vue`,
12-
`${srcDir}/pages/**/*.vue`,
13-
`${srcDir}/composables/**/*.{js,ts}`,
14-
`${srcDir}/plugins/**/*.{js,ts}`,
15-
`${srcDir}/App.{js,ts,vue}`,
16-
`${srcDir}/app.{js,ts,vue}`,
17-
`${srcDir}/Error.{js,ts,vue}`,
18-
`${srcDir}/error.{js,ts,vue}`
1910
// TODO: This makes issues with import protection
2011
// Split in two files to avoid watching issues (https://github.com/nuxt-community/tailwindcss-module/issues/359)
2112
// `${rootDir}/nuxt.config.js`,

‎yarn.lock

+1,198-133
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.