Skip to content
Open
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
63 changes: 41 additions & 22 deletions src/loaders/sass/importer.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,60 @@
import path from "path";
import { packageFilterBuilder, resolveAsync, resolveSync } from "../../utils/resolve";
import { getUrlOfPartial, isModule, normalizeUrl } from "../../utils/url";
import { isAbsolutePath, isRelativePath } from "../../utils/path";
import { packageFilterBuilder, resolveAsync, ResolveOpts, resolveSync } from "../../utils/resolve";
import { getUrlOfPartial, hasModuleSpecifier, normalizeUrl } from "../../utils/url";

const extensions = [".scss", ".sass", ".css"];
const conditions = ["sass", "style"];

export const importer: sass.Importer = (url, importer, done): void => {
const finalize = (id: string): void => done({ file: id.replace(/\.css$/i, "") });
const next = (): void => done(null);
/**
* The exact behavior of importers defined here differ slightly between dart-sass and node-sass:
* https://github.com/sass/dart-sass/issues/574
*
* In short, dart-sass specifies that the *correct* behavior is to only call importers when a
* stylesheet fails to resolve via relative path. Since these importers below are implementation-
* agnostic, the first attempt to resolve a file by a relative is unneeded in dart-sass and can be
* removed once support for node-sass is fully deprecated.
*/
function importerImpl<T extends (ids: string[], userOpts: ResolveOpts) => unknown>(
url: string,
importer: string,
resolve: T,
): ReturnType<T> {
const candidates: string[] = [];
if (hasModuleSpecifier(url)) {
const moduleUrl = normalizeUrl(url);
// Give precedence to importing a partial
candidates.push(getUrlOfPartial(moduleUrl), moduleUrl);
} else {
const relativeUrl = normalizeUrl(url);
candidates.push(getUrlOfPartial(relativeUrl), relativeUrl);

if (!isModule(url)) return next();
const moduleUrl = normalizeUrl(url);
const partialUrl = getUrlOfPartial(moduleUrl);
// fall back to module imports
if (!isAbsolutePath(url) && !isRelativePath(url)) {
const moduleUrl = normalizeUrl(`~${url}`);
candidates.push(getUrlOfPartial(moduleUrl), moduleUrl);
}
}
const options = {
caller: "Sass importer",
basedirs: [path.dirname(importer)],
extensions,
packageFilter: packageFilterBuilder({ conditions }),
};
// Give precedence to importing a partial
resolveAsync([partialUrl, moduleUrl], options).then(finalize).catch(next);
};
return resolve(candidates, options) as ReturnType<T>;
}

const finalize = (id: string): sass.Data => ({ file: id.replace(/\.css$/i, "") });

export const importer: sass.Importer = (url, importer, done): void => {
void importerImpl(url, importer, resolveAsync)
.then(id => done(finalize(id)))
.catch(() => done(null));
};

export const importerSync: sass.Importer = (url, importer): sass.Data => {
if (!isModule(url)) return null;
const moduleUrl = normalizeUrl(url);
const partialUrl = getUrlOfPartial(moduleUrl);
const options = {
caller: "Sass importer",
basedirs: [path.dirname(importer)],
extensions,
packageFilter: packageFilterBuilder({ conditions }),
};
// Give precedence to importing a partial
try {
return finalize(resolveSync([partialUrl, moduleUrl], options));
return finalize(importerImpl(url, importer, resolveSync));
} catch {
return null;
}
Expand Down
4 changes: 2 additions & 2 deletions src/utils/url.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import path from "path";
import { isAbsolutePath, isRelativePath, normalizePath } from "./path";

export const isModule = (url: string): boolean => /^~[\d@A-Za-z]/.test(url);
export const hasModuleSpecifier = (url: string): boolean => /^~[\d@A-Za-z]/.test(url);

export function getUrlOfPartial(url: string): string {
const { dir, base } = path.parse(url);
return dir ? `${normalizePath(dir)}/_${base}` : `_${base}`;
}

export function normalizeUrl(url: string): string {
if (isModule(url)) return normalizePath(url.slice(1));
if (hasModuleSpecifier(url)) return normalizePath(url.slice(1));
if (isAbsolutePath(url) || isRelativePath(url)) return normalizePath(url);
return `./${normalizePath(url)}`;
}