Skip to content

Instantly share code, notes, and snippets.

@kubijo
Last active March 6, 2022 18:36
Show Gist options
  • Save kubijo/8c2346dcfe6a3380a700906c4bd6bb04 to your computer and use it in GitHub Desktop.
Save kubijo/8c2346dcfe6a3380a700906c4bd6bb04 to your computer and use it in GitHub Desktop.
const path = require('path');
const { xfs } = require('@yarnpkg/fslib');
/**
* @example preffixCssPath('foo/bar/baz.scss') // >> 'foo/bar/_baz.scss'
*
* @param {String} p
* @return {string}
*/
function preffixCssPath(p) {
return path.join(path.dirname(p), `_${path.basename(p)}`);
}
/**
* Neither node-sass (C) or sass (Dart) node modules support Yarn's PNP architecture,
* but node-sass is much faster and supports custom importer function.
*
* So I've took the distictive pleassure of writing one that uses
* yarn's patched `require.resolve` & `fs` modules.
*
* @see https://yarnpkg.com/features/pnp
* @see https://yarnpkg.com/api/modules/yarnpkg_fslib.html
*
* Also, SASS allows import of partials (files starting with "_")
* without explicit mention of "_", so we need to try that as well…
*
* @param {String} url
* The `@import` request as it's encountered in the sources.
* Passed in from node-sass's importer function.
*
* @param {String} prev
* The current file's `@import` declaration (afaik ¯\_(ツ)_/¯).
* Passed in from node-sass's importer function.
*
* @param {function({ contents: String }): void} done
* Passed in from node-sass's importer function.
*
* @return {undefined|Error}
*/
function sassPnpImporter(url, prev, done) {
/**
* SASS alows us to omit the extension, but we need it for `require.resolve`
* to see the files as by default it searches for javascript modules
* and changing `require.extensions` is discouraged.
*
* @see https://nodejs.org/api/modules.html#modules_require_extensions
*
* Also webpack came with a great idea for tilde at the front to signify import from module…
* (which actually helps us here as we then know for sure what user means by that)
*/
const cleanedUrl = url
// Remove '~' at the start
.replace(/^~/, '')
// Add extension if missing
.replace(/(\.scss)?$/, '.scss');
// This will be the real absolute path on filesystem,
// that we'll try to load at the end…
let realPath = null;
// Explicitly marked as node module by "~"
if (url.startsWith('~')) {
// Partials import ... first try the normal version, then the underscored one.
try {
realPath = require.resolve(cleanedUrl, 'explicit node module');
} catch (_) {
realPath = require.resolve(preffixCssPath(cleanedUrl), '_explicit node module');
}
}
// Relative
else if (
// starts with `./` or `../`
/^\.{1,2}\//.test(url) ||
// No path delimiter
!url.includes('/')
) {
const p = path.join(
// We've got current file's import location by reporting it in `done({ file: "…" })`,
// so now we have something to base the relative imports from.
path.dirname(prev),
cleanedUrl,
);
// Partials import ... first try the normal version, then the underscored one.
try {
realPath = require.resolve(p, 'local');
} catch (_) {
realPath = require.resolve(preffixCssPath(p), '_local');
}
}
// Asume node module resolution
else {
// Partials import ... first try the normal version, then the underscored one.
try {
realPath = require.resolve(cleanedUrl);
} catch (_) {
realPath = require.resolve(preffixCssPath(cleanedUrl));
}
}
// If we have a path now, it should be load-able by Yarn's patched `fs`
if (typeof realPath === 'string' && realPath.length) {
// By including the `file` parameter we'll get it later as origin of current file!
done({ file: realPath, contents: xfs.readFileSync(realPath, 'utf-8') });
return;
}
// (╯°□°)╯︵ ┻━┻
return new Error(`Couldn't load requested SASS import "${url}"!`);
}
// … later on
// {
// loader: 'sass-loader',
// options: {
// sassOptions: { importer: sassPnpImporter },
// },
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment