11import 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 = / @ ( i m p o r t | u s e | f o r w a r d ) \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
0 commit comments