diff --git a/CHANGELOG.md b/CHANGELOG.md index 69a7e2cb9..0b1c90fa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/lib/handleImportAtRules.js b/src/lib/handleImportAtRules.js new file mode 100644 index 000000000..57587d309 --- /dev/null +++ b/src/lib/handleImportAtRules.js @@ -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() + } + }) + }, + ] +} diff --git a/src/lib/normalizeTailwindDirectives.js b/src/lib/normalizeTailwindDirectives.js index 3349a7e3a..530ff35b3 100644 --- a/src/lib/normalizeTailwindDirectives.js +++ b/src/lib/normalizeTailwindDirectives.js @@ -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' + } } } diff --git a/src/oxide/cli/build/plugin.ts b/src/oxide/cli/build/plugin.ts index aa9bb5b99..f6193b534 100644 --- a/src/oxide/cli/build/plugin.ts +++ b/src/oxide/cli/build/plugin.ts @@ -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', [ diff --git a/src/plugin.js b/src/plugin.js index 59f5fab01..86f39d962 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -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 diff --git a/tests/import-processing-a.css b/tests/import-processing-a.css new file mode 100644 index 000000000..65dd5f63a --- /dev/null +++ b/tests/import-processing-a.css @@ -0,0 +1 @@ +@tailwind utilities; diff --git a/tests/import-processing-b.css b/tests/import-processing-b.css new file mode 100644 index 000000000..69d5390f1 --- /dev/null +++ b/tests/import-processing-b.css @@ -0,0 +1,5 @@ +@layer utilities { + .foo { + color: red; + } +} diff --git a/tests/import-processing-c.css b/tests/import-processing-c.css new file mode 100644 index 000000000..508c10fe2 --- /dev/null +++ b/tests/import-processing-c.css @@ -0,0 +1,5 @@ +@tailwind utilities; + +.foo { + color: red; +} diff --git a/tests/import-processing-c.js b/tests/import-processing-c.js new file mode 100644 index 000000000..164503d5e --- /dev/null +++ b/tests/import-processing-c.js @@ -0,0 +1,9 @@ +module.exports = { + darkMode: 'class', + content: [ + { + raw: `
`, + }, + ], + corePlugins: { preflight: false }, +} diff --git a/tests/import-processing.test.js b/tests/import-processing.test.js new file mode 100644 index 000000000..962b7b28c --- /dev/null +++ b/tests/import-processing.test.js @@ -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``, + }, + ], + 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``, + }, + ], + 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; + } + `) + }) + }) +}) diff --git a/tests/util/run.js b/tests/util/run.js index 118edcb75..964d5d5f5 100644 --- a/tests/util/run.js +++ b/tests/util/run.js @@ -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, },