diff --git a/src/module.ts b/src/module.ts index 139cee0e..b4615666 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,6 +1,5 @@ import { existsSync } from 'fs' import { join, relative } from 'pathe' -import { defuArrayFn } from 'defu' import { watch } from 'chokidar' import { underline, yellow } from 'colorette' import { @@ -22,7 +21,8 @@ import defaultTailwindConfig from 'tailwindcss/stubs/config.simple.js' import { eventHandler, sendRedirect, H3Event } from 'h3' import { name, version } from '../package.json' import vitePlugin from './hmr' -import { createTemplates, InjectPosition, resolveInjectPosition } from './utils' +import { configMerger, createTemplates, InjectPosition, resolveInjectPosition } from './utils' +import { addTemplate } from '@nuxt/kit' const logger = useLogger('nuxt:tailwindcss') @@ -45,7 +45,7 @@ type Arrayable = T | T[] declare module '@nuxt/schema' { interface NuxtHooks { 'tailwindcss:config': (tailwindConfig: Partial) => void; - 'tailwindcss:resolvedConfig': (tailwindConfig: Config) => void; + 'tailwindcss:resolvedConfig': (tailwindConfig: ReturnType>) => void; } } @@ -137,10 +137,11 @@ export default defineNuxtModule({ } // Default tailwind config - let tailwindConfig = defuArrayFn(moduleOptions.config, { content: contentPaths }) as Config + let tailwindConfig = configMerger(moduleOptions.config, { content: contentPaths }) + // Recursively resolve each config and merge tailwind configs together. for (const configPath of configPaths) { - let _tailwindConfig: Config | undefined + let _tailwindConfig: Partial | undefined try { _tailwindConfig = requireModule(configPath, { clearCache: true }) } catch (e) { @@ -152,14 +153,14 @@ export default defineNuxtModule({ _tailwindConfig.content = _tailwindConfig.purge } if (_tailwindConfig) { - tailwindConfig = defuArrayFn(_tailwindConfig, tailwindConfig) + tailwindConfig = configMerger(_tailwindConfig, tailwindConfig) } } // Allow extending tailwindcss config by other modules await nuxt.callHook('tailwindcss:config', tailwindConfig) - const resolvedConfig = resolveConfig(tailwindConfig) as Config + const resolvedConfig = resolveConfig(tailwindConfig as Config) await nuxt.callHook('tailwindcss:resolvedConfig', resolvedConfig) // Expose resolved tailwind config as an alias @@ -179,18 +180,24 @@ export default defineNuxtModule({ // Include CSS file in project css let resolvedCss: string + if (typeof cssPath === 'string') { if (existsSync(cssPath)) { logger.info(`Using Tailwind CSS from ~/${relative(nuxt.options.srcDir, cssPath)}`) resolvedCss = cssPath } else { - logger.info('Using default Tailwind CSS file from runtime/tailwind.css') + logger.info('Using default Tailwind CSS file') // @ts-ignore - resolvedCss = createResolver(import.meta.url).resolve('runtime/tailwind.css') + resolvedCss = 'tailwindcss/tailwind.css' } } else { logger.info('No Tailwind CSS file found. Skipping...') - resolvedCss = createResolver(import.meta.url).resolve('runtime/empty.css') + const emptyCSSPath = addTemplate({ + filename: 'tailwind-empty.css', + write: true, + getContents: () => '' + }).dst + resolvedCss = createResolver(import.meta.url).resolve(emptyCSSPath) } nuxt.options.css = nuxt.options.css ?? [] diff --git a/src/runtime/empty.css b/src/runtime/empty.css deleted file mode 100644 index e69de29b..00000000 diff --git a/src/runtime/tailwind.css b/src/runtime/tailwind.css deleted file mode 100644 index b5c61c95..00000000 --- a/src/runtime/tailwind.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/src/utils.ts b/src/utils.ts index f2dc1b32..55dba0da 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,10 +1,32 @@ import { dirname, join } from 'pathe' import { useNuxt, addTemplate } from '@nuxt/kit' import type { Config } from 'tailwindcss' +import { createDefu } from 'defu' const NON_ALPHANUMERIC_RE = /^[0-9a-z]+$/i const isJSObject = (value: any) => typeof value === 'object' && !Array.isArray(value) +export const configMerger: ( + ...p: Array | Record> +) => Partial = createDefu((obj, key, value) => { + if (key === 'content') { + if (isJSObject(obj[key]) && Array.isArray(value)) { + obj[key]['files'] = [...(obj[key]['files'] || []), ...value] + return true + } else if (Array.isArray(obj[key]) && isJSObject(value)) { + obj[key] = { ...value, files: [...obj[key], ...(value.files || [])]} + return true + } + } + + // keeping arrayFn + if (Array.isArray(obj[key]) && typeof value === "function") { + obj[key] = value(obj[key]) + return true + } +}) + + export type InjectPosition = 'first' | 'last' | number | { after: string }; /** @@ -47,7 +69,7 @@ export function resolveInjectPosition (css: string[], position: InjectPosition) * @param maxLevel maximum level of depth * @param nuxt nuxt app */ -export const createTemplates = (resolvedConfig: Config, maxLevel: number, nuxt = useNuxt()) => { +export function createTemplates (resolvedConfig: Partial, maxLevel: number, nuxt = useNuxt()) { const dtsContent: string[] = [] const populateMap = (obj: any, path: string[] = [], level = 1) => { diff --git a/test/configs.test.ts b/test/configs.test.ts index fdbb75e0..e7a81171 100644 --- a/test/configs.test.ts +++ b/test/configs.test.ts @@ -18,7 +18,8 @@ describe('tailwindcss module configs', async () => { 'alt-tailwind.config.js', 'malformed-tailwind.config', 'ts-tailwind.config', - 'override-tailwind.config.js' + 'override-tailwind.config.js', + 'content-obj.config' ], cssPath: 'tailwind.css' }) @@ -50,9 +51,18 @@ describe('tailwindcss module configs', async () => { const vfsKey = Object.keys(nuxt.vfs).find(k => k.includes('tailwind.config.')) // set from override-tailwind.config.ts const contentFiles = destr(nuxt.vfs[vfsKey].replace(/^(module\.exports = )/, '')).content.files - expect(contentFiles.length).toBe(4) + expect(contentFiles[0]).toBe('ts-content/**/*.md') expect(contentFiles[1]).toBe('./custom-theme/**/*.vue') expect(contentFiles.slice(2).filter(c => c.endsWith('vue')).length).toBe(2) }) + + test('content merges with objects', () => { + const nuxt = useTestContext().nuxt + const vfsKey = Object.keys(nuxt.vfs).find(k => k.includes('tailwind.config.')) + const { content } = destr(nuxt.vfs[vfsKey].replace(/^(module\.exports = )/, '')) + + expect(content.relative).toBeTruthy() + expect(content.files.pop()).toBe('./my-components/**/*.tsx') + }) }) diff --git a/test/fixture/basic/content-obj.config.ts b/test/fixture/basic/content-obj.config.ts new file mode 100644 index 00000000..aca9be6e --- /dev/null +++ b/test/fixture/basic/content-obj.config.ts @@ -0,0 +1,13 @@ +import type { Config } from 'tailwindcss' + +const config: Config = { + content: { + relative: true, + files: ['./my-components/**/*.tsx'], + extract: { + jpg: () => ['bg-red'] + } + } +} + +export default config;