|
1 | 1 | import path from "path"; |
2 | | -import { packageFilterBuilder, resolveAsync, resolveSync } from "../../utils/resolve"; |
3 | | -import { getUrlOfPartial, isModule, normalizeUrl } from "../../utils/url"; |
| 2 | +import { isAbsolutePath, isRelativePath } from "../../utils/path"; |
| 3 | +import { packageFilterBuilder, resolveAsync, ResolveOpts, resolveSync } from "../../utils/resolve"; |
| 4 | +import { getUrlOfPartial, hasModuleSpecifier, normalizeUrl } from "../../utils/url"; |
4 | 5 |
|
5 | 6 | const extensions = [".scss", ".sass", ".css"]; |
6 | 7 | const conditions = ["sass", "style"]; |
7 | 8 |
|
8 | | -export const importer: sass.Importer = (url, importer, done): void => { |
9 | | - const finalize = (id: string): void => done({ file: id.replace(/\.css$/i, "") }); |
10 | | - const next = (): void => done(null); |
| 9 | +/** |
| 10 | + * The exact behavior of importers defined here differ slightly between dart-sass and node-sass: |
| 11 | + * https://github.com/sass/dart-sass/issues/574 |
| 12 | + * |
| 13 | + * In short, dart-sass specifies that the *correct* behavior is to only call importers when a |
| 14 | + * stylesheet fails to resolve via relative path. Since these importers below are implementation- |
| 15 | + * agnostic, the first attempt to resolve a file by a relative is unneeded in dart-sass and can be |
| 16 | + * removed once support for node-sass is fully deprecated. |
| 17 | + */ |
| 18 | +function importerImpl<T extends (ids: string[], userOpts: ResolveOpts) => unknown>( |
| 19 | + url: string, |
| 20 | + importer: string, |
| 21 | + resolve: T, |
| 22 | +): ReturnType<T> { |
| 23 | + const candidates: string[] = []; |
| 24 | + if (hasModuleSpecifier(url)) { |
| 25 | + const moduleUrl = normalizeUrl(url); |
| 26 | + // Give precedence to importing a partial |
| 27 | + candidates.push(getUrlOfPartial(moduleUrl), moduleUrl); |
| 28 | + } else { |
| 29 | + const relativeUrl = normalizeUrl(url); |
| 30 | + candidates.push(getUrlOfPartial(relativeUrl), relativeUrl); |
11 | 31 |
|
12 | | - if (!isModule(url)) return next(); |
13 | | - const moduleUrl = normalizeUrl(url); |
14 | | - const partialUrl = getUrlOfPartial(moduleUrl); |
| 32 | + // fall back to module imports |
| 33 | + if (!isAbsolutePath(url) && !isRelativePath(url)) { |
| 34 | + const moduleUrl = normalizeUrl(`~${url}`); |
| 35 | + candidates.push(getUrlOfPartial(moduleUrl), moduleUrl); |
| 36 | + } |
| 37 | + } |
15 | 38 | const options = { |
16 | 39 | caller: "Sass importer", |
17 | 40 | basedirs: [path.dirname(importer)], |
18 | 41 | extensions, |
19 | 42 | packageFilter: packageFilterBuilder({ conditions }), |
20 | 43 | }; |
21 | | - // Give precedence to importing a partial |
22 | | - resolveAsync([partialUrl, moduleUrl], options).then(finalize).catch(next); |
23 | | -}; |
| 44 | + return resolve(candidates, options) as ReturnType<T>; |
| 45 | +} |
24 | 46 |
|
25 | 47 | const finalize = (id: string): sass.Data => ({ file: id.replace(/\.css$/i, "") }); |
| 48 | + |
| 49 | +export const importer: sass.Importer = (url, importer, done): void => { |
| 50 | + void importerImpl(url, importer, resolveAsync) |
| 51 | + .then(id => done(finalize(id))) |
| 52 | + .catch(() => done(null)); |
| 53 | +}; |
| 54 | + |
26 | 55 | export const importerSync: sass.Importer = (url, importer): sass.Data => { |
27 | | - if (!isModule(url)) return null; |
28 | | - const moduleUrl = normalizeUrl(url); |
29 | | - const partialUrl = getUrlOfPartial(moduleUrl); |
30 | | - const options = { |
31 | | - caller: "Sass importer", |
32 | | - basedirs: [path.dirname(importer)], |
33 | | - extensions, |
34 | | - packageFilter: packageFilterBuilder({ conditions }), |
35 | | - }; |
36 | | - // Give precedence to importing a partial |
37 | 56 | try { |
38 | | - return finalize(resolveSync([partialUrl, moduleUrl], options)); |
| 57 | + return finalize(importerImpl(url, importer, resolveSync)); |
39 | 58 | } catch { |
40 | 59 | return null; |
41 | 60 | } |
|
0 commit comments