Skip to content

Commit bf131cf

Browse files
dnjstromegoist
authored andcommitted
Limit threads when transpiling with node-sass (#105)
This PR introduces a worker queue which limits the number of concurrent threads similar to how webpack [sass-loader](https://github.com/webpack-contrib/sass-loader/blob/2529c0716b1bca321c22d16636b1385682b1c730/lib/loader.js#L14) does. The changes are a workaround for the issues in node-sass discussed in #104.
1 parent 73e67fe commit bf131cf

3 files changed

Lines changed: 60 additions & 44 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"cssnano": "^3.10.0",
4949
"fs-extra": "^5.0.0",
5050
"import-cwd": "^2.1.0",
51+
"p-queue": "^2.4.2",
5152
"pify": "^3.0.0",
5253
"postcss": "^6.0.21",
5354
"postcss-load-config": "^1.2.0",
@@ -79,4 +80,4 @@
7980
]
8081
]
8182
}
82-
}
83+
}

src/sass-loader.js

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import path from 'path'
22
import pify from 'pify'
33
import resolve from 'resolve'
44
import importCwd from 'import-cwd'
5+
import PQueue from 'p-queue'
6+
7+
// This queue makes sure node-sass leaves one thread available for executing fs tasks
8+
// See: https://github.com/sass/node-sass/issues/857
9+
const threadPoolSize = process.env.UV_THREADPOOL_SIZE || 4
10+
const workQueue = new PQueue({ concurrency: threadPoolSize - 1 })
511

612
const moduleRe = /^~([a-z0-9]|@).+/i
713

@@ -17,54 +23,59 @@ export default {
1723
test: /\.s[ac]ss$/,
1824
async process({ code }) {
1925
const sass = importCwd('node-sass')
20-
const res = await pify(sass.render.bind(sass))({
21-
...this.options,
22-
file: this.id,
23-
data: code,
24-
indentedSyntax: /\.sass$/.test(this.id),
25-
sourceMap: this.sourceMap,
26-
importer: [
27-
(url, importer, done) => {
28-
if (!moduleRe.test(url)) return done({ file: url })
2926

30-
const moduleUrl = url.slice(1)
31-
const partialUrl = getUrlOfPartial(moduleUrl)
27+
return new Promise((resolve, reject) => {
28+
workQueue.add(() =>
29+
pify(sass.render.bind(sass))({
30+
...this.options,
31+
file: this.id,
32+
data: code,
33+
indentedSyntax: /\.sass$/.test(this.id),
34+
sourceMap: this.sourceMap,
35+
importer: [
36+
(url, importer, done) => {
37+
if (!moduleRe.test(url)) return done({ file: url })
3238

33-
const options = {
34-
basedir: path.dirname(importer),
35-
extensions: ['.scss', '.sass', '.css']
36-
}
37-
const finishImport = id => {
38-
done({
39-
// Do not add `.css` extension in order to inline the file
40-
file: id.endsWith('.css') ? id.replace(/\.css$/, '') : id
41-
})
42-
}
39+
const moduleUrl = url.slice(1)
40+
const partialUrl = getUrlOfPartial(moduleUrl)
4341

44-
const next = () => {
45-
// Catch all resolving errors, return the original file and pass responsibility back to other custom importers
46-
done({ file: url })
47-
}
42+
const options = {
43+
basedir: path.dirname(importer),
44+
extensions: ['.scss', '.sass', '.css']
45+
}
46+
const finishImport = id => {
47+
done({
48+
// Do not add `.css` extension in order to inline the file
49+
file: id.endsWith('.css') ? id.replace(/\.css$/, '') : id
50+
})
51+
}
4852

49-
// Give precedence to importing a partial
50-
resolvePromise(partialUrl, options)
51-
.then(finishImport)
52-
.catch(err => {
53-
if (err.code === 'MODULE_NOT_FOUND' || err.code === 'ENOENT') {
54-
resolvePromise(moduleUrl, options)
55-
.then(finishImport)
56-
.catch(next)
57-
} else {
58-
next()
53+
const next = () => {
54+
// Catch all resolving errors, return the original file and pass responsibility back to other custom importers
55+
done({ file: url })
5956
}
60-
})
61-
}
62-
].concat(this.options.importer || [])
63-
})
6457

65-
return {
66-
code: res.css.toString(),
67-
map: res.map && res.map.toString()
68-
}
58+
// Give precedence to importing a partial
59+
resolvePromise(partialUrl, options)
60+
.then(finishImport)
61+
.catch(err => {
62+
if (err.code === 'MODULE_NOT_FOUND' || err.code === 'ENOENT') {
63+
resolvePromise(moduleUrl, options)
64+
.then(finishImport)
65+
.catch(next)
66+
} else {
67+
next()
68+
}
69+
})
70+
}
71+
].concat(this.options.importer || [])
72+
}).then(res =>
73+
resolve({
74+
code: res.css.toString(),
75+
map: res.map && res.map.toString()
76+
})
77+
).catch(reject)
78+
)
79+
})
6980
}
7081
}

yarn.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4556,6 +4556,10 @@ p-locate@^2.0.0:
45564556
dependencies:
45574557
p-limit "^1.1.0"
45584558

4559+
p-queue@^2.4.2:
4560+
version "2.4.2"
4561+
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34"
4562+
45594563
package-json@^4.0.0:
45604564
version "4.0.1"
45614565
resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"

0 commit comments

Comments
 (0)