Skip to content

Commit fdb85ee

Browse files
committed
Ridiculously large config loading refactor
1 parent 47eea4f commit fdb85ee

File tree

8 files changed

+516
-393
lines changed

8 files changed

+516
-393
lines changed

src/config.ts

Lines changed: 179 additions & 319 deletions
Large diffs are not rendered by default.

src/expiring-map.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
interface ExpiringMap<K, V> {
22
get(key: K): V | undefined
3+
remember(key: K, factory: () => V): V
34
set(key: K, value: V): void
45
}
56

@@ -19,6 +20,20 @@ export function expiringMap<K, V>(duration: number): ExpiringMap<K, V> {
1920
return undefined
2021
},
2122

23+
remember(key: K, factory: () => V) {
24+
let result = map.get(key)
25+
26+
if (result && result.expiration > new Date()) {
27+
return result.value
28+
}
29+
30+
map.delete(key)
31+
32+
let value = factory()
33+
this.set(key, value)
34+
return value
35+
},
36+
2237
set(key: K, value: V) {
2338
let expiration = new Date()
2439
expiration.setMilliseconds(expiration.getMilliseconds() + duration)

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function createParser(
4242
},
4343

4444
async parse(text: string, options: ParserOptions) {
45-
let { context, generateRules } = await getTailwindConfig(options)
45+
let context = await getTailwindConfig(options)
4646

4747
let original = base.originalParser(parserFormat, options)
4848

@@ -58,7 +58,7 @@ function createParser(
5858
let changes: any[] = []
5959

6060
transform(ast, {
61-
env: { context, customizations, generateRules, parsers: {}, options },
61+
env: { context, customizations, parsers: {}, options },
6262
changes,
6363
})
6464

src/sorting.ts

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,8 @@
1-
import type { LegacyTailwindContext, TransformerEnv } from './types'
2-
import './index'
3-
4-
export function bigSign(bigIntValue: bigint) {
5-
return Number(bigIntValue > 0n) - Number(bigIntValue < 0n)
6-
}
7-
8-
function prefixCandidate(
9-
context: LegacyTailwindContext,
10-
selector: string,
11-
): string {
12-
let prefix = context.tailwindConfig.prefix
13-
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
14-
}
15-
16-
// Polyfill for older Tailwind CSS versions
17-
function getClassOrderPolyfill(
18-
classes: string[],
19-
{ env }: { env: TransformerEnv },
20-
): [string, bigint | null][] {
21-
// A list of utilities that are used by certain Tailwind CSS utilities but
22-
// that don't exist on their own. This will result in them "not existing" and
23-
// sorting could be weird since you still require them in order to make the
24-
// host utitlies work properly. (Thanks Biology)
25-
let parasiteUtilities = new Set([
26-
prefixCandidate(env.context, 'group'),
27-
prefixCandidate(env.context, 'peer'),
28-
])
29-
30-
let classNamesWithOrder: [string, bigint | null][] = []
31-
32-
for (let className of classes) {
33-
let order: bigint | null =
34-
env
35-
.generateRules(new Set([className]), env.context)
36-
.sort(([a], [z]) => bigSign(z - a))[0]?.[0] ?? null
37-
38-
if (order === null && parasiteUtilities.has(className)) {
39-
// This will make sure that it is at the very beginning of the
40-
// `components` layer which technically means 'before any
41-
// components'.
42-
order = env.context.layerOrder.components
43-
}
44-
45-
classNamesWithOrder.push([className, order])
46-
}
47-
48-
return classNamesWithOrder
49-
}
1+
import type { TransformerEnv } from './types'
2+
import { bigSign } from './utils'
503

514
function reorderClasses(classList: string[], { env }: { env: TransformerEnv }) {
52-
let orderedClasses = env.context.getClassOrder
53-
? env.context.getClassOrder(classList)
54-
: getClassOrderPolyfill(classList, { env })
5+
let orderedClasses = env.context.getClassOrder(classList)
556

567
return orderedClasses.sort(([nameA, a], [nameZ, z]) => {
578
// Move `...` to the end of the list

src/types.ts

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,17 @@ export interface TransformerContext {
1818
changes: StringChange[]
1919
}
2020

21-
export interface LegacyTailwindContext {
22-
tailwindConfig: {
23-
prefix: string | ((selector: string) => string)
24-
}
25-
26-
getClassOrder?: (classList: string[]) => [string, bigint | null][]
27-
28-
layerOrder: {
29-
components: bigint
30-
}
21+
export interface UnifiedApi {
22+
getClassOrder(classList: string[]): [string, bigint | null][]
3123
}
3224

3325
export interface TransformerEnv {
34-
context: LegacyTailwindContext
26+
context: UnifiedApi
3527
customizations: Customizations
36-
generateRules: (
37-
classes: Iterable<string>,
38-
context: LegacyTailwindContext,
39-
) => [bigint][]
4028
parsers: any
4129
options: ParserOptions
4230
}
4331

44-
export interface ContextContainer {
45-
context: any
46-
generateRules: () => any
47-
}
48-
4932
export interface StringChange {
5033
start: number
5134
end: number

src/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,7 @@ export function spliceChangesIntoString(str: string, changes: StringChange[]) {
134134

135135
return result
136136
}
137+
138+
export function bigSign(bigIntValue: bigint) {
139+
return Number(bigIntValue > 0n) - Number(bigIntValue < 0n)
140+
}

src/versions/v3.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// @ts-check
2+
import * as path from 'path'
3+
import clearModule from 'clear-module'
4+
// @ts-ignore
5+
import { generateRules as generateRulesFallback } from 'tailwindcss/lib/lib/generateRules'
6+
// @ts-ignore
7+
import { createContext as createContextFallback } from 'tailwindcss/lib/lib/setupContextUtils'
8+
import loadConfigFallback from 'tailwindcss/loadConfig'
9+
import resolveConfigFallback from 'tailwindcss/resolveConfig'
10+
import type { RequiredConfig } from 'tailwindcss/types/config.js'
11+
import type { UnifiedApi } from '../types'
12+
import { bigSign } from '../utils'
13+
14+
interface LegacyTailwindContext {
15+
tailwindConfig: {
16+
prefix: string | ((selector: string) => string)
17+
}
18+
19+
getClassOrder?: (classList: string[]) => [string, bigint | null][]
20+
21+
layerOrder: {
22+
components: bigint
23+
}
24+
}
25+
26+
interface GenerateRules {
27+
(classes: Iterable<string>, context: LegacyTailwindContext): [bigint][]
28+
}
29+
30+
function prefixCandidate(context: LegacyTailwindContext, selector: string): string {
31+
let prefix = context.tailwindConfig.prefix
32+
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
33+
}
34+
35+
export async function loadV3(pkgDir: string | null, jsConfig: string | null): Promise<UnifiedApi> {
36+
let createContext = createContextFallback
37+
let generateRules: GenerateRules = generateRulesFallback
38+
let resolveConfig = resolveConfigFallback
39+
let loadConfig = loadConfigFallback
40+
let tailwindConfig: RequiredConfig = { content: [] }
41+
42+
try {
43+
if (pkgDir) {
44+
resolveConfig = require(path.join(pkgDir, 'resolveConfig'))
45+
createContext = require(path.join(pkgDir, 'lib/lib/setupContextUtils')).createContext
46+
generateRules = require(path.join(pkgDir, 'lib/lib/generateRules')).generateRules
47+
// Prior to `[email protected]` this won't exist so we load it last
48+
loadConfig = require(path.join(pkgDir, 'loadConfig'))
49+
}
50+
} catch {}
51+
52+
try {
53+
if (jsConfig) {
54+
clearModule(jsConfig)
55+
let loadedConfig = loadConfig(jsConfig)
56+
tailwindConfig = loadedConfig.default ?? loadedConfig
57+
}
58+
} catch (err) {
59+
console.error(`Unable to load your Tailwind CSS v3 config: ${jsConfig}`)
60+
throw err
61+
}
62+
63+
// suppress "empty content" warning
64+
tailwindConfig.content = ['no-op']
65+
66+
// Create the context
67+
let context: LegacyTailwindContext = createContext(resolveConfig(tailwindConfig))
68+
69+
// Polyfill for older Tailwind CSS versions
70+
function getClassOrderPolyfill(classes: string[]): [string, bigint | null][] {
71+
// A list of utilities that are used by certain Tailwind CSS utilities but
72+
// that don't exist on their own. This will result in them "not existing" and
73+
// sorting could be weird since you still require them in order to make the
74+
// host utitlies work properly. (Thanks Biology)
75+
let parasiteUtilities = new Set([prefixCandidate(context, 'group'), prefixCandidate(context, 'peer')])
76+
77+
let classNamesWithOrder: [string, bigint | null][] = []
78+
79+
for (let className of classes) {
80+
let order: bigint | null =
81+
generateRules(new Set([className]), context).sort(([a], [z]) => bigSign(z - a))[0]?.[0] ?? null
82+
83+
if (order === null && parasiteUtilities.has(className)) {
84+
// This will make sure that it is at the very beginning of the
85+
// `components` layer which technically means 'before any
86+
// components'.
87+
order = context.layerOrder.components
88+
}
89+
90+
classNamesWithOrder.push([className, order])
91+
}
92+
93+
return classNamesWithOrder
94+
}
95+
96+
context.getClassOrder ??= getClassOrderPolyfill
97+
98+
return {
99+
getClassOrder: (classList: string[]) => {
100+
return context.getClassOrder ? context.getClassOrder(classList) : getClassOrderPolyfill(classList)
101+
},
102+
}
103+
}

0 commit comments

Comments
 (0)