index.js
6.79 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
/*
* MIT License http://opensource.org/licenses/MIT
* Author: Ben Holloway @bholloway
*/
'use strict';
var path = require('path'),
fs = require('fs'),
loaderUtils = require('loader-utils'),
camelcase = require('camelcase'),
SourceMapConsumer = require('source-map').SourceMapConsumer;
var adjustSourceMap = require('adjust-sourcemap-loader/lib/process');
var valueProcessor = require('./lib/value-processor');
var joinFn = require('./lib/join-function');
var logToTestHarness = require('./lib/log-to-test-harness');
var PACKAGE_NAME = require('./package.json').name;
/**
* A webpack loader that resolves absolute url() paths relative to their original source file.
* Requires source-maps to do any meaningful work.
* @param {string} content Css content
* @param {object} sourceMap The source-map
* @returns {string|String}
*/
function resolveUrlLoader(content, sourceMap) {
/* jshint validthis:true */
// details of the file being processed
var loader = this;
// a relative loader.context is a problem
if (/^\./.test(loader.context)) {
return handleAsError(
'webpack misconfiguration',
'loader.context is relative, expected absolute'
);
}
// webpack 1: prefer loader query, else options object
// webpack 2: prefer loader options
// webpack 3: deprecate loader.options object
// webpack 4: loader.options no longer defined
var options = Object.assign(
{
sourceMap: loader.sourceMap,
engine : 'postcss',
silent : false,
absolute : false,
keepQuery: false,
removeCR : false,
root : false,
debug : false,
join : joinFn.defaultJoin
},
!!loader.options && loader.options[camelcase(PACKAGE_NAME)],
loaderUtils.getOptions(loader)
);
// maybe log options for the test harness
logToTestHarness(options);
// defunct options
if ('attempts' in options) {
handleAsWarning(
'loader misconfiguration',
'"attempts" option is defunct (consider "join" option if search is needed)'
);
}
if ('includeRoot' in options) {
handleAsWarning(
'loader misconfiguration',
'"includeRoot" option is defunct (consider "join" option if search is needed)'
);
}
if ('fail' in options) {
handleAsWarning(
'loader misconfiguration',
'"fail" option is defunct'
);
}
// validate join option
if (typeof options.join !== 'function') {
return handleAsError(
'loader misconfiguration',
'"join" option must be a Function'
);
} else if (options.join.length !== 2) {
return handleAsError(
'loader misconfiguration',
'"join" Function must take exactly 2 arguments (filename and options hash)'
);
}
// validate root option
if (typeof options.root === 'string') {
var isValid = (options.root === '') ||
(path.isAbsolute(options.root) && fs.existsSync(options.root) && fs.statSync(options.root).isDirectory());
if (!isValid) {
return handleAsError(
'loader misconfiguration',
'"root" option must be an empty string or an absolute path to an existing directory'
);
}
} else if (options.root !== false) {
handleAsWarning(
'loader misconfiguration',
'"root" option must be string where used or false where unused'
);
}
// loader result is cacheable
loader.cacheable();
// incoming source-map
var sourceMapConsumer, absSourceMap;
if (sourceMap) {
// support non-standard string encoded source-map (per less-loader)
if (typeof sourceMap === 'string') {
try {
sourceMap = JSON.parse(sourceMap);
}
catch (exception) {
return handleAsError(
'source-map error',
'cannot parse source-map string (from less-loader?)'
);
}
}
// leverage adjust-sourcemap-loader's codecs to avoid having to make any assumptions about the sourcemap
// historically this is a regular source of breakage
try {
absSourceMap = adjustSourceMap(loader, {format: 'absolute'}, sourceMap);
}
catch (exception) {
return handleAsError(
'source-map error',
exception.message
);
}
// prepare the adjusted sass source-map for later look-ups
sourceMapConsumer = new SourceMapConsumer(absSourceMap);
}
// choose a CSS engine
var enginePath = /^\w+$/.test(options.engine) && path.join(__dirname, 'lib', 'engine', options.engine + '.js');
var isValidEngine = fs.existsSync(enginePath);
if (!isValidEngine) {
return handleAsError(
'loader misconfiguration',
'"engine" option is not valid'
);
}
// process async
var callback = loader.async();
Promise
.resolve(require(enginePath)(loader.resourcePath, content, {
outputSourceMap : !!options.sourceMap,
transformDeclaration: valueProcessor(loader.resourcePath, options),
absSourceMap : absSourceMap,
sourceMapConsumer : sourceMapConsumer,
removeCR : options.removeCR
}))
.catch(onFailure)
.then(onSuccess);
function onFailure(error) {
callback(encodeError('CSS error', error));
}
function onSuccess(reworked) {
if (reworked) {
// complete with source-map
// source-map sources are relative to the file being processed
if (options.sourceMap) {
var finalMap = adjustSourceMap(loader, {format: 'sourceRelative'}, reworked.map);
callback(null, reworked.content, finalMap);
}
// complete without source-map
else {
callback(null, reworked.content);
}
}
}
/**
* Push a warning for the given exception and return the original content.
* @param {string} label Summary of the error
* @param {string|Error} [exception] Optional extended error details
* @returns {string} The original CSS content
*/
function handleAsWarning(label, exception) {
if (!options.silent) {
loader.emitWarning(encodeError(label, exception));
}
return content;
}
/**
* Push a warning for the given exception and return the original content.
* @param {string} label Summary of the error
* @param {string|Error} [exception] Optional extended error details
* @returns {string} The original CSS content
*/
function handleAsError(label, exception) {
loader.emitError(encodeError(label, exception));
return content;
}
function encodeError(label, exception) {
return new Error(
[
PACKAGE_NAME,
': ',
[label]
.concat(
(typeof exception === 'string') && exception ||
(exception instanceof Error) && [exception.message, exception.stack.split('\n')[1].trim()] ||
[]
)
.filter(Boolean)
.join('\n ')
].join('')
);
}
}
module.exports = Object.assign(resolveUrlLoader, joinFn);