Skip to content
Closed
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
19 changes: 1 addition & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 20 additions & 2 deletions packages/css/src/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { stableClass } from './stableSelectors.js'

import { collectStyleImports } from './moduleGraph.js'
import type { ModuleGraphOptions } from './moduleGraph.js'
import { createSassImporter } from './sassInternals.js'
import { createSassImporter, createPkgImporter } from './sassInternals.js'
import type { CssResolver } from './types.js'
export type { AutoStableOption } from './autoStableSelectors.js'

Expand Down Expand Up @@ -332,12 +332,30 @@ async function compileSass(
const loadPaths = buildSassLoadPaths(filePath)

if (typeof (sass as { compileAsync?: Function }).compileAsync === 'function') {
const importers: unknown[] = []
/*
* Add custom importer first to handle user-provided resolver.
* Then add built-in pkg:# importer for native bundler-style resolution.
* Note: NodePackageImporter is not added because it conflicts with pkg:#
* imports (it throws an error for pkg:# URLs instead of returning null).
* Our pkgImporter handles pkg:# natively using oxc-resolver.
*/
if (importer) {
importers.push(importer)
}
/* Add built-in pkg:# importer using Node.js resolution */
const pkgImporter = createPkgImporter({
cwd,
extensions: ['.scss', '.sass', '.css'],
})
importers.push(pkgImporter)

const result = await (
sass as { compileAsync: typeof import('sass').compileAsync }
).compileAsync(filePath, {
style: 'expanded',
loadPaths,
importers: importer ? [importer] : undefined,
importers: importers.length > 0 ? (importers as never) : undefined,
})
return result.css
}
Expand Down
21 changes: 14 additions & 7 deletions packages/css/src/generateTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,22 +416,29 @@ async function resolveImportPath(
resolutionExtensions: string[] = RESOLUTION_EXTENSIONS,
): Promise<string | undefined> {
if (!resourceSpecifier) return undefined
if (resourceSpecifier.startsWith('.')) {
/* Strip pkg: prefix for Sass node package imports */
let normalizedSpecifier = resourceSpecifier
if (resourceSpecifier.startsWith('pkg:')) {
normalizedSpecifier = resourceSpecifier.slice('pkg:'.length)
}
if (normalizedSpecifier.startsWith('.')) {
return resolveWithExtensionFallback(
path.resolve(path.dirname(importerPath), resourceSpecifier),
path.resolve(path.dirname(importerPath), normalizedSpecifier),
)
}
if (resourceSpecifier.startsWith('/')) {
return resolveWithExtensionFallback(path.resolve(rootDir, resourceSpecifier.slice(1)))
if (normalizedSpecifier.startsWith('/')) {
return resolveWithExtensionFallback(
path.resolve(rootDir, normalizedSpecifier.slice(1)),
)
}
const tsconfigResolved = await resolveWithTsconfigPaths(resourceSpecifier, tsconfig)
const tsconfigResolved = await resolveWithTsconfigPaths(normalizedSpecifier, tsconfig)
if (tsconfigResolved) {
return resolveWithExtensionFallback(tsconfigResolved)
}
if (resolverFactory) {
const resolved = resolveWithFactory(
resolverFactory,
resourceSpecifier,
normalizedSpecifier,
importerPath,
resolutionExtensions,
)
Expand All @@ -441,7 +448,7 @@ async function resolveImportPath(
}
const requireFromRoot = getProjectRequire(rootDir)
try {
return requireFromRoot.resolve(resourceSpecifier)
return requireFromRoot.resolve(normalizedSpecifier)
} catch {
return undefined
}
Expand Down
17 changes: 16 additions & 1 deletion packages/css/src/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,17 @@ function normalizeSpecifier(raw: string): string {
if (!trimmed || trimmed.startsWith('\0')) {
return ''
}
/*
* For pkg: scheme, only strip actual query strings (?key=value),
* but preserve # as it's part of Sass package importer syntax
*/
if (trimmed.startsWith('pkg:')) {
const queryIndex = trimmed.indexOf('?')
if (queryIndex >= 0) {
return trimmed.slice(0, queryIndex)
}
return trimmed
}
const querySearchOffset = trimmed.startsWith('#') ? 1 : 0
const remainder = trimmed.slice(querySearchOffset)
const queryMatchIndex = remainder.search(/[?#]/)
Expand All @@ -189,7 +200,11 @@ function normalizeSpecifier(raw: string): string {
if (!withoutQuery) {
return ''
}
if (/^[a-z][\w+.-]*:/i.test(withoutQuery) && !withoutQuery.startsWith('file:')) {
if (
/^[a-z][\w+.-]*:/i.test(withoutQuery) &&
!withoutQuery.startsWith('file:') &&
!withoutQuery.startsWith('pkg:')
) {
return ''
}
return withoutQuery
Expand Down
17 changes: 16 additions & 1 deletion packages/css/src/moduleGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,17 @@ function normalizeSpecifier(raw: string): string {
if (!trimmed || trimmed.startsWith('\0')) {
return ''
}
/*
* For pkg: scheme, only strip actual query strings (?key=value),
* but preserve # as it's part of Sass package importer syntax
*/
if (trimmed.startsWith('pkg:')) {
const queryIndex = trimmed.indexOf('?')
if (queryIndex >= 0) {
return trimmed.slice(0, queryIndex)
}
return trimmed
}
const querySearchOffset = trimmed.startsWith('#') ? 1 : 0
const remainder = trimmed.slice(querySearchOffset)
const queryMatchIndex = remainder.search(/[?#]/)
Expand All @@ -292,7 +303,11 @@ function normalizeSpecifier(raw: string): string {
if (!withoutQuery) {
return ''
}
if (/^[a-z][\w+.-]*:/i.test(withoutQuery) && !withoutQuery.startsWith('file:')) {
if (
/^[a-z][\w+.-]*:/i.test(withoutQuery) &&
!withoutQuery.startsWith('file:') &&
!withoutQuery.startsWith('pkg:')
) {
return ''
}
return withoutQuery
Expand Down
9 changes: 7 additions & 2 deletions packages/css/src/moduleResolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@ export function resolveWithFactory(
return undefined
}
}
if (/^[a-z][\w+.-]*:/i.test(specifier)) {
/* Strip pkg: prefix for Sass node package imports before resolution */
let normalizedSpecifier = specifier
if (specifier.startsWith('pkg:')) {
normalizedSpecifier = specifier.slice('pkg:'.length)
}
if (/^[a-z][\w+.-]*:/i.test(normalizedSpecifier)) {
return undefined
}
try {
const result = factory.resolveFileSync(importer, specifier)
const result = factory.resolveFileSync(importer, normalizedSpecifier)
return result?.path
} catch {
return undefined
Expand Down
Loading
Loading