Skip to content

Commit 7e39e44

Browse files
committed
feat: add resolveAlias option to compilers and transformers for improved alias handling
1 parent 5a74274 commit 7e39e44

13 files changed

+472
-21
lines changed

src/compilerCss.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function compilerCss(
1111
filepath: string = isNodeEnvironment() ? process.cwd() : '',
1212
globalCss?: string,
1313
debug?: boolean,
14+
resolveAlias?: any,
1415
) {
1516
// 添加输入验证
1617
if (typeof css !== 'string') {
@@ -34,11 +35,11 @@ export function compilerCss(
3435

3536
switch (lang) {
3637
case 'stylus':
37-
return stylusCompiler(css, filepath, globalCss, debug)
38+
return stylusCompiler(css, filepath, globalCss, debug, resolveAlias)
3839
case 'less':
39-
return lessCompiler(css, filepath, globalCss, debug)
40+
return lessCompiler(css, filepath, globalCss, debug, resolveAlias)
4041
case 'scss':
41-
return sassCompiler(css, filepath, globalCss, debug)
42+
return sassCompiler(css, filepath, globalCss, debug, resolveAlias)
4243
default:
4344
return css
4445
}

src/lessCompiler.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import path from 'node:path'
2+
import process from 'node:process'
3+
14
export async function lessCompiler(
25
css: string,
36
filepath: string,
47
globalCss?: string,
58
debug?: boolean,
9+
resolveAlias?: any,
610
) {
711
if (typeof window !== 'undefined')
812
throw new Error('lessCompiler is not supported in this browser')
@@ -22,12 +26,44 @@ export async function lessCompiler(
2226
'less-plugin-module-resolver'
2327
)
2428

29+
// normalize resolveAlias into plain alias object { find: replacement }
30+
const aliasObj: Record<string, string> = {}
31+
try {
32+
if (resolveAlias) {
33+
if (Array.isArray(resolveAlias)) {
34+
for (const a of resolveAlias) {
35+
if (a && a.find && a.replacement) {
36+
aliasObj[a.find] = path.isAbsolute(a.replacement)
37+
? a.replacement
38+
: path.resolve(process.cwd(), a.replacement)
39+
}
40+
}
41+
}
42+
else if (typeof resolveAlias === 'object') {
43+
for (const k of Object.keys(resolveAlias)) {
44+
const val = resolveAlias[k]
45+
aliasObj[k] = path.isAbsolute(val)
46+
? val
47+
: path.resolve(process.cwd(), val)
48+
}
49+
}
50+
}
51+
}
52+
catch (e) {
53+
if (debug) {
54+
console.warn(
55+
'[transform-to-unocss] less resolveAlias normalize failed',
56+
e,
57+
)
58+
}
59+
}
60+
2561
result = (
2662
await less.default.render(result, {
2763
filename: filepath,
2864
plugins: [
2965
new LessPluginModuleResolver({
30-
alias: {},
66+
alias: aliasObj,
3167
}),
3268
],
3369
})

src/sassCompiler.ts

Lines changed: 153 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'node:path'
2+
import process from 'node:process'
23

34
/**
45
* Sass 编译器 - 处理 SCSS/Sass 文件编译
@@ -23,6 +24,7 @@ export async function sassCompiler(
2324
filepath: string,
2425
globalCss?: string | any,
2526
debug?: boolean,
27+
resolveAlias?: any,
2628
) {
2729
if (typeof window !== 'undefined')
2830
throw new Error('sassCompiler is not supported in this browser')
@@ -92,6 +94,23 @@ export async function sassCompiler(
9294
}
9395
result += css
9496

97+
if (process.env.DEBUG_SASS) {
98+
console.log(
99+
'[transform-to-unocss] [sassCompiler] globalCss type:',
100+
typeof globalCss,
101+
)
102+
103+
console.log(
104+
'[transform-to-unocss] [sassCompiler] result before replace length:',
105+
result.length,
106+
)
107+
108+
console.log(
109+
'[transform-to-unocss] [sassCompiler] result before replace snippet:',
110+
result.slice(0, 200),
111+
)
112+
}
113+
95114
try {
96115
// 使用用户项目中的 sass 版本(通过 peerDependencies)
97116
const sass = await import('sass')
@@ -129,7 +148,8 @@ export async function sassCompiler(
129148
// 启用现代 Sass API
130149
syntax: 'scss',
131150
// 支持 @use 和 @forward,让 Sass 使用默认的文件解析
132-
loadPaths: [baseDir],
151+
// 同时添加项目 src 目录到 loadPaths,方便解析 '@/...' 别名
152+
loadPaths: [baseDir, path.resolve(process.cwd(), 'src')],
133153
}
134154

135155
// 为现代版本的 Sass 添加兼容性配置
@@ -182,6 +202,136 @@ export async function sassCompiler(
182202
}
183203
}
184204

205+
// 在编译前,尝试将源码中以 '@/...' 或 '~@/...' 开头的别名导入替换为可解析的绝对路径。
206+
// 这能解决在没有构建别名解析器(如 vite/webpack)的环境中,Sass 无法直接解析别名的问题。
207+
const fs = await import('node:fs')
208+
209+
const replaceAliasImports = (source: string) => {
210+
const importRegex = /@(import|use|forward)\s+(['"])(~?@\/[\w\-./]+)\2/g
211+
212+
const resolveAliasLocal = (impPath: string) => {
213+
// impPath like '@/styles/foo' or '~@/styles/foo'
214+
const rel = impPath.replace(/^~?@\//, '')
215+
// If we have a resolver map from Vite/Rollup, try to resolve via it
216+
try {
217+
if (resolveAlias) {
218+
// config.resolve.alias can be array or object
219+
if (Array.isArray(resolveAlias)) {
220+
for (const a of resolveAlias) {
221+
// { find, replacement }
222+
if (
223+
typeof a.find === 'string'
224+
&& impPath.startsWith(a.find)
225+
) {
226+
return path.resolve(
227+
a.replacement,
228+
impPath.slice(a.find.length),
229+
)
230+
}
231+
if (a.find instanceof RegExp) {
232+
const m = impPath.match(a.find)
233+
if (m)
234+
return impPath.replace(a.find, a.replacement)
235+
}
236+
}
237+
}
238+
else if (typeof resolveAlias === 'object') {
239+
for (const key of Object.keys(resolveAlias)) {
240+
if (impPath.startsWith(key)) {
241+
return path.resolve(
242+
resolveAlias[key],
243+
impPath.slice(key.length),
244+
)
245+
}
246+
}
247+
}
248+
}
249+
}
250+
catch (e) {
251+
// ignore resolver errors and fallback
252+
if (debug)
253+
console.warn('[transform-to-unocss] resolveAlias failed', e)
254+
}
255+
const candidateSrc = path.resolve(process.cwd(), 'src', rel)
256+
const candidateRoot = path.resolve(process.cwd(), rel)
257+
258+
const exts = ['', '.scss', '.sass', '.css']
259+
const underscoreVariants = (p: string) => {
260+
const dir = path.dirname(p)
261+
const base = path.basename(p)
262+
return path.join(dir, `_${base}`)
263+
}
264+
265+
const tryPaths = (base: string) => {
266+
for (const e of exts) {
267+
const p1 = base + e
268+
if (fs.existsSync(p1))
269+
return p1
270+
const p2 = underscoreVariants(base) + e
271+
if (fs.existsSync(p2))
272+
return p2
273+
}
274+
return null
275+
}
276+
277+
// Prefer src-based resolution
278+
let found = tryPaths(candidateSrc)
279+
if (found)
280+
return found
281+
282+
found = tryPaths(candidateRoot)
283+
if (found)
284+
return found
285+
286+
// 最后回退到 src 路径(即使文件可能不存在),让 Sass 去进一步解析
287+
return candidateSrc
288+
}
289+
290+
return source.replace(importRegex, (match, kw, quote, impPath) => {
291+
try {
292+
const resolved = resolveAliasLocal(impPath)
293+
if (resolved && resolved !== impPath) {
294+
// ensure resolved is absolute-ish; if it's relative, resolve from cwd
295+
let finalPath = resolved
296+
try {
297+
// If resolved looks like a path (contains /) but is not absolute, make it absolute
298+
if (!path.isAbsolute(finalPath)) {
299+
finalPath = path.resolve(process.cwd(), finalPath)
300+
}
301+
}
302+
catch (e) {
303+
// keep original resolved if path ops fail
304+
}
305+
306+
if (debug) {
307+
console.log(
308+
`[transform-to-unocss] Rewriting ${kw} ${impPath} -> ${finalPath}`,
309+
)
310+
}
311+
return `@${kw} ${quote}${finalPath}${quote}`
312+
}
313+
}
314+
catch (e) {
315+
// Ignore resolution errors and leave original import
316+
if (debug)
317+
console.warn('[transform-to-unocss] alias resolution error', e)
318+
}
319+
320+
return match
321+
})
322+
}
323+
324+
// 在编译前把 alias 导入替换并准备要编译的源
325+
const sourceToCompile = replaceAliasImports(result)
326+
327+
// 可选调试:打印最终传入 Sass 的源码,方便定位 globalCss 是否被包含
328+
if (process.env.DEBUG_SASS) {
329+
console.log(
330+
'[transform-to-unocss] [sassCompiler] sourceToCompile:',
331+
sourceToCompile,
332+
)
333+
}
334+
185335
// 优先使用新的 compile API(如果可用),否则回退到 compileString
186336
let compiledResult: any
187337

@@ -190,12 +340,11 @@ export async function sassCompiler(
190340

191341
if (sass.compile && typeof sass.compile === 'function') {
192342
// 使用新的 API - 需要写入临时文件
193-
const fs = await import('node:fs')
194343
const os = await import('node:os')
195344
const tempFilePath = `${os.tmpdir()}/transform-to-unocss-${Date.now()}.scss`
196345

197346
try {
198-
fs.writeFileSync(tempFilePath, result)
347+
fs.writeFileSync(tempFilePath, sourceToCompile)
199348
compiledResult = sass.compile(tempFilePath, compileOptions)
200349
}
201350
finally {
@@ -210,7 +359,7 @@ export async function sassCompiler(
210359
}
211360
else {
212361
// 回退到旧的 API
213-
compiledResult = sass.compileString(result, compileOptions)
362+
compiledResult = sass.compileString(sourceToCompile, compileOptions)
214363
}
215364

216365
result = compiledResult.css

src/stylusCompiler.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import path from 'node:path'
2+
import process from 'node:process'
3+
14
export async function stylusCompiler(
25
css: string,
36
filepath: string,
47
globalCss?: string,
58
debug?: boolean,
9+
resolveAlias?: any,
610
) {
711
if (typeof window !== 'undefined')
812
throw new Error('Stylus is not supported in this browser')
@@ -15,12 +19,89 @@ export async function stylusCompiler(
1519

1620
let result = globalCss ? `${globalCss}${css}` : css
1721

22+
// Pre-replace alias imports like @import '@/foo' using resolveAlias map if provided
23+
if (resolveAlias) {
24+
try {
25+
const importRegex = /@import\s+['"](~?@\/[\w\-./]+)['"]/g
26+
result = result.replace(importRegex, (m, imp) => {
27+
const rel = imp.replace(/^~?@\//, '')
28+
// try array/object alias
29+
if (Array.isArray(resolveAlias)) {
30+
for (const a of resolveAlias) {
31+
if (typeof a.find === 'string' && imp.startsWith(a.find)) {
32+
const resolved = path.isAbsolute(a.replacement)
33+
? path.join(a.replacement, imp.slice(a.find.length))
34+
: path.join(
35+
process.cwd(),
36+
a.replacement,
37+
imp.slice(a.find.length),
38+
)
39+
return `@import '${resolved}'`
40+
}
41+
}
42+
}
43+
else if (typeof resolveAlias === 'object') {
44+
for (const k of Object.keys(resolveAlias)) {
45+
if (imp.startsWith(k)) {
46+
const val = resolveAlias[k]
47+
const resolved = path.isAbsolute(val)
48+
? path.join(val, imp.slice(k.length))
49+
: path.join(process.cwd(), val, imp.slice(k.length))
50+
return `@import '${resolved}'`
51+
}
52+
}
53+
}
54+
// fallback to src
55+
return `@import '${path.resolve(process.cwd(), 'src', rel)}'`
56+
})
57+
}
58+
catch (e) {
59+
if (debug)
60+
console.warn('[transform-to-unocss] stylus alias replace failed', e)
61+
}
62+
}
63+
1864
try {
1965
// 使用用户项目中的 stylus 版本(通过 peerDependencies)
2066
const stylus = await import('stylus')
2167

68+
// collect include paths from resolveAlias
69+
const includePaths: string[] = []
70+
try {
71+
if (resolveAlias) {
72+
if (Array.isArray(resolveAlias)) {
73+
for (const a of resolveAlias) {
74+
if (a && a.replacement) {
75+
includePaths.push(
76+
path.isAbsolute(a.replacement)
77+
? a.replacement
78+
: path.resolve(process.cwd(), a.replacement),
79+
)
80+
}
81+
}
82+
}
83+
else if (typeof resolveAlias === 'object') {
84+
for (const k of Object.keys(resolveAlias)) {
85+
const v = resolveAlias[k]
86+
includePaths.push(
87+
path.isAbsolute(v) ? v : path.resolve(process.cwd(), v),
88+
)
89+
}
90+
}
91+
}
92+
}
93+
catch (e) {
94+
if (debug) {
95+
console.warn(
96+
'[transform-to-unocss] stylus resolveAlias normalize failed',
97+
e,
98+
)
99+
}
100+
}
101+
22102
result = stylus.default.render(result, {
23103
filename: filepath,
104+
paths: includePaths.length ? includePaths : undefined,
24105
})
25106
return result
26107
}

0 commit comments

Comments
 (0)