Skip to content

Commit

Permalink
feat(sass): sync mode support with dart implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Anidetrix committed Jun 8, 2020
1 parent bd8ac6e commit 91846bc
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 38 deletions.
33 changes: 25 additions & 8 deletions src/loaders/sass/importer.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
import path from "path";
import { sync as resolveSync } from "resolve";
import resolveAsync from "../../utils/resolve-async";
import { getUrlOfPartial, isModule, normalizeUrl } from "../../utils/url";

const importer: sass.Importer = (url, importer, done) => {
// Do not add `.css` extension in order to inline the file
const finishImport = (id: string): void => done({ file: id.replace(/\.css$/i, "") });
const extensions = [".scss", ".sass", ".css"];

// Pass responsibility back to other custom importers
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);

if (!isModule(url)) return next();
const moduleUrl = normalizeUrl(url);
const partialUrl = getUrlOfPartial(moduleUrl);
const options = { basedir: path.dirname(importer), extensions: [".scss", ".sass", ".css"] };
const options = { basedir: path.dirname(importer), extensions };

// Give precedence to importing a partial
resolveAsync(partialUrl, options)
.then(finishImport)
.then(finalize)
.catch(() => {
resolveAsync(moduleUrl, options).then(finishImport).catch(next);
resolveAsync(moduleUrl, options).then(finalize).catch(next);
});
};

export default importer;
const finalize = (id: string): sass.Data => ({ file: id.replace(/\.css$/i, "") });
export const importerSync: sass.Importer = (url, importer): sass.Data => {
if (!isModule(url)) return null;
const moduleUrl = normalizeUrl(url);
const partialUrl = getUrlOfPartial(moduleUrl);
const options = { basedir: path.dirname(importer), extensions };

// Give precedence to importing a partial
try {
try {
return finalize(resolveSync(partialUrl, options));
} catch {
return finalize(resolveSync(moduleUrl, options));
}
} catch {
return null;
}
};
37 changes: 24 additions & 13 deletions src/loaders/sass/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
import { Loader, SASSLoaderOptions } from "../../types";
import loadModule from "../../utils/load-module";
import { normalizePath } from "../../utils/path";
import { Loader } from "../types";
import loadSass from "./load";
import { importer, importerSync } from "./importer";

import { loadSass } from "./load";
import importer from "./importer";
/** Options for Sass loader */
// https://github.com/microsoft/TypeScript/issues/37901
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
export interface SASSLoaderOptions extends Record<string, unknown>, sass.Options {
/** Force Sass implementation */
impl?: string;
/** Forcefully enable/disable `fibers` */
fibers?: boolean;
/** Forcefully enable/disable sync mode */
sync?: boolean;
}

const loader: Loader<SASSLoaderOptions> = {
name: "sass",
test: /\.(sass|scss)$/i,
async process({ code, map }) {
const { options } = this;

const [sass, type] = await loadSass(options.impl);

// `fibers` doesn't work in testing
const useFibers = options.fibers ?? (type === "sass" && process.env.NODE_ENV !== "test");
const fiber = useFibers ? await loadModule("fibers") : undefined;
const options = { ...this.options };
const [sass, type] = loadSass(options.impl);
const useFibers = options.fibers ?? type === "sass";
const fiber = useFibers ? (loadModule("fibers") as fibers.Fiber) : undefined;
const sync = options.sync ?? (type !== "node-sass" && !fiber);

const render = async (options: sass.Options): Promise<sass.Result> =>
new Promise((resolve, reject) => {
sass.render(options, (err, css) => (err ? reject(err) : resolve(css)));
if (sync) resolve(sass.renderSync(options));
else sass.render(options, (err, css) => (err ? reject(err) : resolve(css)));
});

// Remove non-Sass options
delete options.fibers;
delete options.impl;
delete options.fibers;
delete options.sync;

// node-sass won't produce source maps if the `data`
// option is used and `sourceMap` option is not a string.
Expand All @@ -43,7 +54,7 @@ const loader: Loader<SASSLoaderOptions> = {
sourceMap: this.id,
omitSourceMapUrl: true,
sourceMapContents: true,
importer: [importer].concat(options.importer ?? []),
importer: [sync ? importerSync : importer].concat(options.importer ?? []),
fiber,
});

Expand Down
26 changes: 9 additions & 17 deletions src/loaders/sass/load.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
import loadModule from "../../utils/load-module";
import arrayFmt from "../../utils/array-fmt";

const allSassIDs = ["node-sass", "sass"] as const;

const idFmt = allSassIDs
.map((id, i, arr) => {
const newId = `\`${id}\``;
if (i === arr.length - 2) return newId;
if (i === arr.length - 1) return `or ${newId}`;
return `${newId},`;
})
.join(" ");

export async function loadSass(impl?: string): Promise<[sass.Sass, string]> {
const ids = ["node-sass", "sass"];
const idsFmt = arrayFmt(ids);
export default function (impl?: string): [sass.Sass, string] {
// Loading provided implementation
if (impl) {
const provided = await loadModule(impl);
const provided = loadModule(impl);
if (provided) return [provided as sass.Sass, impl];
throw new Error(`Could not load \`${impl}\` Sass implementation`);
}

// Loading one of the supported modules
for await (const id of allSassIDs) {
const sass = await loadModule(id);
if (sass) return [sass, id];
for (const id of ids) {
const sass = loadModule(id);
if (sass) return [sass as sass.Sass, id];
}

throw new Error(`You need to install ${idFmt} package in order to process Sass files`);
throw new Error(`You need to install ${idsFmt} package in order to process Sass files`);
}

0 comments on commit 91846bc

Please sign in to comment.