Skip to content

Commit 4dd357b

Browse files
Add support for new loadModule and loadStylesheet APIs from v4 (#1058)
Related to tailwindlabs/tailwindcss#14446 We'll be handling `@import` resolution in core with the appropriate hooks to ensure that all I/O is done outside of the core package. This PR preps for that.
1 parent 4a3c9f7 commit 4dd357b

File tree

2 files changed

+70
-17
lines changed

2 files changed

+70
-17
lines changed

packages/tailwindcss-language-server/src/css/resolve-css-imports.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ const resolver = createResolver({
1111

1212
const resolveImports = postcss([
1313
postcssImport({
14-
resolve(id, basedir) {
15-
let paths = resolver.resolveSync({}, basedir, id)
16-
return paths ? paths : id
17-
},
14+
resolve: (id, base) => resolveCssFrom(base, id),
1815
}),
1916
fixRelativePaths(),
2017
])
2118

2219
export function resolveCssImports() {
2320
return resolveImports
2421
}
22+
23+
export function resolveCssFrom(base: string, id: string) {
24+
return resolver.resolveSync({}, base, id) || id
25+
}

packages/tailwindcss-language-server/src/util/v4/design-system.ts

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { DesignSystem } from '@tailwindcss/language-service/src/util/v4'
22

33
import postcss from 'postcss'
4+
import * as fs from 'node:fs/promises'
45
import * as path from 'node:path'
5-
import { resolveCssImports } from '../../css'
6+
import { resolveCssFrom, resolveCssImports } from '../../css'
67
import { resolveFrom } from '../resolveFrom'
78
import { pathToFileURL } from 'tailwindcss-language-server/src/utils'
89

@@ -25,29 +26,40 @@ export async function isMaybeV4(css: string): Promise<boolean> {
2526
* Create a loader function that can load plugins and config files relative to
2627
* the CSS file that uses them. However, we don't want missing files to prevent
2728
* everything from working so we'll let the error handler decide how to proceed.
28-
*
29-
* @param {object} param0
30-
* @returns
3129
*/
3230
function createLoader<T>({
31+
legacy,
3332
filepath,
3433
onError,
3534
}: {
35+
legacy: boolean
3636
filepath: string
37-
onError: (id: string, error: unknown) => T
37+
onError: (id: string, error: unknown, resourceType: string) => T
3838
}) {
39-
let baseDir = path.dirname(filepath)
4039
let cacheKey = `${+Date.now()}`
4140

42-
return async function loadFile(id: string) {
41+
async function loadFile(id: string, base: string, resourceType: string) {
4342
try {
44-
let resolved = resolveFrom(baseDir, id)
43+
let resolved = resolveFrom(base, id)
44+
4545
let url = pathToFileURL(resolved)
4646
url.searchParams.append('t', cacheKey)
4747

4848
return await import(url.href).then((m) => m.default ?? m)
4949
} catch (err) {
50-
return onError(id, err)
50+
return onError(id, err, resourceType)
51+
}
52+
}
53+
54+
if (legacy) {
55+
let baseDir = path.dirname(filepath)
56+
return (id: string) => loadFile(id, baseDir, 'module')
57+
}
58+
59+
return async (id: string, base: string, resourceType: string) => {
60+
return {
61+
base,
62+
module: await loadFile(id, base, resourceType),
5163
}
5264
}
5365
}
@@ -65,14 +77,53 @@ export async function loadDesignSystem(
6577
return null
6678
}
6779

80+
let supportsImports = false
81+
try {
82+
await tailwindcss.__unstable__loadDesignSystem(css, {
83+
loadStylesheet: async (id: string, base: string) => {
84+
supportsImports = true
85+
return { base, content: '' }
86+
},
87+
})
88+
} catch {}
89+
6890
// Step 2: Use postcss to resolve `@import` rules in the CSS file
69-
// TODO: What if someone is actively editing their config and introduces a syntax error?
70-
// We don't want to necessarily throw away the knowledge that we have a v4 project.
71-
let resolved = await resolveCssImports().process(css, { from: filepath })
91+
if (!supportsImports) {
92+
let resolved = await resolveCssImports().process(css, { from: filepath })
93+
css = resolved.css
94+
}
7295

7396
// Step 3: Take the resolved CSS and pass it to v4's `loadDesignSystem`
74-
let design: DesignSystem = await tailwindcss.__unstable__loadDesignSystem(resolved.css, {
97+
let design: DesignSystem = await tailwindcss.__unstable__loadDesignSystem(css, {
98+
base: path.dirname(filepath),
99+
100+
// v4.0.0-alpha.25+
101+
loadModule: createLoader({
102+
legacy: false,
103+
filepath,
104+
onError: (id, err, resourceType) => {
105+
console.error(`Unable to load ${resourceType}: ${id}`, err)
106+
107+
if (resourceType === 'config') {
108+
return {}
109+
} else if (resourceType === 'plugin') {
110+
return () => {}
111+
}
112+
},
113+
}),
114+
115+
loadStylesheet: async (id: string, base: string) => {
116+
let resolved = resolveCssFrom(base, id)
117+
118+
return {
119+
base: path.dirname(resolved),
120+
content: await fs.readFile(resolved, 'utf-8'),
121+
}
122+
},
123+
124+
// v4.0.0-alpha.24 and below
75125
loadPlugin: createLoader({
126+
legacy: true,
76127
filepath,
77128
onError(id, err) {
78129
console.error(`Unable to load plugin: ${id}`, err)
@@ -82,6 +133,7 @@ export async function loadDesignSystem(
82133
}),
83134

84135
loadConfig: createLoader({
136+
legacy: true,
85137
filepath,
86138
onError(id, err) {
87139
console.error(`Unable to load config: ${id}`, err)

0 commit comments

Comments
 (0)