Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: move heavy populateMap function to utils #637

Merged
merged 2 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 3 additions & 71 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { existsSync } from 'fs'
import { join, relative, dirname } from 'pathe'
import { join, relative } from 'pathe'
import { defuArrayFn } from 'defu'
import { watch } from 'chokidar'
import chalk from 'chalk'
Expand All @@ -21,7 +21,7 @@ import { eventHandler, sendRedirect } from 'h3'
import { name, version } from '../package.json'
import vitePlugin from './hmr'
import defaultTailwindConfig from './tailwind.config'
import { InjectPosition, resolveInjectPosition } from './utils'
import { createTemplates, InjectPosition, resolveInjectPosition } from './utils'

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

Expand Down Expand Up @@ -173,75 +173,7 @@ export default defineNuxtModule<ModuleOptions>({
// Expose resolved tailwind config as an alias
// https://tailwindcss.com/docs/configuration/#referencing-in-javascript
if (moduleOptions.exposeConfig) {
const dtsContent: string[] = []

/**
* Creates MJS exports for properties of the config
*
* @param obj config
* @param path parent properties trace
* @param level level of object depth
* @param maxLevel maximum level of depth
*/
const populateMap = (obj: any, path: string[] = [], level = 1, maxLevel = moduleOptions.exposeLevel) => {
Object.entries(obj).forEach(([key, value = {} as any]) => {
const subpath = path.concat(key).join('/')

if (
level >= maxLevel || // if recursive call is more than desired
typeof value !== 'object' || // if its not an object, no more recursion required
Array.isArray(value) || // arrays are objects in JS, but we can't break it down
Object.keys(value).find(k => !k.match(/^[0-9a-z]+$/i)) // object has non-alphanumeric property (unsafe var name)
) {
if (typeof value === 'object' && !Array.isArray(value)) {
const validKeys: string[] = []
const invalidKeys: string[] = []
Object.keys(value).forEach(i => (/^[0-9a-z]+$/i.test(i) ? validKeys : invalidKeys).push(i))

addTemplate({
filename: `tailwind.config/${subpath}.mjs`,
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(', _')} }`
})
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; }`)
} else {
addTemplate({
filename: `tailwind.config/${subpath}.mjs`,
getContents: () => `export default ${JSON.stringify(value, null, 2)}`
})
dtsContent.push(`declare module "#tailwind-config/${subpath}" { const defaultExport: ${JSON.stringify(value)}; export default defaultExport; }`)
}
} else {
// recurse through nested objects
populateMap(value, path.concat(key), level + 1, maxLevel)

const values = Object.keys(value)
addTemplate({
filename: `tailwind.config/${subpath}.mjs`,
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(', _')} }`
})
dtsContent.push(`declare module "#tailwind-config/${subpath}" {${Object.keys(value).map(v => ` export const _${v}: typeof import("#tailwind-config/${join(`${key}/${subpath}`, `../${v}`)}");`).join('')} const defaultExport: { ${values.map(k => `"${k}": typeof _${k}`).join(', ')} }; export default defaultExport; }`)
}
})
}

populateMap(resolvedConfig)
const configOptions = Object.keys(resolvedConfig)

const template = addTemplate({
filename: 'tailwind.config/index.mjs',
getContents: () => `${configOptions.map(v => `import ${v} from "#build/tailwind.config/${v}.mjs"`).join('\n')}\nconst config = { ${configOptions.join(', ')} }\nexport { config as default, ${configOptions.join(', ')} }`,
write: true
})
dtsContent.push(`declare module "#tailwind-config" {${configOptions.map(v => ` export const ${v}: typeof import("${join('#tailwind-config', v)}");`).join('')} const defaultExport: { ${configOptions.map(v => `"${v}": typeof ${v}`)} }; export default defaultExport; }`)
const typesTemplate = addTemplate({
filename: 'tailwind.config.d.ts',
getContents: () => dtsContent.join('\n'),
write: true
})
nuxt.options.alias['#tailwind-config'] = dirname(template.dst)
nuxt.hook('prepare:types', (opts) => {
opts.references.push({ path: typesTemplate.dst })
})
createTemplates(resolvedConfig, moduleOptions.exposeLevel, nuxt)
}

// Allow extending tailwindcss config by other modules
Expand Down
89 changes: 87 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { dirname, join } from 'pathe'
import { useNuxt, addTemplate } from '@nuxt/kit'
import type { Config } from 'tailwindcss'

const NON_ALPHANUMERIC_RE = /^[0-9a-z]+$/i
const isJSObject = (value: any) => typeof value === 'object' && !Array.isArray(value)

export type InjectPosition = 'first' | 'last' | number | { after: string };

/** Resolve human-readable inject position specification into absolute index in the array */
export function resolveInjectPosition (css: string[], position: InjectPosition): number {
/**
* Resolve human-readable inject position specification into absolute index in the array
*
* @param css nuxt css config
* @param position position to inject
*
* @returns index in the css array
*/
export function resolveInjectPosition (css: string[], position: InjectPosition) {
if (typeof (position) === 'number') {
return ~~Math.min(position, css.length + 1)
}
Expand All @@ -25,3 +39,74 @@ export function resolveInjectPosition (css: string[], position: InjectPosition):

throw new Error('invalid position: ' + JSON.stringify(position))
}

/**
* Creates MJS exports for properties of the config
*
* @param resolvedConfig tailwind config
* @param maxLevel maximum level of depth
* @param nuxt nuxt app
*/
export const createTemplates = (resolvedConfig: Config, maxLevel: number, nuxt = useNuxt()) => {
const dtsContent: string[] = []

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

if (
level >= maxLevel || // if recursive call is more than desired
!isJSObject(value) || // if its not an object, no more recursion required
Object.keys(value).find(k => !k.match(NON_ALPHANUMERIC_RE)) // object has non-alphanumeric property (unsafe var name)
) {
if (isJSObject(value)) {
const [validKeys, invalidKeys]: [string[], string[]] = [[], []]
Object.keys(value).forEach(i => (NON_ALPHANUMERIC_RE.test(i) ? validKeys : invalidKeys).push(i))

addTemplate({
filename: `tailwind.config/${subpath}.mjs`,
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(', _')} }`
})
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; }`)
} else {
addTemplate({
filename: `tailwind.config/${subpath}.mjs`,
getContents: () => `export default ${JSON.stringify(value, null, 2)}`
})
dtsContent.push(`declare module "#tailwind-config/${subpath}" { const defaultExport: ${JSON.stringify(value)}; export default defaultExport; }`)
}
} else {
// recurse through nested objects
populateMap(value, path.concat(key), level + 1)

const values = Object.keys(value)
addTemplate({
filename: `tailwind.config/${subpath}.mjs`,
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(', _')} }`
})
dtsContent.push(`declare module "#tailwind-config/${subpath}" {${Object.keys(value).map(v => ` export const _${v}: typeof import("#tailwind-config/${join(`${key}/${subpath}`, `../${v}`)}");`).join('')} const defaultExport: { ${values.map(k => `"${k}": typeof _${k}`).join(', ')} }; export default defaultExport; }`)
}
})
}

populateMap(resolvedConfig)
const configOptions = Object.keys(resolvedConfig)

const template = addTemplate({
filename: 'tailwind.config/index.mjs',
getContents: () => `${configOptions.map(v => `import ${v} from "#build/tailwind.config/${v}.mjs"`).join('\n')}\nconst config = { ${configOptions.join(', ')} }\nexport { config as default, ${configOptions.join(', ')} }`,
write: true
})

dtsContent.push(`declare module "#tailwind-config" {${configOptions.map(v => ` export const ${v}: typeof import("${join('#tailwind-config', v)}");`).join('')} const defaultExport: { ${configOptions.map(v => `"${v}": typeof ${v}`)} }; export default defaultExport; }`)
const typesTemplate = addTemplate({
filename: 'tailwind.config.d.ts',
getContents: () => dtsContent.join('\n'),
write: true
})

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