Integrations setup (#4354)

* add integration test tools

* setup jest in the integrations folder

* add `test:integrations` script

The default `npm test` script will ignore all the tests in the
`integrations` folder.

* add integration tests with `webpack 5`

* add integration tests with `postcss-cli`

* add `npm run install:integrations` script

This script will run `npm install` in every integration, and in the
integrations folder itself (to setup Jest for example).

* add `toIncludeCss` custom matcher

* increate Jest timeout for integration tests

* add integration tests with `vite`

* add integration tests with `webpack 4`

* add isomorphic fetch

* add the ability to wait for specific stdout/stderr output

* write vite tests, assert using API calls

We will wait for the correct stdout/stderr output, once we know that we
can request the fresh css, we will fetch it and make assertions
accordingly.

Port is currently hardcoded, maybe we should use a packaage to ensure
that we use a free port.

* add integration tests with `rollup`

* add integration tests with `parcel`

* run all integration tests in band

* add .gitignore in integrations folder
This commit is contained in:
Robin Malfait 2021-05-18 17:21:35 +02:00 committed by GitHub
parent 7d4f053bb6
commit 7565099c1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 64042 additions and 1 deletions

1
integrations/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

99
integrations/execute.js Normal file
View File

@ -0,0 +1,99 @@
let path = require('path')
let { spawn } = require('child_process')
let resolveToolRoot = require('./resolve-tool-root')
let runningProcessess = []
afterEach(() => {
runningProcessess.splice(0).forEach((runningProcess) => runningProcess.stop())
})
module.exports = function $(command, options = {}) {
let abortController = new AbortController()
let cwd = resolveToolRoot()
let args = command.split(' ')
command = args.shift()
command = path.resolve(cwd, 'node_modules', '.bin', command)
let stdoutMessages = []
let stderrMessages = []
let stdoutActors = []
let stderrActors = []
function notifyNext(actors, messages) {
if (actors.length <= 0) return
let [next] = actors
for (let [idx, message] of messages.entries()) {
if (next.predicate(message)) {
messages.splice(0, idx + 1)
let actorIdx = actors.indexOf(next)
actors.splice(actorIdx, 1)
next.resolve()
break
}
}
}
function notifyNextStdoutActor() {
return notifyNext(stdoutActors, stdoutMessages)
}
function notifyNextStderrActor() {
return notifyNext(stderrActors, stderrMessages)
}
let runningProcess = new Promise((resolve, reject) => {
let child = spawn(command, args, {
...options,
env: {
...process.env,
...options.env,
},
signal: abortController.signal,
cwd,
})
let stdout = ''
let stderr = ''
child.stdout.on('data', (data) => {
stdoutMessages.push(data.toString())
notifyNextStdoutActor()
stdout += data
})
child.stderr.on('data', (data) => {
stderrMessages.push(data.toString())
notifyNextStderrActor()
stderr += data
})
child.on('close', (code, signal) => {
;(signal === 'SIGTERM' ? resolve : code === 0 ? resolve : reject)({ code, stdout, stderr })
})
})
runningProcessess.push(runningProcess)
return Object.assign(runningProcess, {
stop() {
abortController.abort()
return runningProcess
},
onStdout(predicate) {
return new Promise((resolve) => {
stdoutActors.push({ predicate, resolve })
notifyNextStdoutActor()
})
},
onStderr(predicate) {
return new Promise((resolve) => {
stderrActors.push({ predicate, resolve })
notifyNextStderrActor()
})
},
})
}

4
integrations/html.js Normal file
View File

@ -0,0 +1,4 @@
// Small helper to allow for html highlighting / formatting in most editors.
module.exports = function html(templates) {
return templates.join('')
}

139
integrations/io.js Normal file
View File

@ -0,0 +1,139 @@
let { rm, existsSync } = require('fs')
let path = require('path')
let fs = require('fs/promises')
let chokidar = require('chokidar')
let resolveToolRoot = require('./resolve-tool-root')
module.exports = function ({
/** Output directory, relative to the tool. */
output = 'dist',
/** Input directory, relative to the tool. */
input = 'src',
/** Whether or not you want to cleanup the output directory. */
cleanup = true,
} = {}) {
let toolRoot = resolveToolRoot()
let fileCache = {}
let absoluteOutputFolder = path.resolve(toolRoot, output)
let absoluteInputFolder = path.resolve(toolRoot, input)
if (cleanup) {
beforeAll((done) => rm(absoluteOutputFolder, { recursive: true, force: true }, done))
afterEach((done) => rm(absoluteOutputFolder, { recursive: true, force: true }, done))
}
// Restore all written files
afterEach(async () => {
await Promise.all(
Object.entries(fileCache).map(([file, content]) => fs.writeFile(file, content, 'utf8'))
)
})
async function readdir(start, parent = []) {
let files = await fs.readdir(start, { withFileTypes: true })
let resolvedFiles = await Promise.all(
files.map((file) => {
if (file.isDirectory()) {
return readdir(path.resolve(start, file.name), [...parent, file.name])
}
return parent.concat(file.name).join(path.sep)
})
)
return resolvedFiles.flat(Infinity)
}
async function resolveFile(fileOrRegex, directory) {
if (fileOrRegex instanceof RegExp) {
let files = await readdir(directory)
if (files.length === 0) {
throw new Error(`No files exists in "${directory}"`)
}
let filtered = files.filter((file) => fileOrRegex.test(file))
if (filtered.length === 0) {
throw new Error(`Not a single file matched: ${fileOrRegex}`)
} else if (filtered.length > 1) {
throw new Error(`Multiple files matched: ${fileOrRegex}`)
}
return filtered[0]
}
return fileOrRegex
}
return {
async readOutputFile(file) {
file = await resolveFile(file, absoluteOutputFolder)
return fs.readFile(path.resolve(absoluteOutputFolder, file), 'utf8')
},
async appendToInputFile(file, contents) {
let filePath = path.resolve(absoluteInputFolder, file)
if (!fileCache[filePath]) {
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
}
return fs.appendFile(filePath, contents, 'utf8')
},
async writeInputFile(file, contents) {
let filePath = path.resolve(absoluteInputFolder, file)
if (!fileCache[filePath]) {
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
}
return fs.writeFile(path.resolve(absoluteInputFolder, file), contents, 'utf8')
},
async waitForOutputFileCreation(file) {
if (file instanceof RegExp) {
let r = file
let watcher = chokidar.watch(absoluteOutputFolder)
return new Promise((resolve) => {
watcher.on('add', (file) => {
if (r.test(file)) {
watcher.close()
resolve()
}
})
})
} else {
let filePath = path.resolve(absoluteOutputFolder, file)
let watcher = chokidar.watch(filePath)
let watcherPromise = new Promise((resolve) => {
watcher.once('add', () => {
watcher.close()
resolve()
})
})
if (existsSync(filePath)) {
watcher.close()
return Promise.resolve()
}
return watcherPromise
}
},
async waitForOutputFileChange(file, cb = () => {}) {
file = await resolveFile(file, absoluteOutputFolder)
let filePath = path.resolve(absoluteOutputFolder, file)
let watcher = chokidar.watch(filePath)
return new Promise((resolve) => {
let chain = Promise.resolve()
watcher.once('change', () => {
watcher.close()
chain.then(() => resolve())
})
chain.then(() => cb())
})
},
}
}

11125
integrations/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
integrations/package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "integrations",
"private": true,
"version": "0.0.0",
"scripts": {
"test": "jest --runInBand"
},
"jest": {
"testTimeout": 30000,
"projects": [
"<rootDir>/*/package.json"
]
},
"devDependencies": {
"isomorphic-fetch": "^3.0.0",
"jest": "^26.6.3"
}
}

5
integrations/parcel/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
dist/
node_modules/
.parcel-cache/
!tailwind.config.js
!index.html

25858
integrations/parcel/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
{
"name": "parcel",
"private": true,
"version": "0.0.0",
"scripts": {
"build": "parcel build ./src/index.html --no-cache",
"dev": "parcel watch ./src/index.html --no-cache",
"test": "jest"
},
"jest": {
"displayName": "parcel",
"setupFilesAfterEnv": [
"<rootDir>/../../jest/customMatchers.js"
]
},
"devDependencies": {
"parcel": "nightly"
}
}

View File

@ -0,0 +1,5 @@
let path = require('path')
module.exports = {
plugins: [require(path.resolve('..', '..'))],
}

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./index.css">
<title>Document</title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,15 @@
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}

View File

@ -0,0 +1,262 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let {
readOutputFile,
appendToInputFile,
writeInputFile,
waitForOutputFileCreation,
waitForOutputFileChange,
} = require('../../io')({ output: 'dist', input: 'src' })
describe('static build', () => {
it('should be possible to generate tailwind output', async () => {
await writeInputFile(
'index.html',
html`
<link rel="stylesheet" href="./index.css" />
<div class="font-bold"></div>
`
)
await $('parcel build ./src/index.html --no-cache', {
env: { NODE_ENV: 'production' },
})
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
})
})
describe('watcher', () => {
test('classes are generated when the html file changes', async () => {
await writeInputFile(
'index.html',
html`
<link rel="stylesheet" href="./index.css" />
<div class="font-bold"></div>
`
)
let runningProcess = $('parcel watch ./src/index.html --no-cache', {
env: { TAILWIND_MODE: 'watch' },
})
await waitForOutputFileCreation(/index\.\w+\.css$/)
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange(/index\.\w+\.css$/, async () => {
await appendToInputFile('index.html', html`<div class="font-normal"></div>`)
})
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
await waitForOutputFileChange(/index\.\w+\.css$/, async () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
})
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
test('classes are generated when the tailwind.config.js file changes', async () => {
await writeInputFile(
'index.html',
html`
<link rel="stylesheet" href="./index.css" />
<div class="font-bold md:font-medium"></div>
`
)
let runningProcess = $('parcel watch ./src/index.html --no-cache', {
env: { TAILWIND_MODE: 'watch' },
})
await waitForOutputFileCreation(/index\.\w+\.css$/)
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
@media (min-width: 768px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
await waitForOutputFileChange(/index\.\w+\.css$/, async () => {
await writeInputFile(
'../tailwind.config.js',
javascript`
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
screens: {
md: '800px'
},
fontWeight: {
bold: 'bold'
}
},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}
`
)
})
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.font-bold {
font-weight: bold;
}
@media (min-width: 800px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
return runningProcess.stop()
})
test('classes are generated when the index.css file changes', async () => {
await writeInputFile(
'index.html',
html`
<link rel="stylesheet" href="./index.css" />
<div class="font-bold btn"></div>
`
)
let runningProcess = $('parcel watch ./src/index.html --no-cache', {
env: { TAILWIND_MODE: 'watch' },
})
await waitForOutputFileCreation(/index\.\w+\.css$/)
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange(/index\.\w+\.css$/, async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded;
}
}
`
)
})
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange(/index\.\w+\.css$/, async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded bg-red-500;
}
}
`
)
})
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
})

4
integrations/postcss-cli/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
dist/
node_modules/
!tailwind.config.js
!index.html

1914
integrations/postcss-cli/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
{
"name": "postcss-cli",
"private": true,
"version": "0.0.0",
"scripts": {
"build": "NODE_ENV=production postcss ./src/index.css -o ./dist/main.css",
"test": "jest"
},
"jest": {
"displayName": "PostCSS CLI",
"setupFilesAfterEnv": [
"<rootDir>/../../jest/customMatchers.js"
]
},
"devDependencies": {
"postcss": "^8.2.15",
"postcss-cli": "^8.3.1"
}
}

View File

@ -0,0 +1,5 @@
let path = require('path')
module.exports = {
plugins: [require(path.resolve('..', '..'))],
}

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,15 @@
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}

View File

@ -0,0 +1,238 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let {
readOutputFile,
appendToInputFile,
writeInputFile,
waitForOutputFileCreation,
waitForOutputFileChange,
} = require('../../io')({ output: 'dist', input: 'src' })
describe('static build', () => {
it('should be possible to generate tailwind output', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
await $('postcss ./src/index.css -o ./dist/main.css', {
env: { NODE_ENV: 'production' },
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
})
})
describe('watcher', () => {
test('classes are generated when the html file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let runningProcess = $('postcss ./src/index.css -o ./dist/main.css -w', {
env: { TAILWIND_MODE: 'watch' },
})
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('index.html', html`<div class="font-normal"></div>`)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
test('classes are generated when the tailwind.config.js file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold md:font-medium"></div>`)
let runningProcess = $('postcss ./src/index.css -o ./dist/main.css -w', {
env: { TAILWIND_MODE: 'watch' },
})
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
@media (min-width: 768px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
await waitForOutputFileChange('main.css', async () => {
await writeInputFile(
'../tailwind.config.js',
javascript`
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
screens: {
md: '800px'
},
fontWeight: {
bold: 'bold'
}
},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}
`
)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: bold;
}
@media (min-width: 800px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
return runningProcess.stop()
})
test('classes are generated when the index.css file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
let runningProcess = $('postcss ./src/index.css -o ./dist/main.css -w', {
env: { TAILWIND_MODE: 'watch' },
})
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded;
}
}
`
)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded bg-red-500;
}
}
`
)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
})

View File

@ -0,0 +1,14 @@
let path = require('path')
module.exports = function resolveToolRoot() {
let { testPath } = expect.getState()
let separator = '/' // TODO: Does this resolve correctly on windows, or should we use `path.sep` instead.
return path.resolve(
__dirname,
testPath
.replace(__dirname + separator, '')
.split(separator)
.shift()
)
}

4
integrations/rollup/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
dist/
node_modules/
!tailwind.config.js
!index.html

9940
integrations/rollup/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
{
"name": "rollup.js",
"private": true,
"version": "0.0.0",
"scripts": {
"build": "rollup -c",
"test": "jest"
},
"jest": {
"displayName": "rollup.js",
"setupFilesAfterEnv": [
"<rootDir>/../../jest/customMatchers.js"
]
},
"devDependencies": {
"rollup": "^2.48.0",
"rollup-plugin-postcss": "^4.0.0"
}
}

View File

@ -0,0 +1,5 @@
let path = require('path')
module.exports = {
plugins: [require(path.resolve('..', '..'))],
}

View File

@ -0,0 +1,14 @@
import postcss from 'rollup-plugin-postcss'
export default {
input: './src/index.js',
output: {
file: './dist/index.js',
format: 'cjs',
},
plugins: [
postcss({
extract: true,
}),
],
}

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1 @@
import './index.css'

View File

@ -0,0 +1,15 @@
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}

View File

@ -0,0 +1,238 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let {
readOutputFile,
appendToInputFile,
writeInputFile,
waitForOutputFileCreation,
waitForOutputFileChange,
} = require('../../io')({ output: 'dist', input: 'src' })
describe('static build', () => {
it('should be possible to generate tailwind output', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
await $('rollup -c', {
env: { NODE_ENV: 'production' },
})
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
})
})
describe('watcher', () => {
test('classes are generated when the html file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let runningProcess = $('rollup -c --watch', {
env: { TAILWIND_MODE: 'watch' },
})
await waitForOutputFileCreation('index.css')
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('index.css', async () => {
await appendToInputFile('index.html', html`<div class="font-normal"></div>`)
})
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
await waitForOutputFileChange('index.css', async () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
})
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
test('classes are generated when the tailwind.config.js file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold md:font-medium"></div>`)
let runningProcess = $('rollup -c --watch', {
env: { TAILWIND_MODE: 'watch' },
})
await waitForOutputFileCreation('index.css')
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
@media (min-width: 768px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
await waitForOutputFileChange('index.css', async () => {
await writeInputFile(
'../tailwind.config.js',
javascript`
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
screens: {
md: '800px'
},
fontWeight: {
bold: 'bold'
}
},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}
`
)
})
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.font-bold {
font-weight: bold;
}
@media (min-width: 800px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
return runningProcess.stop()
})
test('classes are generated when the index.css file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
let runningProcess = $('rollup -c --watch', {
env: { TAILWIND_MODE: 'watch' },
})
await waitForOutputFileCreation('index.css')
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('index.css', async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded;
}
}
`
)
})
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('index.css', async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded bg-red-500;
}
}
`
)
})
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
})

6
integrations/syntax.js Normal file
View File

@ -0,0 +1,6 @@
// Small helper to allow for css, html and JavaScript highlighting / formatting in most editors.
function syntax(templates) {
return templates.join('')
}
module.exports = { css: syntax, html: syntax, javascript: syntax }

4
integrations/vite/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
dist/
node_modules/
!tailwind.config.js
!index.html

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./index.css">
<title>Document</title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1 @@
import './index.css'

274
integrations/vite/package-lock.json generated Normal file
View File

@ -0,0 +1,274 @@
{
"name": "vite",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.0.0",
"devDependencies": {
"vite": "^2.3.3"
}
},
"node_modules/colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true
},
"node_modules/esbuild": {
"version": "0.11.23",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.23.tgz",
"integrity": "sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/is-core-module": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
"integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
"dev": true,
"dependencies": {
"has": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/nanoid": {
"version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"node_modules/postcss": {
"version": "8.2.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz",
"integrity": "sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==",
"dev": true,
"dependencies": {
"colorette": "^1.2.2",
"nanoid": "^3.1.23",
"source-map": "^0.6.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/resolve": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
"dev": true,
"dependencies": {
"is-core-module": "^2.2.0",
"path-parse": "^1.0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rollup": {
"version": "2.48.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.48.0.tgz",
"integrity": "sha512-wl9ZSSSsi5579oscSDYSzGn092tCS076YB+TQrzsGuSfYyJeep8eEWj0eaRjuC5McuMNmcnR8icBqiE/FWNB1A==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=10.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.1"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vite": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.3.3.tgz",
"integrity": "sha512-eO1iwRbn3/BfkNVMNJDeANAFCZ5NobYOFPu7IqfY7DcI7I9nFGjJIZid0EViTmLDGwwSUPmRAq3cRBbO3+DsMA==",
"dev": true,
"dependencies": {
"esbuild": "^0.11.23",
"postcss": "^8.2.10",
"resolve": "^1.19.0",
"rollup": "^2.38.5"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": ">=12.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.1"
}
}
},
"dependencies": {
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true
},
"esbuild": {
"version": "0.11.23",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.23.tgz",
"integrity": "sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==",
"dev": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
"is-core-module": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
"integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
},
"nanoid": {
"version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"postcss": {
"version": "8.2.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz",
"integrity": "sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==",
"dev": true,
"requires": {
"colorette": "^1.2.2",
"nanoid": "^3.1.23",
"source-map": "^0.6.1"
}
},
"resolve": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
"dev": true,
"requires": {
"is-core-module": "^2.2.0",
"path-parse": "^1.0.6"
}
},
"rollup": {
"version": "2.48.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.48.0.tgz",
"integrity": "sha512-wl9ZSSSsi5579oscSDYSzGn092tCS076YB+TQrzsGuSfYyJeep8eEWj0eaRjuC5McuMNmcnR8icBqiE/FWNB1A==",
"dev": true,
"requires": {
"fsevents": "~2.3.1"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"vite": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.3.3.tgz",
"integrity": "sha512-eO1iwRbn3/BfkNVMNJDeANAFCZ5NobYOFPu7IqfY7DcI7I9nFGjJIZid0EViTmLDGwwSUPmRAq3cRBbO3+DsMA==",
"dev": true,
"requires": {
"esbuild": "^0.11.23",
"fsevents": "~2.3.1",
"postcss": "^8.2.10",
"resolve": "^1.19.0",
"rollup": "^2.38.5"
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"name": "vite",
"private": true,
"version": "0.0.0",
"main": "./src/index.js",
"browser": "./src/index.js",
"scripts": {
"build": "vite build",
"test": "jest"
},
"jest": {
"displayName": "vite",
"setupFilesAfterEnv": [
"<rootDir>/../../jest/customMatchers.js"
]
},
"devDependencies": {
"vite": "^2.3.3"
}
}

View File

@ -0,0 +1,5 @@
let path = require('path')
module.exports = {
plugins: [require(path.resolve('..', '..'))],
}

View File

@ -0,0 +1,15 @@
module.exports = {
purge: ['./index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}

View File

@ -0,0 +1,264 @@
require('isomorphic-fetch')
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({
output: 'dist',
input: '.',
})
let PORT = 1337
async function fetchCSS() {
let response = await fetch(`http://0.0.0.0:${PORT}/index.css`, {
headers: {
Accept: 'text/css',
},
})
return response.text()
}
describe('static build', () => {
it('should be possible to generate tailwind output', async () => {
await writeInputFile(
'index.html',
html`
<link rel="stylesheet" href="./index.css" />
<div class="font-bold"></div>
`
)
await $('vite build', {
env: { NODE_ENV: 'production' },
})
expect(await readOutputFile(/index.\w+\.css$/)).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
})
})
describe('watcher', () => {
test('classes are generated when the html file changes', async () => {
await writeInputFile(
'index.html',
html`
<link rel="stylesheet" href="./index.css" />
<div class="font-bold"></div>
`
)
let runningProcess = $(`vite --port ${PORT}`, {
env: { TAILWIND_MODE: 'watch' },
})
await runningProcess.onStdout((message) => message.includes('ready in'))
expect(await fetchCSS()).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await appendToInputFile('index.html', html`<div class="font-normal"></div>`)
await runningProcess.onStdout((message) => message.includes('hmr update /index.css'))
expect(await fetchCSS()).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStdout((message) => message.includes('hmr update /index.css'))
expect(await fetchCSS()).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
test('classes are generated when the tailwind.config.js file changes', async () => {
await writeInputFile(
'index.html',
html`
<link rel="stylesheet" href="./index.css" />
<div class="font-bold md:font-medium"></div>
`
)
let runningProcess = $(`vite --port ${PORT}`, {
env: { TAILWIND_MODE: 'watch' },
})
await runningProcess.onStdout((message) => message.includes('ready in'))
expect(await fetchCSS()).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
@media (min-width: 768px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
await writeInputFile(
'tailwind.config.js',
javascript`
module.exports = {
purge: ['./index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
screens: {
md: '800px'
},
fontWeight: {
bold: 'bold'
}
},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}
`
)
await runningProcess.onStdout((message) => message.includes('hmr update /index.css'))
expect(await fetchCSS()).toIncludeCss(
css`
.font-bold {
font-weight: bold;
}
@media (min-width: 800px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
return runningProcess.stop()
})
test('classes are generated when the index.css file changes', async () => {
await writeInputFile(
'index.html',
html`
<link rel="stylesheet" href="./index.css" />
<div class="font-bold btn"></div>
`
)
let runningProcess = $(`vite --port ${PORT}`, {
env: { TAILWIND_MODE: 'watch' },
})
await runningProcess.onStdout((message) => message.includes('ready in'))
expect(await fetchCSS()).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded;
}
}
`
)
await runningProcess.onStdout((message) => message.includes('hmr update /index.css'))
expect(await fetchCSS()).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded bg-red-500;
}
}
`
)
await runningProcess.onStdout((message) => message.includes('hmr update /index.css'))
expect(await fetchCSS()).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
})

4
integrations/webpack-4/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
dist/
node_modules/
!tailwind.config.js
!index.html

9399
integrations/webpack-4/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
{
"name": "webpack-4",
"private": true,
"version": "0.0.0",
"main": "./src/index.js",
"browser": "./src/index.js",
"scripts": {
"build": "webpack --mode=production",
"dev": "webpack --mode=development",
"test": "jest"
},
"jest": {
"displayName": "webpack 4",
"setupFilesAfterEnv": [
"<rootDir>/../../jest/customMatchers.js"
]
},
"devDependencies": {
"css-loader": "^5.2.4",
"mini-css-extract-plugin": "^1.6.0",
"postcss-loader": "^4.3.0",
"webpack": "^4.46.0",
"webpack-cli": "^4.7.0"
}
}

View File

@ -0,0 +1,5 @@
let path = require('path')
module.exports = {
plugins: [require(path.resolve('..', '..'))],
}

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1 @@
import './index.css'

View File

@ -0,0 +1,15 @@
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}

View File

@ -0,0 +1,236 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let {
readOutputFile,
appendToInputFile,
writeInputFile,
waitForOutputFileCreation,
waitForOutputFileChange,
} = require('../../io')({ output: 'dist', input: 'src' })
describe('static build', () => {
it('should be possible to generate tailwind output', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
await $('webpack --mode=production')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
})
})
describe('watcher', () => {
test('classes are generated when the html file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let runningProcess = $('webpack --mode=development --watch', {
env: { TAILWIND_MODE: 'watch', TAILWIND_DISABLE_TOUCH: true },
})
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('index.html', html`<div class="font-normal"></div>`)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
test('classes are generated when the tailwind.config.js file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold md:font-medium"></div>`)
let runningProcess = $('webpack --mode=development --watch', {
env: { TAILWIND_MODE: 'watch', TAILWIND_DISABLE_TOUCH: true },
})
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
@media (min-width: 768px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
await waitForOutputFileChange('main.css', async () => {
await writeInputFile(
'../tailwind.config.js',
javascript`
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
screens: {
md: '800px'
},
fontWeight: {
bold: 'bold'
}
},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}
`
)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: bold;
}
@media (min-width: 800px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
return runningProcess.stop()
})
test('classes are generated when the index.css file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
let runningProcess = $('webpack --mode=development --watch', {
env: { TAILWIND_MODE: 'watch', TAILWIND_DISABLE_TOUCH: true },
})
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded;
}
}
`
)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded bg-red-500;
}
}
`
)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
})

View File

@ -0,0 +1,13 @@
let MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
},
],
},
}

4
integrations/webpack-5/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
dist/
node_modules/
!tailwind.config.js
!index.html

3299
integrations/webpack-5/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
{
"name": "webpack-5",
"private": true,
"version": "0.0.0",
"main": "./src/index.js",
"browser": "./src/index.js",
"scripts": {
"build": "webpack --mode=production",
"dev": "webpack --mode=development",
"test": "jest"
},
"jest": {
"displayName": "webpack 5",
"setupFilesAfterEnv": [
"<rootDir>/../../jest/customMatchers.js"
]
},
"devDependencies": {
"css-loader": "^5.2.4",
"mini-css-extract-plugin": "^1.6.0",
"postcss-loader": "^5.3.0",
"webpack": "^5.37.0",
"webpack-cli": "^4.7.0"
}
}

View File

@ -0,0 +1,5 @@
let path = require('path')
module.exports = {
plugins: [require(path.resolve('..', '..'))],
}

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1 @@
import './index.css'

View File

@ -0,0 +1,15 @@
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}

View File

@ -0,0 +1,236 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let {
readOutputFile,
appendToInputFile,
writeInputFile,
waitForOutputFileCreation,
waitForOutputFileChange,
} = require('../../io')({ output: 'dist', input: 'src' })
describe('static build', () => {
it('should be possible to generate tailwind output', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
await $('webpack --mode=production')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
})
})
describe('watcher', () => {
test('classes are generated when the html file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let runningProcess = $('webpack --mode=development --watch', {
env: { TAILWIND_MODE: 'watch', TAILWIND_DISABLE_TOUCH: true },
})
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('index.html', html`<div class="font-normal"></div>`)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
test('classes are generated when the tailwind.config.js file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold md:font-medium"></div>`)
let runningProcess = $('webpack --mode=development --watch', {
env: { TAILWIND_MODE: 'watch', TAILWIND_DISABLE_TOUCH: true },
})
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
@media (min-width: 768px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
await waitForOutputFileChange('main.css', async () => {
await writeInputFile(
'../tailwind.config.js',
javascript`
module.exports = {
purge: ['./src/index.html'],
mode: 'jit',
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
screens: {
md: '800px'
},
fontWeight: {
bold: 'bold'
}
},
},
variants: {
extend: {},
},
corePlugins: {
preflight: false,
},
plugins: [],
}
`
)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: bold;
}
@media (min-width: 800px) {
.md\\:font-medium {
font-weight: 500;
}
}
`
)
return runningProcess.stop()
})
test('classes are generated when the index.css file changes', async () => {
await writeInputFile('index.html', html`<div class="font-bold btn"></div>`)
let runningProcess = $('webpack --mode=development --watch', {
env: { TAILWIND_MODE: 'watch', TAILWIND_DISABLE_TOUCH: true },
})
await waitForOutputFileCreation('main.css')
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded;
}
}
`
)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
await waitForOutputFileChange('main.css', async () => {
await writeInputFile(
'index.css',
css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply px-2 py-1 rounded bg-red-500;
}
}
`
)
})
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
})

View File

@ -0,0 +1,16 @@
let MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
},
],
},
output: {
clean: true,
},
}

View File

@ -53,6 +53,48 @@ expect.extend({
return { actual: received, message, pass }
},
toIncludeCss(received, argument) {
function stripped(str) {
return str.replace(/\s/g, '').replace(/;/g, '')
}
const options = {
comment: 'stripped(received).includes(stripped(argument))',
isNot: this.isNot,
promise: this.promise,
}
const pass = stripped(received).includes(stripped(argument))
const message = pass
? () => {
return (
this.utils.matcherHint('toIncludeCss', undefined, undefined, options) +
'\n\n' +
`Expected: not ${this.utils.printExpected(format(received))}\n` +
`Received: ${this.utils.printReceived(format(argument))}`
)
}
: () => {
const actual = format(received)
const expected = format(argument)
const diffString = diff(expected, actual, {
expand: this.expand,
})
return (
this.utils.matcherHint('toIncludeCss', undefined, undefined, options) +
'\n\n' +
(diffString && diffString.includes('- Expect')
? `Difference:\n\n${diffString}`
: `Expected: ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(actual)}`)
)
}
return { actual: received, message, pass }
},
})
expect.extend({

View File

@ -24,6 +24,8 @@
"prepublishOnly": "npm run babelify && ncc build lib/cli.js -o cli && babel-node scripts/build.js",
"style": "eslint .",
"test": "cross-env TAILWIND_MODE=build jest",
"test:integrations": "npm run test --prefix ./integrations",
"install:integrations": "node scripts/install-integrations.js",
"posttest": "npm run style",
"compat": "node scripts/compat.js --prepare",
"compat:restore": "node scripts/compat.js --restore"
@ -111,7 +113,8 @@
"testTimeout": 30000,
"setupFilesAfterEnv": [
"<rootDir>/jest/customMatchers.js"
]
],
"testPathIgnorePatterns": ["/node_modules/", "/integrations/"]
},
"engines": {
"node": ">=12.13.0"

View File

@ -0,0 +1,27 @@
let fs = require('fs/promises')
let { spawn } = require('child_process')
let path = require('path')
let root = process.cwd()
function npmInstall(cwd) {
return new Promise((resolve) => {
let childProcess = spawn('npm', ['install'], { cwd })
childProcess.on('exit', resolve)
})
}
async function install() {
let base = path.resolve(root, 'integrations')
let ignoreFolders = ['node_modules']
let integrations = (await fs.readdir(base, { withFileTypes: true }))
.filter((integration) => integration.isDirectory())
.filter((integration) => !ignoreFolders.includes(integration.name))
.map((folder) => path.resolve(base, folder.name))
.concat([base])
.map((integration) => npmInstall(integration))
await Promise.all(integrations)
console.log('Done!')
}
install()