Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix loading of built-in plugins when using an ESM or TypeScript config with the Standalone CLI #12506

Merged
merged 2 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
8 changes: 8 additions & 0 deletions src/lib/load-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { transform } from 'sucrase'
import { Config } from '../../types/config'

let jiti: ReturnType<typeof jitiFactory> | 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<typeof jitiFactory>) {
jiti = _jiti
}

function lazyJiti() {
return (
jiti ??
Expand Down
50 changes: 50 additions & 0 deletions standalone-cli/patch-require.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const Module = require('node:module')

/**
* @param {Record<string, any>} 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))
}
}
41 changes: 33 additions & 8 deletions standalone-cli/standalone.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
let Module = require('module')
let origRequire = Module.prototype.require
let log = require('tailwindcss/lib/util/log').default

let localModules = {
Expand All @@ -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')