[Oxide] Add built-in @import processing support (#11239)

* run PostCSS relative to test file

The PostCSS `from` was always set to the `tests/util/run` file with the
current test name as a query param to make it unique. However, that
results in issues with relative file paths because they are now relative
to this file instead of the actual test file.

Luckily, `expect.getState()` exposes the `testPath` value that points to
the current test file itself.

* add built in `@import` processing

+ additional features such that you can use `@tailwind` and `@config`
  directives before the `@import` at rule which isn't normally allowed.
  However "@tailwind" in a way is an `@import` and `@config` in a way is
  a `@charset` which can be used before the `@import` at rule.

* drop partial normalizeTailwindDirectives when in the Oxide engine

This will now be handled by Tailwind from a higher level, so the core
doesn't have to worry about normalizing this anymore since it will
always be normalized right now.

* add `handleImportAtRules` to CLI

* update changelog

* scope `import processing` to the oxide engine

* update Changelog

* use direct comparison

We don't need the `startsWith` check anymore because the contents of the
comment will be the actual value we expect.
This commit is contained in:
Robin Malfait 2023-05-15 19:29:57 +02:00 • committed by GitHub
parent 0e171fd2dd
commit f49b054b7d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 170 additions and 63 deletions

View file

@ -17,9 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Add `aria-busy` utility ([#10966](https://github.com/tailwindlabs/tailwindcss/pull/10966))
- [Oxide] Use `lightningcss` for nesting and vendor prefixes in PostCSS plugin ([#10399](https://github.com/tailwindlabs/tailwindcss/pull/10399))
- Support `@import "tailwindcss"` using top-level `index.css` file ([#11205](https://github.com/tailwindlabs/tailwindcss/pull/11205))
- [Oxide] Use `lightningcss` for nesting and vendor prefixes in PostCSS plugin ([#10399](https://github.com/tailwindlabs/tailwindcss/pull/10399))
- [Oxide] Automatically detect content paths when no `content` configuration is provided ([#11173](https://github.com/tailwindlabs/tailwindcss/pull/11173), [#11221](https://github.com/tailwindlabs/tailwindcss/pull/11221))
- [Oxide] Process and inline `@import` at-rules natively ([#11239](https://github.com/tailwindlabs/tailwindcss/pull/11239))
### Changed

View file

@ -0,0 +1,33 @@
import postcss from 'postcss'
const TAILWIND = Symbol()
export function handleImportAtRules(postcssImport = require('postcss-import')) {
let RESTORE_ATRULE_COMMENT = '__TAILWIND_RESTORE__'
let atRulesToRestore = ['tailwind', 'config']
return [
(root) => {
root.walkAtRules((rule) => {
if (!atRulesToRestore.includes(rule.name)) return rule
rule.after(
postcss.comment({
text: RESTORE_ATRULE_COMMENT,
raws: { [TAILWIND]: { rule } },
})
)
rule.remove()
})
},
postcssImport(),
(root) => {
root.walkComments((rule) => {
if (rule.text === RESTORE_ATRULE_COMMENT) {
rule.after(rule.raws[TAILWIND].rule)
rule.remove()
}
})
},
]
}

View file

@ -10,30 +10,32 @@ export default function normalizeTailwindDirectives(root) {
applyDirectives.add(atRule)
}
if (atRule.name === 'import') {
if (atRule.params === '"tailwindcss/base"' || atRule.params === "'tailwindcss/base'") {
atRule.name = 'tailwind'
atRule.params = 'base'
} else if (
atRule.params === '"tailwindcss/components"' ||
atRule.params === "'tailwindcss/components'"
) {
atRule.name = 'tailwind'
atRule.params = 'components'
} else if (
atRule.params === '"tailwindcss/utilities"' ||
atRule.params === "'tailwindcss/utilities'"
) {
atRule.name = 'tailwind'
atRule.params = 'utilities'
} else if (
atRule.params === '"tailwindcss/screens"' ||
atRule.params === "'tailwindcss/screens'" ||
atRule.params === '"tailwindcss/variants"' ||
atRule.params === "'tailwindcss/variants'"
) {
atRule.name = 'tailwind'
atRule.params = 'variants'
if (!__OXIDE__) {
if (atRule.name === 'import') {
if (atRule.params === '"tailwindcss/base"' || atRule.params === "'tailwindcss/base'") {
atRule.name = 'tailwind'
atRule.params = 'base'
} else if (
atRule.params === '"tailwindcss/components"' ||
atRule.params === "'tailwindcss/components'"
) {
atRule.name = 'tailwind'
atRule.params = 'components'
} else if (
atRule.params === '"tailwindcss/utilities"' ||
atRule.params === "'tailwindcss/utilities'"
) {
atRule.name = 'tailwind'
atRule.params = 'utilities'
} else if (
atRule.params === '"tailwindcss/screens"' ||
atRule.params === "'tailwindcss/screens'" ||
atRule.params === '"tailwindcss/variants"' ||
atRule.params === "'tailwindcss/variants'"
) {
atRule.name = 'tailwind'
atRule.params = 'variants'
}
}
}

View file

@ -19,6 +19,7 @@ import { loadConfig } from '../../../lib/load-config'
import getModuleDependencies from '../../../lib/getModuleDependencies'
import type { Config } from '../../../../types'
import { validateConfig } from '../../../util/validateConfig'
import { handleImportAtRules } from '../../../lib/handleImportAtRules'
/**
*
@ -75,39 +76,6 @@ async function loadPostCssPlugins(customPostCssPath) {
return [beforePlugins, afterPlugins, config.options]
}
function loadBuiltinPostcssPlugins() {
let postcss = loadPostcss()
let IMPORT_COMMENT = '__TAILWIND_RESTORE_IMPORT__: '
return [
[
(root) => {
root.walkAtRules('import', (rule) => {
if (rule.params.slice(1).startsWith('tailwindcss/')) {
rule.after(postcss.comment({ text: IMPORT_COMMENT + rule.params }))
rule.remove()
}
})
},
loadPostcssImport(),
(root) => {
root.walkComments((rule) => {
if (rule.text.startsWith(IMPORT_COMMENT)) {
rule.after(
postcss.atRule({
name: 'import',
params: rule.text.replace(IMPORT_COMMENT, ''),
})
)
rule.remove()
}
})
},
],
[],
{},
]
}
let state = {
/** @type {any} */
context: null,
@ -266,7 +234,9 @@ export async function createProcessor(args, cliConfigPath) {
let [beforePlugins, afterPlugins, postcssOptions] = includePostCss
? await loadPostCssPlugins(customPostCssPath)
: loadBuiltinPostcssPlugins()
: [[], [], {}]
beforePlugins.unshift(...handleImportAtRules(loadPostcssImport()))
if (args['--purge']) {
log.warn('purge-flag-deprecated', [

View file

@ -2,6 +2,7 @@ import setupTrackingContext from './lib/setupTrackingContext'
import processTailwindFeatures from './processTailwindFeatures'
import { env } from './lib/sharedState'
import { findAtConfigPath } from './lib/findAtConfigPath'
import { handleImportAtRules } from './lib/handleImportAtRules'
module.exports = function tailwindcss(configOrPath) {
return {
@ -13,6 +14,7 @@ module.exports = function tailwindcss(configOrPath) {
console.time('JIT TOTAL')
return root
},
...(__OXIDE__ ? handleImportAtRules() : []),
function (root, result) {
// Use the path for the `@config` directive if it exists, otherwise use the
// path for the file being processed

View file

@ -0,0 +1 @@
@tailwind utilities;

View file

@ -0,0 +1,5 @@
@layer utilities {
.foo {
color: red;
}
}

View file

@ -0,0 +1,5 @@
@tailwind utilities;
.foo {
color: red;
}

View file

@ -0,0 +1,9 @@
module.exports = {
darkMode: 'class',
content: [
{
raw: `<div class="foo underline" />`,
},
],
corePlugins: { preflight: false },
}

View file

@ -0,0 +1,79 @@
import { html, css, run, crosscheck } from './util/run'
crosscheck(({ oxide, stable }) => {
stable.test.todo('Import processing is an oxide engine feature')
oxide.describe('import processing', () => {
it('should be possible to import another css file', async () => {
let config = {
darkMode: 'class',
content: [
{
raw: html`<div class="underline" />`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@import './import-processing-a.css';
`
let result = await run(input, config)
expect(result.css).toMatchFormattedCss(css`
.underline {
text-decoration-line: underline;
}
`)
})
it('should be possible to import another css file after @tailwind directive', async () => {
let config = {
darkMode: 'class',
content: [
{
raw: html`<div class="foo underline" />`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
@import './import-processing-b.css';
`
let result = await run(input, config)
expect(result.css).toMatchFormattedCss(css`
.underline {
text-decoration-line: underline;
}
.foo {
color: red;
}
`)
})
it('should be possible to add @config before @import statements', async () => {
let input = css`
@config "./import-processing-c.js";
@import './import-processing-c.css';
`
let result = await run(input)
expect(result.css).toMatchFormattedCss(css`
.underline {
text-decoration-line: underline;
}
.foo {
color: red;
}
`)
})
})
})

View file

@ -17,18 +17,18 @@ export let map = JSON.stringify({
globalThis.__OXIDE__ = env.ENGINE === 'oxide'
export function run(input, config, plugin = tailwind) {
let { currentTestName } = expect.getState()
let { currentTestName, testPath } = expect.getState()
return postcss(plugin(config)).process(input, {
from: `${path.resolve(__filename)}?test=${currentTestName}`,
from: `${path.resolve(testPath)}?test=${currentTestName}`,
})
}
export function runWithSourceMaps(input, config, plugin = tailwind) {
let { currentTestName } = expect.getState()
let { currentTestName, testPath } = expect.getState()
return postcss(plugin(config)).process(input, {
from: `${path.resolve(__filename)}?test=${currentTestName}`,
from: `${path.resolve(testPath)}?test=${currentTestName}`,
map: {
prev: map,
},