Skip to content

Commit

Permalink
feat(tailwindcss): updated tailwindcss integration from v3 to v4. closes
Browse files Browse the repository at this point in the history
  • Loading branch information
tomgobich committed Jan 31, 2025
1 parent 45ff162 commit 5f48365
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 140 deletions.
156 changes: 18 additions & 138 deletions src/scaffolds/tailwind_scaffold.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,25 @@
import ConfigureCommand from '@adonisjs/core/commands/configure'
import { mkdir, writeFile } from 'node:fs/promises'
import { SourceFile, Symbol } from 'ts-morph'
import { SyntaxKind } from 'typescript'
import { stubsRoot } from '../../stubs/main.js'
import { readFileOrDefault } from '../utils/file_helper.js'
import BaseScaffold from './base_scaffold.js'

type Import = {
name: string
module: string
}

export default class TailwindScaffold extends BaseScaffold {
#imports = new Map<string, Import>([
[
'tailwind',
{
name: 'tailwind',
module: 'tailwindcss',
},
],
[
'autoprefixer',
{
name: 'autoprefixer',
module: 'autoprefixer',
},
],
])

constructor(protected command: ConfigureCommand) {
super(command)
}

static installs: { name: string; isDevDependency: boolean }[] = [
{ name: 'tailwindcss', isDevDependency: true },
{ name: 'autoprefixer', isDevDependency: true },
{ name: '@tailwindcss/vite', isDevDependency: true },
]

async run() {
await this.boot()

const cssPath = this.app.makePath('resources/css')
const cssFile = this.app.makePath('resources/css/app.css')
const cssContents = '@tailwind base;\n@tailwind components;\n@tailwind utilities;\n'

await this.codemods.makeUsingStub(stubsRoot, 'configs/tailwind.config.stub', {})
const cssContents = '@import "tailwindcss";\n@source "../views";\n'
const defaultReset = '* {\n margin: 0;\n padding: 0;\n}'

let css = await readFileOrDefault(cssFile, '')
let wasChanged = false
Expand All @@ -56,127 +30,33 @@ export default class TailwindScaffold extends BaseScaffold {
wasChanged = true
}

if (!css.includes('@tailwind')) {
if (!css.includes('@import tailwindcss')) {
css = css ? `${cssContents}\n${css}` : cssContents
wasChanged = true
}

if (css.includes(defaultReset)) {
css = css.replace(defaultReset, '')
wasChanged = true
}

if (wasChanged) {
await mkdir(cssPath, { recursive: true })
await writeFile(cssFile, css)

this.logger.action('update resources/css/app.css')
}

await this.#addViteConfig()
}

async #addViteConfig() {
const project = await this.codemods.getTsMorphProject()
const file = project?.getSourceFile(this.app.makePath('vite.config.ts'))
const defaultExport = file?.getDefaultExportSymbol()

if (!file) {
throw new Error('Cannot find the vite.config.ts file')
}

if (!defaultExport) {
throw new Error('Cannot find the default export in vite.config.ts')
}

const imports = await this.#addVitePostcssPlugins(defaultExport)

this.#addMissingImports(file, imports)

if (imports.length) {
file.formatText({ indentSize: 2 })

this.logger.action('create tailwind.config.ts')
}

await file.save()
await this.#addVitePlugin()
}

async #addVitePostcssPlugins(defaultExport: Symbol) {
// get the object contents of `defineConfig`
const declaration = defaultExport.getDeclarations()[0]
const options =
declaration.getChildrenOfKind(SyntaxKind.ObjectLiteralExpression)[0] ||
declaration.getChildrenOfKind(SyntaxKind.CallExpression)[0].getArguments()[0]

// 1. if there isn't already a `css` property, we can add the whole thing
const cssProperty = options
.getProperty('css')
?.getFirstChildByKind(SyntaxKind.ObjectLiteralExpression)

if (!cssProperty?.getFullText()) {
options.addPropertyAssignment({
name: 'css',
initializer: `{ postcss: { plugins: [tailwind(), autoprefixer()] } }`,
})
return [...this.#imports.values()]
}

// 2. if there is a `css` property but not a `postcss` property,
// we can add the whole `postcss` config
const postcssProperty = cssProperty
.getProperty('postcss')
?.getFirstChildByKind(SyntaxKind.ObjectLiteralExpression)

if (!postcssProperty?.getFullText()) {
cssProperty.addPropertyAssignment({
name: 'postcss',
initializer: '{ plugins: [tailwind(), autoprefixer()] }',
})
return [...this.#imports.values()]
}

// 3. if there is a `css.postcss` property, but it doesn't contain `plugins`,
// we can add the plugins
const plugins = postcssProperty
?.getProperty('plugins')
?.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression)

if (!plugins?.getFullText()) {
postcssProperty.addPropertyAssignment({
name: 'plugins',
initializer: '[tailwind(), autoprefixer()]',
})
return [...this.#imports.values()]
}

// 4. if there is a `css.postcss.plugins` property,
// determine if either the tailwind or autoprefixer plugis are missing
const pluginItems = plugins
?.getElements()
.filter((element) => ['tailwind()', 'autoprefixer()'].includes(element.getText()))
.map((element) => element.getText())

const imports: Import[] = []

if (!pluginItems?.includes('tailwind()')) {
plugins.insertElement(0, 'tailwind()')
imports.push(this.#imports.get('tailwind')!)
}

if (!pluginItems.includes('autoprefixer()')) {
plugins.addElement('autoprefixer()')
imports.push(this.#imports.get('autoprefixer')!)
}

return imports
}

#addMissingImports(file: SourceFile, imports: Import[]) {
const defaultImports = file.getImportDeclarations().map((r) => r.getDefaultImport()?.getText())

imports?.forEach((imp) => {
if (defaultImports?.includes(imp.name)) return

file.addImportDeclaration({
defaultImport: imp.name,
moduleSpecifier: imp.module,
})
})
async #addVitePlugin() {
this.codemods.registerVitePlugin('tailwindcss()', [
{
isNamed: false,
module: '@tailwindcss/vite',
identifier: 'tailwindcss',
},
])
}
}
5 changes: 3 additions & 2 deletions stubs/views/components/layout/index.edge
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
</a>
<nav class="flex flex-wrap items-center mb-5 text-sm md:mb-0 md:pl-8 md:ml-8 md:border-l md:border-gray-200">
<a href="{{ route('jumpstart') }}" class="mr-5 font-medium leading-6 text-gray-600 hover:text-gray-900">Home</a>
<a href="#_" class="mr-5 font-medium leading-6 text-gray-600 hover:text-gray-900">Documentation</a>
<a href="https://adonisjs.com" target="_blank" class="mr-5 font-medium leading-6 text-gray-600 hover:text-gray-900">AdonisJS</a>
<a href="https://adocasts.com" target="_blank" class="mr-5 font-medium leading-6 text-gray-600 hover:text-gray-900">Adocasts</a>
</nav>
Expand Down Expand Up @@ -79,7 +78,9 @@
<!-- End Navigation -->

<main class="px-8">
{{{ await $slots.main() }}}
<div class="max-w-7xl mx-auto">
{{{ await $slots.main() }}}
</div>
</main>

@!toast.flash()
Expand Down

0 comments on commit 5f48365

Please sign in to comment.