Skip to content

Commit 1d0d289

Browse files
committed
Patch createRequire() and loadConfig() to handle builtin modules in the standalone CLI
1 parent 817c466 commit 1d0d289

File tree

3 files changed

+91
-8
lines changed

3 files changed

+91
-8
lines changed

src/lib/load-config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ import { transform } from 'sucrase'
44
import { Config } from '../../types/config'
55

66
let jiti: ReturnType<typeof jitiFactory> | null = null
7+
8+
// @internal
9+
// This WILL be removed in some future release
10+
// If you rely on this your stuff WILL break
11+
export function useCustomJiti(_jiti: ReturnType<typeof jitiFactory>) {
12+
jiti = _jiti
13+
}
14+
715
function lazyJiti() {
816
return (
917
jiti ??

standalone-cli/patch-require.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const Module = require("node:module")
2+
3+
/**
4+
* @param {Record<string, any>} mods
5+
*/
6+
module.exports.patchRequire = function patchRequire(mods, parentCache) {
7+
function wrapRequire(origRequire) {
8+
return Object.assign(
9+
function (id) {
10+
// Patch require(…) to return the cached module
11+
if (mods.hasOwnProperty(id)) {
12+
return mods[id]
13+
}
14+
15+
return origRequire.apply(this, arguments)
16+
},
17+
18+
// Make sure we carry over other properties of the original require(…)
19+
origRequire,
20+
21+
{
22+
resolve (id) {
23+
// Defer to the "parent" require cache when resolving the module
24+
// This also requires that the module be provided as a "native module" to JITI
25+
26+
// The path returned here is VERY important as it ensures that the `isNativeRe` in JITI
27+
// passes which is required for the module to be loaded via the native require(…) function
28+
// Thankfully, the regex just means that it needs to be in a node_modules folder which is true
29+
// even when bundled using Vercel's `pkg`
30+
if (parentCache.hasOwnProperty(id)) {
31+
return parentCache[id].filename
32+
}
33+
34+
return origRequire.resolve.apply(this, arguments)
35+
}
36+
}
37+
)
38+
}
39+
40+
let origRequire = Module.prototype.require
41+
let origCreateRequire = Module.createRequire
42+
43+
// We have to augment the default "require" in every module
44+
Module.prototype.require = wrapRequire(origRequire)
45+
46+
// And any "require" created by the "createRequire" method
47+
Module.createRequire = function () {
48+
return wrapRequire(
49+
origCreateRequire.apply(this, arguments)
50+
)
51+
}
52+
}

standalone-cli/standalone.js

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
let Module = require('module')
2-
let origRequire = Module.prototype.require
31
let log = require('tailwindcss/lib/util/log').default
42

53
let localModules = {
@@ -25,11 +23,36 @@ let localModules = {
2523
tailwindcss: require('tailwindcss'),
2624
}
2725

28-
Module.prototype.require = function (id) {
29-
if (localModules.hasOwnProperty(id)) {
30-
return localModules[id]
31-
}
32-
return origRequire.apply(this, arguments)
33-
}
26+
// Swap out the default JITI implementation with one that has the built-in modules above preloaded as "native modules"
27+
// NOTE: This uses a private, internal API of Tailwind CSS and is subject to change at any time
28+
let { useCustomJiti } = require("tailwindcss/lib/lib/load-config")
29+
let { transform } = require('sucrase')
30+
31+
useCustomJiti(() => require('jiti')(__filename, {
32+
interopDefault: true,
33+
nativeModules: Object.keys(localModules),
34+
transform: (opts) => {
35+
return transform(opts.source, {
36+
transforms: ['typescript', 'imports'],
37+
})
38+
},
39+
}))
40+
41+
let { patchRequire } = require('./patch-require.js')
42+
patchRequire(
43+
// Patch require(…) to return the bundled modules above so they don't need to be installed
44+
localModules,
45+
46+
// Create a require cache that maps module IDs to module objects
47+
// This MUST be done before require is patched to handle caching
48+
Object.fromEntries(
49+
Object.keys(localModules).map((id) => [
50+
id,
51+
id === '@tailwindcss/line-clamp'
52+
? `node_modules/@tailwindcss/line-clamp/`
53+
: require.cache[require.resolve(id)],
54+
])
55+
)
56+
)
3457

3558
require('tailwindcss/lib/cli')

0 commit comments

Comments
 (0)