-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR does multiple things: 1. It adds support for the `@plugin` and `@source` directive in the CSS language. 2. We will now scan V4 config files for eventually defined `@source` rules and respect them similarly how we handled custom content paths in V3. 3. Add support for the the Oxide API coming in Alpha 20 (tailwindlabs/tailwindcss#14187) For detecting the right content, we load the Oxide API installed in the user's Tailwind project. To do this in a backward compatible way, we now also load the `package.json` file of the installed Oxide version and support previous alpha releases for a limited amount of time. --------- Co-authored-by: Jordan Pittman <[email protected]>
- Loading branch information
1 parent
3f79be2
commit 3fc767b
Showing
31 changed files
with
582 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
packages/tailwindcss-language-server/src/css/extract-source-directives.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { Plugin } from 'postcss' | ||
|
||
export function extractSourceDirectives(sources: string[]): Plugin { | ||
return { | ||
postcssPlugin: 'extract-at-rules', | ||
AtRule: { | ||
source: ({ params }) => { | ||
if (params[0] !== '"' && params[0] !== "'") return | ||
sources.push(params.slice(1, -1)) | ||
}, | ||
}, | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
packages/tailwindcss-language-server/src/css/fix-relative-paths.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import path from 'node:path' | ||
import type { AtRule, Plugin } from 'postcss' | ||
import { normalizePath } from '../utils' | ||
|
||
const SINGLE_QUOTE = "'" | ||
const DOUBLE_QUOTE = '"' | ||
|
||
export function fixRelativePaths(): Plugin { | ||
// Retain a list of touched at-rules to avoid infinite loops | ||
let touched: WeakSet<AtRule> = new WeakSet() | ||
|
||
function fixRelativePath(atRule: AtRule) { | ||
if (touched.has(atRule)) return | ||
|
||
let rootPath = atRule.root().source?.input.file | ||
if (!rootPath) return | ||
|
||
let inputFilePath = atRule.source?.input.file | ||
if (!inputFilePath) return | ||
|
||
let value = atRule.params[0] | ||
|
||
let quote = | ||
value[0] === DOUBLE_QUOTE && value[value.length - 1] === DOUBLE_QUOTE | ||
? DOUBLE_QUOTE | ||
: value[0] === SINGLE_QUOTE && value[value.length - 1] === SINGLE_QUOTE | ||
? SINGLE_QUOTE | ||
: null | ||
|
||
if (!quote) return | ||
|
||
let glob = atRule.params.slice(1, -1) | ||
|
||
// Handle eventual negative rules. We only support one level of negation. | ||
let negativePrefix = '' | ||
if (glob.startsWith('!')) { | ||
glob = glob.slice(1) | ||
negativePrefix = '!' | ||
} | ||
|
||
// We only want to rewrite relative paths. | ||
if (!glob.startsWith('./') && !glob.startsWith('../')) { | ||
return | ||
} | ||
|
||
let absoluteGlob = path.posix.join(normalizePath(path.dirname(inputFilePath)), glob) | ||
let absoluteRootPosixPath = path.posix.dirname(normalizePath(rootPath)) | ||
|
||
let relative = path.posix.relative(absoluteRootPosixPath, absoluteGlob) | ||
|
||
// If the path points to a file in the same directory, `path.relative` will | ||
// remove the leading `./` and we need to add it back in order to still | ||
// consider the path relative | ||
if (!relative.startsWith('.')) { | ||
relative = './' + relative | ||
} | ||
|
||
atRule.params = quote + negativePrefix + relative + quote | ||
touched.add(atRule) | ||
} | ||
|
||
return { | ||
postcssPlugin: 'tailwindcss-postcss-fix-relative-paths', | ||
AtRule: { | ||
source: fixRelativePath, | ||
plugin: fixRelativePath, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './resolve-css-imports' | ||
export * from './extract-source-directives' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { lte } from 'tailwindcss-language-service/src/util/semver' | ||
|
||
// This covers the Oxide API from v4.0.0-alpha.1 to v4.0.0-alpha.18 | ||
declare namespace OxideV1 { | ||
interface GlobEntry { | ||
base: string | ||
glob: string | ||
} | ||
|
||
interface ScanOptions { | ||
base: string | ||
globs?: boolean | ||
} | ||
|
||
interface ScanResult { | ||
files: Array<string> | ||
globs: Array<GlobEntry> | ||
} | ||
} | ||
|
||
// This covers the Oxide API from v4.0.0-alpha.19 | ||
declare namespace OxideV2 { | ||
interface GlobEntry { | ||
base: string | ||
pattern: string | ||
} | ||
|
||
interface ScanOptions { | ||
base: string | ||
sources: Array<GlobEntry> | ||
} | ||
|
||
interface ScanResult { | ||
files: Array<string> | ||
globs: Array<GlobEntry> | ||
} | ||
} | ||
|
||
// This covers the Oxide API from v4.0.0-alpha.20+ | ||
declare namespace OxideV3 { | ||
interface GlobEntry { | ||
base: string | ||
pattern: string | ||
} | ||
|
||
interface ScannerOptions { | ||
detectSources?: { base: string } | ||
sources: Array<GlobEntry> | ||
} | ||
|
||
interface ScannerConstructor { | ||
new (options: ScannerOptions): Scanner | ||
} | ||
|
||
interface Scanner { | ||
files: Array<string> | ||
globs: Array<GlobEntry> | ||
} | ||
} | ||
|
||
interface Oxide { | ||
scanDir?(options: OxideV1.ScanOptions): OxideV1.ScanResult | ||
scanDir?(options: OxideV2.ScanOptions): OxideV2.ScanResult | ||
Scanner?: OxideV3.ScannerConstructor | ||
} | ||
|
||
async function loadOxideAtPath(id: string): Promise<Oxide | null> { | ||
let oxide = await import(id) | ||
|
||
// This is a much older, unsupported version of Oxide before v4.0.0-alpha.1 | ||
if (!oxide.scanDir) return null | ||
|
||
return oxide | ||
} | ||
|
||
interface GlobEntry { | ||
base: string | ||
pattern: string | ||
} | ||
|
||
interface ScanOptions { | ||
oxidePath: string | ||
oxideVersion: string | ||
basePath: string | ||
sources: Array<GlobEntry> | ||
} | ||
|
||
interface ScanResult { | ||
files: Array<string> | ||
globs: Array<GlobEntry> | ||
} | ||
|
||
/** | ||
* This is a helper function that leverages the Oxide API to scan a directory | ||
* and a set of sources and turn them into files and globs. | ||
* | ||
* Because the Oxide API has changed over time this function presents a unified | ||
* interface that works with all versions of the Oxide API but the results may | ||
* be different depending on the version of Oxide that is being used. | ||
* | ||
* For example, the `sources` option is ignored before v4.0.0-alpha.19. | ||
*/ | ||
export async function scan(options: ScanOptions): Promise<ScanResult | null> { | ||
const oxide = await loadOxideAtPath(options.oxidePath) | ||
if (!oxide) return null | ||
|
||
// V1 | ||
if (lte(options.oxideVersion, '4.0.0-alpha.18')) { | ||
let result = oxide.scanDir?.({ | ||
base: options.basePath, | ||
globs: true, | ||
}) | ||
|
||
return { | ||
files: result.files, | ||
globs: result.globs.map((g) => ({ base: g.base, pattern: g.glob })), | ||
} | ||
} | ||
|
||
// V2 | ||
if (lte(options.oxideVersion, '4.0.0-alpha.19')) { | ||
let result = oxide.scanDir({ | ||
base: options.basePath, | ||
sources: options.sources, | ||
}) | ||
|
||
return { | ||
files: result.files, | ||
globs: result.globs, | ||
} | ||
} | ||
|
||
// V3 | ||
let scanner = new oxide.Scanner({ | ||
detectSources: { base: options.basePath }, | ||
sources: options.sources, | ||
}) | ||
|
||
return { | ||
files: scanner.files, | ||
globs: scanner.globs, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.