@parcel/transformer-css 2.0.0-beta.1 → 2.0.0-nightly.1002
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/CSSTransformer.js +311 -103
- package/package.json +19 -10
- package/src/CSSTransformer.js +259 -102
package/lib/CSSTransformer.js
CHANGED
|
@@ -5,32 +5,113 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
function _hash() {
|
|
9
|
+
const data = require("@parcel/hash");
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
_hash = function () {
|
|
12
|
+
return data;
|
|
13
|
+
};
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function _sourceMap() {
|
|
19
|
+
const data = _interopRequireDefault(require("@parcel/source-map"));
|
|
20
|
+
|
|
21
|
+
_sourceMap = function () {
|
|
22
|
+
return data;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _plugin() {
|
|
29
|
+
const data = require("@parcel/plugin");
|
|
30
|
+
|
|
31
|
+
_plugin = function () {
|
|
32
|
+
return data;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function _utils() {
|
|
39
|
+
const data = require("@parcel/utils");
|
|
40
|
+
|
|
41
|
+
_utils = function () {
|
|
42
|
+
return data;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function _postcss() {
|
|
49
|
+
const data = _interopRequireDefault(require("postcss"));
|
|
50
|
+
|
|
51
|
+
_postcss = function () {
|
|
52
|
+
return data;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return data;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _nullthrows() {
|
|
59
|
+
const data = _interopRequireDefault(require("nullthrows"));
|
|
60
|
+
|
|
61
|
+
_nullthrows = function () {
|
|
62
|
+
return data;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return data;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function _postcssValueParser() {
|
|
69
|
+
const data = _interopRequireDefault(require("postcss-value-parser"));
|
|
70
|
+
|
|
71
|
+
_postcssValueParser = function () {
|
|
72
|
+
return data;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function _semver() {
|
|
79
|
+
const data = _interopRequireDefault(require("semver"));
|
|
13
80
|
|
|
14
|
-
|
|
81
|
+
_semver = function () {
|
|
82
|
+
return data;
|
|
83
|
+
};
|
|
15
84
|
|
|
16
|
-
|
|
85
|
+
return data;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function _path() {
|
|
89
|
+
const data = _interopRequireDefault(require("path"));
|
|
17
90
|
|
|
18
|
-
|
|
91
|
+
_path = function () {
|
|
92
|
+
return data;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return data;
|
|
96
|
+
}
|
|
19
97
|
|
|
20
98
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
21
99
|
|
|
22
|
-
const URL_RE = /url\s*\(
|
|
100
|
+
const URL_RE = /url\s*\(/;
|
|
23
101
|
const IMPORT_RE = /@import/;
|
|
102
|
+
const COMPOSES_RE = /composes:.+from\s*("|').*("|')\s*;?/;
|
|
103
|
+
const FROM_IMPORT_RE = /.+from\s*(?:"|')(.*)(?:"|')\s*;?/;
|
|
104
|
+
const MODULE_BY_NAME_RE = /\.module\./;
|
|
24
105
|
|
|
25
106
|
function canHaveDependencies(filePath, code) {
|
|
26
107
|
return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code);
|
|
27
108
|
}
|
|
28
109
|
|
|
29
|
-
var _default = new _plugin.Transformer({
|
|
110
|
+
var _default = new (_plugin().Transformer)({
|
|
30
111
|
canReuseAST({
|
|
31
112
|
ast
|
|
32
113
|
}) {
|
|
33
|
-
return ast.type === 'postcss' && _semver.default.satisfies(ast.version, '^
|
|
114
|
+
return ast.type === 'postcss' && _semver().default.satisfies(ast.version, '^8.2.1');
|
|
34
115
|
},
|
|
35
116
|
|
|
36
117
|
async parse({
|
|
@@ -42,109 +123,134 @@ var _default = new _plugin.Transformer({
|
|
|
42
123
|
// to be filled in later. When the CSS transformer runs, it would pick that up and try to
|
|
43
124
|
// resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do
|
|
44
125
|
// it this way since the resulting CSS doesn't need to be re-parsed.
|
|
45
|
-
|
|
126
|
+
let isCSSModule = asset.meta.cssModulesCompiled !== true && MODULE_BY_NAME_RE.test(asset.filePath);
|
|
127
|
+
|
|
128
|
+
if (asset.meta.hasDependencies === false && !isCSSModule) {
|
|
46
129
|
return null;
|
|
47
130
|
}
|
|
48
131
|
|
|
49
132
|
let code = await asset.getCode();
|
|
50
133
|
|
|
51
|
-
if (code != null && !canHaveDependencies(asset.filePath, code)) {
|
|
134
|
+
if (code != null && !canHaveDependencies(asset.filePath, code) && !isCSSModule) {
|
|
52
135
|
return null;
|
|
53
136
|
}
|
|
54
137
|
|
|
55
138
|
return {
|
|
56
139
|
type: 'postcss',
|
|
57
|
-
version: '
|
|
58
|
-
program: _postcss.default.parse(code, {
|
|
140
|
+
version: '8.2.1',
|
|
141
|
+
program: _postcss().default.parse(code, {
|
|
59
142
|
from: asset.filePath
|
|
60
|
-
})
|
|
143
|
+
}).toJSON()
|
|
61
144
|
};
|
|
62
145
|
},
|
|
63
146
|
|
|
64
147
|
async transform({
|
|
65
|
-
asset
|
|
148
|
+
asset,
|
|
149
|
+
resolve,
|
|
150
|
+
options
|
|
66
151
|
}) {
|
|
67
152
|
// Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
|
|
68
153
|
// For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
|
|
154
|
+
let env = asset.env;
|
|
69
155
|
asset.setEnvironment({
|
|
70
156
|
context: 'browser',
|
|
71
157
|
engines: {
|
|
72
158
|
browsers: asset.env.engines.browsers
|
|
73
159
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (asset.isSplittable == null) {
|
|
79
|
-
asset.isSplittable = true;
|
|
80
|
-
} // Check for `hasDependencies` being false here as well, as it's possible
|
|
160
|
+
shouldOptimize: asset.env.shouldOptimize,
|
|
161
|
+
sourceMap: asset.env.sourceMap
|
|
162
|
+
});
|
|
163
|
+
let isCSSModule = asset.meta.cssModulesCompiled !== true && MODULE_BY_NAME_RE.test(asset.filePath); // Check for `hasDependencies` being false here as well, as it's possible
|
|
81
164
|
// another transformer (such as PostCSSTransformer) has already parsed an
|
|
82
165
|
// ast and CSSTransformer's parse was never called.
|
|
83
166
|
|
|
84
|
-
|
|
85
167
|
let ast = await asset.getAST();
|
|
86
168
|
|
|
87
|
-
if (!ast || asset.meta.hasDependencies === false) {
|
|
169
|
+
if (!ast || asset.meta.hasDependencies === false && !isCSSModule) {
|
|
88
170
|
return [asset];
|
|
89
171
|
}
|
|
90
172
|
|
|
173
|
+
let program = _postcss().default.fromJSON(ast.program);
|
|
174
|
+
|
|
175
|
+
let assets = [asset];
|
|
176
|
+
|
|
177
|
+
if (isCSSModule) {
|
|
178
|
+
assets = await compileCSSModules(asset, env, program, resolve, options);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (asset.meta.hasDependencies === false) {
|
|
182
|
+
return assets;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let originalSourceMap = await asset.getMap();
|
|
186
|
+
|
|
187
|
+
let createLoc = (start, specifier, lineOffset, colOffset, o) => {
|
|
188
|
+
let loc = (0, _utils().createDependencyLocation)(start, specifier, lineOffset, colOffset, o);
|
|
189
|
+
|
|
190
|
+
if (originalSourceMap) {
|
|
191
|
+
loc = (0, _utils().remapSourceLocation)(loc, originalSourceMap);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return loc;
|
|
195
|
+
};
|
|
196
|
+
|
|
91
197
|
let isDirty = false;
|
|
92
|
-
|
|
93
|
-
let params = (0, _postcssValueParser.default)(rule.params);
|
|
198
|
+
program.walkAtRules('import', rule => {
|
|
199
|
+
let params = (0, _postcssValueParser().default)(rule.params);
|
|
94
200
|
let [name, ...media] = params.nodes;
|
|
95
|
-
let
|
|
201
|
+
let specifier;
|
|
96
202
|
|
|
97
203
|
if (name.type === 'function' && name.value === 'url' && name.nodes.length) {
|
|
98
204
|
name = name.nodes[0];
|
|
99
205
|
}
|
|
100
206
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!
|
|
104
|
-
throw new Error('Could not find import name for ' + rule);
|
|
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
|
-
asset.addDependency(dep);
|
|
130
|
-
rule.remove(); // }
|
|
131
|
-
}
|
|
207
|
+
specifier = name.value;
|
|
208
|
+
|
|
209
|
+
if (!specifier) {
|
|
210
|
+
throw new Error('Could not find import name for ' + String(rule));
|
|
211
|
+
} // If this came from an inline <style> tag, don't inline the imported file. Replace with the correct URL instead.
|
|
212
|
+
// TODO: run CSSPackager on inline style tags.
|
|
213
|
+
// let inlineHTML =
|
|
214
|
+
// this.options.rendition && this.options.rendition.inlineHTML;
|
|
215
|
+
// if (inlineHTML) {
|
|
216
|
+
// name.value = asset.addURLDependency(dep, {loc: rule.source.start});
|
|
217
|
+
// rule.params = params.toString();
|
|
218
|
+
// } else {
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
media = _postcssValueParser().default.stringify(media).trim();
|
|
222
|
+
let dep = {
|
|
223
|
+
specifier,
|
|
224
|
+
specifierType: 'url',
|
|
225
|
+
// Offset by 8 as it does not include `@import `
|
|
226
|
+
loc: createLoc((0, _nullthrows().default)(rule.source.start), specifier, 0, 8),
|
|
227
|
+
meta: {
|
|
228
|
+
// For the glob resolver to distinguish between `@import` and other URL dependencies.
|
|
229
|
+
isCSSImport: true,
|
|
230
|
+
media
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
asset.addDependency(dep);
|
|
234
|
+
rule.remove(); // }
|
|
132
235
|
|
|
133
236
|
isDirty = true;
|
|
134
237
|
});
|
|
135
|
-
|
|
238
|
+
program.walkDecls(decl => {
|
|
136
239
|
if (URL_RE.test(decl.value)) {
|
|
137
|
-
let parsed = (0, _postcssValueParser.default)(decl.value);
|
|
240
|
+
let parsed = (0, _postcssValueParser().default)(decl.value);
|
|
138
241
|
let isDeclDirty = false;
|
|
139
242
|
parsed.walk(node => {
|
|
140
243
|
if (node.type === 'function' && node.value === 'url' && node.nodes.length > 0 && !node.nodes[0].value.startsWith('#') // IE's `behavior: url(#default#VML)`
|
|
141
244
|
) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
245
|
+
let urlNode = node.nodes[0];
|
|
246
|
+
let url = asset.addURLDependency(urlNode.value, {
|
|
247
|
+
loc: decl.source && decl.source.start && createLoc(decl.source.start, urlNode.value, 0, decl.source.start.offset + urlNode.sourceIndex + 1, 0)
|
|
248
|
+
});
|
|
249
|
+
isDeclDirty = urlNode.value !== url;
|
|
250
|
+
urlNode.type = 'string';
|
|
251
|
+
urlNode.quote = '"';
|
|
252
|
+
urlNode.value = url;
|
|
253
|
+
}
|
|
148
254
|
});
|
|
149
255
|
|
|
150
256
|
if (isDeclDirty) {
|
|
@@ -155,60 +261,43 @@ var _default = new _plugin.Transformer({
|
|
|
155
261
|
});
|
|
156
262
|
|
|
157
263
|
if (isDirty) {
|
|
158
|
-
asset.setAST(ast
|
|
264
|
+
asset.setAST({ ...ast,
|
|
265
|
+
program: program.toJSON()
|
|
266
|
+
});
|
|
159
267
|
}
|
|
160
268
|
|
|
161
|
-
return
|
|
269
|
+
return assets;
|
|
162
270
|
},
|
|
163
271
|
|
|
164
272
|
async generate({
|
|
273
|
+
asset,
|
|
165
274
|
ast,
|
|
166
275
|
options
|
|
167
276
|
}) {
|
|
168
|
-
let
|
|
169
|
-
|
|
170
|
-
if (Object.getPrototypeOf(ast.program) === Object.prototype) {
|
|
171
|
-
root = _postcss.default.root(ast.program);
|
|
172
|
-
|
|
173
|
-
let convert = (parent, node, index) => {
|
|
174
|
-
let type = node.type === 'atrule' ? 'atRule' : node.type;
|
|
175
|
-
|
|
176
|
-
let result = _postcss.default[type](node);
|
|
177
|
-
|
|
178
|
-
result.parent = parent;
|
|
179
|
-
|
|
180
|
-
if (parent) {
|
|
181
|
-
parent.nodes[index] = result;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (result.walk) {
|
|
185
|
-
// $FlowFixMe
|
|
186
|
-
const container = result;
|
|
187
|
-
container.each((node, index) => {
|
|
188
|
-
convert(container, node, index);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
root.each((node, index) => convert(root, node, index));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
let result = await (0, _postcss.default)().process(root, {
|
|
277
|
+
let result = await (0, _postcss().default)().process(_postcss().default.fromJSON(ast.program), {
|
|
197
278
|
from: undefined,
|
|
198
279
|
to: options.projectRoot + '/index',
|
|
199
280
|
map: {
|
|
200
281
|
annotation: false,
|
|
201
|
-
inline: false
|
|
282
|
+
inline: false,
|
|
283
|
+
sourcesContent: false
|
|
202
284
|
},
|
|
203
285
|
// Pass postcss's own stringifier to it to silence its warning
|
|
204
286
|
// as we don't want to perform any transformations -- only generate
|
|
205
|
-
stringifier: _postcss.default.stringify
|
|
287
|
+
stringifier: _postcss().default.stringify
|
|
206
288
|
});
|
|
207
|
-
let map;
|
|
289
|
+
let map = null;
|
|
290
|
+
let originalSourceMap = await asset.getMap();
|
|
208
291
|
|
|
209
292
|
if (result.map != null) {
|
|
210
|
-
map = new _sourceMap.default();
|
|
211
|
-
map.
|
|
293
|
+
map = new (_sourceMap().default)(options.projectRoot);
|
|
294
|
+
map.addVLQMap(result.map.toJSON());
|
|
295
|
+
|
|
296
|
+
if (originalSourceMap) {
|
|
297
|
+
map.extends(originalSourceMap.toBuffer());
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
map = originalSourceMap;
|
|
212
301
|
}
|
|
213
302
|
|
|
214
303
|
return {
|
|
@@ -219,4 +308,123 @@ var _default = new _plugin.Transformer({
|
|
|
219
308
|
|
|
220
309
|
});
|
|
221
310
|
|
|
222
|
-
exports.default = _default;
|
|
311
|
+
exports.default = _default;
|
|
312
|
+
|
|
313
|
+
async function compileCSSModules(asset, env, program, resolve, options) {
|
|
314
|
+
let cssModules;
|
|
315
|
+
let code = asset.isASTDirty() ? null : await asset.getCode();
|
|
316
|
+
|
|
317
|
+
if (code == null || COMPOSES_RE.test(code)) {
|
|
318
|
+
program.walkDecls(decl => {
|
|
319
|
+
let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
|
|
320
|
+
|
|
321
|
+
if (decl.prop === 'composes' && importPath != null) {
|
|
322
|
+
let parsed = (0, _postcssValueParser().default)(decl.value);
|
|
323
|
+
let start = decl.source.start;
|
|
324
|
+
parsed.walk(node => {
|
|
325
|
+
if (node.type === 'string') {
|
|
326
|
+
asset.addDependency({
|
|
327
|
+
specifier: importPath,
|
|
328
|
+
specifierType: 'url',
|
|
329
|
+
loc: start ? {
|
|
330
|
+
filePath: asset.filePath,
|
|
331
|
+
start,
|
|
332
|
+
end: {
|
|
333
|
+
line: start.line,
|
|
334
|
+
column: start.column + importPath.length
|
|
335
|
+
}
|
|
336
|
+
} : undefined
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let postcssModules = await options.packageManager.require('postcss-modules', asset.filePath, {
|
|
345
|
+
range: '^4.3.0',
|
|
346
|
+
saveDev: true,
|
|
347
|
+
shouldAutoInstall: options.shouldAutoInstall
|
|
348
|
+
});
|
|
349
|
+
let {
|
|
350
|
+
root
|
|
351
|
+
} = await (0, _postcss().default)([postcssModules({
|
|
352
|
+
getJSON: (filename, json) => cssModules = json,
|
|
353
|
+
Loader: await createLoader(asset, resolve, options),
|
|
354
|
+
generateScopedName: (name, filename) => `${name}_${(0, _hash().hashString)(_path().default.relative(options.projectRoot, filename)).substr(0, 6)}`
|
|
355
|
+
})]).process(program, {
|
|
356
|
+
from: asset.filePath,
|
|
357
|
+
to: asset.filePath
|
|
358
|
+
});
|
|
359
|
+
asset.setAST({
|
|
360
|
+
type: 'postcss',
|
|
361
|
+
version: '8.2.1',
|
|
362
|
+
program: root.toJSON()
|
|
363
|
+
});
|
|
364
|
+
let assets = [asset];
|
|
365
|
+
|
|
366
|
+
if (cssModules) {
|
|
367
|
+
// $FlowFixMe
|
|
368
|
+
let cssModulesList = Object.entries(cssModules);
|
|
369
|
+
let deps = asset.getDependencies().filter(dep => dep.priority === 'sync');
|
|
370
|
+
let code;
|
|
371
|
+
|
|
372
|
+
if (deps.length > 0) {
|
|
373
|
+
code = `
|
|
374
|
+
module.exports = Object.assign({}, ${deps.map(dep => `require(${JSON.stringify(dep.specifier)})`).join(', ')}, ${JSON.stringify(cssModules, null, 2)});
|
|
375
|
+
`;
|
|
376
|
+
} else {
|
|
377
|
+
code = cssModulesList.map( // This syntax enables shaking the invidual statements, so that unused classes don't even exist in JS.
|
|
378
|
+
([className, classNameHashed]) => `module.exports[${JSON.stringify(className)}] = ${JSON.stringify(classNameHashed)};`).join('\n');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
asset.symbols.ensure();
|
|
382
|
+
|
|
383
|
+
for (let [k, v] of cssModulesList) {
|
|
384
|
+
asset.symbols.set(k, v);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
asset.symbols.set('default', 'default');
|
|
388
|
+
assets.push({
|
|
389
|
+
type: 'js',
|
|
390
|
+
content: code,
|
|
391
|
+
env
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return assets;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function createLoader(asset, resolve, options) {
|
|
399
|
+
let {
|
|
400
|
+
default: FileSystemLoader
|
|
401
|
+
} = await options.packageManager.require('postcss-modules/build/css-loader-core/loader', asset.filePath);
|
|
402
|
+
return class extends FileSystemLoader {
|
|
403
|
+
async fetch(composesPath, relativeTo) {
|
|
404
|
+
let importPath = composesPath.replace(/^["']|["']$/g, '');
|
|
405
|
+
let resolved = await resolve(relativeTo, importPath);
|
|
406
|
+
|
|
407
|
+
let rootRelativePath = _path().default.resolve(_path().default.dirname(relativeTo), resolved);
|
|
408
|
+
|
|
409
|
+
let root = _path().default.resolve('/'); // fixes an issue on windows which is part of the css-modules-loader-core
|
|
410
|
+
// see https://github.com/css-modules/css-modules-loader-core/issues/230
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
if (rootRelativePath.startsWith(root)) {
|
|
414
|
+
rootRelativePath = rootRelativePath.substr(root.length);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let source = await asset.fs.readFile(resolved, 'utf-8');
|
|
418
|
+
let {
|
|
419
|
+
exportTokens
|
|
420
|
+
} = await this.core.load(source, rootRelativePath, undefined, // $FlowFixMe[method-unbinding]
|
|
421
|
+
this.fetch.bind(this));
|
|
422
|
+
return exportTokens;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
get finalSource() {
|
|
426
|
+
return '';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
};
|
|
430
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parcel/transformer-css",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-nightly.1002+5530a6ef",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
+
"funding": {
|
|
9
|
+
"type": "opencollective",
|
|
10
|
+
"url": "https://opencollective.com/parcel"
|
|
11
|
+
},
|
|
8
12
|
"repository": {
|
|
9
13
|
"type": "git",
|
|
10
14
|
"url": "https://github.com/parcel-bundler/parcel.git"
|
|
@@ -12,16 +16,21 @@
|
|
|
12
16
|
"main": "lib/CSSTransformer.js",
|
|
13
17
|
"source": "src/CSSTransformer.js",
|
|
14
18
|
"engines": {
|
|
15
|
-
"node": ">=
|
|
16
|
-
"parcel": "
|
|
19
|
+
"node": ">= 12.0.0",
|
|
20
|
+
"parcel": "2.0.0-nightly.1000+5530a6ef"
|
|
17
21
|
},
|
|
18
22
|
"dependencies": {
|
|
19
|
-
"@parcel/
|
|
20
|
-
"@parcel/
|
|
21
|
-
"@parcel/
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
23
|
+
"@parcel/hash": "2.3.2-nightly.2625+5530a6ef",
|
|
24
|
+
"@parcel/plugin": "2.0.0-nightly.1002+5530a6ef",
|
|
25
|
+
"@parcel/source-map": "^2.0.0",
|
|
26
|
+
"@parcel/utils": "2.0.0-nightly.1002+5530a6ef",
|
|
27
|
+
"nullthrows": "^1.1.1",
|
|
28
|
+
"postcss": "^8.4.5",
|
|
29
|
+
"postcss-value-parser": "^4.2.0",
|
|
30
|
+
"semver": "^5.7.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"postcss-modules": "^4.3.0"
|
|
25
34
|
},
|
|
26
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "5530a6eff8b619873353baeb0457ae4ec591e9fa"
|
|
27
36
|
}
|
package/src/CSSTransformer.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
3
|
+
import type {Root} from 'postcss';
|
|
4
|
+
import type {FilePath, MutableAsset, PluginOptions} from '@parcel/types';
|
|
5
5
|
|
|
6
|
+
import {hashString} from '@parcel/hash';
|
|
6
7
|
import SourceMap from '@parcel/source-map';
|
|
7
8
|
import {Transformer} from '@parcel/plugin';
|
|
8
|
-
import {createDependencyLocation,
|
|
9
|
+
import {createDependencyLocation, remapSourceLocation} from '@parcel/utils';
|
|
9
10
|
import postcss from 'postcss';
|
|
11
|
+
import nullthrows from 'nullthrows';
|
|
10
12
|
import valueParser from 'postcss-value-parser';
|
|
11
13
|
import semver from 'semver';
|
|
14
|
+
import path from 'path';
|
|
12
15
|
|
|
13
|
-
const URL_RE = /url\s*\(
|
|
16
|
+
const URL_RE = /url\s*\(/;
|
|
14
17
|
const IMPORT_RE = /@import/;
|
|
18
|
+
const COMPOSES_RE = /composes:.+from\s*("|').*("|')\s*;?/;
|
|
19
|
+
const FROM_IMPORT_RE = /.+from\s*(?:"|')(.*)(?:"|')\s*;?/;
|
|
20
|
+
const MODULE_BY_NAME_RE = /\.module\./;
|
|
15
21
|
|
|
16
22
|
function canHaveDependencies(filePath: FilePath, code: string) {
|
|
17
23
|
return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code);
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
export default new Transformer({
|
|
26
|
+
export default (new Transformer({
|
|
21
27
|
canReuseAST({ast}) {
|
|
22
|
-
return ast.type === 'postcss' && semver.satisfies(ast.version, '^
|
|
28
|
+
return ast.type === 'postcss' && semver.satisfies(ast.version, '^8.2.1');
|
|
23
29
|
},
|
|
24
30
|
|
|
25
31
|
async parse({asset}) {
|
|
@@ -29,54 +35,88 @@ export default new Transformer({
|
|
|
29
35
|
// to be filled in later. When the CSS transformer runs, it would pick that up and try to
|
|
30
36
|
// resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do
|
|
31
37
|
// it this way since the resulting CSS doesn't need to be re-parsed.
|
|
32
|
-
|
|
38
|
+
let isCSSModule =
|
|
39
|
+
asset.meta.cssModulesCompiled !== true &&
|
|
40
|
+
MODULE_BY_NAME_RE.test(asset.filePath);
|
|
41
|
+
if (asset.meta.hasDependencies === false && !isCSSModule) {
|
|
33
42
|
return null;
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
let code = await asset.getCode();
|
|
37
|
-
if (
|
|
46
|
+
if (
|
|
47
|
+
code != null &&
|
|
48
|
+
!canHaveDependencies(asset.filePath, code) &&
|
|
49
|
+
!isCSSModule
|
|
50
|
+
) {
|
|
38
51
|
return null;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
return {
|
|
42
55
|
type: 'postcss',
|
|
43
|
-
version: '
|
|
44
|
-
program: postcss
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
version: '8.2.1',
|
|
57
|
+
program: postcss
|
|
58
|
+
.parse(code, {
|
|
59
|
+
from: asset.filePath,
|
|
60
|
+
})
|
|
61
|
+
.toJSON(),
|
|
47
62
|
};
|
|
48
63
|
},
|
|
49
64
|
|
|
50
|
-
async transform({asset}) {
|
|
65
|
+
async transform({asset, resolve, options}) {
|
|
51
66
|
// Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
|
|
52
67
|
// For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
|
|
68
|
+
let env = asset.env;
|
|
53
69
|
asset.setEnvironment({
|
|
54
70
|
context: 'browser',
|
|
55
71
|
engines: {
|
|
56
72
|
browsers: asset.env.engines.browsers,
|
|
57
73
|
},
|
|
58
|
-
|
|
74
|
+
shouldOptimize: asset.env.shouldOptimize,
|
|
75
|
+
sourceMap: asset.env.sourceMap,
|
|
59
76
|
});
|
|
60
77
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
asset.isSplittable = true;
|
|
65
|
-
}
|
|
78
|
+
let isCSSModule =
|
|
79
|
+
asset.meta.cssModulesCompiled !== true &&
|
|
80
|
+
MODULE_BY_NAME_RE.test(asset.filePath);
|
|
66
81
|
|
|
67
82
|
// Check for `hasDependencies` being false here as well, as it's possible
|
|
68
83
|
// another transformer (such as PostCSSTransformer) has already parsed an
|
|
69
84
|
// ast and CSSTransformer's parse was never called.
|
|
70
85
|
let ast = await asset.getAST();
|
|
71
|
-
if (!ast || asset.meta.hasDependencies === false) {
|
|
86
|
+
if (!ast || (asset.meta.hasDependencies === false && !isCSSModule)) {
|
|
72
87
|
return [asset];
|
|
73
88
|
}
|
|
74
89
|
|
|
90
|
+
let program: Root = postcss.fromJSON(ast.program);
|
|
91
|
+
let assets = [asset];
|
|
92
|
+
if (isCSSModule) {
|
|
93
|
+
assets = await compileCSSModules(asset, env, program, resolve, options);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (asset.meta.hasDependencies === false) {
|
|
97
|
+
return assets;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let originalSourceMap = await asset.getMap();
|
|
101
|
+
let createLoc = (start, specifier, lineOffset, colOffset, o) => {
|
|
102
|
+
let loc = createDependencyLocation(
|
|
103
|
+
start,
|
|
104
|
+
specifier,
|
|
105
|
+
lineOffset,
|
|
106
|
+
colOffset,
|
|
107
|
+
o,
|
|
108
|
+
);
|
|
109
|
+
if (originalSourceMap) {
|
|
110
|
+
loc = remapSourceLocation(loc, originalSourceMap);
|
|
111
|
+
}
|
|
112
|
+
return loc;
|
|
113
|
+
};
|
|
114
|
+
|
|
75
115
|
let isDirty = false;
|
|
76
|
-
|
|
116
|
+
program.walkAtRules('import', rule => {
|
|
77
117
|
let params = valueParser(rule.params);
|
|
78
118
|
let [name, ...media] = params.nodes;
|
|
79
|
-
let
|
|
119
|
+
let specifier;
|
|
80
120
|
if (
|
|
81
121
|
name.type === 'function' &&
|
|
82
122
|
name.value === 'url' &&
|
|
@@ -85,52 +125,39 @@ export default new Transformer({
|
|
|
85
125
|
name = name.nodes[0];
|
|
86
126
|
}
|
|
87
127
|
|
|
88
|
-
|
|
128
|
+
specifier = name.value;
|
|
89
129
|
|
|
90
|
-
if (!
|
|
91
|
-
throw new Error('Could not find import name for ' + rule);
|
|
130
|
+
if (!specifier) {
|
|
131
|
+
throw new Error('Could not find import name for ' + String(rule));
|
|
92
132
|
}
|
|
93
133
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
rule.source.start,
|
|
118
|
-
moduleSpecifier,
|
|
119
|
-
0,
|
|
120
|
-
8,
|
|
121
|
-
),
|
|
122
|
-
meta: {
|
|
123
|
-
media,
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
asset.addDependency(dep);
|
|
127
|
-
rule.remove();
|
|
128
|
-
// }
|
|
129
|
-
}
|
|
134
|
+
// If this came from an inline <style> tag, don't inline the imported file. Replace with the correct URL instead.
|
|
135
|
+
// TODO: run CSSPackager on inline style tags.
|
|
136
|
+
// let inlineHTML =
|
|
137
|
+
// this.options.rendition && this.options.rendition.inlineHTML;
|
|
138
|
+
// if (inlineHTML) {
|
|
139
|
+
// name.value = asset.addURLDependency(dep, {loc: rule.source.start});
|
|
140
|
+
// rule.params = params.toString();
|
|
141
|
+
// } else {
|
|
142
|
+
media = valueParser.stringify(media).trim();
|
|
143
|
+
let dep = {
|
|
144
|
+
specifier,
|
|
145
|
+
specifierType: 'url',
|
|
146
|
+
// Offset by 8 as it does not include `@import `
|
|
147
|
+
loc: createLoc(nullthrows(rule.source.start), specifier, 0, 8),
|
|
148
|
+
meta: {
|
|
149
|
+
// For the glob resolver to distinguish between `@import` and other URL dependencies.
|
|
150
|
+
isCSSImport: true,
|
|
151
|
+
media,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
asset.addDependency(dep);
|
|
155
|
+
rule.remove();
|
|
156
|
+
// }
|
|
130
157
|
isDirty = true;
|
|
131
158
|
});
|
|
132
159
|
|
|
133
|
-
|
|
160
|
+
program.walkDecls(decl => {
|
|
134
161
|
if (URL_RE.test(decl.value)) {
|
|
135
162
|
let parsed = valueParser(decl.value);
|
|
136
163
|
let isDeclDirty = false;
|
|
@@ -142,14 +169,23 @@ export default new Transformer({
|
|
|
142
169
|
node.nodes.length > 0 &&
|
|
143
170
|
!node.nodes[0].value.startsWith('#') // IE's `behavior: url(#default#VML)`
|
|
144
171
|
) {
|
|
145
|
-
let
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
172
|
+
let urlNode = node.nodes[0];
|
|
173
|
+
let url = asset.addURLDependency(urlNode.value, {
|
|
174
|
+
loc:
|
|
175
|
+
decl.source &&
|
|
176
|
+
decl.source.start &&
|
|
177
|
+
createLoc(
|
|
178
|
+
decl.source.start,
|
|
179
|
+
urlNode.value,
|
|
180
|
+
0,
|
|
181
|
+
decl.source.start.offset + urlNode.sourceIndex + 1,
|
|
182
|
+
0,
|
|
183
|
+
),
|
|
150
184
|
});
|
|
151
|
-
isDeclDirty =
|
|
152
|
-
|
|
185
|
+
isDeclDirty = urlNode.value !== url;
|
|
186
|
+
urlNode.type = 'string';
|
|
187
|
+
urlNode.quote = '"';
|
|
188
|
+
urlNode.value = url;
|
|
153
189
|
}
|
|
154
190
|
});
|
|
155
191
|
|
|
@@ -161,54 +197,39 @@ export default new Transformer({
|
|
|
161
197
|
});
|
|
162
198
|
|
|
163
199
|
if (isDirty) {
|
|
164
|
-
asset.setAST(
|
|
200
|
+
asset.setAST({
|
|
201
|
+
...ast,
|
|
202
|
+
program: program.toJSON(),
|
|
203
|
+
});
|
|
165
204
|
}
|
|
166
205
|
|
|
167
|
-
return
|
|
206
|
+
return assets;
|
|
168
207
|
},
|
|
169
208
|
|
|
170
|
-
async generate({ast, options}) {
|
|
171
|
-
let
|
|
172
|
-
|
|
173
|
-
// $FlowFixMe
|
|
174
|
-
if (Object.getPrototypeOf(ast.program) === Object.prototype) {
|
|
175
|
-
root = postcss.root(ast.program);
|
|
176
|
-
let convert = (parent: Container, node: Node, index: number) => {
|
|
177
|
-
let type = node.type === 'atrule' ? 'atRule' : node.type;
|
|
178
|
-
let result = postcss[type](node);
|
|
179
|
-
result.parent = parent;
|
|
180
|
-
if (parent) {
|
|
181
|
-
parent.nodes[index] = result;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (result.walk) {
|
|
185
|
-
// $FlowFixMe
|
|
186
|
-
const container = (result: Container);
|
|
187
|
-
container.each((node, index) => {
|
|
188
|
-
convert(container, node, index);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
root.each((node, index) => convert(root, node, index));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
let result = await postcss().process(root, {
|
|
209
|
+
async generate({asset, ast, options}) {
|
|
210
|
+
let result = await postcss().process(postcss.fromJSON(ast.program), {
|
|
197
211
|
from: undefined,
|
|
198
212
|
to: options.projectRoot + '/index',
|
|
199
213
|
map: {
|
|
200
214
|
annotation: false,
|
|
201
215
|
inline: false,
|
|
216
|
+
sourcesContent: false,
|
|
202
217
|
},
|
|
203
218
|
// Pass postcss's own stringifier to it to silence its warning
|
|
204
219
|
// as we don't want to perform any transformations -- only generate
|
|
205
220
|
stringifier: postcss.stringify,
|
|
206
221
|
});
|
|
207
222
|
|
|
208
|
-
let map;
|
|
223
|
+
let map = null;
|
|
224
|
+
let originalSourceMap = await asset.getMap();
|
|
209
225
|
if (result.map != null) {
|
|
210
|
-
map = new SourceMap();
|
|
211
|
-
map.
|
|
226
|
+
map = new SourceMap(options.projectRoot);
|
|
227
|
+
map.addVLQMap(result.map.toJSON());
|
|
228
|
+
if (originalSourceMap) {
|
|
229
|
+
map.extends(originalSourceMap.toBuffer());
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
map = originalSourceMap;
|
|
212
233
|
}
|
|
213
234
|
|
|
214
235
|
return {
|
|
@@ -216,4 +237,140 @@ export default new Transformer({
|
|
|
216
237
|
map,
|
|
217
238
|
};
|
|
218
239
|
},
|
|
219
|
-
});
|
|
240
|
+
}): Transformer);
|
|
241
|
+
|
|
242
|
+
async function compileCSSModules(asset, env, program, resolve, options) {
|
|
243
|
+
let cssModules;
|
|
244
|
+
|
|
245
|
+
let code = asset.isASTDirty() ? null : await asset.getCode();
|
|
246
|
+
if (code == null || COMPOSES_RE.test(code)) {
|
|
247
|
+
program.walkDecls(decl => {
|
|
248
|
+
let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
|
|
249
|
+
if (decl.prop === 'composes' && importPath != null) {
|
|
250
|
+
let parsed = valueParser(decl.value);
|
|
251
|
+
let start = (decl.source.start: any);
|
|
252
|
+
|
|
253
|
+
parsed.walk(node => {
|
|
254
|
+
if (node.type === 'string') {
|
|
255
|
+
asset.addDependency({
|
|
256
|
+
specifier: importPath,
|
|
257
|
+
specifierType: 'url',
|
|
258
|
+
loc: start
|
|
259
|
+
? {
|
|
260
|
+
filePath: asset.filePath,
|
|
261
|
+
start,
|
|
262
|
+
end: {
|
|
263
|
+
line: start.line,
|
|
264
|
+
column: start.column + importPath.length,
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
: undefined,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let postcssModules = await options.packageManager.require(
|
|
276
|
+
'postcss-modules',
|
|
277
|
+
asset.filePath,
|
|
278
|
+
{
|
|
279
|
+
range: '^4.3.0',
|
|
280
|
+
saveDev: true,
|
|
281
|
+
shouldAutoInstall: options.shouldAutoInstall,
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
let {root} = await postcss([
|
|
286
|
+
postcssModules({
|
|
287
|
+
getJSON: (filename, json) => (cssModules = json),
|
|
288
|
+
Loader: await createLoader(asset, resolve, options),
|
|
289
|
+
generateScopedName: (name, filename) =>
|
|
290
|
+
`${name}_${hashString(
|
|
291
|
+
path.relative(options.projectRoot, filename),
|
|
292
|
+
).substr(0, 6)}`,
|
|
293
|
+
}),
|
|
294
|
+
]).process(program, {from: asset.filePath, to: asset.filePath});
|
|
295
|
+
asset.setAST({
|
|
296
|
+
type: 'postcss',
|
|
297
|
+
version: '8.2.1',
|
|
298
|
+
program: root.toJSON(),
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
let assets = [asset];
|
|
302
|
+
if (cssModules) {
|
|
303
|
+
// $FlowFixMe
|
|
304
|
+
let cssModulesList = (Object.entries(cssModules): Array<[string, string]>);
|
|
305
|
+
let deps = asset.getDependencies().filter(dep => dep.priority === 'sync');
|
|
306
|
+
let code: string;
|
|
307
|
+
if (deps.length > 0) {
|
|
308
|
+
code = `
|
|
309
|
+
module.exports = Object.assign({}, ${deps
|
|
310
|
+
.map(dep => `require(${JSON.stringify(dep.specifier)})`)
|
|
311
|
+
.join(', ')}, ${JSON.stringify(cssModules, null, 2)});
|
|
312
|
+
`;
|
|
313
|
+
} else {
|
|
314
|
+
code = cssModulesList
|
|
315
|
+
.map(
|
|
316
|
+
// This syntax enables shaking the invidual statements, so that unused classes don't even exist in JS.
|
|
317
|
+
([className, classNameHashed]) =>
|
|
318
|
+
`module.exports[${JSON.stringify(className)}] = ${JSON.stringify(
|
|
319
|
+
classNameHashed,
|
|
320
|
+
)};`,
|
|
321
|
+
)
|
|
322
|
+
.join('\n');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
asset.symbols.ensure();
|
|
326
|
+
for (let [k, v] of cssModulesList) {
|
|
327
|
+
asset.symbols.set(k, v);
|
|
328
|
+
}
|
|
329
|
+
asset.symbols.set('default', 'default');
|
|
330
|
+
|
|
331
|
+
assets.push({
|
|
332
|
+
type: 'js',
|
|
333
|
+
content: code,
|
|
334
|
+
env,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return assets;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function createLoader(
|
|
341
|
+
asset: MutableAsset,
|
|
342
|
+
resolve: (from: FilePath, to: string) => Promise<FilePath>,
|
|
343
|
+
options: PluginOptions,
|
|
344
|
+
) {
|
|
345
|
+
let {default: FileSystemLoader} = await options.packageManager.require(
|
|
346
|
+
'postcss-modules/build/css-loader-core/loader',
|
|
347
|
+
asset.filePath,
|
|
348
|
+
);
|
|
349
|
+
return class ParcelFileSystemLoader extends FileSystemLoader {
|
|
350
|
+
async fetch(composesPath, relativeTo) {
|
|
351
|
+
let importPath = composesPath.replace(/^["']|["']$/g, '');
|
|
352
|
+
let resolved = await resolve(relativeTo, importPath);
|
|
353
|
+
let rootRelativePath = path.resolve(path.dirname(relativeTo), resolved);
|
|
354
|
+
let root = path.resolve('/');
|
|
355
|
+
// fixes an issue on windows which is part of the css-modules-loader-core
|
|
356
|
+
// see https://github.com/css-modules/css-modules-loader-core/issues/230
|
|
357
|
+
if (rootRelativePath.startsWith(root)) {
|
|
358
|
+
rootRelativePath = rootRelativePath.substr(root.length);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let source = await asset.fs.readFile(resolved, 'utf-8');
|
|
362
|
+
let {exportTokens} = await this.core.load(
|
|
363
|
+
source,
|
|
364
|
+
rootRelativePath,
|
|
365
|
+
undefined,
|
|
366
|
+
// $FlowFixMe[method-unbinding]
|
|
367
|
+
this.fetch.bind(this),
|
|
368
|
+
);
|
|
369
|
+
return exportTokens;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
get finalSource() {
|
|
373
|
+
return '';
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|