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

Add option to setup TailwindUI with TailwindCSS #1408

Closed
wants to merge 18 commits into from
Closed
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
2 changes: 1 addition & 1 deletion packages/cli/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/*.test.[jt]s?(x)'],
testMatch: ['**/__tests__/**/*.test.[jt]s?(x)', '**/*.test.[jt]s?(x)'],
testPathIgnorePatterns: ['fixtures'],
moduleNameMapper: {
'src/(.*)': '<rootDir>/src/$1',
Expand Down
168 changes: 26 additions & 142 deletions packages/cli/src/commands/setup/tailwind/tailwind.js
Original file line number Diff line number Diff line change
@@ -1,176 +1,60 @@
import fs from 'fs'
import path from 'path'

import chalk from 'chalk'
import execa from 'execa'
import Listr from 'listr'

import { getPaths, writeFile } from 'src/lib'
import {
configurePostCSS,
installPackages,
yarnCheckFiles,
initTailwind,
addCSSImports,
} from './tasks'
import c from 'src/lib/colors'

export const command = 'tailwind'
export const description = 'Setup tailwindcss and PostCSS'
export const builder = (yargs) => {
yargs.option('force', {
alias: 'f',
default: false,
description: 'Overwrite existing configuration',
type: 'boolean',
})
}

const tailwindImportsAndNotes = [
'/**',
' * START --- TAILWIND GENERATOR EDIT',
' *',
' * `yarn rw setup tailwind` placed these imports here',
" * to inject Tailwind's styles into your CSS.",
' * For more information, see: https://tailwindcss.com/docs/installation#add-tailwind-to-your-css',
' */',
'@import "tailwindcss/base";',
'@import "tailwindcss/components";',
'@import "tailwindcss/utilities";',
'/**',
' * END --- TAILWIND GENERATOR EDIT',
' */\n',
]

const INDEX_CSS_PATH = path.join(getPaths().web.src, 'index.css')

const tailwindImportsExist = (indexCSS) => {
let content = indexCSS.toString()

const hasBaseImport = () => /@import "tailwindcss\/base"/.test(content)

const hasComponentsImport = () =>
/@import "tailwindcss\/components"/.test(content)

const hasUtilitiesImport = () =>
/@import "tailwindcss\/utilities"/.test(content)

return hasBaseImport() && hasComponentsImport() && hasUtilitiesImport()
yargs
.option('force', {
alias: 'f',
default: false,
description: 'Overwrite existing configuration',
type: 'boolean',
})
.option('ui', {
default: false,
description: 'Include TailwindUI Installation',
type: 'boolean',
})
}

const postCSSConfigExists = () => {
return fs.existsSync(getPaths().web.postcss)
}

export const handler = async ({ force }) => {
export const handler = async (args) => {
const tasks = new Listr([
{
title: 'Installing packages...',
task: () => {
return new Listr([
{
title: 'Install postcss-loader, tailwindcss, and autoprefixer',
task: async () => {
/**
* Install postcss-loader, tailwindcss, and autoprefixer
* RedwoodJS currently uses PostCSS v7; postcss-loader and autoprefixers pinned for compatibility
*/
await execa('yarn', [
'workspace',
'web',
'add',
'-D',
'postcss-loader@4.0.2',
'tailwindcss@npm:@tailwindcss/postcss7-compat',
'autoprefixer@9.8.6',
])
},
task: installPackages(args),
},
{
title: 'Sync yarn.lock and node_modules',
task: async () => {
/**
* Sync yarn.lock file and node_modules folder.
* Refer https://github.com/redwoodjs/redwood/issues/1301 for more details.
*/
await execa('yarn', ['install', '--check-files'])
},
task: yarnCheckFiles(args),
},
])
},
},
{
title: 'Configuring PostCSS...',
task: () => {
/**
* Make web/config if it doesn't exist
* and write postcss.config.js there
*/

/**
* Check if PostCSS config already exists.
* If it exists, throw an error.
*/
if (!force && postCSSConfigExists()) {
throw new Error(
'PostCSS config already exists.\nUse --force to override existing config.'
)
} else {
return writeFile(
getPaths().web.postcss,
fs
.readFileSync(
path.resolve(
__dirname,
'templates',
'postcss.config.js.template'
)
)
.toString(),
{ overwriteExisting: force }
)
}
},
task: configurePostCSS(args),
},
{
title: 'Initializing Tailwind CSS...',
task: async () => {
const basePath = getPaths().web.base
const tailwindConfigPath = path.join(basePath, 'tailwind.config.js')
const configExists = fs.existsSync(tailwindConfigPath)

if (configExists) {
if (force) {
// yarn tailwindcss init will fail if the file already exists
fs.unlinkSync(tailwindConfigPath)
} else {
throw new Error(
'Tailwindcss config already exists.\nUse --force to override existing config.'
)
}
}

await execa('yarn', ['tailwindcss', 'init'], { cwd: basePath })

// opt-in to upcoming changes
const config = fs.readFileSync(tailwindConfigPath, 'utf-8')

const uncommentFlags = (str) =>
str.replace(/\/{2} ([\w-]+: true)/g, '$1')

const newConfig = config.replace(/future.*purge/s, uncommentFlags)

fs.writeFileSync(tailwindConfigPath, newConfig)
},
task: initTailwind(args),
},
{
title: 'Adding imports to index.css...',
task: (_ctx, task) => {
/**
* Add tailwind imports and notes to the top of index.css
*/
let indexCSS = fs.readFileSync(INDEX_CSS_PATH)

if (tailwindImportsExist(indexCSS)) {
task.skip('Imports already exist in index.css')
} else {
indexCSS = tailwindImportsAndNotes.join('\n') + indexCSS
fs.writeFileSync(INDEX_CSS_PATH, indexCSS)
}
},
task: addCSSImports(args),
},
{
title: 'One more thing...',
Expand All @@ -188,7 +72,7 @@ export const handler = async ({ force }) => {
])

try {
await tasks.run()
await tasks(args).run()
} catch (e) {
console.log(c.error(e.message))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import fs from 'fs'

import 'src/lib/test'
import path from 'path'
import * as lib from 'src/lib'
import addCSSImports from '..'

jest.mock('src/lib', () => {
return {
...jest.requireActual('src/lib'),
getPaths: () => ({
api: {},
web: {
src: 'some/path/to/web/src',
},
}),
writeFile: jest.fn(),
}
})

describe('rw setup tailwind - addCSSImports task', () => {
const cssPath = path.join(lib.getPaths().web.src, 'index.css')
const cssImports = fs
.readFileSync(path.join(__dirname, '..', 'css-imports.template.css'))
.toString()

const task = addCSSImports()

beforeEach(() => {
jest.clearAllMocks()
})

it('skips if main CSS already includes TailwindCSS imports', () => {
jest.spyOn(fs, 'readFileSync').mockImplementation(() => {
// Always return `cssImports`, which means `web/src/index.css` will contain the imports
return cssImports
})

const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync')

const taskSkip = jest.fn()
task(undefined, { skip: taskSkip })

expect(taskSkip).toHaveBeenCalledWith(`Imports already exist in ${cssPath}`)
expect(writeFileSyncSpy).not.toHaveBeenCalled()
})

it("writes CSS imports to web/src/index.css when they're missing", () => {
const cssContent = '.beautiful-text { font-family: "Comic Sans" }'

jest
.spyOn(fs, 'readFileSync')
.mockImplementation((path) =>
path === cssPath ? cssContent : cssImports
)

const taskObj = {}
task(undefined, taskObj)

expect(lib.writeFile).toHaveBeenCalledWith(
cssPath,
cssImports + cssContent,
{ overwriteExisting: true },
taskObj
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* START --- TAILWIND GENERATOR EDIT
*
* `yarn rw setup tailwind` placed these imports here
* to inject Tailwind's styles into your CSS.
* For more information, see: https://tailwindcss.com/docs/installation#add-tailwind-to-your-css
*/
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
/**
* END --- TAILWIND GENERATOR EDIT
*
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import fs from 'fs'
import path from 'path'
import { getPaths, writeFile } from 'src/lib'

function tailwindImportsExist(content) {
const hasBaseImport = /@import "tailwindcss\/base"/.test(content)
const hasComponentsImport = /@import "tailwindcss\/components"/.test(content)
const hasUtilitiesImport = /@import "tailwindcss\/utilities"/.test(content)

return hasBaseImport && hasComponentsImport && hasUtilitiesImport
}

export default () => (_ctx, task) => {
/**
* Add tailwind imports and notes to the top of index.css
*/

const INDEX_CSS_PATH = path.join(getPaths().web.src, 'index.css')

const tailwindImportsAndNotes = fs
.readFileSync(path.join(__dirname, 'css-imports.template.css'))
.toString()

const cssPath = path.join(getPaths().web.src, 'INDEX_CSS_PATH')
const cssContent = fs.readFileSync(cssPath).toString()

if (tailwindImportsExist(cssContent)) {
task.skip(`Imports already exist in ${cssPath}`)
} else {
writeFile(
cssPath,
tailwindImportsAndNotes + cssContent,
{ overwriteExisting: true },
task
)
}
}
Loading