Skip to content

Sass takes excessively long to compile #10122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
solonovamax opened this issue Mar 24, 2025 · 3 comments
Open

Sass takes excessively long to compile #10122

solonovamax opened this issue Mar 24, 2025 · 3 comments

Comments

@solonovamax
Copy link
Contributor

🐛 bug report

Sass takes excessively long to compile, when importing a bunch of sass functions. (not actually sure how long it takes, and it could be infinite. I did not bother to find out and killed it after 5 minutes)

🎛 Configuration (.babelrc, package.json, cli command)

N/A

🤔 Expected Behavior

It doesn't take excessively long to compile.

😯 Current Behavior

It does take excessively long to compile.

💁 Possible Solution

Use the sass-embedded instead of the sass package for @parcel/transformer-sass.

🔦 Context

💻 Code Sample

  1. run yarn install -D @sass-fairy/string
  2. Create a sass file with the following at the top:
    @use "@sass-fairy/string";

🌍 Your Environment

Software Version(s)
Parcel 2.14.1
Node v23.10.0
npm/Yarn yarn v1.22.22
Operating System Arch Linux 6.13.7-arch1-1 x86_64
@solonovamax
Copy link
Contributor Author

solonovamax commented Mar 24, 2025

while trying out my own modified version of @parcel/transformer-sass, I've noticed some interesting behaviour:
when using the sass package, it will first try to resolve something via a relative path, followed by more global path. however, if the same is attempted with sass-embedded, it will only attempt the embedded path, and error if it does not work. however, the docs state that they should have the same behaviour.

here is an example:
if I have a sass file which includes

@use "@sass-fairy/string/string";

parcel will attempt to resolve @sass-fairy/string/string, which is resolved to node_modules/@sass-fairy/string/string.sass.
this file includes the following:

@forward '@sass-fairy/string'
@forward 'sass:string' hide index

so, it will go back to parcel to resolve these.
if using the sass package, then parcel will first attempt to resolve ../../../node_modules/@sass-fairy/string/@sass-fairy/string, which will fail. it will then attempt to resolve @sass-fairy/string, which will succeed, resolving to node_modules/@sass-fairy/string/index.sass.
however, if the only thing you change is to replace sass with sass-embedded, then it will first attempt to resolve ../../../node_modules/@sass-fairy/string/@sass-fairy/string, and when it fails it will result in the following error:

@solonovamax/parcel-transformer-sass: Error: TypeError: Cannot read properties of undefined (reading 'toString')
  ╷
5 │ @forward '@sass-fairy/string'
  │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ╵
  ../../../@sass-fairy/string/string.sass 5:1  @use
  ../../../../src/assets/scss/fonts.scss 28:1  root stylesheet

  Error: Error: TypeError: Cannot read properties of undefined (reading 'toString')
    ╷
  5 │ @forward '@sass-fairy/string'
    │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ╵
    ../../../@sass-fairy/string/string.sass 5:1  @use
    ../../../../src/assets/scss/fonts.scss 28:1  root stylesheet
      at handleCompileResponse
  (/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/node_modules/sass-embedded/dist/lib/src/compiler/utils.js:155:15)
      at AsyncCompiler.compileRequestAsync
  (/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/node_modules/sass-embedded/dist/lib/src/compiler/async.js:100:54)
      at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
      at async compileStringAsync
  (/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/node_modules/sass-embedded/dist/lib/src/compile.js:45:16)

this seems to entirely be an issue on sass' end, so I'll report it to them.

until this upstream issue is fixed, it will be impossible to use.
edit: nvm, this is an issue with the plugin using the behaviour that both null and undefined are falsy in js, however in dart it checks if it's null.
the type definition of the canonicalize function (see Importer in sass/types/importer.d.ts) is:

canonicalize(
  url: string,
  context: CanonicalizeContext
): PromiseOr<URL | null, sync>;

so, it expects null to be returned. however, if no file is matched, the function does not return, thus resulting in undefined being the return value.
so, adding return null to the bottom of the canonicalize function fixes this.

@solonovamax
Copy link
Contributor Author

solonovamax commented Mar 24, 2025

I'm convinced that this issue has to do with the resolution step:

if (options.env.SASS_PATH) {
paths.push(
...options.env.SASS_PATH.split(
process.platform === 'win32' ? ';' : ':',
).map(p => path.resolve(options.projectRoot, p)),
);
}
// The importer should look for stylesheets by adding the prefix _ to the URL's basename,
// and by adding the extensions .sass and .scss if the URL doesn't already have one of those extensions.
const urls = [url];
const urlFileName = path.basename(url);
if (urlFileName[0] !== '_') {
urls.push(path.posix.join(path.dirname(url), `_${urlFileName}`));
}
let ext = path.extname(urlFileName);
if (ext !== '.sass' && ext !== '.scss') {
for (let url of [...urls]) {
urls.push(url + '.sass');
urls.push(url + '.scss');
}
}
// If none of the possible paths is valid, the importer should perform the same resolution on the URL followed by /index.
urls.push(path.posix.join(url, 'index.sass'));
urls.push(path.posix.join(url, 'index.scss'));
urls.push(path.posix.join(url, '_index.sass'));
urls.push(path.posix.join(url, '_index.scss'));
if (url[0] !== '~') {
for (let p of paths) {
for (let u of urls) {
let filePath = path.resolve(p, u);
let stat;
try {
stat = await asset.fs.stat(filePath);
} catch (err) {
// ignore.
}
if (stat?.isFile()) {
return pathToFileURL(filePath);
}
asset.invalidateOnFileCreate({filePath});
}
}
}
// If none of the default sass rules apply, try Parcel's resolver.
for (let u of urls) {
if (NODE_MODULE_ALIAS_RE.test(u)) {
u = u.slice(1);
}
try {
const filePath = await resolve(containingPath, u, {
packageConditions: ['sass', 'style'],
});
return pathToFileURL(filePath);
} catch (err) {
continue;
}
}

specifically, that call to resolve which comes from the transform method on the Transformer.

if I add some error logging, then I will see it attempting to resolve 5-10 different files before finally finding the right one. see this comment.

@solonovamax
Copy link
Contributor Author

This issue has something to do with parcel's resolver being extremely slow.

if I throw in some performance.now() calls into modern.js file (technically I'm using a slightly modified version of it, but it's almost identical), you can observe something similar to the following (note: here, I am printing the url, urls, and paths variables in the canonicalize inner method in from modern.js)

The modified code looks like this:

let startTime = performance.now();
let endTime;
if (url[0] !== '~') {
  for (let p of paths) {
    for (let u of urls) {
      let filePath = path.resolve(p, u);
      let stat;
      try {
        stat = await asset.fs.stat(filePath);
      } catch (err) {
        // ignore.
      }
      if (stat?.isFile()) {
        endTime = performance.now();
        console.log(`fs resolve took ${endTime - startTime} milliseconds (success)`);
        return pathToFileURL(filePath);
      }

      asset.invalidateOnFileCreate({filePath});
    }
  }
}
endTime = performance.now();
console.log(`fs resolve took ${endTime - startTime} milliseconds (failure)`);

// If none of the default sass rules apply, try Parcel's resolver.
startTime = performance.now();
for (let u of urls) {
  if (NODE_MODULE_ALIAS_RE.test(u)) {
    u = u.slice(1);
  }
  try {
    const filePath = await resolve(containingPath, u, {
      packageConditions: ['sass', 'style'],
    });
    endTime = performance.now();
    console.log(`parcel resolve took ${endTime - startTime} milliseconds (success)`);
    return pathToFileURL(filePath);
  } catch (err) {
    continue;
  }
}
endTime = performance.now();
console.log(`parcel resolve took ${endTime - startTime} milliseconds (failure)`);

this will result in log messages that are similar to the following:

trying imports for asset  /home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss/fonts.scss
url:  @sass-fairy/string/string
urls:  [
  '@sass-fairy/string/string',
  '@sass-fairy/string/_string',
  '@sass-fairy/string/string.sass',
  '@sass-fairy/string/string.scss',
  '@sass-fairy/string/_string.sass',
  '@sass-fairy/string/_string.scss',
  '@sass-fairy/string/string/index.sass',
  '@sass-fairy/string/string/index.scss',
  '@sass-fairy/string/string/_index.sass',
  '@sass-fairy/string/string/_index.scss'
]
paths:  [
  '/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss'
]
fs resolve took 1.0980270000000019 milliseconds (failure)
parcel resolve took 7.267514000000119 milliseconds (success)
trying imports for asset  /home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss/fonts.scss
url:  ../../../node_modules/@sass-fairy/string/@sass-fairy/string
urls:  [
  '../../../node_modules/@sass-fairy/string/@sass-fairy/string',
  '../../../node_modules/@sass-fairy/string/@sass-fairy/_string',
  '../../../node_modules/@sass-fairy/string/@sass-fairy/string.sass',
  '../../../node_modules/@sass-fairy/string/@sass-fairy/string.scss',
  '../../../node_modules/@sass-fairy/string/@sass-fairy/_string.sass',
  '../../../node_modules/@sass-fairy/string/@sass-fairy/_string.scss',
  '../../../node_modules/@sass-fairy/string/@sass-fairy/string/index.sass',
  '../../../node_modules/@sass-fairy/string/@sass-fairy/string/index.scss',
  '../../../node_modules/@sass-fairy/string/@sass-fairy/string/_index.sass',
  '../../../node_modules/@sass-fairy/string/@sass-fairy/string/_index.scss'
]
paths:  [
  '/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss'
]
fs resolve took 0.45846400000004905 milliseconds (failure)
parcel resolve took 20499.101478999997 milliseconds (failure)
trying imports for asset  /home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss/fonts.scss
url:  @sass-fairy/string
urls:  [
  '@sass-fairy/string',
  '@sass-fairy/_string',
  '@sass-fairy/string.sass',
  '@sass-fairy/string.scss',
  '@sass-fairy/_string.sass',
  '@sass-fairy/_string.scss',
  '@sass-fairy/string/index.sass',
  '@sass-fairy/string/index.scss',
  '@sass-fairy/string/_index.sass',
  '@sass-fairy/string/_index.scss'
]
paths:  [
  '/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/node_modules/@sass-fairy/string'
]
fs resolve took 1.273206999998365 milliseconds (failure)
parcel resolve took 0.7081130000005942 milliseconds (success)
trying imports for asset  /home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss/fonts.scss
url:  ../../../node_modules/@sass-fairy/string/src/ends-with
urls:  [
  '../../../node_modules/@sass-fairy/string/src/ends-with',
  '../../../node_modules/@sass-fairy/string/src/_ends-with',
  '../../../node_modules/@sass-fairy/string/src/ends-with.sass',
  '../../../node_modules/@sass-fairy/string/src/ends-with.scss',
  '../../../node_modules/@sass-fairy/string/src/_ends-with.sass',
  '../../../node_modules/@sass-fairy/string/src/_ends-with.scss',
  '../../../node_modules/@sass-fairy/string/src/ends-with/index.sass',
  '../../../node_modules/@sass-fairy/string/src/ends-with/index.scss',
  '../../../node_modules/@sass-fairy/string/src/ends-with/_index.sass',
  '../../../node_modules/@sass-fairy/string/src/ends-with/_index.scss'
]
paths:  [
  '/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss'
]
fs resolve took 0.5911830000004556 milliseconds (success)
trying imports for asset  /home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss/fonts.scss
url:  ../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/parameter-type
urls:  [
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/parameter-type',
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/_parameter-type',
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/parameter-type.sass',
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/parameter-type.scss',
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/_parameter-type.sass',
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/_parameter-type.scss',
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/parameter-type/index.sass',
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/parameter-type/index.scss',
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/parameter-type/_index.sass',
  '../../../node_modules/@sass-fairy/string/src/@sass-fairy/exception/src/parameter-type/_index.scss'
]
paths:  [
  '/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss'
]
fs resolve took 0.46377299999949173 milliseconds (failure)
parcel resolve took 10308.907701 milliseconds (failure)
trying imports for asset  /home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss/fonts.scss
url:  @sass-fairy/exception/src/parameter-type
urls:  [
  '@sass-fairy/exception/src/parameter-type',
  '@sass-fairy/exception/src/_parameter-type',
  '@sass-fairy/exception/src/parameter-type.sass',
  '@sass-fairy/exception/src/parameter-type.scss',
  '@sass-fairy/exception/src/_parameter-type.sass',
  '@sass-fairy/exception/src/_parameter-type.scss',
  '@sass-fairy/exception/src/parameter-type/index.sass',
  '@sass-fairy/exception/src/parameter-type/index.scss',
  '@sass-fairy/exception/src/parameter-type/_index.sass',
  '@sass-fairy/exception/src/parameter-type/_index.scss'
]
paths:  [
  '/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/node_modules/@sass-fairy/string/src'
]
fs resolve took 0.424418999999034 milliseconds (failure)
parcel resolve took 1.2399049999985436 milliseconds (success)
trying imports for asset  /home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss/fonts.scss
url:  ../../../node_modules/@sass-fairy/exception/src/parameter
urls:  [
  '../../../node_modules/@sass-fairy/exception/src/parameter',
  '../../../node_modules/@sass-fairy/exception/src/_parameter',
  '../../../node_modules/@sass-fairy/exception/src/parameter.sass',
  '../../../node_modules/@sass-fairy/exception/src/parameter.scss',
  '../../../node_modules/@sass-fairy/exception/src/_parameter.sass',
  '../../../node_modules/@sass-fairy/exception/src/_parameter.scss',
  '../../../node_modules/@sass-fairy/exception/src/parameter/index.sass',
  '../../../node_modules/@sass-fairy/exception/src/parameter/index.scss',
  '../../../node_modules/@sass-fairy/exception/src/parameter/_index.sass',
  '../../../node_modules/@sass-fairy/exception/src/parameter/_index.scss'
]
paths:  [
  '/home/solonovamax/Programming/Kotlin/solonovamax.gay/frontend/src/assets/scss'
]
fs resolve took 0.20348299999750452 milliseconds (success)

you can notice that when it succeeds, it takes only a handful of milliseconds for it to do so. However, when parcel fails to find a match, it will take on the order of seconds. Sass will then try again with a non relative path.

this seems to be an issue specifically with parcel's own resolver.

However, if I were to simply add the following code at the top of the method:

if (url.indexOf("node_modules") > -1)
  return null;

then it's able to complete almost instantly.

@devongovett since you seem to be the person primarily maintaining parcel's resolver, do you have any idea why it would be taking so absurdly long for the resolution when it fails?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant