From 32b4a67f5e243ba5e5a301b8f9e99a1995203290 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 29 Nov 2023 12:30:40 -0500 Subject: [PATCH 1/2] Patch createRequire() and loadConfig() to handle builtin modules in the standalone CLI Fix code style --- src/lib/load-config.ts | 8 ++++++ standalone-cli/patch-require.js | 50 +++++++++++++++++++++++++++++++++ standalone-cli/standalone.js | 41 +++++++++++++++++++++------ 3 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 standalone-cli/patch-require.js diff --git a/src/lib/load-config.ts b/src/lib/load-config.ts index 645e8e1d55c4..e879412a3977 100644 --- a/src/lib/load-config.ts +++ b/src/lib/load-config.ts @@ -4,6 +4,14 @@ import { transform } from 'sucrase' import { Config } from '../../types/config' let jiti: ReturnType | null = null + +// @internal +// This WILL be removed in some future release +// If you rely on this your stuff WILL break +export function useCustomJiti(_jiti: ReturnType) { + jiti = _jiti +} + function lazyJiti() { return ( jiti ?? diff --git a/standalone-cli/patch-require.js b/standalone-cli/patch-require.js new file mode 100644 index 000000000000..c5857baf807a --- /dev/null +++ b/standalone-cli/patch-require.js @@ -0,0 +1,50 @@ +const Module = require('node:module') + +/** + * @param {Record} mods + */ +module.exports.patchRequire = function patchRequire(mods, parentCache) { + function wrapRequire(origRequire) { + return Object.assign( + function (id) { + // Patch require(…) to return the cached module + if (mods.hasOwnProperty(id)) { + return mods[id] + } + + return origRequire.apply(this, arguments) + }, + + // Make sure we carry over other properties of the original require(…) + origRequire, + + { + resolve(id) { + // Defer to the "parent" require cache when resolving the module + // This also requires that the module be provided as a "native module" to JITI + + // The path returned here is VERY important as it ensures that the `isNativeRe` in JITI + // passes which is required for the module to be loaded via the native require(…) function + // Thankfully, the regex just means that it needs to be in a node_modules folder which is true + // even when bundled using Vercel's `pkg` + if (parentCache.hasOwnProperty(id)) { + return parentCache[id].filename + } + + return origRequire.resolve.apply(this, arguments) + }, + } + ) + } + + let origRequire = Module.prototype.require + let origCreateRequire = Module.createRequire + + // We have to augment the default "require" in every module + Module.prototype.require = wrapRequire(origRequire) + + // And any "require" created by the "createRequire" method + Module.createRequire = function () { + return wrapRequire(origCreateRequire.apply(this, arguments)) + } +} diff --git a/standalone-cli/standalone.js b/standalone-cli/standalone.js index 28ddf69a960d..ce0bde1cb64e 100644 --- a/standalone-cli/standalone.js +++ b/standalone-cli/standalone.js @@ -1,5 +1,3 @@ -let Module = require('module') -let origRequire = Module.prototype.require let log = require('tailwindcss/lib/util/log').default let localModules = { @@ -25,11 +23,38 @@ let localModules = { tailwindcss: require('tailwindcss'), } -Module.prototype.require = function (id) { - if (localModules.hasOwnProperty(id)) { - return localModules[id] - } - return origRequire.apply(this, arguments) -} +// Swap out the default JITI implementation with one that has the built-in modules above preloaded as "native modules" +// NOTE: This uses a private, internal API of Tailwind CSS and is subject to change at any time +let { useCustomJiti } = require('tailwindcss/lib/lib/load-config') +let { transform } = require('sucrase') + +useCustomJiti(() => + require('jiti')(__filename, { + interopDefault: true, + nativeModules: Object.keys(localModules), + transform: (opts) => { + return transform(opts.source, { + transforms: ['typescript', 'imports'], + }) + }, + }) +) + +let { patchRequire } = require('./patch-require.js') +patchRequire( + // Patch require(…) to return the bundled modules above so they don't need to be installed + localModules, + + // Create a require cache that maps module IDs to module objects + // This MUST be done before require is patched to handle caching + Object.fromEntries( + Object.keys(localModules).map((id) => [ + id, + id === '@tailwindcss/line-clamp' + ? `node_modules/@tailwindcss/line-clamp/` + : require.cache[require.resolve(id)], + ]) + ) +) require('tailwindcss/lib/cli') From acf52581f7b79f05f00262f3aefb7d0f3c428096 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 1 Dec 2023 11:44:00 -0500 Subject: [PATCH 2/2] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eaa047bc86e..2c8c9838de12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Don't crash when given applying a variant to a negated version of a simple utility ([#12514](https://github.com/tailwindlabs/tailwindcss/pull/12514)) - Fix support for slashes in arbitrary modifiers ([#12515](https://github.com/tailwindlabs/tailwindcss/pull/12515)) - Fix source maps of variant utilities that come from an `@layer` rule ([#12508](https://github.com/tailwindlabs/tailwindcss/pull/12508)) +- Fix loading of built-in plugins when using an ESM or TypeScript config with the Standalone CLI ([#12506](https://github.com/tailwindlabs/tailwindcss/pull/12506)) - [Oxide] Remove `autoprefixer` dependency ([#11315](https://github.com/tailwindlabs/tailwindcss/pull/11315)) - [Oxide] Fix source maps issue resulting in a crash ([#11319](https://github.com/tailwindlabs/tailwindcss/pull/11319)) - [Oxide] Fallback to RegEx based parser when using custom transformers or extractors ([#11335](https://github.com/tailwindlabs/tailwindcss/pull/11335))