mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Invalidate context when main css changes (#7626)
* Invalidate context when CSS changes * Remove invalidation count check * Add sass integration test * Update changelog
This commit is contained in:
parent
d9bc25da6a
commit
bd167635d5
2
.github/workflows/integration-tests.yml
vendored
2
.github/workflows/integration-tests.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
integration: [parcel, postcss-cli, rollup, tailwindcss-cli, vite, webpack-4, webpack-5]
|
||||
integration: [parcel, postcss-cli, rollup, rollup-sass, tailwindcss-cli, vite, webpack-4, webpack-5]
|
||||
node-version: [16]
|
||||
|
||||
steps:
|
||||
|
||||
@ -7,17 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Prevent nesting plugin from breaking other plugins ([#7563](https://github.com/tailwindlabs/tailwindcss/pull/7563))
|
||||
- Recursively collapse adjacent rules ([#7565](https://github.com/tailwindlabs/tailwindcss/pull/7565))
|
||||
- Allow default ring color to be a function ([#7587](https://github.com/tailwindlabs/tailwindcss/pull/7587))
|
||||
- Preserve source maps for generated CSS ([#7588](https://github.com/tailwindlabs/tailwindcss/pull/7588))
|
||||
- Split box shadows on top-level commas only ([#7479](https://github.com/tailwindlabs/tailwindcss/pull/7479))
|
||||
- Use local user CSS cache for `@apply` ([#7524](https://github.com/tailwindlabs/tailwindcss/pull/7524))
|
||||
- Invalidate context when main CSS changes ([#7626](https://github.com/tailwindlabs/tailwindcss/pull/7626))
|
||||
|
||||
### Changed
|
||||
|
||||
- Replace `chalk` with `picocolors` ([#6039](https://github.com/tailwindlabs/tailwindcss/pull/6039))
|
||||
|
||||
### Added
|
||||
|
||||
- Allow default ring color to be a function ([#7587](https://github.com/tailwindlabs/tailwindcss/pull/7587))
|
||||
|
||||
## [3.0.23] - 2022-02-16
|
||||
|
||||
### Fixed
|
||||
|
||||
4
integrations/rollup-sass/.gitignore
vendored
Normal file
4
integrations/rollup-sass/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
dist/
|
||||
node_modules/
|
||||
!tailwind.config.js
|
||||
!index.html
|
||||
2688
integrations/rollup-sass/package-lock.json
generated
Normal file
2688
integrations/rollup-sass/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
integrations/rollup-sass/package.json
Normal file
19
integrations/rollup-sass/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "rollup.js",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"test": "jest --runInBand --forceExit"
|
||||
},
|
||||
"jest": {
|
||||
"testTimeout": 10000,
|
||||
"displayName": "rollup.js",
|
||||
"setupFilesAfterEnv": ["<rootDir>/../../jest/customMatchers.js"]
|
||||
},
|
||||
"devDependencies": {
|
||||
"rollup": "^2.48.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"sass": "^1.49.9"
|
||||
}
|
||||
}
|
||||
5
integrations/rollup-sass/postcss.config.js
Normal file
5
integrations/rollup-sass/postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
let path = require('path')
|
||||
|
||||
module.exports = {
|
||||
plugins: [require(path.resolve('..', '..'))],
|
||||
}
|
||||
14
integrations/rollup-sass/rollup.config.js
Normal file
14
integrations/rollup-sass/rollup.config.js
Normal 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,
|
||||
}),
|
||||
],
|
||||
}
|
||||
1
integrations/rollup-sass/src/imported.scss
Normal file
1
integrations/rollup-sass/src/imported.scss
Normal file
@ -0,0 +1 @@
|
||||
// Stub
|
||||
9
integrations/rollup-sass/src/index.html
Normal file
9
integrations/rollup-sass/src/index.html
Normal file
@ -0,0 +1,9 @@
|
||||
<!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>
|
||||
1
integrations/rollup-sass/src/index.js
Normal file
1
integrations/rollup-sass/src/index.js
Normal file
@ -0,0 +1 @@
|
||||
import './index.scss'
|
||||
4
integrations/rollup-sass/src/index.scss
Normal file
4
integrations/rollup-sass/src/index.scss
Normal file
@ -0,0 +1,4 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "./imported";
|
||||
10
integrations/rollup-sass/tailwind.config.js
Normal file
10
integrations/rollup-sass/tailwind.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
content: ['./src/index.html'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
303
integrations/rollup-sass/tests/integration.test.js
Normal file
303
integrations/rollup-sass/tests/integration.test.js
Normal file
@ -0,0 +1,303 @@
|
||||
let $ = require('../../execute')
|
||||
let { css, html, javascript } = require('../../syntax')
|
||||
|
||||
let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({
|
||||
output: 'dist',
|
||||
input: 'src',
|
||||
})
|
||||
|
||||
function ready(message) {
|
||||
return message.includes('created')
|
||||
}
|
||||
|
||||
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')
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
expect(await readOutputFile('index.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await appendToInputFile('index.html', html`<div class="font-normal"></div>`)
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
expect(await readOutputFile('index.css')).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.onStderr(ready)
|
||||
|
||||
expect(await readOutputFile('index.css')).toIncludeCss(
|
||||
css`
|
||||
.bg-red-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(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')
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
expect(await readOutputFile('index.css')).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 = {
|
||||
content: ['./src/index.html'],
|
||||
theme: {
|
||||
extend: {
|
||||
screens: {
|
||||
md: '800px'
|
||||
},
|
||||
fontWeight: {
|
||||
bold: 'bold'
|
||||
}
|
||||
},
|
||||
},
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
`
|
||||
)
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
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="btn font-bold"></div>`)
|
||||
|
||||
let runningProcess = $('rollup -c --watch')
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
expect(await readOutputFile('index.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await writeInputFile(
|
||||
'index.scss',
|
||||
css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply rounded px-2 py-1;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
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 writeInputFile(
|
||||
'index.scss',
|
||||
css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply rounded bg-red-500 px-2 py-1;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
expect(await readOutputFile('index.css')).toIncludeCss(
|
||||
css`
|
||||
.btn {
|
||||
border-radius: 0.25rem;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(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()
|
||||
})
|
||||
|
||||
test(`classes are generated when the imported.scss file changes`, async () => {
|
||||
await writeInputFile('index.html', html`<div class="btn font-bold"></div>`)
|
||||
|
||||
let runningProcess = $('rollup -c --watch')
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
expect(await readOutputFile('index.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await writeInputFile(
|
||||
'index.scss',
|
||||
css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import './imported';
|
||||
`
|
||||
)
|
||||
|
||||
await writeInputFile(
|
||||
'imported.scss',
|
||||
css`
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply rounded px-2 py-1;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
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 writeInputFile(
|
||||
'imported.scss',
|
||||
css`
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply rounded bg-red-500 px-2 py-1;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
await runningProcess.onStderr(ready)
|
||||
|
||||
expect(await readOutputFile('index.css')).toIncludeCss(
|
||||
css`
|
||||
.btn {
|
||||
border-radius: 0.25rem;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(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()
|
||||
})
|
||||
})
|
||||
52
src/lib/cacheInvalidation.js
Normal file
52
src/lib/cacheInvalidation.js
Normal file
@ -0,0 +1,52 @@
|
||||
import crypto from 'crypto'
|
||||
import * as sharedState from './sharedState'
|
||||
|
||||
/**
|
||||
* Calculate the hash of a string.
|
||||
*
|
||||
* This doesn't need to be cryptographically secure or
|
||||
* anything like that since it's used only to detect
|
||||
* when the CSS changes to invalidate the context.
|
||||
*
|
||||
* This is wrapped in a try/catch because it's really dependent
|
||||
* on how Node itself is build and the environment and OpenSSL
|
||||
* version / build that is installed on the user's machine.
|
||||
*
|
||||
* Based on the environment this can just outright fail.
|
||||
*
|
||||
* See https://github.com/nodejs/node/issues/40455
|
||||
*
|
||||
* @param {string} str
|
||||
*/
|
||||
function getHash(str) {
|
||||
try {
|
||||
return crypto.createHash('md5').update(str, 'utf-8').digest('binary')
|
||||
} catch (err) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the CSS tree is different from the
|
||||
* previous version for the given `sourcePath`.
|
||||
*
|
||||
* @param {string} sourcePath
|
||||
* @param {import('postcss').Node} root
|
||||
*/
|
||||
export function hasContentChanged(sourcePath, root) {
|
||||
let css = root.toString()
|
||||
|
||||
// We only care about files with @tailwind directives
|
||||
// Other files use an existing context
|
||||
if (!css.includes('@tailwind')) {
|
||||
return false
|
||||
}
|
||||
|
||||
let existingHash = sharedState.sourceHashMap.get(sourcePath)
|
||||
let rootHash = getHash(css)
|
||||
let didChange = existingHash !== rootHash
|
||||
|
||||
sharedState.sourceHashMap.set(sourcePath, rootHash)
|
||||
|
||||
return didChange
|
||||
}
|
||||
@ -20,6 +20,7 @@ import log from '../util/log'
|
||||
import negateValue from '../util/negateValue'
|
||||
import isValidArbitraryValue from '../util/isValidArbitraryValue'
|
||||
import { generateRules } from './generateRules'
|
||||
import { hasContentChanged } from './cacheInvalidation.js'
|
||||
|
||||
function prefix(context, selector) {
|
||||
let prefix = context.tailwindConfig.prefix
|
||||
@ -822,6 +823,8 @@ export function getContext(
|
||||
existingContext = context
|
||||
}
|
||||
|
||||
let cssDidChange = hasContentChanged(sourcePath, root)
|
||||
|
||||
// If there's already a context in the cache and we don't need to
|
||||
// reset the context, return the cached context.
|
||||
if (existingContext) {
|
||||
@ -829,7 +832,7 @@ export function getContext(
|
||||
[...contextDependencies],
|
||||
getFileModifiedMap(existingContext)
|
||||
)
|
||||
if (!contextDependenciesChanged) {
|
||||
if (!contextDependenciesChanged && !cssDidChange) {
|
||||
return [existingContext, false]
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ export const env = {
|
||||
export const contextMap = new Map()
|
||||
export const configContextMap = new Map()
|
||||
export const contextSourcesMap = new Map()
|
||||
export const sourceHashMap = new Map()
|
||||
export const NOT_ON_DEMAND = new String('*')
|
||||
|
||||
export function resolveDebug(debug) {
|
||||
|
||||
@ -7,5 +7,7 @@
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="./tailwind.css" />
|
||||
</head>
|
||||
<body></body>
|
||||
<body>
|
||||
<div class="only:custom-utility"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -7,7 +7,9 @@ const configPath = path.resolve(__dirname, './context-reuse.tailwind.config.js')
|
||||
const { css } = require('./util/run.js')
|
||||
|
||||
function run(input, config = {}, from = null) {
|
||||
from = from || path.resolve(__filename)
|
||||
let { currentTestName } = expect.getState()
|
||||
|
||||
from = `${path.resolve(__filename)}?test=${currentTestName}&${from}`
|
||||
|
||||
return postcss(tailwind(config)).process(input, { from })
|
||||
}
|
||||
@ -26,16 +28,14 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
it('re-uses the context across multiple files with the same config', async () => {
|
||||
let from = path.resolve(__filename)
|
||||
|
||||
let results = [
|
||||
await run(`@tailwind utilities;`, configPath, `${from}?id=1`),
|
||||
await run(`@tailwind utilities;`, configPath, `id=1`),
|
||||
|
||||
// Using @apply directives should still re-use the context
|
||||
// They depend on the config but do not the other way around
|
||||
await run(`body { @apply bg-blue-400; }`, configPath, `${from}?id=2`),
|
||||
await run(`body { @apply text-red-400; }`, configPath, `${from}?id=3`),
|
||||
await run(`body { @apply mb-4; }`, configPath, `${from}?id=4`),
|
||||
await run(`body { @apply bg-blue-400; }`, configPath, `id=2`),
|
||||
await run(`body { @apply text-red-400; }`, configPath, `id=3`),
|
||||
await run(`body { @apply mb-4; }`, configPath, `id=4`),
|
||||
]
|
||||
|
||||
let dependencies = results.map((result) => {
|
||||
@ -85,3 +85,43 @@ it('re-uses the context across multiple files with the same config', async () =>
|
||||
// And none of this should have resulted in multiple contexts being created
|
||||
expect(sharedState.contextSourcesMap.size).toBe(1)
|
||||
})
|
||||
|
||||
it('updates layers when any CSS containing @tailwind directives changes', async () => {
|
||||
let result
|
||||
|
||||
// Compile the initial version once
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
.custom-utility {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
result = await run(input, configPath, `id=1`)
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.only\:custom-utility:only-child {
|
||||
color: orange;
|
||||
}
|
||||
`)
|
||||
|
||||
// Save the file with a change
|
||||
input = css`
|
||||
@tailwind utilities;
|
||||
@layer utilities {
|
||||
.custom-utility {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
result = await run(input, configPath, `id=1`)
|
||||
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.only\:custom-utility:only-child {
|
||||
color: blue;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user