Skip to content

Commit 3c77380

Browse files
committed
fix: 支持webpack中sass-loader的路径解析
1 parent 32d723d commit 3c77380

File tree

3 files changed

+257
-102
lines changed

3 files changed

+257
-102
lines changed

packages/mako/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@swc/helpers": "0.5.1",
2828
"@types/resolve": "^1.20.6",
2929
"chalk": "^4.1.2",
30+
"enhanced-resolve": "^5.18.1",
3031
"less": "^4.2.0",
3132
"less-plugin-resolve": "^1.0.2",
3233
"lodash": "^4.17.21",
@@ -90,4 +91,4 @@
9091
"access": "public"
9192
},
9293
"repository": "[email protected]:umijs/mako.git"
93-
}
94+
}
+234-3
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,254 @@
1-
import { type Options } from 'sass';
1+
import fs from 'fs';
2+
import path from 'path';
3+
import url from 'url';
4+
import resolve, { type ResolveFunctionAsync } from 'enhanced-resolve';
5+
import { type ImporterResult, type Options } from 'sass';
26

37
async function render(param: {
48
filename: string;
59
opts: Options<'async'> & { resources: string[] };
610
}): Promise<{ content: string; type: 'css' }> {
7-
let sass;
11+
let sass: any;
812
try {
913
sass = require('sass');
1014
} catch (err) {
1115
throw new Error(
1216
'The "sass" package is not installed. Please run "npm install sass" to install it.',
1317
);
1418
}
19+
20+
const options = { style: 'compressed', ...param.opts };
21+
options.importers = options.importers || [];
22+
options.importers.push({
23+
async canonicalize(originalUrl, context) {
24+
const { fromImport } = context;
25+
const prev = context.containingUrl
26+
? url.fileURLToPath(context.containingUrl.toString())
27+
: param.filename;
28+
29+
const resolver = getResolver(sass?.compileStringAsync);
30+
try {
31+
const result = await resolver(prev, originalUrl, fromImport);
32+
return url.pathToFileURL(result) as URL;
33+
} catch (err) {
34+
return null;
35+
}
36+
},
37+
async load(canonicalUrl) {
38+
const ext = path.extname(canonicalUrl.pathname);
39+
40+
let syntax;
41+
42+
if (ext && ext.toLowerCase() === '.scss') {
43+
syntax = 'scss';
44+
} else if (ext && ext.toLowerCase() === '.sass') {
45+
syntax = 'indented';
46+
} else if (ext && ext.toLowerCase() === '.css') {
47+
syntax = 'css';
48+
} else {
49+
// Fallback to default value
50+
syntax = 'scss';
51+
}
52+
53+
try {
54+
const contents = await new Promise((resolve, reject) => {
55+
const canonicalPath = url.fileURLToPath(canonicalUrl);
56+
57+
fs.readFile(
58+
canonicalPath,
59+
{
60+
encoding: 'utf8',
61+
},
62+
(err, content) => {
63+
if (err) {
64+
reject(err);
65+
return;
66+
}
67+
68+
resolve(content);
69+
},
70+
);
71+
});
72+
73+
return {
74+
contents,
75+
syntax,
76+
sourceMapUrl: canonicalUrl,
77+
} as ImporterResult;
78+
} catch (err) {
79+
return null;
80+
}
81+
},
82+
});
83+
1584
const result = await sass
16-
.compileAsync(param.filename, { style: 'compressed', ...param.opts })
85+
.compileAsync(param.filename, options)
1786
.catch((err: any) => {
1887
throw new Error(err.toString());
1988
});
2089
return { content: result.css, type: 'css' };
2190
}
2291

2392
export { render };
93+
94+
function getResolver(compileStringAsync: any) {
95+
const isModernSass = typeof compileStringAsync !== 'undefined';
96+
97+
const importResolve = resolve.create({
98+
conditionNames: ['sass', 'style', '...'],
99+
mainFields: ['sass', 'style', 'main', '...'],
100+
mainFiles: ['_index.import', '_index', 'index.import', 'index', '...'],
101+
extensions: ['.sass', '.scss', '.css'],
102+
restrictions: [/\.((sa|sc|c)ss)$/i],
103+
preferRelative: true,
104+
});
105+
const moduleResolve = resolve.create({
106+
conditionNames: ['sass', 'style', '...'],
107+
mainFields: ['sass', 'style', 'main', '...'],
108+
mainFiles: ['_index', 'index', '...'],
109+
extensions: ['.sass', '.scss', '.css'],
110+
restrictions: [/\.((sa|sc|c)ss)$/i],
111+
preferRelative: true,
112+
});
113+
114+
return (context: string, request: string, fromImport: boolean) => {
115+
if (!isModernSass && !path.isAbsolute(context)) {
116+
return Promise.reject();
117+
}
118+
119+
const originalRequest = request;
120+
const isFileScheme = originalRequest.slice(0, 5).toLowerCase() === 'file:';
121+
122+
if (isFileScheme) {
123+
try {
124+
request = url.fileURLToPath(originalRequest);
125+
} catch (error) {
126+
request = request.slice(7);
127+
}
128+
}
129+
130+
let resolutionMap: any[] = [];
131+
132+
const webpackPossibleRequests = getPossibleRequests(request, fromImport);
133+
134+
resolutionMap = resolutionMap.concat({
135+
resolve: fromImport ? importResolve : moduleResolve,
136+
context: path.dirname(context),
137+
possibleRequests: webpackPossibleRequests,
138+
});
139+
140+
return startResolving(resolutionMap);
141+
};
142+
}
143+
144+
const MODULE_REQUEST_REGEX = /^[^?]*~/;
145+
146+
// Examples:
147+
// - ~package
148+
// - ~package/
149+
// - ~@org
150+
// - ~@org/
151+
// - ~@org/package
152+
// - ~@org/package/
153+
const IS_MODULE_IMPORT =
154+
/^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;
155+
156+
const IS_PKG_SCHEME = /^pkg:/i;
157+
158+
function getPossibleRequests(url: string, fromImport: boolean) {
159+
console.log('getPossibleRequests', url);
160+
let request = url;
161+
162+
if (MODULE_REQUEST_REGEX.test(url)) {
163+
request = request.replace(MODULE_REQUEST_REGEX, '');
164+
}
165+
166+
if (IS_PKG_SCHEME.test(url)) {
167+
request = `${request.slice(4)}`;
168+
169+
return [...new Set([request, url])];
170+
}
171+
172+
if (IS_MODULE_IMPORT.test(url) || IS_PKG_SCHEME.test(url)) {
173+
request = request[request.length - 1] === '/' ? request : `${request}/`;
174+
175+
return [...new Set([request, url])];
176+
}
177+
178+
const extension = path.extname(request).toLowerCase();
179+
180+
if (extension === '.css') {
181+
return fromImport ? [] : [url];
182+
}
183+
184+
const dirname = path.dirname(request);
185+
const normalizedDirname = dirname === '.' ? '' : `${dirname}/`;
186+
const basename = path.basename(request);
187+
const basenameWithoutExtension = path.basename(request, extension);
188+
189+
return [
190+
...new Set(
191+
([] as any[])
192+
.concat(
193+
fromImport
194+
? [
195+
`${normalizedDirname}_${basenameWithoutExtension}.import${extension}`,
196+
`${normalizedDirname}${basenameWithoutExtension}.import${extension}`,
197+
]
198+
: [],
199+
)
200+
.concat([
201+
`${normalizedDirname}_${basename}`,
202+
`${normalizedDirname}${basename}`,
203+
])
204+
.concat([url]),
205+
),
206+
];
207+
}
208+
209+
async function startResolving(resolutionMap: any[]) {
210+
if (resolutionMap.length === 0) {
211+
return Promise.reject();
212+
}
213+
214+
const [{ possibleRequests }] = resolutionMap;
215+
216+
if (possibleRequests.length === 0) {
217+
return Promise.reject();
218+
}
219+
220+
const [{ resolve, context }] = resolutionMap;
221+
222+
try {
223+
return await asyncResolve(context, possibleRequests[0], resolve);
224+
} catch (_ignoreError) {
225+
const [, ...tailResult] = possibleRequests;
226+
227+
if (tailResult.length === 0) {
228+
const [, ...tailResolutionMap] = resolutionMap;
229+
230+
return startResolving(tailResolutionMap);
231+
}
232+
233+
resolutionMap[0].possibleRequests = tailResult;
234+
235+
return startResolving(resolutionMap);
236+
}
237+
}
238+
239+
function asyncResolve(
240+
context: string,
241+
path: string,
242+
resolve: ResolveFunctionAsync,
243+
): Promise<string> {
244+
return new Promise((res, rej) => {
245+
resolve(context, path, (err, result) => {
246+
if (err) {
247+
rej(err);
248+
return;
249+
}
250+
251+
res(result as string);
252+
});
253+
});
254+
}

0 commit comments

Comments
 (0)