From a5c452266d4c727975522224dd45302fc1f2bb75 Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Fri, 4 Feb 2022 18:10:03 +0100 Subject: [PATCH] feat: support typescript configuration --- index.d.ts | 23 ++++++++++ package-lock.json | 78 ++++++++++++++++++++++++++++++++- package.json | 3 +- src/index.js | 9 ++-- src/lib/setupTrackingContext.js | 23 ++++++++-- src/processTailwindFeatures.js | 4 +- tests/customConfig.test.js | 15 +++++++ tests/fixtures/ts-config.ts | 11 +++++ 8 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 index.d.ts create mode 100644 tests/fixtures/ts-config.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 000000000000..d9f5ae7ac321 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,23 @@ +type TailwindConfig = any + +/** + * Defines the Tailwind CSS configuration. + * + * @example + * ```ts + * import { defineConfig } from 'tailwindcss' + * + * export default defineConfig({ + * content: ['src/*.vue'], + * theme: { + * // ... + * } + * }) + * ``` + */ +export function defineConfig(options?: TailwindConfig): TailwindConfig + +/** + * Tailwind CSS as a PostCSS plugin. + */ +export const tailwindcss: Plugin diff --git a/package-lock.json b/package-lock.json index c24da713bc2c..6f65beb9efd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,8 @@ "postcss-selector-parser": "^6.0.9", "postcss-value-parser": "^4.2.0", "quick-lru": "^5.1.1", - "resolve": "^1.22.0" + "resolve": "^1.22.0", + "unconfig": "^0.2.2" }, "bin": { "tailwind": "lib/cli.js", @@ -59,6 +60,17 @@ "postcss": "^8.0.9" } }, + "node_modules/@antfu/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-UU8TLr/EoXdg7OjMp0h9oDoIAVr+Z/oW9cpOxQQyrsz6Qzd2ms/1CdWx8fl2OQdFpxGmq5Vc4TwfLHId6nAZjA==", + "dependencies": { + "@types/throttle-debounce": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -1447,6 +1459,11 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==" + }, "node_modules/@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -2351,6 +2368,11 @@ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" }, + "node_modules/defu": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/defu/-/defu-5.0.1.tgz", + "integrity": "sha512-EPS1carKg+dkEVy3qNTqIdp2qV7mUP08nIsupfwQpz++slCVRw7qbQyWvSTig+kFPwz2XXp5/kIIkH+CwrJKkQ==" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4418,6 +4440,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.12.15", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.12.15.tgz", + "integrity": "sha512-/+K89y6KJA2nISbWrlc/773XdpDgSQq/LdQ+ZZyw2jRxUNyquPtbsDCCCMRzzNORUgroUGc4nAXxJEnQvpViCA==", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6282,6 +6312,19 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/unconfig": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.2.2.tgz", + "integrity": "sha512-JN1MeYJ/POnjBj7NgOJJxPp6+NcD6Nd0hEuK0D89kjm9GvQQUq8HeE2Eb7PZgtu+64mWkDiqeJn1IZoLH7htPg==", + "dependencies": { + "@antfu/utils": "^0.3.0", + "defu": "^5.0.0", + "jiti": "^1.12.9" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -6554,6 +6597,14 @@ } }, "dependencies": { + "@antfu/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-UU8TLr/EoXdg7OjMp0h9oDoIAVr+Z/oW9cpOxQQyrsz6Qzd2ms/1CdWx8fl2OQdFpxGmq5Vc4TwfLHId6nAZjA==", + "requires": { + "@types/throttle-debounce": "^2.1.0" + } + }, "@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -7565,6 +7616,11 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==" + }, "@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -8251,6 +8307,11 @@ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" }, + "defu": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/defu/-/defu-5.0.1.tgz", + "integrity": "sha512-EPS1carKg+dkEVy3qNTqIdp2qV7mUP08nIsupfwQpz++slCVRw7qbQyWvSTig+kFPwz2XXp5/kIIkH+CwrJKkQ==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -9756,6 +9817,11 @@ } } }, + "jiti": { + "version": "1.12.15", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.12.15.tgz", + "integrity": "sha512-/+K89y6KJA2nISbWrlc/773XdpDgSQq/LdQ+ZZyw2jRxUNyquPtbsDCCCMRzzNORUgroUGc4nAXxJEnQvpViCA==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11051,6 +11117,16 @@ "is-typedarray": "^1.0.0" } }, + "unconfig": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.2.2.tgz", + "integrity": "sha512-JN1MeYJ/POnjBj7NgOJJxPp6+NcD6Nd0hEuK0D89kjm9GvQQUq8HeE2Eb7PZgtu+64mWkDiqeJn1IZoLH7htPg==", + "requires": { + "@antfu/utils": "^0.3.0", + "defu": "^5.0.0", + "jiti": "^1.12.9" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/package.json b/package.json index 75790c243caa..9b3a7c1c7a4b 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,8 @@ "postcss-selector-parser": "^6.0.9", "postcss-value-parser": "^4.2.0", "quick-lru": "^5.1.1", - "resolve": "^1.22.0" + "resolve": "^1.22.0", + "unconfig": "^0.2.2" }, "browserslist": [ "> 1%", diff --git a/src/index.js b/src/index.js index 0cba8bd75a73..f97391d39f0e 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ import setupTrackingContext from './lib/setupTrackingContext' import processTailwindFeatures from './processTailwindFeatures' import { env } from './lib/sharedState' -module.exports = function tailwindcss(configOrPath) { +const plugin = function tailwindcss(configOrPath) { return { postcssPlugin: 'tailwindcss', plugins: [ @@ -12,8 +12,8 @@ module.exports = function tailwindcss(configOrPath) { console.time('JIT TOTAL') return root }, - function (root, result) { - processTailwindFeatures(setupTrackingContext(configOrPath))(root, result) + async function (root, result) { + await processTailwindFeatures(setupTrackingContext(configOrPath))(root, result) }, env.DEBUG && function (root) { @@ -25,4 +25,7 @@ module.exports = function tailwindcss(configOrPath) { } } +module.exports = plugin module.exports.postcss = true +module.exports.tailwindcss = plugin +module.exports.defineConfig = (config) => config diff --git a/src/lib/setupTrackingContext.js b/src/lib/setupTrackingContext.js index 49050fd3bdca..8e2591071b09 100644 --- a/src/lib/setupTrackingContext.js +++ b/src/lib/setupTrackingContext.js @@ -4,6 +4,7 @@ import path from 'path' import fastGlob from 'fast-glob' import LRU from 'quick-lru' import normalizePath from 'normalize-path' +import { loadConfig } from 'unconfig' import hash from '../util/hashConfig' import getModuleDependencies from '../lib/getModuleDependencies' @@ -34,9 +35,25 @@ function getCandidateFiles(context, tailwindConfig) { } // Get the config object based on a path -function getTailwindConfig(configOrPath) { +export async function getTailwindConfig(configOrPath) { let userConfigPath = resolveConfigPath(configOrPath) + // Try loading with unconfig first + if (!userConfigPath || typeof userConfigPath === 'string') { + const unconfig = await loadConfig({ + merge: false, + sources: [ + { files: userConfigPath?.replace(/\.ts$/, ''), extensions: ['ts'], skipOnError: true }, + { files: 'tailwind.config', extensions: ['ts'], skipOnError: true }, + ], + }) + + if (unconfig.sources.length > 0) { + const resolvedConfig = resolveConfig(unconfig.config) + return [resolvedConfig, null, hash(resolvedConfig), []] + } + } + if (userConfigPath !== null) { let [prevConfig, prevConfigHash, prevDeps, prevModified] = configPathCache.get(userConfigPath) || [] @@ -113,9 +130,9 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) { // plugins) then return it export default function setupTrackingContext(configOrPath) { return ({ tailwindDirectives, registerDependency, applyDirectives }) => { - return (root, result) => { + return async (root, result) => { let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] = - getTailwindConfig(configOrPath) + await getTailwindConfig(configOrPath) let contextDependencies = new Set(configDependencies) diff --git a/src/processTailwindFeatures.js b/src/processTailwindFeatures.js index 952e17a13762..f7bf431f23fd 100644 --- a/src/processTailwindFeatures.js +++ b/src/processTailwindFeatures.js @@ -12,7 +12,7 @@ import { createContext } from './lib/setupContextUtils' import { issueFlagNotices } from './featureFlags' export default function processTailwindFeatures(setupContext) { - return function (root, result) { + return async function (root, result) { let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root) detectNesting()(root, result) @@ -21,7 +21,7 @@ export default function processTailwindFeatures(setupContext) { // itself. partitionApplyAtRules()(root, result) - let context = setupContext({ + let context = await setupContext({ tailwindDirectives, applyDirectives, registerDependency(dependency) { diff --git a/tests/customConfig.test.js b/tests/customConfig.test.js index baea15d76884..8ae41d70321f 100644 --- a/tests/customConfig.test.js +++ b/tests/customConfig.test.js @@ -4,6 +4,7 @@ import { cjsConfigFile, defaultConfigFile } from '../src/constants' import inTempDirectory from '../jest/runInTempDirectory' import { run, html, css, javascript } from './util/run' +import { getTailwindConfig } from '../src/lib/setupTrackingContext' test('it uses the values from the custom config file', () => { let config = require(path.resolve(`${__dirname}/fixtures/custom-config.js`)) @@ -19,6 +20,20 @@ test('it uses the values from the custom config file', () => { }) }) +test('it resolves a typescript config file', async () => { + let [config] = await getTailwindConfig(path.resolve(`${__dirname}/fixtures/ts-config.ts`)) + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (min-width: 400px) { + .mobile\:font-bold { + font-weight: 700; + } + } + `) + }) +}) + test('custom config can be passed as an object', () => { let config = { content: [{ raw: html`
` }], diff --git a/tests/fixtures/ts-config.ts b/tests/fixtures/ts-config.ts new file mode 100644 index 000000000000..c3f8540fa6d4 --- /dev/null +++ b/tests/fixtures/ts-config.ts @@ -0,0 +1,11 @@ +// @ts-ignore +import { defineConfig } from '../../src' + +export default defineConfig({ + content: [{ raw: '
' }], + theme: { + screens: { + mobile: '400px', + }, + }, +})