forked from webpack/sass-loader
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
270 lines (233 loc) · 8.06 KB
/
index.js
File metadata and controls
270 lines (233 loc) · 8.06 KB
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
'use strict';
var utils = require('loader-utils');
var sass = require('node-sass');
var path = require('path');
var os = require('os');
var fs = require('fs');
var deasync = require('deasync');
// A typical sass error looks like this
var SassError = {
message: 'invalid property name',
column: 14,
line: 1,
file: 'stdin',
status: 1
};
var resolveError = /Cannot resolve/;
/**
* The sass-loader makes node-sass available to webpack modules.
*
* @param {string} content
* @returns {*}
*/
module.exports = function (content) {
var callback = this.async();
var isSync = typeof callback !== 'function';
var self = this;
var resourcePath = this.resourcePath;
var extensionMatcher = /\.(sass|scss)$/;
var fileExt;
var opt;
var contextMatch;
var extension;
/**
* Enhances the sass error with additional information about what actually went wrong.
*
* @param {SassError} err
*/
function formatSassError(err) {
var msg = err.message;
if (err.file === 'stdin') {
err.file = resourcePath;
}
// The 'Current dir' hint of node-sass does not help us, we're providing
// additional information by reading the err.file property
msg = msg.replace(/\s*Current dir:\s*/, '');
err.message = getFileExcerptIfPossible(err) +
msg.charAt(0).toUpperCase() + msg.slice(1) + os.EOL +
' in ' + err.file + ' (line ' + err.line + ', column ' + err.column + ')';
// Instruct webpack to hide the JS stack from the console
// Usually you're only interested in the SASS stack in this case.
err.hideStack = true;
}
/**
* Returns an importer that uses webpack's resolving algorithm.
*
* It's important that the returned function has the correct number of arguments
* (based on whether the call is sync or async) because otherwise node-sass doesn't exit.
*
* @returns {function}
*/
function getWebpackImporter() {
return function syncWebpackImporter(url, context) {
url = urlToRequest(url, context);
context = normalizeContext(context);
var result = null;
asyncResolve(self, url, context, function(ret) {
result = ret;
});
deasync.loopWhile(function() {return result === null;});
return result;
};
}
function urlToRequest(url, context) {
contextMatch = context.match(extensionMatcher);
// Add sass/scss extension if it is missing
// The extension is inherited from importing resource or the default is used
if (!url.match(extensionMatcher)) {
extension = contextMatch && contextMatch[0] || fileExt;
url = url + extension;
}
return utils.urlToRequest(url, opt.root);
}
function normalizeContext(context) {
// The first file is 'stdin' when we're using the data option
if (context === 'stdin') {
context = resourcePath;
}
return path.dirname(context);
}
this.cacheable();
opt = utils.parseQuery(this.query);
opt.data = content;
// Skip empty files, otherwise it will stop webpack, see issue #21
if (opt.data.trim() === '') {
return callback(null, content);
}
// opt.outputStyle
if (!opt.outputStyle && this.minimize) {
opt.outputStyle = 'compressed';
}
// opt.sourceMap
// Not using the `this.sourceMap` flag because css source maps are different
// @see https://github.com/webpack/css-loader/pull/40
if (opt.sourceMap) {
// deliberately overriding the sourceMap option
// this value is (currently) ignored by libsass when using the data input instead of file input
// however, it is still necessary for correct relative paths in result.map.sources
opt.sourceMap = this.options.output.path + '/sass.map';
}
// indentedSyntax is a boolean flag
opt.indentedSyntax = Boolean(opt.indentedSyntax);
fileExt = '.' + (opt.indentedSyntax? 'sass' : 'scss');
// opt.importer
opt.importer = getWebpackImporter();
// do the actual rendering
var result;
try {
result = sass.renderSync(opt);
} catch (err) {
formatSassError(err);
throw err;
}
if (result.map && result.map !== '{}') {
result.map = JSON.parse(result.map);
result.map.file = resourcePath;
// The first source is 'stdin' according to libsass because we've used the data input
// Now let's override that value with the correct relative path
result.map.sources[0] = path.relative(self.options.output.path, resourcePath);
} else {
result.map = null;
}
callback(null, result.css.toString(), result.map);
};
/**
* Tries to get an excerpt of the file where the error happened.
* Uses err.line and err.column.
*
* Returns an empty string if the excerpt could not be retrieved.
*
* @param {SassError} err
* @returns {string}
*/
function getFileExcerptIfPossible(err) {
var content;
try {
content = fs.readFileSync(err.file, 'utf8');
return os.EOL +
content.split(os.EOL)[err.line - 1] + os.EOL +
new Array(err.column - 1).join(' ') + '^' + os.EOL +
' ';
} catch (err) {
// If anything goes wrong here, we don't want any errors to be reported to the user
return '';
}
}
/**
* Tries to resolve the given url synchronously. If a resolve error occurs, a second try for the same
* module prefixed with an underscore is started.
*
* @param {object} loaderContext
* @param {string} url
* @param {string} context
* @returns {object}
*/
function syncResolve(loaderContext, url, context) {
var filename;
var basename;
try {
filename = loaderContext.resolveSync(context, url);
loaderContext.dependency && loaderContext.dependency(filename);
} catch (err) {
basename = path.basename(url);
if (requiresLookupForUnderscoreModule(err, basename)) {
url = addUnderscoreToBasename(url, basename);
return syncResolve(loaderContext, url, context);
}
// Unfortunately we can't return an error inside a custom importer yet
// @see https://github.com/sass/node-sass/issues/651#issuecomment-73317319
filename = url;
}
return {
file: filename
};
}
/**
* Tries to resolve the given url asynchronously. If a resolve error occurs, a second try for the same
* module prefixed with an underscore is started.
*
* @param {object} loaderContext
* @param {string} url
* @param {string} context
* @param {function} done
*/
function asyncResolve(loaderContext, url, context, done) {
loaderContext.resolve(context, url, function onWebpackResolve(err, filename) {
var basename;
if (err) {
basename = path.basename(url);
if (requiresLookupForUnderscoreModule(err, basename)) {
url = addUnderscoreToBasename(url, basename);
return asyncResolve(loaderContext, url, context, done);
}
// Unfortunately we can't return an error inside a custom importer yet
// @see https://github.com/sass/node-sass/issues/651#issuecomment-73317319
filename = url;
} else {
loaderContext.dependency && loaderContext.dependency(filename);
}
// Use self.loadModule() before calling done() to make imported files available to
// other webpack tools like postLoaders etc.?
done({
file: filename
});
});
}
/**
* Check whether its a resolve error and the basename does *not* start with an underscore.
*
* @param {Error} err
* @param {string} basename
* @returns {boolean}
*/
function requiresLookupForUnderscoreModule(err, basename) {
return resolveError.test(err.message) && basename.charAt(0) !== '_';
}
/**
* @param {string} url
* @param {string} basename
* @returns {string}
*/
function addUnderscoreToBasename(url, basename) {
return url.slice(0, -basename.length) + '_' + basename;
}