-
-
Notifications
You must be signed in to change notification settings - Fork 427
/
loader.js
188 lines (155 loc) · 5.54 KB
/
loader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
'use strict';
const path = require('path');
const async = require('neo-async');
const pify = require('pify');
const semver = require('semver');
const formatSassError = require('./formatSassError');
const webpackImporter = require('./webpackImporter');
const normalizeOptions = require('./normalizeOptions');
let nodeSassJobQueue = null;
/**
* The sass-loader makes node-sass and dart-sass available to webpack modules.
*
* @this {LoaderContext}
* @param {string} content
*/
function sassLoader(content) {
const callback = this.async();
const isSync = typeof callback !== 'function';
const self = this;
const { resourcePath } = this;
function addNormalizedDependency(file) {
// node-sass returns POSIX paths
self.dependency(path.normalize(file));
}
if (isSync) {
throw new Error(
'Synchronous compilation is not supported anymore. See https://github.com/webpack-contrib/sass-loader/issues/333'
);
}
let resolve = pify(this.resolve);
// Supported since v4.27.0
if (this.getResolve) {
resolve = this.getResolve({
mainFields: ['sass', 'main'],
extensions: ['.scss', '.sass', '.css'],
});
}
const options = normalizeOptions(
this,
content,
webpackImporter(resourcePath, resolve, addNormalizedDependency)
);
// Skip empty files, otherwise it will stop webpack, see issue #21
if (options.data.trim() === '') {
callback(null, '');
return;
}
const render = getRenderFuncFromSassImpl(
// eslint-disable-next-line import/no-extraneous-dependencies, global-require
options.implementation || getDefaultSassImpl()
);
render(options, (err, result) => {
if (err) {
formatSassError(err, this.resourcePath);
if (err.file) {
this.dependency(err.file);
}
callback(err);
return;
}
if (result.map && result.map !== '{}') {
// eslint-disable-next-line no-param-reassign
result.map = JSON.parse(result.map);
// result.map.file is an optional property that provides the output filename.
// Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
// eslint-disable-next-line no-param-reassign
delete result.map.file;
// One of the sources is 'stdin' according to dart-sass/node-sass because we've used the data input.
// Now let's override that value with the correct relative path.
// Since we specified options.sourceMap = path.join(process.cwd(), "/sass.map"); in normalizeOptions,
// we know that this path is relative to process.cwd(). This is how node-sass works.
// eslint-disable-next-line no-param-reassign
const stdinIndex = result.map.sources.findIndex(
(source) => source.indexOf('stdin') !== -1
);
if (stdinIndex !== -1) {
result.map.sources[stdinIndex] = path.relative(
process.cwd(),
resourcePath
);
}
// node-sass returns POSIX paths, that's why we need to transform them back to native paths.
// This fixes an error on windows where the source-map module cannot resolve the source maps.
// @see https://github.com/webpack-contrib/sass-loader/issues/366#issuecomment-279460722
// eslint-disable-next-line no-param-reassign
result.map.sourceRoot = path.normalize(result.map.sourceRoot);
// eslint-disable-next-line no-param-reassign
result.map.sources = result.map.sources.map(path.normalize);
} else {
// eslint-disable-next-line no-param-reassign
result.map = null;
}
result.stats.includedFiles.forEach(addNormalizedDependency);
callback(null, result.css.toString(), result.map);
});
}
/**
* Verifies that the implementation and version of Sass is supported by this loader.
*
* @param {Object} module
* @returns {Function}
*/
function getRenderFuncFromSassImpl(module) {
const { info } = module;
const components = info.split('\t');
if (components.length < 2) {
throw new Error(`Unknown Sass implementation "${info}".`);
}
const [implementation, version] = components;
if (!semver.valid(version)) {
throw new Error(`Invalid Sass version "${version}".`);
}
if (implementation === 'dart-sass') {
if (!semver.satisfies(version, '^1.3.0')) {
throw new Error(
`Dart Sass version ${version} is incompatible with ^1.3.0.`
);
}
return module.render.bind(module);
} else if (implementation === 'node-sass') {
if (!semver.satisfies(version, '^4.0.0')) {
throw new Error(
`Node Sass version ${version} is incompatible with ^4.0.0.`
);
}
// There is an issue with node-sass when async custom importers are used
// See https://github.com/sass/node-sass/issues/857#issuecomment-93594360
// We need to use a job queue to make sure that one thread is always available to the UV lib
if (nodeSassJobQueue === null) {
const threadPoolSize = Number(process.env.UV_THREADPOOL_SIZE || 4);
nodeSassJobQueue = async.queue(
module.render.bind(module),
threadPoolSize - 1
);
}
return nodeSassJobQueue.push.bind(nodeSassJobQueue);
}
throw new Error(`Unknown Sass implementation "${implementation}".`);
}
function getDefaultSassImpl() {
let sassImplPkg = 'node-sass';
try {
require.resolve('node-sass');
} catch (error) {
try {
require.resolve('sass');
sassImplPkg = 'sass';
} catch (ignoreError) {
sassImplPkg = 'node-sass';
}
}
// eslint-disable-next-line import/no-dynamic-require, global-require
return require(sassImplPkg);
}
module.exports = sassLoader;