diff --git a/.gitignore b/.gitignore index 885a6ecc14bc..059188cc133c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,11 +10,8 @@ index.html yarn.lock yarn-error.log -# "External" plugins -/nesting - # Perf related files isolate*.log # Generated files -/src/corePluginList.js \ No newline at end of file +/src/corePluginList.js diff --git a/dist/.gitignore b/dist/.gitignore deleted file mode 100644 index 2f41fac0ac11..000000000000 --- a/dist/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!.npmignore diff --git a/dist/.npmignore b/dist/.npmignore deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/integrations/parcel/package.json b/integrations/parcel/package.json index e77f59aeb9c6..1bd163406196 100644 --- a/integrations/parcel/package.json +++ b/integrations/parcel/package.json @@ -5,7 +5,7 @@ "scripts": { "build": "parcel build ./src/index.html --no-cache", "dev": "parcel watch ./src/index.html --no-cache", - "test": "jest" + "test": "jest --runInBand" }, "jest": { "displayName": "parcel", diff --git a/integrations/parcel/tailwind.config.js b/integrations/parcel/tailwind.config.js index 5ff7f8b6f24d..665802ac245f 100644 --- a/integrations/parcel/tailwind.config.js +++ b/integrations/parcel/tailwind.config.js @@ -1,13 +1,8 @@ module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: {}, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/parcel/tests/integration.test.js b/integrations/parcel/tests/integration.test.js index 1283289051ce..f61536df73e6 100644 --- a/integrations/parcel/tests/integration.test.js +++ b/integrations/parcel/tests/integration.test.js @@ -127,9 +127,7 @@ describe.skip('watcher', () => { '../tailwind.config.js', javascript` module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: { screens: { @@ -140,9 +138,6 @@ describe.skip('watcher', () => { } }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/postcss-cli/package.json b/integrations/postcss-cli/package.json index 931d9b745c97..bed103c0cf9b 100644 --- a/integrations/postcss-cli/package.json +++ b/integrations/postcss-cli/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "scripts": { "build": "NODE_ENV=production postcss ./src/index.css -o ./dist/main.css", - "test": "jest" + "test": "jest --runInBand" }, "jest": { "displayName": "PostCSS CLI", diff --git a/integrations/postcss-cli/tailwind.config.js b/integrations/postcss-cli/tailwind.config.js index 5ff7f8b6f24d..665802ac245f 100644 --- a/integrations/postcss-cli/tailwind.config.js +++ b/integrations/postcss-cli/tailwind.config.js @@ -1,13 +1,8 @@ module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: {}, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/postcss-cli/tests/integration.test.js b/integrations/postcss-cli/tests/integration.test.js index 867ee770c87e..15681e3e95c9 100644 --- a/integrations/postcss-cli/tests/integration.test.js +++ b/integrations/postcss-cli/tests/integration.test.js @@ -109,9 +109,7 @@ describe('watcher', () => { '../tailwind.config.js', javascript` module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: { screens: { @@ -122,9 +120,6 @@ describe('watcher', () => { } }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/rollup/package.json b/integrations/rollup/package.json index 9362f5759a0e..9cfbcd1ffba6 100644 --- a/integrations/rollup/package.json +++ b/integrations/rollup/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "scripts": { "build": "rollup -c", - "test": "jest" + "test": "jest --runInBand" }, "jest": { "displayName": "rollup.js", diff --git a/integrations/rollup/tailwind.config.js b/integrations/rollup/tailwind.config.js index 5ff7f8b6f24d..665802ac245f 100644 --- a/integrations/rollup/tailwind.config.js +++ b/integrations/rollup/tailwind.config.js @@ -1,13 +1,8 @@ module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: {}, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/rollup/tests/integration.test.js b/integrations/rollup/tests/integration.test.js index 45acc9935c82..ab17a5d5440d 100644 --- a/integrations/rollup/tests/integration.test.js +++ b/integrations/rollup/tests/integration.test.js @@ -105,9 +105,7 @@ describe.each([{ TAILWIND_MODE: 'watch' }, { TAILWIND_MODE: undefined }])('watch '../tailwind.config.js', javascript` module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: { screens: { @@ -118,9 +116,6 @@ describe.each([{ TAILWIND_MODE: 'watch' }, { TAILWIND_MODE: undefined }])('watch } }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/tailwindcss-cli/tailwind.config.js b/integrations/tailwindcss-cli/tailwind.config.js index 5ff7f8b6f24d..665802ac245f 100644 --- a/integrations/tailwindcss-cli/tailwind.config.js +++ b/integrations/tailwindcss-cli/tailwind.config.js @@ -1,13 +1,8 @@ module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: {}, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/tailwindcss-cli/tests/cli.test.js b/integrations/tailwindcss-cli/tests/cli.test.js index 2a9843a0290a..b73fc61e0f49 100644 --- a/integrations/tailwindcss-cli/tests/cli.test.js +++ b/integrations/tailwindcss-cli/tests/cli.test.js @@ -115,9 +115,7 @@ describe('Build command', () => { let customConfig = `module.exports = ${JSON.stringify( { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: { fontWeight: { @@ -125,9 +123,6 @@ describe('Build command', () => { }, }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, @@ -258,7 +253,7 @@ describe('Build command', () => { expect(combined).toMatchInlineSnapshot(` " - tailwindcss v2.2.8 + tailwindcss v2.2.9 Usage: tailwindcss build [options] @@ -267,8 +262,7 @@ describe('Build command', () => { -i, --input Input file -o, --output Output file -w, --watch Watch for changes and rebuild as needed - --jit Build using JIT mode - --purge Content paths to use for removing unused classes + --content Content paths to use for removing unused classes --postcss Load custom PostCSS configuration -m, --minify Minify the output -c, --config Path to a custom config file @@ -298,34 +292,6 @@ describe('Init command', () => { expect((await readOutputFile('../full.config.js')).split('\n').length).toBeGreaterThan(50) }) - test('--jit', async () => { - cleanupFile('with-jit.config.js') - - let { combined } = await $(`${EXECUTABLE} init with-jit.config.js --jit`) - - expect(combined).toMatchInlineSnapshot(` - " - Created Tailwind CSS config file: with-jit.config.js - " - `) - - expect(await readOutputFile('../with-jit.config.js')).toContain("mode: 'jit'") - }) - - test('--full, --jit', async () => { - cleanupFile('full-with-jit.config.js') - - let { combined } = await $(`${EXECUTABLE} init full-with-jit.config.js --jit --full`) - - expect(combined).toMatchInlineSnapshot(` - " - Created Tailwind CSS config file: full-with-jit.config.js - " - `) - - expect(await readOutputFile('../full-with-jit.config.js')).toContain("mode: 'jit'") - }) - test('--postcss', async () => { expect(await fileExists('postcss.config.js')).toBe(true) await removeFile('postcss.config.js') @@ -348,13 +314,12 @@ describe('Init command', () => { expect(combined).toMatchInlineSnapshot(` " - tailwindcss v2.2.8 + tailwindcss v2.2.9 Usage: tailwindcss init [options] Options: - --jit Initialize for JIT mode -f, --full Initialize a full \`tailwind.config.js\` file -p, --postcss Initialize a \`postcss.config.js\` file -h, --help Display usage information diff --git a/integrations/tailwindcss-cli/tests/integration.test.js b/integrations/tailwindcss-cli/tests/integration.test.js index 1ba8d0f07e8c..c080805796dc 100644 --- a/integrations/tailwindcss-cli/tests/integration.test.js +++ b/integrations/tailwindcss-cli/tests/integration.test.js @@ -32,19 +32,14 @@ describe('static build', () => { '../tailwind.config.js', javascript` module.exports = { - purge: { + content: { content: ['./src/index.html'], safelist: ['bg-red-500','bg-red-600'] }, - mode: 'jit', - darkMode: false, // or 'media' or 'class' theme: { extend: { }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, @@ -212,9 +207,7 @@ describe('watcher', () => { '../tailwind.config.js', javascript` module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: { screens: { @@ -225,9 +218,6 @@ describe('watcher', () => { } }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/vite/package.json b/integrations/vite/package.json index 4d29bbde9bed..9734a834cf26 100644 --- a/integrations/vite/package.json +++ b/integrations/vite/package.json @@ -6,7 +6,7 @@ "browser": "./src/index.js", "scripts": { "build": "vite build", - "test": "jest" + "test": "jest --runInBand" }, "jest": { "displayName": "vite", diff --git a/integrations/vite/tailwind.config.js b/integrations/vite/tailwind.config.js index e8961ac0e8df..b12350994882 100644 --- a/integrations/vite/tailwind.config.js +++ b/integrations/vite/tailwind.config.js @@ -1,13 +1,8 @@ module.exports = { - purge: ['./index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./index.html'], theme: { extend: {}, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/vite/tests/integration.test.js b/integrations/vite/tests/integration.test.js index e458a216cfe7..18ff59c791eb 100644 --- a/integrations/vite/tests/integration.test.js +++ b/integrations/vite/tests/integration.test.js @@ -132,9 +132,7 @@ describe('watcher', () => { 'tailwind.config.js', javascript` module.exports = { - purge: ['./index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./index.html'], theme: { extend: { screens: { @@ -145,9 +143,6 @@ describe('watcher', () => { } }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/webpack-4/package.json b/integrations/webpack-4/package.json index 13d5701b27dc..32d665c4f8d9 100644 --- a/integrations/webpack-4/package.json +++ b/integrations/webpack-4/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "webpack --mode=production", "dev": "webpack --mode=development --watch", - "test": "jest" + "test": "jest --runInBand" }, "jest": { "displayName": "webpack 4", diff --git a/integrations/webpack-4/tailwind.config.js b/integrations/webpack-4/tailwind.config.js index 5ff7f8b6f24d..665802ac245f 100644 --- a/integrations/webpack-4/tailwind.config.js +++ b/integrations/webpack-4/tailwind.config.js @@ -1,13 +1,8 @@ module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: {}, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/webpack-4/tests/integration.test.js b/integrations/webpack-4/tests/integration.test.js index c66b6291288e..ce1d5dba0991 100644 --- a/integrations/webpack-4/tests/integration.test.js +++ b/integrations/webpack-4/tests/integration.test.js @@ -103,9 +103,7 @@ describe.each([{ TAILWIND_MODE: 'watch' }, { TAILWIND_MODE: undefined }])('watch '../tailwind.config.js', javascript` module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: { screens: { @@ -116,9 +114,6 @@ describe.each([{ TAILWIND_MODE: 'watch' }, { TAILWIND_MODE: undefined }])('watch } }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/webpack-5/tailwind.config.js b/integrations/webpack-5/tailwind.config.js index 5ff7f8b6f24d..665802ac245f 100644 --- a/integrations/webpack-5/tailwind.config.js +++ b/integrations/webpack-5/tailwind.config.js @@ -1,13 +1,8 @@ module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: {}, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/integrations/webpack-5/tests/integration.test.js b/integrations/webpack-5/tests/integration.test.js index deaaa12d0904..6112b7bf08ad 100644 --- a/integrations/webpack-5/tests/integration.test.js +++ b/integrations/webpack-5/tests/integration.test.js @@ -103,9 +103,7 @@ describe.each([{ TAILWIND_MODE: 'watch' }, { TAILWIND_MODE: undefined }])('watch '../tailwind.config.js', javascript` module.exports = { - purge: ['./src/index.html'], - mode: 'jit', - darkMode: false, // or 'media' or 'class' + content: ['./src/index.html'], theme: { extend: { screens: { @@ -116,9 +114,6 @@ describe.each([{ TAILWIND_MODE: 'watch' }, { TAILWIND_MODE: undefined }])('watch } }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, @@ -234,19 +229,14 @@ describe.each([{ TAILWIND_MODE: 'watch' }, { TAILWIND_MODE: undefined }])('watch '../tailwind.config.js', javascript` module.exports = { - purge: { + content: { content: ['./src/index.html'], safelist: ['bg-red-500','bg-red-600'] }, - mode: 'jit', - darkMode: false, // or 'media' or 'class' theme: { extend: { }, }, - variants: { - extend: {}, - }, corePlugins: { preflight: false, }, diff --git a/plugins/nesting/README.md b/nesting/README.md similarity index 100% rename from plugins/nesting/README.md rename to nesting/README.md diff --git a/plugins/nesting/index.js b/nesting/index.js similarity index 100% rename from plugins/nesting/index.js rename to nesting/index.js diff --git a/plugins/nesting/plugin.js b/nesting/plugin.js similarity index 100% rename from plugins/nesting/plugin.js rename to nesting/plugin.js diff --git a/package-lock.json b/package-lock.json index 7ba848a3cfe4..30c8c7dcc644 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "license": "MIT", "dependencies": { "arg": "^5.0.1", - "bytes": "^3.0.0", "chalk": "^4.1.2", "chokidar": "^3.5.2", "color": "^4.0.1", @@ -18,14 +17,10 @@ "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.7", - "fs-extra": "^10.0.0", "glob-parent": "^6.0.1", - "html-tags": "^3.1.0", "is-glob": "^4.0.1", "lodash": "^4.17.21", - "lodash.topath": "^4.5.2", "modern-normalize": "^1.1.0", - "node-emoji": "^1.11.0", "normalize-path": "^3.0.0", "object-hash": "^2.2.0", "postcss-js": "^3.0.3", @@ -33,8 +28,6 @@ "postcss-nested": "5.0.6", "postcss-selector-parser": "^6.0.6", "postcss-value-parser": "^4.1.0", - "pretty-hrtime": "^1.0.3", - "purgecss": "^4.0.3", "quick-lru": "^5.1.1", "reduce-css-calc": "^2.1.8", "resolve": "^1.20.0", @@ -53,7 +46,6 @@ "@vercel/ncc": "^0.29.2", "autoprefixer": "^10.3.3", "babel-jest": "^27.0.6", - "clean-css": "5.1.4", "cross-env": "^7.0.3", "cssnano": "^5.0.8", "eslint": "^7.32.0", @@ -3090,14 +3082,6 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -3409,27 +3393,6 @@ "node": ">=0.10.0" } }, - "node_modules/clean-css": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.1.4.tgz", - "integrity": "sha512-e6JAuR0T2ahg7fOSv98Nxqh7mHWOac5TaCSgrr61h/6mkPLwlxX38hzob4h6IKj/UHlrrLXvAEjWqXlvi8r8lQ==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/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/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -5094,19 +5057,6 @@ "node": ">=0.10.0" } }, - "node_modules/fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -5297,7 +5247,8 @@ "node_modules/graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true }, "node_modules/has": { "version": "1.0.3", @@ -5411,14 +5362,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", - "engines": { - "node": ">=8" - } - }, "node_modules/http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -7016,6 +6959,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, "dependencies": { "universalify": "^2.0.0" }, @@ -7151,11 +7095,6 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, - "node_modules/lodash.topath": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", - "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=" - }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -7483,14 +7422,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dependencies": { - "lodash": "^4.17.21" - } - }, "node_modules/node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -8686,6 +8617,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true, "engines": { "node": ">= 0.8" } @@ -8734,28 +8666,6 @@ "node": ">=6" } }, - "node_modules/purgecss": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.0.3.tgz", - "integrity": "sha512-PYOIn5ibRIP34PBU9zohUcCI09c7drPJJtTDAc0Q6QlRz2/CHQ8ywGLdE7ZhxU2VTqB7p5wkvj5Qcm05Rz3Jmw==", - "dependencies": { - "commander": "^6.0.0", - "glob": "^7.0.0", - "postcss": "^8.2.1", - "postcss-selector-parser": "^6.0.2" - }, - "bin": { - "purgecss": "bin/purgecss.js" - } - }, - "node_modules/purgecss/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "engines": { - "node": ">= 6" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -10119,6 +10029,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, "engines": { "node": ">= 10.0.0" } @@ -12744,11 +12655,6 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -12988,23 +12894,6 @@ } } }, - "clean-css": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.1.4.tgz", - "integrity": "sha512-e6JAuR0T2ahg7fOSv98Nxqh7mHWOac5TaCSgrr61h/6mkPLwlxX38hzob4h6IKj/UHlrrLXvAEjWqXlvi8r8lQ==", - "dev": true, - "requires": { - "source-map": "~0.6.0" - }, - "dependencies": { - "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 - } - } - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -14272,16 +14161,6 @@ "map-cache": "^0.2.2" } }, - "fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -14416,7 +14295,8 @@ "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true }, "has": { "version": "1.0.3", @@ -14502,11 +14382,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "html-tags": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", - "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" - }, "http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -15725,6 +15600,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" @@ -15840,11 +15716,6 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, - "lodash.topath": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", - "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=" - }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -16108,14 +15979,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "requires": { - "lodash": "^4.17.21" - } - }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -16927,7 +16790,8 @@ "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true }, "process-nextick-args": { "version": "2.0.1", @@ -16964,24 +16828,6 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "purgecss": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.0.3.tgz", - "integrity": "sha512-PYOIn5ibRIP34PBU9zohUcCI09c7drPJJtTDAc0Q6QlRz2/CHQ8ywGLdE7ZhxU2VTqB7p5wkvj5Qcm05Rz3Jmw==", - "requires": { - "commander": "^6.0.0", - "glob": "^7.0.0", - "postcss": "^8.2.1", - "postcss-selector-parser": "^6.0.2" - }, - "dependencies": { - "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" - } - } - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -18060,7 +17906,8 @@ "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true }, "unset-value": { "version": "1.0.0", diff --git a/package.json b/package.json index fc96f88abe58..18f75ee9da94 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,12 @@ "babelify": "babel src --out-dir lib --copy-files", "postbabelify": "ncc build lib/cli-peer-dependencies.js -o peers", "rebuild-fixtures": "npm run babelify && babel-node scripts/rebuildFixtures.js", - "prepublishOnly": "npm install --force && npm run babelify && babel-node scripts/build.js && node scripts/build-plugins.js", + "prepublishOnly": "npm install --force && npm run babelify", "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", "generate:plugin-list": "babel-node scripts/create-plugin-list.js" }, "files": [ @@ -52,7 +50,6 @@ "@vercel/ncc": "^0.29.2", "autoprefixer": "^10.3.3", "babel-jest": "^27.0.6", - "clean-css": "5.1.4", "cross-env": "^7.0.3", "cssnano": "^5.0.8", "eslint": "^7.32.0", @@ -71,7 +68,6 @@ }, "dependencies": { "arg": "^5.0.1", - "bytes": "^3.0.0", "chalk": "^4.1.2", "chokidar": "^3.5.2", "color": "^4.0.1", @@ -80,14 +76,10 @@ "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.7", - "fs-extra": "^10.0.0", "glob-parent": "^6.0.1", - "html-tags": "^3.1.0", "is-glob": "^4.0.1", "lodash": "^4.17.21", - "lodash.topath": "^4.5.2", "modern-normalize": "^1.1.0", - "node-emoji": "^1.11.0", "normalize-path": "^3.0.0", "object-hash": "^2.2.0", "postcss-js": "^3.0.3", @@ -95,8 +87,6 @@ "postcss-nested": "5.0.6", "postcss-selector-parser": "^6.0.6", "postcss-value-parser": "^4.1.0", - "pretty-hrtime": "^1.0.3", - "purgecss": "^4.0.3", "quick-lru": "^5.1.1", "reduce-css-calc": "^2.1.8", "resolve": "^1.20.0", diff --git a/package.postcss7.json b/package.postcss7.json deleted file mode 100644 index e5ca69d2324f..000000000000 --- a/package.postcss7.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "devDependencies": { - "cssnano": "^4" - }, - "dependencies": { - "purgecss": "^4.0.3", - "autoprefixer": "^9", - "postcss": "^7", - "postcss-functions": "^3", - "postcss-js": "^2", - "postcss-nested": "^4" - } -} diff --git a/plugins/nesting/index.postcss7.js b/plugins/nesting/index.postcss7.js deleted file mode 100644 index 54f3056890b6..000000000000 --- a/plugins/nesting/index.postcss7.js +++ /dev/null @@ -1,4 +0,0 @@ -let postcss = require('postcss') -let nesting = require('./plugin') - -module.exports = postcss.plugin('tailwindcss/nesting', nesting) diff --git a/scripts/build-plugins.js b/scripts/build-plugins.js deleted file mode 100644 index 2ac5e281bfe0..000000000000 --- a/scripts/build-plugins.js +++ /dev/null @@ -1,47 +0,0 @@ -let fs = require('fs') -let path = require('path') - -let plugins = fs.readdirSync(fromRootPath('plugins')) - -for (let plugin of plugins) { - // Cleanup - let pluginDest = fromRootPath(plugin) - if (fs.existsSync(pluginDest)) { - fs.rmdirSync(pluginDest, { recursive: true }) - } - - // Copy plugin over - copyFolder(fromRootPath('plugins', plugin), pluginDest, (file) => { - // Ignore test files - if (file.endsWith('.test.js')) return false - // Ignore postcss7 files - if (file.endsWith('.postcss7.js')) return false - // Ignore postcss8 files - if (file.endsWith('.postcss8.js')) return false - - return true - }) -} - -// --- - -function fromRootPath(...paths) { - return path.resolve(process.cwd(), ...paths) -} - -function copy(fromPath, toPath) { - fs.mkdirSync(path.dirname(toPath), { recursive: true }) // Ensure folder exists - fs.copyFileSync(fromPath, toPath) -} - -function copyFolder(fromPath, toPath, shouldCopy = () => true) { - let stats = fs.statSync(fromPath) - if (stats.isDirectory()) { - let filesAndFolders = fs.readdirSync(fromPath) - for (let file of filesAndFolders) { - copyFolder(path.resolve(fromPath, file), path.resolve(toPath, file), shouldCopy) - } - } else if (shouldCopy(fromPath)) { - copy(fromPath, toPath) - } -} diff --git a/scripts/build.js b/scripts/build.js deleted file mode 100644 index 1d6f27eb0098..000000000000 --- a/scripts/build.js +++ /dev/null @@ -1,46 +0,0 @@ -import fs from 'fs' -import postcss from 'postcss' -import tailwind from '..' -import CleanCSS from 'clean-css' - -function buildDistFile(filename, config = {}, outFilename = filename) { - return new Promise((resolve, reject) => { - console.log(`Processing ./${filename}.css...`) - - fs.readFile(`./${filename}.css`, (err, css) => { - if (err) throw err - - return postcss([tailwind(config), require('autoprefixer')]) - .process(css, { - from: `./${filename}.css`, - to: `./dist/${outFilename}.css`, - }) - .then((result) => { - fs.writeFileSync(`./dist/${outFilename}.css`, result.css) - return result - }) - .then((result) => { - const minified = new CleanCSS().minify(result.css) - fs.writeFileSync(`./dist/${outFilename}.min.css`, minified.styles) - }) - .then(resolve) - .catch((error) => { - console.log(error) - reject() - }) - }) - }) -} - -console.info('Building Tailwind!') - -Promise.all([ - buildDistFile('base'), - buildDistFile('components'), - buildDistFile('utilities'), - buildDistFile('tailwind'), - buildDistFile('tailwind', { darkMode: 'class' }, 'tailwind-dark'), - buildDistFile('tailwind', { future: 'all', experimental: 'all' }, 'tailwind-experimental'), -]).then(() => { - console.log('Finished Building Tailwind!') -}) diff --git a/scripts/compat.js b/scripts/compat.js deleted file mode 100644 index aef701a07fc8..000000000000 --- a/scripts/compat.js +++ /dev/null @@ -1,85 +0,0 @@ -let fs = require('fs') -let path = require('path') -let merge = require('lodash/merge') -let fastGlob = require('fast-glob') - -let postcss7 = fastGlob.sync(['./**/*.postcss7.*']).filter((file) => !file.startsWith('lib/')) -let postcss8 = fastGlob.sync(['./**/*.postcss8.*']).filter((file) => !file.startsWith('lib/')) - -if (process.argv.includes('--prepare')) { - if (postcss8.length > 0) { - console.error('\n\n[ABORT] Already in PostCSS 7 compatibility mode!\n\n') - process.exit(1) - } - - let mainPackageJson = require('../package.json') - let compatPackageJson = require('../package.postcss7.json') - - // Use postcss7 files - for (let file of postcss7) { - let bareFile = file.replace('.postcss7', '') - let postcss8File = file.replace('.postcss7', '.postcss8') - - // Backup - copy(fromRootPath(bareFile), fromRootPath(postcss8File)) - - // Swap - copy(fromRootPath(file), fromRootPath(bareFile)) - } - - // Deep merge package.json contents - let packageJson = merge({}, mainPackageJson, compatPackageJson) - - // Remove peerDependencies - delete packageJson.peerDependencies - - // Cleanup devDependencies - for (let key in packageJson.devDependencies) { - if (key.includes('postcss')) delete packageJson.devDependencies[key] - } - - // Use new name - packageJson.name = '@tailwindcss/postcss7-compat' - - // Make sure you can publish - packageJson.publishConfig = { access: 'public' } - - // Write package.json with the new contents - fs.writeFileSync(fromRootPath('package.json'), JSON.stringify(packageJson, null, 2), 'utf8') - - // Print some useful information to make publishing easy - console.log() - console.log('You can safely publish `tailwindcss` in PostCSS 7 compatibility mode:\n') - console.log() -} else if (process.argv.includes('--restore')) { - if (postcss8.length === 0) { - console.error('\n\n[ABORT] Already in latest PostCSS mode!\n\n') - process.exit(1) - } - - // Use postcss8 files - for (let file of postcss8) { - let bareFile = file.replace('.postcss8', '') - - // Restore - copy(fromRootPath(file), fromRootPath(bareFile)) - - // Remove - fs.unlinkSync(fromRootPath(file)) - } - - // Done - console.log() - console.log('Restored from PostCSS 7 mode to latest PostCSS mode!') - console.log() -} - -// --- - -function fromRootPath(...paths) { - return path.resolve(process.cwd(), ...paths) -} - -function copy(fromPath, toPath) { - fs.copyFileSync(fromPath, toPath) -} diff --git a/src/cli.js b/src/cli.js index 9a79ce3f96e2..b3a37cd0662e 100644 --- a/src/cli.js +++ b/src/cli.js @@ -9,11 +9,11 @@ import fs from 'fs' import postcssrc from 'postcss-load-config' import { cosmiconfig } from 'cosmiconfig' import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API -import tailwindJit from './jit/processTailwindFeatures' -import tailwindAot from './processTailwindFeatures' +import tailwind from './processTailwindFeatures' import resolveConfigInternal from '../resolveConfig' import fastGlob from 'fast-glob' import getModuleDependencies from './lib/getModuleDependencies' +import log from './util/log' import packageJson from '../package.json' let env = { @@ -86,7 +86,9 @@ function help({ message, usage, commands, options }) { console.log() console.log('Options:') - for (let { flags, description } of Object.values(groupedOptions)) { + for (let { flags, description, deprecated } of Object.values(groupedOptions)) { + if (deprecated) continue + if (flags.length === 1) { console.log( ' '.repeat(indent + 4 /* 4 = "-i, ".length */), @@ -126,7 +128,6 @@ let commands = { init: { run: init, args: { - '--jit': { type: Boolean, description: 'Initialize for JIT mode' }, '--full': { type: Boolean, description: 'Initialize a full `tailwind.config.js` file' }, '--postcss': { type: Boolean, description: 'Initialize a `postcss.config.js` file' }, '-f': '--full', @@ -139,8 +140,14 @@ let commands = { '--input': { type: String, description: 'Input file' }, '--output': { type: String, description: 'Output file' }, '--watch': { type: Boolean, description: 'Watch for changes and rebuild as needed' }, - '--jit': { type: Boolean, description: 'Build using JIT mode' }, - '--purge': { type: String, description: 'Content paths to use for removing unused classes' }, + '--content': { + type: String, + description: 'Content paths to use for removing unused classes', + }, + '--purge': { + type: String, + deprecated: true, + }, '--postcss': { type: oneOf(String, Boolean), description: 'Load custom PostCSS configuration', @@ -306,15 +313,6 @@ function init() { // Change colors import stubFile = stubFile.replace('../colors', 'tailwindcss/colors') - // --jit mode - if (args['--jit']) { - // Add jit mode - stubFile = stubFile.replace('module.exports = {', "module.exports = {\n mode: 'jit',") - - // Deleting variants - stubFile = stubFile.replace(/variants: {(.*)},\n /gs, '') - } - fs.writeFileSync(tailwindConfigLocation, stubFile, 'utf8') messages.push(`Created Tailwind CSS config file: ${path.basename(tailwindConfigLocation)}`) @@ -420,39 +418,21 @@ async function build() { let resolvedConfig = resolveConfigInternal(config) if (args['--purge']) { - resolvedConfig.purge = { - enabled: true, - content: args['--purge'].split(/(? { - if (typeof content === 'string') { - return { raw: content, extension: 'html' } - } - - if (content instanceof RegExp) { - throw new Error( - "Values inside 'purge.safelist' can only be of type 'string', found 'regex'." - ) - } - - throw new Error( - `Values inside 'purge.safelist' can only be of type 'string', found '${typeof content}'.` - ) - }) - ) + return config.content.content.concat(config.content.safelist) } function extractFileGlobs(config) { @@ -473,7 +453,7 @@ async function build() { function getChangedContent(config) { let changedContent = [] - // Resolve globs from the purge config + // Resolve globs from the content config let globs = extractFileGlobs(config) let files = fastGlob.sync(globs) @@ -496,26 +476,18 @@ async function build() { let config = resolveConfig() let changedContent = getChangedContent(config) - let tailwindPlugin = - config.mode === 'jit' - ? () => { - return { - postcssPlugin: 'tailwindcss', - Once(root, { result }) { - tailwindJit(({ createContext }) => { - return () => { - return createContext(config, changedContent) - } - })(root, result) - }, + let tailwindPlugin = () => { + return { + postcssPlugin: 'tailwindcss', + Once(root, { result }) { + tailwind(({ createContext }) => { + return () => { + return createContext(config, changedContent) } - } - : () => { - return { - postcssPlugin: 'tailwindcss', - plugins: [tailwindAot(() => config, configPath)], - } - } + })(root, result) + }, + } + } tailwindPlugin.postcss = true @@ -637,39 +609,31 @@ async function build() { async function rebuild(config) { env.DEBUG && console.time('Finished in') - let tailwindPlugin = - config.mode === 'jit' - ? () => { - return { - postcssPlugin: 'tailwindcss', - Once(root, { result }) { - env.DEBUG && console.time('Compiling CSS') - tailwindJit(({ createContext }) => { - console.error() - console.error('Rebuilding...') - - return () => { - if (context !== null) { - context.changedContent = changedContent.splice(0) - return context - } - - env.DEBUG && console.time('Creating context') - context = createContext(config, changedContent.splice(0)) - env.DEBUG && console.timeEnd('Creating context') - return context - } - })(root, result) - env.DEBUG && console.timeEnd('Compiling CSS') - }, - } - } - : () => { - return { - postcssPlugin: 'tailwindcss', - plugins: [tailwindAot(() => config, configPath)], + let tailwindPlugin = () => { + return { + postcssPlugin: 'tailwindcss', + Once(root, { result }) { + env.DEBUG && console.time('Compiling CSS') + tailwind(({ createContext }) => { + console.error() + console.error('Rebuilding...') + + return () => { + if (context !== null) { + context.changedContent = changedContent.splice(0) + return context + } + + env.DEBUG && console.time('Creating context') + context = createContext(config, changedContent.splice(0)) + env.DEBUG && console.timeEnd('Creating context') + return context } - } + })(root, result) + env.DEBUG && console.timeEnd('Compiling CSS') + }, + } + } tailwindPlugin.postcss = true diff --git a/src/corePlugins.js b/src/corePlugins.js index 3821dfc7302a..997a44dba092 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -1,57 +1,335 @@ -import * as plugins from './plugins/index.js' -import configurePlugins from './util/configurePlugins' +import postcss from 'postcss' +import * as corePlugins from './plugins' +import buildMediaQuery from './util/buildMediaQuery' +import prefixSelector from './util/prefixSelector' +import { + applyPseudoToMarker, + updateLastClasses, + updateAllClasses, + transformAllSelectors, + transformAllClasses, + transformLastClasses, +} from './util/pluginUtils' +import log from './util/log' -function move(items, item, befores) { - let lowestBefore = -1 +export default { + pseudoElementVariants: function ({ config, addVariant }) { + addVariant( + 'first-letter', + transformAllSelectors((selector) => { + return updateAllClasses(selector, (className, { withPseudo }) => { + return withPseudo(`first-letter${config('separator')}${className}`, '::first-letter') + }) + }) + ) - for (let before of befores) { - let index = items.indexOf(before) - if (index >= 0 && (index < lowestBefore || lowestBefore === -1)) { - lowestBefore = index + addVariant( + 'first-line', + transformAllSelectors((selector) => { + return updateAllClasses(selector, (className, { withPseudo }) => { + return withPseudo(`first-line${config('separator')}${className}`, '::first-line') + }) + }) + ) + + addVariant('marker', [ + transformAllSelectors((selector) => { + let variantSelector = updateAllClasses(selector, (className) => { + return `marker${config('separator')}${className}` + }) + + return `${variantSelector} *::marker` + }), + transformAllSelectors((selector) => { + return updateAllClasses(selector, (className, { withPseudo }) => { + return withPseudo(`marker${config('separator')}${className}`, '::marker') + }) + }), + ]) + + addVariant('selection', [ + transformAllSelectors((selector) => { + let variantSelector = updateAllClasses(selector, (className) => { + return `selection${config('separator')}${className}` + }) + + return `${variantSelector} *::selection` + }), + transformAllSelectors((selector) => { + return updateAllClasses(selector, (className, { withPseudo }) => { + return withPseudo(`selection${config('separator')}${className}`, '::selection') + }) + }), + ]) + + addVariant( + 'before', + transformAllSelectors( + (selector) => { + return updateAllClasses(selector, (className, { withPseudo }) => { + return withPseudo(`before${config('separator')}${className}`, '::before') + }) + }, + { + withRule: (rule) => { + let foundContent = false + rule.walkDecls('content', () => { + foundContent = true + }) + if (!foundContent) { + rule.prepend(postcss.decl({ prop: 'content', value: '""' })) + } + }, + } + ) + ) + + addVariant( + 'after', + transformAllSelectors( + (selector) => { + return updateAllClasses(selector, (className, { withPseudo }) => { + return withPseudo(`after${config('separator')}${className}`, '::after') + }) + }, + { + withRule: (rule) => { + let foundContent = false + rule.walkDecls('content', () => { + foundContent = true + }) + if (!foundContent) { + rule.prepend(postcss.decl({ prop: 'content', value: '""' })) + } + }, + } + ) + ) + }, + pseudoClassVariants: function ({ config, addVariant }) { + let pseudoVariants = [ + // Positional + ['first', 'first-child'], + ['last', 'last-child'], + ['only', 'only-child'], + ['odd', 'nth-child(odd)'], + ['even', 'nth-child(even)'], + 'first-of-type', + 'last-of-type', + 'only-of-type', + + // State + 'visited', + 'target', + + // Forms + 'default', + 'checked', + 'indeterminate', + 'placeholder-shown', + 'autofill', + 'required', + 'valid', + 'invalid', + 'in-range', + 'out-of-range', + 'read-only', + + // Content + 'empty', + + // Interactive + 'focus-within', + 'hover', + 'focus', + 'focus-visible', + 'active', + 'disabled', + ] + + for (let variant of pseudoVariants) { + let [variantName, state] = Array.isArray(variant) ? variant : [variant, variant] + + addVariant( + variantName, + transformAllClasses((className, { withPseudo }) => { + return withPseudo(`${variantName}${config('separator')}${className}`, `:${state}`) + }) + ) } - } - - if (items.indexOf(item) === -1 || lowestBefore === -1) { - return items - } - - items = [...items] - let fromIndex = items.indexOf(item) - let toIndex = lowestBefore - items.splice(fromIndex, 1) - items.splice(toIndex, 0, item) - return items -} -export default function ({ corePlugins: corePluginConfig }) { - let pluginOrder = Object.keys(plugins) - - pluginOrder = configurePlugins(corePluginConfig, pluginOrder) - pluginOrder = move(pluginOrder, 'transform', ['translate', 'rotate', 'skew', 'scale']) - pluginOrder = move(pluginOrder, 'filter', [ - 'blur', - 'brightness', - 'contrast', - 'dropShadow', - 'grayscale', - 'hueRotate', - 'invert', - 'saturate', - 'sepia', - ]) - pluginOrder = move(pluginOrder, 'backdropFilter', [ - 'backdropBlur', - 'backdropBrightness', - 'backdropContrast', - 'backdropGrayscale', - 'backdropHueRotate', - 'backdropInvert', - 'backdropOpacity', - 'backdropSaturate', - 'backdropSepia', - ]) - - return pluginOrder.map((pluginName) => { - return plugins[pluginName]() - }) + let groupMarker = prefixSelector(config('prefix'), '.group') + for (let variant of pseudoVariants) { + let [variantName, state] = Array.isArray(variant) ? variant : [variant, variant] + let groupVariantName = `group-${variantName}` + + addVariant( + groupVariantName, + transformAllSelectors((selector) => { + let variantSelector = updateAllClasses(selector, (className) => { + if (`.${className}` === groupMarker) return className + return `${groupVariantName}${config('separator')}${className}` + }) + + if (variantSelector === selector) { + return null + } + + return applyPseudoToMarker( + variantSelector, + groupMarker, + state, + (marker, selector) => `${marker} ${selector}` + ) + }) + ) + } + + let peerMarker = prefixSelector(config('prefix'), '.peer') + for (let variant of pseudoVariants) { + let [variantName, state] = Array.isArray(variant) ? variant : [variant, variant] + let peerVariantName = `peer-${variantName}` + + addVariant( + peerVariantName, + transformAllSelectors((selector) => { + let variantSelector = updateAllClasses(selector, (className) => { + if (`.${className}` === peerMarker) return className + return `${peerVariantName}${config('separator')}${className}` + }) + + if (variantSelector === selector) { + return null + } + + return applyPseudoToMarker(variantSelector, peerMarker, state, (marker, selector) => + selector.trim().startsWith('~') ? `${marker}${selector}` : `${marker} ~ ${selector}` + ) + }) + ) + } + }, + directionVariants: function ({ config, addVariant }) { + addVariant( + 'ltr', + transformAllSelectors( + (selector) => + `[dir="ltr"] ${updateAllClasses( + selector, + (className) => `ltr${config('separator')}${className}` + )}` + ) + ) + + addVariant( + 'rtl', + transformAllSelectors( + (selector) => + `[dir="rtl"] ${updateAllClasses( + selector, + (className) => `rtl${config('separator')}${className}` + )}` + ) + ) + }, + reducedMotionVariants: function ({ config, addVariant }) { + addVariant( + 'motion-safe', + transformLastClasses( + (className) => { + return `motion-safe${config('separator')}${className}` + }, + { + wrap: () => + postcss.atRule({ + name: 'media', + params: '(prefers-reduced-motion: no-preference)', + }), + } + ) + ) + + addVariant( + 'motion-reduce', + transformLastClasses( + (className) => { + return `motion-reduce${config('separator')}${className}` + }, + { + wrap: () => + postcss.atRule({ + name: 'media', + params: '(prefers-reduced-motion: reduce)', + }), + } + ) + ) + }, + darkVariants: function ({ config, addVariant }) { + let mode = config('darkMode', 'media') + if (mode === false) { + mode = 'media' + log.warn([ + '`darkMode` is set to `false` in your config.', + 'This will behave just like the `media` value.', + ]) + } + + if (mode === 'class') { + addVariant( + 'dark', + transformAllSelectors((selector) => { + let variantSelector = updateLastClasses(selector, (className) => { + return `dark${config('separator')}${className}` + }) + + if (variantSelector === selector) { + return null + } + + let darkSelector = prefixSelector(config('prefix'), `.dark`) + + return `${darkSelector} ${variantSelector}` + }) + ) + } else if (mode === 'media') { + addVariant( + 'dark', + transformLastClasses( + (className) => { + return `dark${config('separator')}${className}` + }, + { + wrap: () => + postcss.atRule({ + name: 'media', + params: '(prefers-color-scheme: dark)', + }), + } + ) + ) + } + }, + screenVariants: function ({ config, theme, addVariant }) { + for (let screen in theme('screens')) { + let size = theme('screens')[screen] + let query = buildMediaQuery(size) + + addVariant( + screen, + transformLastClasses( + (className) => { + return `${screen}${config('separator')}${className}` + }, + { wrap: () => postcss.atRule({ name: 'media', params: query }) } + ) + ) + } + }, + + ...Object.fromEntries( + Object.entries(corePlugins).map(([pluginName, plugin]) => { + return [pluginName, plugin()] + }) + ), } diff --git a/src/index.js b/src/index.js index 856bd3cbdfd3..08a709a6577b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,98 +1,33 @@ -import path from 'path' -import fs from 'fs' - -import _ from 'lodash' - -import getModuleDependencies from './lib/getModuleDependencies' -import registerConfigAsDependency from './lib/registerConfigAsDependency' +import setupTrackingContext from './lib/setupTrackingContext' +import setupWatchingContext from './lib/setupWatchingContext' import processTailwindFeatures from './processTailwindFeatures' -import formatCSS from './lib/formatCSS' -import resolveConfig from './util/resolveConfig' -import getAllConfigs from './util/getAllConfigs' -import { supportedConfigFiles } from './constants' -import defaultConfig from '../stubs/defaultConfig.stub.js' - -import jitPlugins from './jit' - -function resolveConfigPath(filePath) { - // require('tailwindcss')({ theme: ..., variants: ... }) - if (_.isObject(filePath) && !_.has(filePath, 'config') && !_.isEmpty(filePath)) { - return undefined - } - - // require('tailwindcss')({ config: 'custom-config.js' }) - if (_.isObject(filePath) && _.has(filePath, 'config') && _.isString(filePath.config)) { - return path.resolve(filePath.config) - } - - // require('tailwindcss')({ config: { theme: ..., variants: ... } }) - if (_.isObject(filePath) && _.has(filePath, 'config') && _.isObject(filePath.config)) { - return undefined - } - - // require('tailwindcss')('custom-config.js') - if (_.isString(filePath)) { - return path.resolve(filePath) - } - - // require('tailwindcss') - for (const configFile of supportedConfigFiles) { - try { - const configPath = path.resolve(configFile) - fs.accessSync(configPath) - return configPath - } catch (err) {} - } - - return undefined -} - -const getConfigFunction = (config) => () => { - if (_.isUndefined(config)) { - return resolveConfig([ - ...getAllConfigs(defaultConfig), - { corePlugins: { caretColor: false, content: false } }, - ]) - } - - // Skip this if Jest is running: https://github.com/facebook/jest/pull/9841#issuecomment-621417584 - if (process.env.JEST_WORKER_ID === undefined) { - if (!_.isObject(config)) { - getModuleDependencies(config).forEach((mdl) => { - delete require.cache[require.resolve(mdl.file)] - }) - } - } - - const configObject = _.isObject(config) ? _.get(config, 'config', config) : require(config) - - return resolveConfig([ - ...getAllConfigs(configObject), - { corePlugins: { caretColor: false, content: false } }, - ]) -} - -module.exports = function tailwindcss(config) { - const resolvedConfigPath = resolveConfigPath(config) - const getConfig = getConfigFunction(resolvedConfigPath || config) - const mode = _.get(getConfig(), 'mode', 'aot') - - if (mode === 'jit') { - return { - postcssPlugin: 'tailwindcss', - plugins: jitPlugins(config), - } - } - - const plugins = [] - - if (!_.isUndefined(resolvedConfigPath)) { - plugins.push(registerConfigAsDependency(resolvedConfigPath)) - } +import { env } from './lib/sharedState' +module.exports = function tailwindcss(configOrPath) { return { postcssPlugin: 'tailwindcss', - plugins: [...plugins, processTailwindFeatures(getConfig), formatCSS], + plugins: [ + env.DEBUG && + function (root) { + console.log('\n') + console.time('JIT TOTAL') + return root + }, + function (root, result) { + let setupContext = + env.TAILWIND_MODE === 'watch' + ? setupWatchingContext(configOrPath) + : setupTrackingContext(configOrPath) + + processTailwindFeatures(setupContext)(root, result) + }, + env.DEBUG && + function (root) { + console.timeEnd('JIT TOTAL') + console.log('\n') + return root + }, + ].filter(Boolean), } } diff --git a/src/index.postcss7.js b/src/index.postcss7.js deleted file mode 100644 index aa33f1bffcdf..000000000000 --- a/src/index.postcss7.js +++ /dev/null @@ -1,87 +0,0 @@ -import path from 'path' -import fs from 'fs' - -import _ from 'lodash' -import postcss from 'postcss' - -import getModuleDependencies from './lib/getModuleDependencies' -import registerConfigAsDependency from './lib/registerConfigAsDependency' -import processTailwindFeatures from './processTailwindFeatures' -import formatCSS from './lib/formatCSS' -import resolveConfig from './util/resolveConfig' -import getAllConfigs from './util/getAllConfigs' -import { supportedConfigFiles } from './constants' -import defaultConfig from '../stubs/defaultConfig.stub.js' - -import jitPlugins from './jit' - -function resolveConfigPath(filePath) { - // require('tailwindcss')({ theme: ..., variants: ... }) - if (_.isObject(filePath) && !_.has(filePath, 'config') && !_.isEmpty(filePath)) { - return undefined - } - - // require('tailwindcss')({ config: 'custom-config.js' }) - if (_.isObject(filePath) && _.has(filePath, 'config') && _.isString(filePath.config)) { - return path.resolve(filePath.config) - } - - // require('tailwindcss')({ config: { theme: ..., variants: ... } }) - if (_.isObject(filePath) && _.has(filePath, 'config') && _.isObject(filePath.config)) { - return undefined - } - - // require('tailwindcss')('custom-config.js') - if (_.isString(filePath)) { - return path.resolve(filePath) - } - - // require('tailwindcss') - for (const configFile of supportedConfigFiles) { - try { - const configPath = path.resolve(configFile) - fs.accessSync(configPath) - return configPath - } catch (err) {} - } - - return undefined -} - -const getConfigFunction = (config) => () => { - if (_.isUndefined(config)) { - return resolveConfig([...getAllConfigs(defaultConfig)]) - } - - // Skip this if Jest is running: https://github.com/facebook/jest/pull/9841#issuecomment-621417584 - if (process.env.JEST_WORKER_ID === undefined) { - if (!_.isObject(config)) { - getModuleDependencies(config).forEach((mdl) => { - delete require.cache[require.resolve(mdl.file)] - }) - } - } - - const configObject = _.isObject(config) ? _.get(config, 'config', config) : require(config) - - return resolveConfig([...getAllConfigs(configObject)]) -} - -const plugin = postcss.plugin('tailwindcss', (config) => { - const resolvedConfigPath = resolveConfigPath(config) - const getConfig = getConfigFunction(resolvedConfigPath || config) - const mode = _.get(getConfig(), 'mode', 'aot') - - if (mode === 'jit') { - return postcss(jitPlugins(config)) - } - - const plugins = [] - if (!_.isUndefined(resolvedConfigPath)) { - plugins.push(registerConfigAsDependency(resolvedConfigPath)) - } - - return postcss([...plugins, processTailwindFeatures(getConfig), formatCSS]) -}) - -module.exports = plugin diff --git a/src/jit/corePlugins.js b/src/jit/corePlugins.js deleted file mode 100644 index 054d57f48561..000000000000 --- a/src/jit/corePlugins.js +++ /dev/null @@ -1,325 +0,0 @@ -import postcss from 'postcss' -import * as corePlugins from '../plugins' -import buildMediaQuery from '../util/buildMediaQuery' -import prefixSelector from '../util/prefixSelector' -import { - applyPseudoToMarker, - updateLastClasses, - updateAllClasses, - transformAllSelectors, - transformAllClasses, - transformLastClasses, -} from '../util/pluginUtils' - -export default { - pseudoElementVariants: function ({ config, addVariant }) { - addVariant( - 'first-letter', - transformAllSelectors((selector) => { - return updateAllClasses(selector, (className, { withPseudo }) => { - return withPseudo(`first-letter${config('separator')}${className}`, '::first-letter') - }) - }) - ) - - addVariant( - 'first-line', - transformAllSelectors((selector) => { - return updateAllClasses(selector, (className, { withPseudo }) => { - return withPseudo(`first-line${config('separator')}${className}`, '::first-line') - }) - }) - ) - - addVariant('marker', [ - transformAllSelectors((selector) => { - let variantSelector = updateAllClasses(selector, (className) => { - return `marker${config('separator')}${className}` - }) - - return `${variantSelector} *::marker` - }), - transformAllSelectors((selector) => { - return updateAllClasses(selector, (className, { withPseudo }) => { - return withPseudo(`marker${config('separator')}${className}`, '::marker') - }) - }), - ]) - - addVariant('selection', [ - transformAllSelectors((selector) => { - let variantSelector = updateAllClasses(selector, (className) => { - return `selection${config('separator')}${className}` - }) - - return `${variantSelector} *::selection` - }), - transformAllSelectors((selector) => { - return updateAllClasses(selector, (className, { withPseudo }) => { - return withPseudo(`selection${config('separator')}${className}`, '::selection') - }) - }), - ]) - - addVariant( - 'before', - transformAllSelectors( - (selector) => { - return updateAllClasses(selector, (className, { withPseudo }) => { - return withPseudo(`before${config('separator')}${className}`, '::before') - }) - }, - { - withRule: (rule) => { - let foundContent = false - rule.walkDecls('content', () => { - foundContent = true - }) - if (!foundContent) { - rule.prepend(postcss.decl({ prop: 'content', value: '""' })) - } - }, - } - ) - ) - - addVariant( - 'after', - transformAllSelectors( - (selector) => { - return updateAllClasses(selector, (className, { withPseudo }) => { - return withPseudo(`after${config('separator')}${className}`, '::after') - }) - }, - { - withRule: (rule) => { - let foundContent = false - rule.walkDecls('content', () => { - foundContent = true - }) - if (!foundContent) { - rule.prepend(postcss.decl({ prop: 'content', value: '""' })) - } - }, - } - ) - ) - }, - pseudoClassVariants: function ({ config, addVariant }) { - let pseudoVariants = [ - // Positional - ['first', 'first-child'], - ['last', 'last-child'], - ['only', 'only-child'], - ['odd', 'nth-child(odd)'], - ['even', 'nth-child(even)'], - 'first-of-type', - 'last-of-type', - 'only-of-type', - - // State - 'visited', - 'target', - - // Forms - 'default', - 'checked', - 'indeterminate', - 'placeholder-shown', - 'autofill', - 'required', - 'valid', - 'invalid', - 'in-range', - 'out-of-range', - 'read-only', - - // Content - 'empty', - - // Interactive - 'focus-within', - 'hover', - 'focus', - 'focus-visible', - 'active', - 'disabled', - ] - - for (let variant of pseudoVariants) { - let [variantName, state] = Array.isArray(variant) ? variant : [variant, variant] - - addVariant( - variantName, - transformAllClasses((className, { withPseudo }) => { - return withPseudo(`${variantName}${config('separator')}${className}`, `:${state}`) - }) - ) - } - - let groupMarker = prefixSelector(config('prefix'), '.group') - for (let variant of pseudoVariants) { - let [variantName, state] = Array.isArray(variant) ? variant : [variant, variant] - let groupVariantName = `group-${variantName}` - - addVariant( - groupVariantName, - transformAllSelectors((selector) => { - let variantSelector = updateAllClasses(selector, (className) => { - if (`.${className}` === groupMarker) return className - return `${groupVariantName}${config('separator')}${className}` - }) - - if (variantSelector === selector) { - return null - } - - return applyPseudoToMarker( - variantSelector, - groupMarker, - state, - (marker, selector) => `${marker} ${selector}` - ) - }) - ) - } - - let peerMarker = prefixSelector(config('prefix'), '.peer') - for (let variant of pseudoVariants) { - let [variantName, state] = Array.isArray(variant) ? variant : [variant, variant] - let peerVariantName = `peer-${variantName}` - - addVariant( - peerVariantName, - transformAllSelectors((selector) => { - let variantSelector = updateAllClasses(selector, (className) => { - if (`.${className}` === peerMarker) return className - return `${peerVariantName}${config('separator')}${className}` - }) - - if (variantSelector === selector) { - return null - } - - return applyPseudoToMarker(variantSelector, peerMarker, state, (marker, selector) => - selector.trim().startsWith('~') ? `${marker}${selector}` : `${marker} ~ ${selector}` - ) - }) - ) - } - }, - directionVariants: function ({ config, addVariant }) { - addVariant( - 'ltr', - transformAllSelectors( - (selector) => - `[dir="ltr"] ${updateAllClasses( - selector, - (className) => `ltr${config('separator')}${className}` - )}` - ) - ) - - addVariant( - 'rtl', - transformAllSelectors( - (selector) => - `[dir="rtl"] ${updateAllClasses( - selector, - (className) => `rtl${config('separator')}${className}` - )}` - ) - ) - }, - reducedMotionVariants: function ({ config, addVariant }) { - addVariant( - 'motion-safe', - transformLastClasses( - (className) => { - return `motion-safe${config('separator')}${className}` - }, - { - wrap: () => - postcss.atRule({ - name: 'media', - params: '(prefers-reduced-motion: no-preference)', - }), - } - ) - ) - - addVariant( - 'motion-reduce', - transformLastClasses( - (className) => { - return `motion-reduce${config('separator')}${className}` - }, - { - wrap: () => - postcss.atRule({ - name: 'media', - params: '(prefers-reduced-motion: reduce)', - }), - } - ) - ) - }, - darkVariants: function ({ config, addVariant }) { - if (config('darkMode') === 'class') { - addVariant( - 'dark', - transformAllSelectors((selector) => { - let variantSelector = updateLastClasses(selector, (className) => { - return `dark${config('separator')}${className}` - }) - - if (variantSelector === selector) { - return null - } - - let darkSelector = prefixSelector(config('prefix'), `.dark`) - - return `${darkSelector} ${variantSelector}` - }) - ) - } else if (config('darkMode') === 'media') { - addVariant( - 'dark', - transformLastClasses( - (className) => { - return `dark${config('separator')}${className}` - }, - { - wrap: () => - postcss.atRule({ - name: 'media', - params: '(prefers-color-scheme: dark)', - }), - } - ) - ) - } - }, - screenVariants: function ({ config, theme, addVariant }) { - for (let screen in theme('screens')) { - let size = theme('screens')[screen] - let query = buildMediaQuery(size) - - addVariant( - screen, - transformLastClasses( - (className) => { - return `${screen}${config('separator')}${className}` - }, - { wrap: () => postcss.atRule({ name: 'media', params: query }) } - ) - ) - } - }, - - ...Object.fromEntries( - Object.entries(corePlugins).map(([pluginName, plugin]) => { - return [pluginName, plugin()] - }) - ), -} diff --git a/src/jit/index.js b/src/jit/index.js deleted file mode 100644 index 1f531846fe95..000000000000 --- a/src/jit/index.js +++ /dev/null @@ -1,29 +0,0 @@ -import setupTrackingContext from './lib/setupTrackingContext' -import setupWatchingContext from './lib/setupWatchingContext' -import { env } from './lib/sharedState' -import processTailwindFeatures from './processTailwindFeatures' - -export default function (configOrPath = {}) { - return [ - env.DEBUG && - function (root) { - console.log('\n') - console.time('JIT TOTAL') - return root - }, - function (root, result) { - let setupContext = - env.TAILWIND_MODE === 'watch' - ? setupWatchingContext(configOrPath) - : setupTrackingContext(configOrPath) - - processTailwindFeatures(setupContext)(root, result) - }, - env.DEBUG && - function (root) { - console.timeEnd('JIT TOTAL') - console.log('\n') - return root - }, - ].filter(Boolean) -} diff --git a/src/jit/processTailwindFeatures.js b/src/jit/processTailwindFeatures.js deleted file mode 100644 index d89c1a7ca634..000000000000 --- a/src/jit/processTailwindFeatures.js +++ /dev/null @@ -1,52 +0,0 @@ -import normalizeTailwindDirectives from './lib/normalizeTailwindDirectives' -import expandTailwindAtRules from './lib/expandTailwindAtRules' -import expandApplyAtRules from './lib/expandApplyAtRules' -import evaluateTailwindFunctions from '../lib/evaluateTailwindFunctions' -import substituteScreenAtRules from '../lib/substituteScreenAtRules' -import resolveDefaultsAtRules from './lib/resolveDefaultsAtRules' -import collapseAdjacentRules from './lib/collapseAdjacentRules' -import { createContext } from './lib/setupContextUtils' -import log from '../util/log' - -let warned = false - -export default function processTailwindFeatures(setupContext) { - return function (root, result) { - if (!warned) { - log.warn([ - `You have enabled the JIT engine which is currently in preview.`, - 'Preview features are not covered by semver, may introduce breaking changes, and can change at any time.', - ]) - warned = true - } - - let tailwindDirectives = normalizeTailwindDirectives(root) - - let context = setupContext({ - tailwindDirectives, - registerDependency(dependency) { - result.messages.push({ - plugin: 'tailwindcss', - parent: result.opts.from, - ...dependency, - }) - }, - createContext(tailwindConfig, changedContent) { - return createContext(tailwindConfig, changedContent, tailwindDirectives, root) - }, - })(root, result) - - if (context.tailwindConfig.separator === '-') { - throw new Error( - "The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead." - ) - } - - expandTailwindAtRules(context)(root, result) - expandApplyAtRules(context)(root, result) - evaluateTailwindFunctions(context)(root, result) - substituteScreenAtRules(context)(root, result) - resolveDefaultsAtRules(context)(root, result) - collapseAdjacentRules(context)(root, result) - } -} diff --git a/src/lib/applyImportantConfiguration.js b/src/lib/applyImportantConfiguration.js deleted file mode 100644 index 5c35759ac09f..000000000000 --- a/src/lib/applyImportantConfiguration.js +++ /dev/null @@ -1,19 +0,0 @@ -export default function applyImportantConfiguration(_config) { - return function (css) { - css.walkRules((rule) => { - const important = rule.__tailwind ? rule.__tailwind.important : false - - if (!important) { - return - } - - if (typeof important === 'string') { - rule.selectors = rule.selectors.map((selector) => { - return `${rule.__tailwind.important} ${selector}` - }) - } else { - rule.walkDecls((decl) => (decl.important = true)) - } - }) - } -} diff --git a/src/jit/lib/collapseAdjacentRules.js b/src/lib/collapseAdjacentRules.js similarity index 100% rename from src/jit/lib/collapseAdjacentRules.js rename to src/lib/collapseAdjacentRules.js diff --git a/src/lib/convertLayerAtRulesToControlComments.js b/src/lib/convertLayerAtRulesToControlComments.js deleted file mode 100644 index 264127256be3..000000000000 --- a/src/lib/convertLayerAtRulesToControlComments.js +++ /dev/null @@ -1,18 +0,0 @@ -import postcss from 'postcss' - -export default function convertLayerAtRulesToControlComments() { - return function (css) { - css.walkAtRules('layer', (atRule) => { - const layer = atRule.params - - if (!['base', 'components', 'utilities'].includes(layer)) { - return - } - - atRule.before(postcss.comment({ text: `tailwind start ${layer}` })) - atRule.before(atRule.nodes) - atRule.before(postcss.comment({ text: `tailwind end ${layer}` })) - atRule.remove() - }) - } -} diff --git a/src/jit/lib/expandApplyAtRules.js b/src/lib/expandApplyAtRules.js similarity index 98% rename from src/jit/lib/expandApplyAtRules.js rename to src/lib/expandApplyAtRules.js index 6e61495007c2..056057c17f79 100644 --- a/src/jit/lib/expandApplyAtRules.js +++ b/src/lib/expandApplyAtRules.js @@ -1,7 +1,7 @@ import postcss from 'postcss' import { resolveMatches } from './generateRules' -import bigSign from '../../util/bigSign' -import escapeClassName from '../../util/escapeClassName' +import bigSign from '../util/bigSign' +import escapeClassName from '../util/escapeClassName' function buildApplyCache(applyCandidates, context) { for (let candidate of applyCandidates) { diff --git a/src/jit/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js similarity index 92% rename from src/jit/lib/expandTailwindAtRules.js rename to src/lib/expandTailwindAtRules.js index 3473866d9616..747ae1458a0e 100644 --- a/src/jit/lib/expandTailwindAtRules.js +++ b/src/lib/expandTailwindAtRules.js @@ -1,7 +1,7 @@ import * as sharedState from './sharedState' import { generateRules } from './generateRules' -import bigSign from '../../util/bigSign' -import cloneNodes from '../../util/cloneNodes' +import bigSign from '../util/bigSign' +import cloneNodes from '../util/cloneNodes' let env = sharedState.env let contentMatchCache = sharedState.contentMatchCache @@ -30,19 +30,18 @@ const builtInTransformers = { } function getExtractor(tailwindConfig, fileExtension) { - let extractors = (tailwindConfig && tailwindConfig.purge && tailwindConfig.purge.extract) || {} - const purgeOptions = - (tailwindConfig && tailwindConfig.purge && tailwindConfig.purge.options) || {} + let extractors = tailwindConfig.content.extract + let contentOptions = tailwindConfig.content.options if (typeof extractors === 'function') { extractors = { DEFAULT: extractors, } } - if (purgeOptions.defaultExtractor) { - extractors.DEFAULT = purgeOptions.defaultExtractor + if (contentOptions.defaultExtractor) { + extractors.DEFAULT = contentOptions.defaultExtractor } - for (let { extensions, extractor } of purgeOptions.extractors || []) { + for (let { extensions, extractor } of contentOptions.extractors || []) { for (let extension of extensions) { extractors[extension] = extractor } @@ -57,8 +56,7 @@ function getExtractor(tailwindConfig, fileExtension) { } function getTransformer(tailwindConfig, fileExtension) { - let transformers = - (tailwindConfig && tailwindConfig.purge && tailwindConfig.purge.transform) || {} + let transformers = tailwindConfig.content.transform if (typeof transformers === 'function') { transformers = { diff --git a/src/lib/formatCSS.js b/src/lib/formatCSS.js deleted file mode 100644 index 75adde499427..000000000000 --- a/src/lib/formatCSS.js +++ /dev/null @@ -1,17 +0,0 @@ -function indentRecursive(node, indent = 0) { - node.each && - node.each((child, i) => { - if (!child.raws.before || child.raws.before.includes('\n')) { - child.raws.before = `\n${node.type !== 'rule' && i > 0 ? '\n' : ''}${' '.repeat(indent)}` - } - child.raws.after = `\n${' '.repeat(indent)}` - indentRecursive(child, indent + 1) - }) -} - -export default function formatNodes(root) { - indentRecursive(root) - if (root.first) { - root.first.raws.before = '' - } -} diff --git a/src/jit/lib/generateRules.js b/src/lib/generateRules.js similarity index 97% rename from src/jit/lib/generateRules.js rename to src/lib/generateRules.js index 10b5f64df96c..53107b966dac 100644 --- a/src/jit/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -1,9 +1,9 @@ import postcss from 'postcss' import selectorParser from 'postcss-selector-parser' -import parseObjectStyles from '../../util/parseObjectStyles' -import isPlainObject from '../../util/isPlainObject' -import prefixSelector from '../../util/prefixSelector' -import { updateAllClasses } from '../../util/pluginUtils' +import parseObjectStyles from '../util/parseObjectStyles' +import isPlainObject from '../util/isPlainObject' +import prefixSelector from '../util/prefixSelector' +import { updateAllClasses } from '../util/pluginUtils' let classNameParser = selectorParser((selectors) => { return selectors.first.filter(({ type }) => type === 'class').pop().value diff --git a/src/jit/lib/normalizeTailwindDirectives.js b/src/lib/normalizeTailwindDirectives.js similarity index 90% rename from src/jit/lib/normalizeTailwindDirectives.js rename to src/lib/normalizeTailwindDirectives.js index 64a471bea868..03d289f41fb2 100644 --- a/src/jit/lib/normalizeTailwindDirectives.js +++ b/src/lib/normalizeTailwindDirectives.js @@ -1,3 +1,5 @@ +import log from '../util/log' + export default function normalizeTailwindDirectives(root) { let tailwindDirectives = new Set() let layerDirectives = new Set() @@ -38,6 +40,11 @@ export default function normalizeTailwindDirectives(root) { } if (['layer', 'responsive', 'variants'].includes(atRule.name)) { + if (['responsive', 'variants'].includes(atRule.name)) { + log.warn([ + `'@${atRule.name}' is deprecated, use '@layer utilities' or '@layer components' instead.`, + ]) + } layerDirectives.add(atRule) } }) diff --git a/src/lib/purgeUnusedStyles.js b/src/lib/purgeUnusedStyles.js deleted file mode 100644 index 23b77d197de5..000000000000 --- a/src/lib/purgeUnusedStyles.js +++ /dev/null @@ -1,223 +0,0 @@ -import _ from 'lodash' -import postcss from 'postcss' -import PurgeCSS, { defaultOptions, standardizeSafelist, mergeExtractorSelectors } from 'purgecss' -import log from '../util/log' -import htmlTags from 'html-tags' -import path from 'path' -import parseDependency from '../util/parseDependency' -import normalizePath from 'normalize-path' - -function removeTailwindMarkers(css) { - css.walkAtRules('tailwind', (rule) => rule.remove()) - css.walkComments((comment) => { - switch (comment.text.trim()) { - case 'tailwind start base': - case 'tailwind end base': - case 'tailwind start components': - case 'tailwind start utilities': - case 'tailwind end components': - case 'tailwind end utilities': - comment.remove() - break - default: - break - } - }) -} - -export function tailwindExtractor(content) { - // Capture as liberally as possible, including things like `h-(screen-1.5)` - const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [] - const broadMatchesWithoutTrailingSlash = broadMatches.map((match) => _.trimEnd(match, '\\')) - - // Capture classes within other delimiters like .block(class="w-1/2") in Pug - const innerMatches = content.match(/[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g) || [] - - return broadMatches.concat(broadMatchesWithoutTrailingSlash).concat(innerMatches) -} - -function getTransformer(config, fileExtension) { - let transformers = (config.purge && config.purge.transform) || {} - - if (typeof transformers === 'function') { - transformers = { - DEFAULT: transformers, - } - } - - return transformers[fileExtension] || transformers.DEFAULT || ((content) => content) -} - -export default function purgeUnusedUtilities(config, configChanged, registerDependency) { - const purgeEnabled = _.get( - config, - 'purge.enabled', - config.purge !== false && config.purge !== undefined && process.env.NODE_ENV === 'production' - ) - - if (!purgeEnabled) { - return removeTailwindMarkers - } - - // Skip if `purge: []` since that's part of the default config - if (Array.isArray(config.purge) && config.purge.length === 0) { - if (configChanged) { - log.warn([ - 'Tailwind is not purging unused styles because no template paths have been provided.', - 'If you have manually configured PurgeCSS outside of Tailwind or are deliberately not removing unused styles, set `purge: false` in your Tailwind config file to silence this warning.', - 'https://tailwindcss.com/docs/controlling-file-size/#removing-unused-css', - ]) - } - - return removeTailwindMarkers - } - - const extractors = config.purge.extract || {} - const transformers = config.purge.transform || {} - let { defaultExtractor: originalDefaultExtractor, ...purgeOptions } = config.purge.options || {} - - if (config.purge?.safelist && !purgeOptions.hasOwnProperty('safelist')) { - purgeOptions.safelist = config.purge.safelist - } - - if (!originalDefaultExtractor) { - originalDefaultExtractor = - typeof extractors === 'function' ? extractors : extractors.DEFAULT || tailwindExtractor - } - - const defaultExtractor = (content) => { - const preserved = originalDefaultExtractor(content) - - if (_.get(config, 'purge.preserveHtmlElements', true)) { - preserved.push(...htmlTags) - } - - return preserved - } - - // If `extractors` is a function then we don't have any file-specific extractors, - // only a default one. - let fileSpecificExtractors = typeof extractors === 'function' ? {} : extractors - - // PurgeCSS doesn't support "transformers," so we implement those using extractors. - // If we have a custom transformer for an extension, but not a matching extractor, - // then we need to create an extractor that we can augment later. - if (typeof transformers !== 'function') { - for (let [extension] of Object.entries(transformers)) { - if (!fileSpecificExtractors[extension]) { - fileSpecificExtractors[extension] = defaultExtractor - } - } - } - - // Augment file-specific extractors by running the transformer before we extract classes. - fileSpecificExtractors = Object.entries(fileSpecificExtractors).map(([extension, extractor]) => { - return { - extensions: [extension], - extractor: (content) => { - const transformer = getTransformer(config, extension) - return extractor(transformer(content)) - }, - } - }) - - let content = ( - Array.isArray(config.purge) ? config.purge : config.purge.content || purgeOptions.content || [] - ).map((item) => { - if (typeof item === 'string') { - return normalizePath(path.resolve(item)) - } - return item - }) - - for (let fileOrGlob of content.filter((item) => typeof item === 'string')) { - registerDependency(parseDependency(fileOrGlob)) - } - - let hasLayers = false - - const mode = _.get(config, 'purge.mode', 'layers') - return postcss([ - function (css) { - if (!['all', 'layers'].includes(mode)) { - throw new Error('Purge `mode` must be one of `layers` or `all`.') - } - - if (mode === 'all') { - return - } - - const layers = _.get(config, 'purge.layers', ['base', 'components', 'utilities']) - - css.walkComments((comment) => { - switch (comment.text.trim()) { - case `purgecss start ignore`: - comment.before(postcss.comment({ text: 'purgecss end ignore' })) - break - case `purgecss end ignore`: - comment.before(postcss.comment({ text: 'purgecss end ignore' })) - comment.text = 'purgecss start ignore' - break - default: - break - } - layers.forEach((layer) => { - switch (comment.text.trim()) { - case `tailwind start ${layer}`: - comment.text = 'purgecss end ignore' - hasLayers = true - break - case `tailwind end ${layer}`: - comment.text = 'purgecss start ignore' - break - default: - break - } - }) - }) - - css.prepend(postcss.comment({ text: 'purgecss start ignore' })) - css.append(postcss.comment({ text: 'purgecss end ignore' })) - }, - removeTailwindMarkers, - - async function (css) { - if (mode === 'layers' && !hasLayers) { - return - } - const purgeCSS = new PurgeCSS() - purgeCSS.options = { - ...defaultOptions, - - defaultExtractor: (content) => { - const transformer = getTransformer(config) - return defaultExtractor(transformer(content)) - }, - extractors: fileSpecificExtractors, - ...purgeOptions, - safelist: standardizeSafelist(purgeOptions.safelist), - } - - if (purgeCSS.options.variables) { - purgeCSS.variablesStructure.safelist = purgeCSS.options.safelist.variables || [] - } - - const fileFormatContents = content.filter((o) => typeof o === 'string') - const rawFormatContents = content.filter((o) => typeof o === 'object') - - const cssFileSelectors = await purgeCSS.extractSelectorsFromFiles( - fileFormatContents, - purgeCSS.options.extractors - ) - const cssRawSelectors = await purgeCSS.extractSelectorsFromString( - rawFormatContents, - purgeCSS.options.extractors - ) - const cssSelectors = mergeExtractorSelectors(cssFileSelectors, cssRawSelectors) - purgeCSS.walkThroughCSS(css, cssSelectors) - if (purgeCSS.options.fontFace) purgeCSS.removeUnusedFontFaces() - if (purgeCSS.options.keyframes) purgeCSS.removeUnusedKeyframes() - if (purgeCSS.options.variables) purgeCSS.removeUnusedCSSVariables() - }, - ]) -} diff --git a/src/lib/registerConfigAsDependency.js b/src/lib/registerConfigAsDependency.js deleted file mode 100644 index 7cff584844a9..000000000000 --- a/src/lib/registerConfigAsDependency.js +++ /dev/null @@ -1,18 +0,0 @@ -import fs from 'fs' -import getModuleDependencies from './getModuleDependencies' - -export default function (configFile) { - if (!fs.existsSync(configFile)) { - throw new Error(`Specified Tailwind config file "${configFile}" doesn't exist.`) - } - - return function (css, opts) { - getModuleDependencies(configFile).forEach((mdl) => { - opts.messages.push({ - type: 'dependency', - parent: css.source.input.file, - file: mdl.file, - }) - }) - } -} diff --git a/src/jit/lib/resolveDefaultsAtRules.js b/src/lib/resolveDefaultsAtRules.js similarity index 100% rename from src/jit/lib/resolveDefaultsAtRules.js rename to src/lib/resolveDefaultsAtRules.js diff --git a/src/jit/lib/setupContextUtils.js b/src/lib/setupContextUtils.js similarity index 97% rename from src/jit/lib/setupContextUtils.js rename to src/lib/setupContextUtils.js index 95eb08c9dbd9..9c6b33253626 100644 --- a/src/jit/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -4,14 +4,14 @@ import postcss from 'postcss' import dlv from 'dlv' import selectorParser from 'postcss-selector-parser' -import transformThemeValue from '../../util/transformThemeValue' -import parseObjectStyles from '../../util/parseObjectStyles' -import prefixSelector from '../../util/prefixSelector' -import isPlainObject from '../../util/isPlainObject' -import escapeClassName from '../../util/escapeClassName' -import nameClass from '../../util/nameClass' -import { coerceValue } from '../../util/pluginUtils' -import bigSign from '../../util/bigSign' +import transformThemeValue from '../util/transformThemeValue' +import parseObjectStyles from '../util/parseObjectStyles' +import prefixSelector from '../util/prefixSelector' +import isPlainObject from '../util/isPlainObject' +import escapeClassName from '../util/escapeClassName' +import nameClass from '../util/nameClass' +import { coerceValue } from '../util/pluginUtils' +import bigSign from '../util/bigSign' import corePlugins from '../corePlugins' import * as sharedState from './sharedState' import { env } from './sharedState' diff --git a/src/jit/lib/setupTrackingContext.js b/src/lib/setupTrackingContext.js similarity index 83% rename from src/jit/lib/setupTrackingContext.js rename to src/lib/setupTrackingContext.js index c38f9aefe601..f0ea6ab2616d 100644 --- a/src/jit/lib/setupTrackingContext.js +++ b/src/lib/setupTrackingContext.js @@ -5,17 +5,17 @@ import fastGlob from 'fast-glob' import LRU from 'quick-lru' import normalizePath from 'normalize-path' -import hash from '../../util/hashConfig' -import getModuleDependencies from '../../lib/getModuleDependencies' +import hash from '../util/hashConfig' +import getModuleDependencies from '../lib/getModuleDependencies' -import resolveConfig from '../../../resolveConfig' +import resolveConfig from '../../resolveConfig' -import resolveConfigPath from '../../util/resolveConfigPath' +import resolveConfigPath from '../util/resolveConfigPath' import { env } from './sharedState' import { getContext, getFileModifiedMap } from './setupContextUtils' -import parseDependency from '../../util/parseDependency' +import parseDependency from '../util/parseDependency' let configPathCache = new LRU({ maxSize: 100 }) @@ -26,13 +26,9 @@ function getCandidateFiles(context, tailwindConfig) { return candidateFilesCache.get(context) } - let purgeContent = Array.isArray(tailwindConfig.purge) - ? tailwindConfig.purge - : tailwindConfig.purge.content - - let candidateFiles = purgeContent + let candidateFiles = tailwindConfig.content.content .filter((item) => typeof item === 'string') - .map((purgePath) => normalizePath(path.resolve(purgePath))) + .map((contentPath) => normalizePath(path.resolve(contentPath))) return candidateFilesCache.set(context, candidateFiles).get(context) } @@ -81,29 +77,9 @@ function getTailwindConfig(configOrPath) { } function resolvedChangedContent(context, candidateFiles, fileModifiedMap) { - let changedContent = ( - Array.isArray(context.tailwindConfig.purge) - ? context.tailwindConfig.purge - : context.tailwindConfig.purge.content - ) + let changedContent = context.tailwindConfig.content.content .filter((item) => typeof item.raw === 'string') - .concat( - (context.tailwindConfig.purge?.safelist ?? []).map((content) => { - if (typeof content === 'string') { - return { raw: content, extension: 'html' } - } - - if (content instanceof RegExp) { - throw new Error( - "Values inside 'purge.safelist' can only be of type 'string', found 'regex'." - ) - } - - throw new Error( - `Values inside 'purge.safelist' can only be of type 'string', found '${typeof content}'.` - ) - }) - ) + .concat(context.tailwindConfig.content.safelist) .map(({ raw, extension }) => ({ content: raw, extension })) for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)) { diff --git a/src/jit/lib/setupWatchingContext.js b/src/lib/setupWatchingContext.js similarity index 88% rename from src/jit/lib/setupWatchingContext.js rename to src/lib/setupWatchingContext.js index edbf5fba5e6f..4e9d772b8557 100644 --- a/src/jit/lib/setupWatchingContext.js +++ b/src/lib/setupWatchingContext.js @@ -7,11 +7,11 @@ import fastGlob from 'fast-glob' import LRU from 'quick-lru' import normalizePath from 'normalize-path' -import hash from '../../util/hashConfig' -import log from '../../util/log' -import getModuleDependencies from '../../lib/getModuleDependencies' -import resolveConfig from '../../../resolveConfig' -import resolveConfigPath from '../../util/resolveConfigPath' +import hash from '../util/hashConfig' +import log from '../util/log' +import getModuleDependencies from '../lib/getModuleDependencies' +import resolveConfig from '../../resolveConfig' +import resolveConfigPath from '../util/resolveConfigPath' import { getContext } from './setupContextUtils' // This is used to trigger rebuilds. Just updating the timestamp @@ -147,13 +147,9 @@ function getCandidateFiles(context, tailwindConfig) { return candidateFilesCache.get(context) } - let purgeContent = Array.isArray(tailwindConfig.purge) - ? tailwindConfig.purge - : tailwindConfig.purge.content - - let candidateFiles = purgeContent + let candidateFiles = tailwindConfig.content.content .filter((item) => typeof item === 'string') - .map((purgePath) => normalizePath(path.resolve(purgePath))) + .map((contentPath) => normalizePath(path.resolve(contentPath))) return candidateFilesCache.set(context, candidateFiles).get(context) } @@ -189,29 +185,9 @@ function getTailwindConfig(configOrPath) { } function resolvedChangedContent(context, candidateFiles) { - let changedContent = ( - Array.isArray(context.tailwindConfig.purge) - ? context.tailwindConfig.purge - : context.tailwindConfig.purge.content - ) + let changedContent = context.tailwindConfig.content.content .filter((item) => typeof item.raw === 'string') - .concat( - (context.tailwindConfig.purge?.safelist ?? []).map((content) => { - if (typeof content === 'string') { - return { raw: content, extension: 'html' } - } - - if (content instanceof RegExp) { - throw new Error( - "Values inside 'purge.safelist' can only be of type 'string', found 'regex'." - ) - } - - throw new Error( - `Values inside 'purge.safelist' can only be of type 'string', found '${typeof content}'.` - ) - }) - ) + .concat(context.tailwindConfig.content.safelist) .map(({ raw, extension }) => ({ content: raw, extension })) for (let changedFile of resolveChangedFiles(context, candidateFiles)) { diff --git a/src/jit/lib/sharedState.js b/src/lib/sharedState.js similarity index 100% rename from src/jit/lib/sharedState.js rename to src/lib/sharedState.js diff --git a/src/lib/substituteClassApplyAtRules.js b/src/lib/substituteClassApplyAtRules.js deleted file mode 100644 index bc7168471a71..000000000000 --- a/src/lib/substituteClassApplyAtRules.js +++ /dev/null @@ -1,409 +0,0 @@ -import _ from 'lodash' -import selectorParser from 'postcss-selector-parser' -import postcss from 'postcss' -import didYouMean from 'didyoumean' -import substituteTailwindAtRules from './substituteTailwindAtRules' -import evaluateTailwindFunctions from './evaluateTailwindFunctions' -import substituteVariantsAtRules from './substituteVariantsAtRules' -import substituteResponsiveAtRules from './substituteResponsiveAtRules' -import convertLayerAtRulesToControlComments from './convertLayerAtRulesToControlComments' -import substituteScreenAtRules from './substituteScreenAtRules' -import prefixSelector from '../util/prefixSelector' -import { useMemo } from '../util/useMemo' - -function hasAtRule(css, atRule, condition) { - let found = false - - css.walkAtRules( - atRule, - condition === undefined - ? () => { - found = true - return false - } - : (node) => { - if (condition(node)) { - found = true - return false - } - } - ) - - return found -} - -function cloneWithoutChildren(node) { - if (node.type === 'atrule') { - return postcss.atRule({ name: node.name, params: node.params }) - } - - if (node.type === 'rule') { - return postcss.rule({ name: node.name, selectors: node.selectors }) - } - - const clone = node.clone() - clone.removeAll() - return clone -} - -const tailwindApplyPlaceholder = selectorParser.attribute({ - attribute: '__TAILWIND-APPLY-PLACEHOLDER__', -}) - -function generateRulesFromApply({ rule, utilityName: className, classPosition }, replaceWiths) { - const parser = selectorParser((selectors) => { - let i = 0 - selectors.walkClasses((c) => { - if (classPosition === i++ && c.value === className) { - c.replaceWith(tailwindApplyPlaceholder) - } - }) - }) - - const processedSelectors = _.flatMap(rule.selectors, (selector) => { - // You could argue we should make this replacement at the AST level, but if we believe - // the placeholder string is safe from collisions then it is safe to do this is a simple - // string replacement, and much, much faster. - return replaceWiths.map((replaceWith) => - parser.processSync(selector).replace('[__TAILWIND-APPLY-PLACEHOLDER__]', replaceWith) - ) - }) - - const cloned = rule.clone() - let current = cloned - let parent = rule.parent - - while (parent && parent.type !== 'root') { - const parentClone = cloneWithoutChildren(parent) - - parentClone.append(current) - current.parent = parentClone - current = parentClone - parent = parent.parent - } - - cloned.selectors = processedSelectors - return current -} - -const extractUtilityNamesParser = selectorParser((selectors) => { - let classes = [] - selectors.walkClasses((c) => classes.push(c.value)) - return classes -}) - -const extractUtilityNames = useMemo( - (selector) => extractUtilityNamesParser.transformSync(selector), - (selector) => selector -) - -const cloneRuleWithParent = useMemo( - (rule) => rule.clone({ parent: rule.parent }), - (rule) => rule -) - -function buildCssUtilityMap(css, startIndex) { - let index = startIndex - const utilityMap = {} - - function handle(getRule, rule) { - const utilityNames = extractUtilityNames(rule.selector) - - utilityNames.forEach((utilityName, i) => { - if (utilityMap[utilityName] === undefined) { - utilityMap[utilityName] = [] - } - - utilityMap[utilityName].push({ - index, - utilityName, - classPosition: i, - ...getRule(rule), - }) - index++ - }) - } - - // This is the end user's css. This might contain rules that we want to - // apply. We want immediate copies of everything in case that we have user - // defined classes that are recursively applied. Down below we are modifying - // the rules directly. We could do a better solution where we keep track of a - // dependency tree, but that is a bit more complex. Might revisit later, - // we'll see how this turns out! - css.walkRules(handle.bind(null, (rule) => ({ rule: cloneRuleWithParent(rule) }))) - - return utilityMap -} - -const buildLookupTreeUtilityMap = useMemo( - (lookupTree) => { - let index = 0 - const utilityMap = {} - - function handle(getRule, rule) { - const utilityNames = extractUtilityNames(rule.selector) - - utilityNames.forEach((utilityName, i) => { - if (utilityMap[utilityName] === undefined) { - utilityMap[utilityName] = [] - } - - utilityMap[utilityName].push({ - index, - utilityName, - classPosition: i, - ...getRule(rule), - }) - index++ - }) - } - - // Lookup tree is the big lookup tree, making the rule lazy allows us to save - // some memory because we don't need everything. - lookupTree.walkRules( - handle.bind(null, (rule) => ({ - get rule() { - return cloneRuleWithParent(rule) - }, - })) - ) - - return utilityMap - }, - (tree) => tree -) - -function mergeAdjacentRules(initialRule, rulesToInsert) { - let previousRule = initialRule - - rulesToInsert.forEach((toInsert) => { - if ( - toInsert.type === 'rule' && - previousRule.type === 'rule' && - toInsert.selector === previousRule.selector - ) { - previousRule.append(toInsert.nodes) - } else if ( - toInsert.type === 'atrule' && - previousRule.type === 'atrule' && - toInsert.params === previousRule.params - ) { - const merged = mergeAdjacentRules( - previousRule.nodes[previousRule.nodes.length - 1], - toInsert.nodes - ) - - previousRule.append(merged) - } else { - previousRule = toInsert - } - - toInsert.walk((n) => { - if (n.nodes && n.nodes.length === 0) { - n.remove() - } - }) - }) - - return rulesToInsert.filter((r) => r.nodes.length > 0) -} - -function makeExtractUtilityRules(css, lookupTree, config) { - const lookupTreeUtilityMap = buildLookupTreeUtilityMap(lookupTree) - const lookupTreeUtilityMapKeys = Object.keys(lookupTreeUtilityMap) - const utilityMap = buildCssUtilityMap(css, lookupTreeUtilityMapKeys.length) - - function getUtility(utilityName) { - const utility = [] - if (lookupTreeUtilityMap[utilityName]) { - utility.push(...lookupTreeUtilityMap[utilityName]) - } - if (utilityMap[utilityName]) { - utility.push(...utilityMap[utilityName]) - } - if (utility.length > 0) return utility - } - - return function extractUtilityRules(utilityNames, rule) { - const combined = [] - - utilityNames.forEach((utilityName) => { - const utility = getUtility(utilityName) - if (utility === undefined) { - // Look for prefixed utility in case the user has goofed - const prefixedUtilityName = prefixSelector(config.prefix, `.${utilityName}`).slice(1) - - const prefixedUtility = getUtility(prefixedUtilityName) - if (prefixedUtility !== undefined) { - throw rule.error( - `The \`${utilityName}\` class does not exist, but \`${prefixedUtilityName}\` does. Did you forget the prefix?` - ) - } - - const suggestedClass = didYouMean( - utilityName, - Object.keys(utilityMap).concat(lookupTreeUtilityMapKeys) - ) - const suggestionMessage = suggestedClass ? `, but \`${suggestedClass}\` does` : '' - - throw rule.error( - `The \`${utilityName}\` class does not exist${suggestionMessage}. If you're sure that \`${utilityName}\` exists, make sure that any \`@import\` statements are being properly processed before Tailwind CSS sees your CSS, as \`@apply\` can only be used for classes in the same CSS tree.`, - { word: utilityName } - ) - } - - combined.push(...utility) - }) - - return combined.sort((a, b) => a.index - b.index) - } -} - -function findParent(rule, predicate) { - let parent = rule.parent - while (parent) { - if (predicate(parent)) { - return parent - } - - parent = parent.parent - } - - throw new Error('No parent could be found') -} - -function processApplyAtRules(css, lookupTree, config) { - const extractUtilityRules = makeExtractUtilityRules(css, lookupTree, config) - - do { - css.walkAtRules('apply', (applyRule) => { - const parent = applyRule.parent // Direct parent - const nearestParentRule = findParent(applyRule, (r) => r.type === 'rule') - const currentUtilityNames = extractUtilityNames(nearestParentRule.selector) - - const [importantEntries, applyUtilityNames, important = importantEntries.length > 0] = - _.partition(applyRule.params.split(/[\s\t\n]+/g), (n) => n === '!important') - - if (_.intersection(applyUtilityNames, currentUtilityNames).length > 0) { - const currentUtilityName = _.intersection(applyUtilityNames, currentUtilityNames)[0] - throw parent.error( - `You cannot \`@apply\` the \`${currentUtilityName}\` utility here because it creates a circular dependency.` - ) - } - - // Extract any post-apply declarations and re-insert them after apply rules - const afterRule = parent.clone({ raws: {} }) - afterRule.nodes = afterRule.nodes.slice(parent.index(applyRule) + 1) - parent.nodes = parent.nodes.slice(0, parent.index(applyRule) + 1) - - // Sort applys to match CSS source order - const applys = extractUtilityRules(applyUtilityNames, applyRule) - - // Get new rules with the utility portion of the selector replaced with the new selector - const rulesToInsert = [] - - applys.forEach( - nearestParentRule === parent - ? (util) => rulesToInsert.push(generateRulesFromApply(util, parent.selectors)) - : (util) => util.rule.nodes.forEach((n) => afterRule.append(n.clone())) - ) - - rulesToInsert.forEach((rule) => { - if (rule.type === 'atrule') { - rule.walkRules((rule) => { - rule.__tailwind = { ...rule.__tailwind, important } - }) - } else { - rule.__tailwind = { ...rule.__tailwind, important } - } - }) - - const { nodes } = _.tap(postcss.root({ nodes: rulesToInsert }), (root) => { - root.walkDecls((d) => { - d.important = important - }) - }) - - const mergedRules = mergeAdjacentRules(nearestParentRule, [...nodes, afterRule]) - - applyRule.remove() - parent.after(mergedRules) - - // If the base rule has nothing in it (all applys were pseudo or responsive variants), - // remove the rule fuggit. - if (parent.nodes.length === 0) { - parent.remove() - } - }) - - // We already know that we have at least 1 @apply rule. Otherwise this - // function would not have been called. Therefore we can execute this code - // at least once. This also means that in the best case scenario we only - // call this 2 times, instead of 3 times. - // 1st time -> before we call this function - // 2nd time -> when we check if we have to do this loop again (because do {} while (check)) - // .. instead of - // 1st time -> before we call this function - // 2nd time -> when we check the first time (because while (check) do {}) - // 3rd time -> when we re-check to see if we should do this loop again - } while (hasAtRule(css, 'apply')) - - return css -} - -let defaultTailwindTree = new Map() - -export default function substituteClassApplyAtRules(config, getProcessedPlugins, configChanged) { - return function (css) { - // We can stop already when we don't have any @apply rules. Vue users: you're welcome! - if (!hasAtRule(css, 'apply')) { - return css - } - - let requiredTailwindAtRules = ['base', 'components', 'utilities'] - if ( - hasAtRule(css, 'tailwind', (node) => { - let idx = requiredTailwindAtRules.indexOf(node.params) - if (idx !== -1) requiredTailwindAtRules.splice(idx, 1) - if (requiredTailwindAtRules.length <= 0) return true - return false - }) - ) { - // Tree already contains all the at rules (requiredTailwindAtRules) - return processApplyAtRules(css, postcss.root(), config) - } - - let lookupKey = requiredTailwindAtRules.join(',') - - // We mutated the `requiredTailwindAtRules`, but when we hit this point in - // time, it means that we don't have all the atrules. The missing atrules - // are listed inside the requiredTailwindAtRules, which we can use to fill - // in the missing pieces. - // - // Important for