Skip to content

Commit

Permalink
Use Lightning CSS in the PostCSS Plugin (#10399)
Browse files Browse the repository at this point in the history
* bump lightningcss

* use `lightningcss` in the main PostCss Plugin

* use lightningcss in our custom matchers

Now that we are using `lightningcss` and nesting in the new `oxide`
engine, the generated output _will_ be different in the majority of test
cases.

Using a combination of `prettier` and `lightningcss` will make the
output consistent.

The moment we are fully using the `oxide` engine, we can drop
`lightningcss` or `prettier` again to improve the performance of the
tests.

* update tests to apply `lightningcss` related changes

* update changelog

* add `lightningcss` and `browserslist` as dev dependencies to stable package.json

* only use `lightningcss` in tests (without prettier)

We will only fallback to prettier if lightningcss fails somehow.

* apply side effect chagnes due to only using lightningcss for tests

* make CI happy (integration tests)

Apply changes to integration tests now that we are using lightningcss

* transform `lightningcss` for Node 12 when running tests

* run prettier on failing tests for `toMatchFormattedCss`

This will result in better diffs because diffs are typically per block
and/or per line. But lightningcss will simplify certain selectors and
the diff won't be as clear.

We will only apply the prettier formatting for failing tests in the diff
view so that diffs are cleaner and we don't pay for the additional
prettier calls when tests pass.
  • Loading branch information
RobinMalfait authored Jan 23, 2023
1 parent f821c71 commit 8e60a3c
Show file tree
Hide file tree
Showing 22 changed files with 650 additions and 415 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add standalone CLI build for 64-bit Windows on ARM (`node16-win-arm64`) ([#10001](https://github.com/tailwindlabs/tailwindcss/pull/10001))
- Add `delay-0` and `duration-0` by default ([#10294](https://github.com/tailwindlabs/tailwindcss/pull/10294))
- Add logical properties support for inline direction ([#10166](https://github.com/tailwindlabs/tailwindcss/pull/10166))
- Use `Lightning CSS` in the PostCSS Plugin ([#10399](https://github.com/tailwindlabs/tailwindcss/pull/10399))

### Fixed

Expand Down
6 changes: 2 additions & 4 deletions integrations/postcss-cli/tests/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,9 @@ describe('watcher', () => {
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
Expand Down
12 changes: 4 additions & 8 deletions integrations/rollup-sass/tests/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,9 @@ describe('watcher', () => {
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
Expand Down Expand Up @@ -307,11 +305,9 @@ describe('watcher', () => {
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
Expand Down
6 changes: 2 additions & 4 deletions integrations/rollup/tests/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,9 @@ describe('watcher', () => {
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
Expand Down
5 changes: 1 addition & 4 deletions integrations/tailwindcss-cli/tests/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,10 +472,7 @@ describe('watcher', () => {
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
Expand Down
6 changes: 2 additions & 4 deletions integrations/vite/tests/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,9 @@ describe('watcher', () => {
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
Expand Down
6 changes: 2 additions & 4 deletions integrations/webpack-4/tests/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,9 @@ describe('watcher', () => {
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
Expand Down
6 changes: 2 additions & 4 deletions integrations/webpack-5/tests/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,9 @@ describe('watcher', () => {
css`
.btn {
border-radius: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
Expand Down
204 changes: 97 additions & 107 deletions jest/customMatchers.js
Original file line number Diff line number Diff line change
@@ -1,85 +1,127 @@
const prettier = require('prettier')
const { diff } = require('jest-diff')
const lightningcss = require('lightningcss')

function format(input) {
function formatPrettier(input) {
return prettier.format(input, {
parser: 'css',
printWidth: 100,
})
}

expect.extend({
// Compare two CSS strings with all whitespace removed
// This is probably naive but it's fast and works well enough.
toMatchCss(received, argument) {
function stripped(str) {
return str.replace(/\s/g, '').replace(/;/g, '')
}

const options = {
comment: 'stripped(received) === stripped(argument)',
isNot: this.isNot,
promise: this.promise,
function format(input) {
try {
return lightningcss
.transform({
filename: 'input.css',
code: Buffer.from(input),
minify: false,
targets: { chrome: 106 << 16 },
drafts: {
nesting: true,
customMedia: true,
},
})
.code.toString('utf8')
} catch (err) {
try {
// Lightning CSS is pretty strict, so it will fail for `@media screen(md) {}` for example,
// in that case we can fallback to prettier since it doesn't really care. However if an
// actual syntax error is made, then we still want to show the proper error.
return formatPrettier(input.replace(/\n/g, ''))
} catch {
let lines = err.source.split('\n')
let e = new Error(
[
'Error formatting using Lightning CSS:',
'',
...[
'```css',
...lines.slice(Math.max(err.loc.line - 3, 0), err.loc.line),
' '.repeat(err.loc.column - 1) + '^-- ' + err.toString(),
...lines.slice(err.loc.line, err.loc.line + 2),
'```',
],
].join('\n')
)
if (Error.captureStackTrace) {
Error.captureStackTrace(e, toMatchFormattedCss)
}
throw e
}
}
}

const pass = stripped(received) === stripped(argument)

const message = pass
? () => {
return (
this.utils.matcherHint('toMatchCss', 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('toMatchCss', undefined, undefined, options) +
'\n\n' +
(diffString && diffString.includes('- Expect')
? `Difference:\n\n${diffString}`
: `Expected: ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(actual)}`)
)
}
function toMatchFormattedCss(received = '', argument = '') {
let options = {
comment: 'formatCSS(received) === formatCSS(argument)',
isNot: this.isNot,
promise: this.promise,
}

let formattedReceived = format(received)
let formattedArgument = format(argument)

let pass = formattedReceived === formattedArgument

let message = pass
? () => {
return (
this.utils.matcherHint('toMatchFormattedCss', undefined, undefined, options) +
'\n\n' +
`Expected: not ${this.utils.printExpected(formattedReceived)}\n` +
`Received: ${this.utils.printReceived(formattedArgument)}`
)
}
: () => {
let actual = formatPrettier(formattedReceived)
let expected = formatPrettier(formattedArgument)

let diffString = diff(expected, actual, {
expand: this.expand,
})

return (
this.utils.matcherHint('toMatchFormattedCss', 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 }
}

return { actual: received, message, pass }
},
expect.extend({
// Compare two CSS strings with all whitespace removed
// This is probably naive but it's fast and works well enough.
toMatchCss: toMatchFormattedCss,
toMatchFormattedCss: toMatchFormattedCss,
toIncludeCss(received, argument) {
function stripped(str) {
return str.replace('/* prettier-ignore */', '').replace(/\s/g, '').replace(/;/g, '')
}

const options = {
let options = {
comment: 'stripped(received).includes(stripped(argument))',
isNot: this.isNot,
promise: this.promise,
}

const pass = stripped(received).includes(stripped(argument))
let pass = format(received).includes(format(argument))

const message = pass
let 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))}`
`Expected: not ${this.utils.printExpected(formatPrettier(received))}\n` +
`Received: ${this.utils.printReceived(formatPrettier(argument))}`
)
}
: () => {
const actual = format(received)
const expected = format(argument)
let actual = formatPrettier(received)
let expected = formatPrettier(argument)

const diffString = diff(expected, actual, {
let diffString = diff(expected, actual, {
expand: this.expand,
})

Expand All @@ -96,55 +138,3 @@ expect.extend({
return { actual: received, message, pass }
},
})

expect.extend({
// Compare two CSS strings with all whitespace removed
// This is probably naive but it's fast and works well enough.
toMatchFormattedCss(received = '', argument = '') {
function format(input) {
return prettier.format(input.replace(/\n/g, ''), {
parser: 'css',
printWidth: 100,
})
}
const options = {
comment: 'stripped(received) === stripped(argument)',
isNot: this.isNot,
promise: this.promise,
}

let formattedReceived = format(received)
let formattedArgument = format(argument)

const pass = formattedReceived === formattedArgument

const message = pass
? () => {
return (
this.utils.matcherHint('toMatchCss', undefined, undefined, options) +
'\n\n' +
`Expected: not ${this.utils.printExpected(formattedReceived)}\n` +
`Received: ${this.utils.printReceived(formattedArgument)}`
)
}
: () => {
const actual = formattedReceived
const expected = formattedArgument

const diffString = diff(expected, actual, {
expand: this.expand,
})

return (
this.utils.matcherHint('toMatchCss', 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 }
},
})
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8e60a3c

Please sign in to comment.