@parcel/transformer-css 2.3.2 → 2.4.0

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.
@@ -5,10 +5,10 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
- function _hash() {
9
- const data = require("@parcel/hash");
8
+ function _path() {
9
+ const data = _interopRequireDefault(require("path"));
10
10
 
11
- _hash = function () {
11
+ _path = function () {
12
12
  return data;
13
13
  };
14
14
 
@@ -35,396 +35,297 @@ function _plugin() {
35
35
  return data;
36
36
  }
37
37
 
38
- function _utils() {
39
- const data = require("@parcel/utils");
38
+ function _css() {
39
+ const data = require("@parcel/css");
40
40
 
41
- _utils = function () {
41
+ _css = function () {
42
42
  return data;
43
43
  };
44
44
 
45
45
  return data;
46
46
  }
47
47
 
48
- function _postcss() {
49
- const data = _interopRequireDefault(require("postcss"));
48
+ function _utils() {
49
+ const data = require("@parcel/utils");
50
50
 
51
- _postcss = function () {
51
+ _utils = function () {
52
52
  return data;
53
53
  };
54
54
 
55
55
  return data;
56
56
  }
57
57
 
58
- function _nullthrows() {
59
- const data = _interopRequireDefault(require("nullthrows"));
58
+ function _browserslist() {
59
+ const data = _interopRequireDefault(require("browserslist"));
60
60
 
61
- _nullthrows = function () {
61
+ _browserslist = function () {
62
62
  return data;
63
63
  };
64
64
 
65
65
  return data;
66
66
  }
67
67
 
68
- function _postcssValueParser() {
69
- const data = _interopRequireDefault(require("postcss-value-parser"));
68
+ function _nullthrows() {
69
+ const data = _interopRequireDefault(require("nullthrows"));
70
70
 
71
- _postcssValueParser = function () {
71
+ _nullthrows = function () {
72
72
  return data;
73
73
  };
74
74
 
75
75
  return data;
76
76
  }
77
77
 
78
- function _semver() {
79
- const data = _interopRequireDefault(require("semver"));
78
+ function _diagnostic() {
79
+ const data = _interopRequireWildcard(require("@parcel/diagnostic"));
80
80
 
81
- _semver = function () {
81
+ _diagnostic = function () {
82
82
  return data;
83
83
  };
84
84
 
85
85
  return data;
86
86
  }
87
87
 
88
- function _path() {
89
- const data = _interopRequireDefault(require("path"));
90
-
91
- _path = function () {
92
- return data;
93
- };
88
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
94
89
 
95
- return data;
96
- }
90
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
97
91
 
98
92
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
99
93
 
100
- const URL_RE = /url\s*\(/;
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\./;
105
-
106
- function canHaveDependencies(filePath, code) {
107
- return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code);
108
- }
109
-
110
94
  var _default = new (_plugin().Transformer)({
111
- canReuseAST({
112
- ast
113
- }) {
114
- return ast.type === 'postcss' && _semver().default.satisfies(ast.version, '^8.2.1');
115
- },
116
-
117
- async parse({
118
- asset
95
+ async loadConfig({
96
+ config,
97
+ options
119
98
  }) {
120
- // This is set by other transformers (e.g. Stylus) to indicate that it has already processed
121
- // all dependencies, and that the CSS transformer can skip this asset completely. This is
122
- // required because when stylus processes e.g. url() it replaces them with a dependency id
123
- // to be filled in later. When the CSS transformer runs, it would pick that up and try to
124
- // resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do
125
- // it this way since the resulting CSS doesn't need to be re-parsed.
126
- let isCSSModule = asset.meta.cssModulesCompiled !== true && MODULE_BY_NAME_RE.test(asset.filePath);
127
-
128
- if (asset.meta.hasDependencies === false && !isCSSModule) {
129
- return null;
130
- }
131
-
132
- let code = await asset.getCode();
133
-
134
- if (code != null && !canHaveDependencies(asset.filePath, code) && !isCSSModule) {
135
- return null;
136
- }
137
-
138
- return {
139
- type: 'postcss',
140
- version: '8.2.1',
141
- program: _postcss().default.parse(code, {
142
- from: asset.filePath
143
- }).toJSON()
144
- };
99
+ let conf = await config.getConfigFrom(options.projectRoot + '/index', [], {
100
+ packageKey: '@parcel/transformer-css'
101
+ });
102
+ return conf === null || conf === void 0 ? void 0 : conf.contents;
145
103
  },
146
104
 
147
105
  async transform({
148
106
  asset,
149
- resolve,
107
+ config,
150
108
  options
151
109
  }) {
152
- // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
153
- // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
154
- let env = asset.env;
155
- asset.setEnvironment({
156
- context: 'browser',
157
- engines: {
158
- browsers: asset.env.engines.browsers
159
- },
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
164
- // another transformer (such as PostCSSTransformer) has already parsed an
165
- // ast and CSSTransformer's parse was never called.
110
+ let [code, originalMap] = await Promise.all([asset.getBuffer(), asset.getMap()]);
111
+ let targets = getTargets(asset.env.engines.browsers);
112
+ let res;
113
+
114
+ try {
115
+ if (asset.meta.type === 'attr') {
116
+ res = (0, _css().transformStyleAttribute)({
117
+ code,
118
+ analyzeDependencies: true,
119
+ targets
120
+ });
121
+ } else {
122
+ var _config$cssModules;
123
+
124
+ res = (0, _css().transform)({
125
+ filename: _path().default.relative(options.projectRoot, asset.filePath),
126
+ code,
127
+ cssModules: (_config$cssModules = config === null || config === void 0 ? void 0 : config.cssModules) !== null && _config$cssModules !== void 0 ? _config$cssModules : asset.meta.cssModulesCompiled !== true && /\.module\./.test(asset.filePath),
128
+ analyzeDependencies: asset.meta.hasDependencies !== false,
129
+ sourceMap: !!asset.env.sourceMap,
130
+ drafts: config === null || config === void 0 ? void 0 : config.drafts,
131
+ pseudoClasses: config === null || config === void 0 ? void 0 : config.pseudoClasses,
132
+ targets
133
+ });
134
+ }
135
+ } catch (err) {
136
+ var _err$data;
166
137
 
167
- let ast = await asset.getAST();
138
+ err.filePath = asset.filePath;
139
+ let diagnostic = (0, _diagnostic().errorToDiagnostic)(err, {
140
+ origin: '@parcel/transformer-css'
141
+ });
168
142
 
169
- if (!ast || asset.meta.hasDependencies === false && !isCSSModule) {
170
- return [asset];
143
+ if (((_err$data = err.data) === null || _err$data === void 0 ? void 0 : _err$data.type) === 'AmbiguousUrlInCustomProperty' && err.data.url) {
144
+ let p = '/' + (0, _utils().relativePath)(options.projectRoot, _path().default.resolve(_path().default.dirname(asset.filePath), err.data.url), false);
145
+ diagnostic[0].hints = [`Replace with: url(${p})`];
146
+ diagnostic[0].documentationURL = 'https://parceljs.org/languages/css/#url()';
147
+ }
148
+
149
+ throw new (_diagnostic().default)({
150
+ diagnostic
151
+ });
171
152
  }
172
153
 
173
- let program = _postcss().default.fromJSON(ast.program);
154
+ asset.setBuffer(res.code);
174
155
 
175
- let assets = [asset];
156
+ if (res.map != null) {
157
+ let vlqMap = JSON.parse(res.map.toString());
158
+ let map = new (_sourceMap().default)(options.projectRoot);
159
+ map.addVLQMap(vlqMap);
176
160
 
177
- if (isCSSModule) {
178
- assets = await compileCSSModules(asset, env, program, resolve, options);
179
- }
161
+ if (originalMap) {
162
+ map.extends(originalMap);
163
+ }
180
164
 
181
- if (asset.meta.hasDependencies === false) {
182
- return assets;
165
+ asset.setMap(map);
183
166
  }
184
167
 
185
- let originalSourceMap = await asset.getMap();
168
+ if (res.dependencies) {
169
+ for (let dep of res.dependencies) {
170
+ let loc = dep.loc;
186
171
 
187
- let createLoc = (start, specifier, lineOffset, colOffset, o) => {
188
- let loc = (0, _utils().createDependencyLocation)(start, specifier, lineOffset, colOffset, o);
172
+ if (originalMap) {
173
+ loc = (0, _utils().remapSourceLocation)(loc, originalMap);
174
+ }
189
175
 
190
- if (originalSourceMap) {
191
- loc = (0, _utils().remapSourceLocation)(loc, originalSourceMap);
176
+ if (dep.type === 'import') {
177
+ asset.addDependency({
178
+ specifier: dep.url,
179
+ specifierType: 'url',
180
+ loc,
181
+ meta: {
182
+ // For the glob resolver to distinguish between `@import` and other URL dependencies.
183
+ isCSSImport: true,
184
+ media: dep.media
185
+ },
186
+ symbols: new Map([['*', {
187
+ local: '*',
188
+ isWeak: true,
189
+ loc
190
+ }]])
191
+ });
192
+ } else if (dep.type === 'url') {
193
+ asset.addURLDependency(dep.url, {
194
+ loc,
195
+ meta: {
196
+ placeholder: dep.placeholder
197
+ }
198
+ });
199
+ }
192
200
  }
201
+ }
193
202
 
194
- return loc;
195
- };
196
-
197
- let isDirty = false;
198
- program.walkAtRules('import', rule => {
199
- let params = (0, _postcssValueParser().default)(rule.params);
200
- let [name, ...media] = params.nodes;
201
- let specifier;
203
+ let assets = [asset];
202
204
 
203
- if (name.type === 'function' && name.value === 'url' && name.nodes.length) {
204
- name = name.nodes[0];
205
+ if (res.exports != null) {
206
+ let exports = res.exports;
207
+ asset.symbols.ensure();
208
+ asset.symbols.set('default', 'default');
209
+ let dependencies = new Map();
210
+ let selfReferences = new Set();
211
+ let locals = new Map();
212
+ let c = 0;
213
+ let depjs = '';
214
+ let js = '';
215
+
216
+ for (let key in exports) {
217
+ locals.set(exports[key].name, key);
205
218
  }
206
219
 
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(); // }
235
-
236
- isDirty = true;
237
- });
238
- program.walkDecls(decl => {
239
- if (URL_RE.test(decl.value)) {
240
- let parsed = (0, _postcssValueParser().default)(decl.value);
241
- let isDeclDirty = false;
242
- parsed.walk(node => {
243
- if (node.type === 'function' && node.value === 'url' && node.nodes.length > 0 && !node.nodes[0].value.startsWith('#') // IE's `behavior: url(#default#VML)`
244
- ) {
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
- }
254
- });
220
+ let seen = new Set();
255
221
 
256
- if (isDeclDirty) {
257
- decl.value = parsed.toString();
258
- isDirty = true;
222
+ let add = key => {
223
+ if (seen.has(key)) {
224
+ return;
259
225
  }
260
- }
261
- });
262
226
 
263
- if (isDirty) {
264
- asset.setAST({ ...ast,
265
- program: program.toJSON()
266
- });
267
- }
227
+ seen.add(key);
228
+ let e = exports[key];
229
+ let s = `module.exports[${JSON.stringify(key)}] = \`${e.name}`;
268
230
 
269
- return assets;
270
- },
231
+ if (e.isReferenced) {
232
+ selfReferences.add(e.name);
233
+ }
271
234
 
272
- async generate({
273
- asset,
274
- ast,
275
- options
276
- }) {
277
- let result = await (0, _postcss().default)().process(_postcss().default.fromJSON(ast.program), {
278
- from: undefined,
279
- to: options.projectRoot + '/index',
280
- map: {
281
- annotation: false,
282
- inline: false,
283
- sourcesContent: false
284
- },
285
- // Pass postcss's own stringifier to it to silence its warning
286
- // as we don't want to perform any transformations -- only generate
287
- stringifier: _postcss().default.stringify
288
- });
289
- let map = null;
290
- let originalSourceMap = await asset.getMap();
235
+ for (let ref of e.composes) {
236
+ s += ' ';
237
+
238
+ if (ref.type === 'local') {
239
+ add((0, _nullthrows().default)(locals.get(ref.name)));
240
+ s += '${' + `module.exports[${JSON.stringify((0, _nullthrows().default)(locals.get(ref.name)))}]` + '}';
241
+ } else if (ref.type === 'global') {
242
+ s += ref.name;
243
+ } else if (ref.type === 'dependency') {
244
+ let d = dependencies.get(ref.specifier);
245
+
246
+ if (d == null) {
247
+ d = `dep_${c++}`;
248
+ depjs += `import * as ${d} from ${JSON.stringify(ref.specifier)};\n`;
249
+ dependencies.set(ref.specifier, d);
250
+ asset.addDependency({
251
+ specifier: ref.specifier,
252
+ specifierType: 'url'
253
+ });
254
+ }
255
+
256
+ s += '${' + `${d}[${JSON.stringify(ref.name)}]` + '}';
257
+ }
258
+ }
291
259
 
292
- if (result.map != null) {
293
- map = new (_sourceMap().default)(options.projectRoot);
294
- map.addVLQMap(result.map.toJSON());
260
+ s += '`;\n';
261
+ js += s;
262
+ };
295
263
 
296
- if (originalSourceMap) {
297
- map.extends(originalSourceMap.toBuffer());
264
+ for (let key in exports) {
265
+ asset.symbols.set(key, exports[key].name);
266
+ add(key);
298
267
  }
299
- } else {
300
- map = originalSourceMap;
301
- }
302
-
303
- return {
304
- content: result.css,
305
- map
306
- };
307
- }
308
268
 
309
- });
269
+ for (let dep of asset.getDependencies()) {
270
+ if (dep.priority === 'sync') {
271
+ // TODO: Figure out how to treeshake this
272
+ let d = `dep_$${c++}`;
273
+ depjs += `import * as ${d} from ${JSON.stringify(dep.specifier)};\n`;
274
+ depjs += `for (let key in ${d}) { if (key in module.exports) module.exports[key] += ' ' + ${d}[key]; else module.exports[key] = ${d}[key]; }\n`;
275
+ }
276
+ }
310
277
 
311
- exports.default = _default;
278
+ assets.push({
279
+ type: 'js',
280
+ content: depjs + js,
281
+ dependencies: [],
282
+ env: asset.env
283
+ });
312
284
 
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
- }
285
+ if (selfReferences.size > 0) {
286
+ asset.addDependency({
287
+ specifier: `./${_path().default.basename(asset.filePath)}`,
288
+ specifierType: 'url',
289
+ symbols: new Map([...locals].filter(([local]) => selfReferences.has(local)).map(([local, exported]) => [exported, {
290
+ local,
291
+ isWeak: false,
292
+ loc: null
293
+ }]))
339
294
  });
340
295
  }
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
- }
296
+ } // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
297
+ // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
380
298
 
381
- asset.symbols.ensure();
382
299
 
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
300
+ asset.setEnvironment({
301
+ context: 'browser',
302
+ engines: {
303
+ browsers: asset.env.engines.browsers
304
+ },
305
+ shouldOptimize: asset.env.shouldOptimize,
306
+ shouldScopeHoist: asset.env.shouldScopeHoist,
307
+ sourceMap: asset.env.sourceMap
392
308
  });
309
+ return assets;
393
310
  }
394
311
 
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
312
+ });
411
313
 
314
+ exports.default = _default;
315
+ let cache = new Map();
412
316
 
413
- if (rootRelativePath.startsWith(root)) {
414
- rootRelativePath = rootRelativePath.substr(root.length);
415
- }
317
+ function getTargets(browsers) {
318
+ if (browsers == null) {
319
+ return undefined;
320
+ }
416
321
 
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
- }
322
+ let cached = cache.get(browsers);
424
323
 
425
- get finalSource() {
426
- return '';
427
- }
324
+ if (cached != null) {
325
+ return cached;
326
+ }
428
327
 
429
- };
328
+ let targets = (0, _css().browserslistToTargets)((0, _browserslist().default)(browsers));
329
+ cache.set(browsers, targets);
330
+ return targets;
430
331
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/transformer-css",
3
- "version": "2.3.2",
3
+ "version": "2.4.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,20 +17,16 @@
17
17
  "source": "src/CSSTransformer.js",
18
18
  "engines": {
19
19
  "node": ">= 12.0.0",
20
- "parcel": "^2.3.2"
20
+ "parcel": "^2.4.0"
21
21
  },
22
22
  "dependencies": {
23
- "@parcel/hash": "2.3.2",
24
- "@parcel/plugin": "2.3.2",
23
+ "@parcel/css": "^1.7.2",
24
+ "@parcel/diagnostic": "2.4.0",
25
+ "@parcel/plugin": "2.4.0",
25
26
  "@parcel/source-map": "^2.0.0",
26
- "@parcel/utils": "2.3.2",
27
- "nullthrows": "^1.1.1",
28
- "postcss": "^8.4.5",
29
- "postcss-value-parser": "^4.2.0",
30
- "semver": "^5.7.1"
27
+ "@parcel/utils": "2.4.0",
28
+ "browserslist": "^4.6.6",
29
+ "nullthrows": "^1.1.1"
31
30
  },
32
- "devDependencies": {
33
- "postcss-modules": "^4.3.0"
34
- },
35
- "gitHead": "47379bf8fabeb2cfe03ade8802d942388b153e5b"
31
+ "gitHead": "b1bbfdab2e902d33c0b6ff75bfba2d22283a8de6"
36
32
  }
@@ -1,376 +1,257 @@
1
- // @flow
1
+ // @flow strict-local
2
2
 
3
- import type {Root} from 'postcss';
4
- import type {FilePath, MutableAsset, PluginOptions} from '@parcel/types';
5
-
6
- import {hashString} from '@parcel/hash';
3
+ import path from 'path';
7
4
  import SourceMap from '@parcel/source-map';
8
5
  import {Transformer} from '@parcel/plugin';
9
- import {createDependencyLocation, remapSourceLocation} from '@parcel/utils';
10
- import postcss from 'postcss';
6
+ import {
7
+ transform,
8
+ transformStyleAttribute,
9
+ browserslistToTargets,
10
+ } from '@parcel/css';
11
+ import {remapSourceLocation, relativePath} from '@parcel/utils';
12
+ import browserslist from 'browserslist';
11
13
  import nullthrows from 'nullthrows';
12
- import valueParser from 'postcss-value-parser';
13
- import semver from 'semver';
14
- import path from 'path';
15
-
16
- const URL_RE = /url\s*\(/;
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\./;
21
-
22
- function canHaveDependencies(filePath: FilePath, code: string) {
23
- return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code);
24
- }
14
+ import ThrowableDiagnostic, {errorToDiagnostic} from '@parcel/diagnostic';
25
15
 
26
16
  export default (new Transformer({
27
- canReuseAST({ast}) {
28
- return ast.type === 'postcss' && semver.satisfies(ast.version, '^8.2.1');
17
+ async loadConfig({config, options}) {
18
+ let conf = await config.getConfigFrom(options.projectRoot + '/index', [], {
19
+ packageKey: '@parcel/transformer-css',
20
+ });
21
+ return conf?.contents;
29
22
  },
23
+ async transform({asset, config, options}) {
24
+ let [code, originalMap] = await Promise.all([
25
+ asset.getBuffer(),
26
+ asset.getMap(),
27
+ ]);
28
+
29
+ let targets = getTargets(asset.env.engines.browsers);
30
+ let res;
31
+ try {
32
+ if (asset.meta.type === 'attr') {
33
+ res = transformStyleAttribute({
34
+ code,
35
+ analyzeDependencies: true,
36
+ targets,
37
+ });
38
+ } else {
39
+ res = transform({
40
+ filename: path.relative(options.projectRoot, asset.filePath),
41
+ code,
42
+ cssModules:
43
+ config?.cssModules ??
44
+ (asset.meta.cssModulesCompiled !== true &&
45
+ /\.module\./.test(asset.filePath)),
46
+ analyzeDependencies: asset.meta.hasDependencies !== false,
47
+ sourceMap: !!asset.env.sourceMap,
48
+ drafts: config?.drafts,
49
+ pseudoClasses: config?.pseudoClasses,
50
+ targets,
51
+ });
52
+ }
53
+ } catch (err) {
54
+ err.filePath = asset.filePath;
55
+ let diagnostic = errorToDiagnostic(err, {
56
+ origin: '@parcel/transformer-css',
57
+ });
58
+ if (err.data?.type === 'AmbiguousUrlInCustomProperty' && err.data.url) {
59
+ let p =
60
+ '/' +
61
+ relativePath(
62
+ options.projectRoot,
63
+ path.resolve(path.dirname(asset.filePath), err.data.url),
64
+ false,
65
+ );
66
+ diagnostic[0].hints = [`Replace with: url(${p})`];
67
+ diagnostic[0].documentationURL =
68
+ 'https://parceljs.org/languages/css/#url()';
69
+ }
30
70
 
31
- async parse({asset}) {
32
- // This is set by other transformers (e.g. Stylus) to indicate that it has already processed
33
- // all dependencies, and that the CSS transformer can skip this asset completely. This is
34
- // required because when stylus processes e.g. url() it replaces them with a dependency id
35
- // to be filled in later. When the CSS transformer runs, it would pick that up and try to
36
- // resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do
37
- // it this way since the resulting CSS doesn't need to be re-parsed.
38
- let isCSSModule =
39
- asset.meta.cssModulesCompiled !== true &&
40
- MODULE_BY_NAME_RE.test(asset.filePath);
41
- if (asset.meta.hasDependencies === false && !isCSSModule) {
42
- return null;
71
+ throw new ThrowableDiagnostic({
72
+ diagnostic,
73
+ });
43
74
  }
44
75
 
45
- let code = await asset.getCode();
46
- if (
47
- code != null &&
48
- !canHaveDependencies(asset.filePath, code) &&
49
- !isCSSModule
50
- ) {
51
- return null;
52
- }
76
+ asset.setBuffer(res.code);
53
77
 
54
- return {
55
- type: 'postcss',
56
- version: '8.2.1',
57
- program: postcss
58
- .parse(code, {
59
- from: asset.filePath,
60
- })
61
- .toJSON(),
62
- };
63
- },
78
+ if (res.map != null) {
79
+ let vlqMap = JSON.parse(res.map.toString());
80
+ let map = new SourceMap(options.projectRoot);
81
+ map.addVLQMap(vlqMap);
64
82
 
65
- async transform({asset, resolve, options}) {
66
- // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
67
- // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
68
- let env = asset.env;
69
- asset.setEnvironment({
70
- context: 'browser',
71
- engines: {
72
- browsers: asset.env.engines.browsers,
73
- },
74
- shouldOptimize: asset.env.shouldOptimize,
75
- sourceMap: asset.env.sourceMap,
76
- });
83
+ if (originalMap) {
84
+ map.extends(originalMap);
85
+ }
77
86
 
78
- let isCSSModule =
79
- asset.meta.cssModulesCompiled !== true &&
80
- MODULE_BY_NAME_RE.test(asset.filePath);
87
+ asset.setMap(map);
88
+ }
89
+
90
+ if (res.dependencies) {
91
+ for (let dep of res.dependencies) {
92
+ let loc = dep.loc;
93
+ if (originalMap) {
94
+ loc = remapSourceLocation(loc, originalMap);
95
+ }
81
96
 
82
- // Check for `hasDependencies` being false here as well, as it's possible
83
- // another transformer (such as PostCSSTransformer) has already parsed an
84
- // ast and CSSTransformer's parse was never called.
85
- let ast = await asset.getAST();
86
- if (!ast || (asset.meta.hasDependencies === false && !isCSSModule)) {
87
- return [asset];
97
+ if (dep.type === 'import') {
98
+ asset.addDependency({
99
+ specifier: dep.url,
100
+ specifierType: 'url',
101
+ loc,
102
+ meta: {
103
+ // For the glob resolver to distinguish between `@import` and other URL dependencies.
104
+ isCSSImport: true,
105
+ media: dep.media,
106
+ },
107
+ symbols: new Map([['*', {local: '*', isWeak: true, loc}]]),
108
+ });
109
+ } else if (dep.type === 'url') {
110
+ asset.addURLDependency(dep.url, {
111
+ loc,
112
+ meta: {
113
+ placeholder: dep.placeholder,
114
+ },
115
+ });
116
+ }
117
+ }
88
118
  }
89
119
 
90
- let program: Root = postcss.fromJSON(ast.program);
91
120
  let assets = [asset];
92
- if (isCSSModule) {
93
- assets = await compileCSSModules(asset, env, program, resolve, options);
94
- }
95
121
 
96
- if (asset.meta.hasDependencies === false) {
97
- return assets;
98
- }
122
+ if (res.exports != null) {
123
+ let exports = res.exports;
124
+ asset.symbols.ensure();
125
+ asset.symbols.set('default', 'default');
99
126
 
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
-
115
- let isDirty = false;
116
- program.walkAtRules('import', rule => {
117
- let params = valueParser(rule.params);
118
- let [name, ...media] = params.nodes;
119
- let specifier;
120
- if (
121
- name.type === 'function' &&
122
- name.value === 'url' &&
123
- name.nodes.length
124
- ) {
125
- name = name.nodes[0];
126
- }
127
+ let dependencies = new Map();
128
+ let selfReferences = new Set();
129
+ let locals = new Map();
130
+ let c = 0;
131
+ let depjs = '';
132
+ let js = '';
127
133
 
128
- specifier = name.value;
134
+ let jsDeps = [];
129
135
 
130
- if (!specifier) {
131
- throw new Error('Could not find import name for ' + String(rule));
136
+ for (let key in exports) {
137
+ locals.set(exports[key].name, key);
132
138
  }
133
139
 
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
- // }
157
- isDirty = true;
158
- });
140
+ let seen = new Set();
141
+ let add = key => {
142
+ if (seen.has(key)) {
143
+ return;
144
+ }
145
+ seen.add(key);
159
146
 
160
- program.walkDecls(decl => {
161
- if (URL_RE.test(decl.value)) {
162
- let parsed = valueParser(decl.value);
163
- let isDeclDirty = false;
164
-
165
- parsed.walk(node => {
166
- if (
167
- node.type === 'function' &&
168
- node.value === 'url' &&
169
- node.nodes.length > 0 &&
170
- !node.nodes[0].value.startsWith('#') // IE's `behavior: url(#default#VML)`
171
- ) {
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
- ),
184
- });
185
- isDeclDirty = urlNode.value !== url;
186
- urlNode.type = 'string';
187
- urlNode.quote = '"';
188
- urlNode.value = url;
189
- }
190
- });
147
+ let e = exports[key];
148
+ let s = `module.exports[${JSON.stringify(key)}] = \`${e.name}`;
191
149
 
192
- if (isDeclDirty) {
193
- decl.value = parsed.toString();
194
- isDirty = true;
150
+ if (e.isReferenced) {
151
+ selfReferences.add(e.name);
195
152
  }
196
- }
197
- });
198
153
 
199
- if (isDirty) {
200
- asset.setAST({
201
- ...ast,
202
- program: program.toJSON(),
203
- });
204
- }
154
+ for (let ref of e.composes) {
155
+ s += ' ';
156
+ if (ref.type === 'local') {
157
+ add(nullthrows(locals.get(ref.name)));
158
+ s +=
159
+ '${' +
160
+ `module.exports[${JSON.stringify(
161
+ nullthrows(locals.get(ref.name)),
162
+ )}]` +
163
+ '}';
164
+ } else if (ref.type === 'global') {
165
+ s += ref.name;
166
+ } else if (ref.type === 'dependency') {
167
+ let d = dependencies.get(ref.specifier);
168
+ if (d == null) {
169
+ d = `dep_${c++}`;
170
+ depjs += `import * as ${d} from ${JSON.stringify(
171
+ ref.specifier,
172
+ )};\n`;
173
+ dependencies.set(ref.specifier, d);
174
+
175
+ asset.addDependency({
176
+ specifier: ref.specifier,
177
+ specifierType: 'url',
178
+ });
179
+ }
180
+ s += '${' + `${d}[${JSON.stringify(ref.name)}]` + '}';
181
+ }
182
+ }
205
183
 
206
- return assets;
207
- },
184
+ s += '`;\n';
185
+ js += s;
186
+ };
208
187
 
209
- async generate({asset, ast, options}) {
210
- let result = await postcss().process(postcss.fromJSON(ast.program), {
211
- from: undefined,
212
- to: options.projectRoot + '/index',
213
- map: {
214
- annotation: false,
215
- inline: false,
216
- sourcesContent: false,
217
- },
218
- // Pass postcss's own stringifier to it to silence its warning
219
- // as we don't want to perform any transformations -- only generate
220
- stringifier: postcss.stringify,
221
- });
188
+ for (let key in exports) {
189
+ asset.symbols.set(key, exports[key].name);
190
+ add(key);
191
+ }
222
192
 
223
- let map = null;
224
- let originalSourceMap = await asset.getMap();
225
- if (result.map != null) {
226
- map = new SourceMap(options.projectRoot);
227
- map.addVLQMap(result.map.toJSON());
228
- if (originalSourceMap) {
229
- map.extends(originalSourceMap.toBuffer());
193
+ for (let dep of asset.getDependencies()) {
194
+ if (dep.priority === 'sync') {
195
+ // TODO: Figure out how to treeshake this
196
+ let d = `dep_$${c++}`;
197
+ depjs += `import * as ${d} from ${JSON.stringify(dep.specifier)};\n`;
198
+ depjs += `for (let key in ${d}) { if (key in module.exports) module.exports[key] += ' ' + ${d}[key]; else module.exports[key] = ${d}[key]; }\n`;
199
+ }
230
200
  }
231
- } else {
232
- map = originalSourceMap;
233
- }
234
201
 
235
- return {
236
- content: result.css,
237
- map,
238
- };
239
- },
240
- }): Transformer);
202
+ assets.push({
203
+ type: 'js',
204
+ content: depjs + js,
205
+ dependencies: jsDeps,
206
+ env: asset.env,
207
+ });
241
208
 
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
- }
209
+ if (selfReferences.size > 0) {
210
+ asset.addDependency({
211
+ specifier: `./${path.basename(asset.filePath)}`,
212
+ specifierType: 'url',
213
+ symbols: new Map(
214
+ [...locals]
215
+ .filter(([local]) => selfReferences.has(local))
216
+ .map(([local, exported]) => [
217
+ exported,
218
+ {local, isWeak: false, loc: null},
219
+ ]),
220
+ ),
270
221
  });
271
222
  }
223
+ }
224
+
225
+ // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
226
+ // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
227
+ asset.setEnvironment({
228
+ context: 'browser',
229
+ engines: {
230
+ browsers: asset.env.engines.browsers,
231
+ },
232
+ shouldOptimize: asset.env.shouldOptimize,
233
+ shouldScopeHoist: asset.env.shouldScopeHoist,
234
+ sourceMap: asset.env.sourceMap,
272
235
  });
273
- }
274
236
 
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
- }
237
+ return assets;
238
+ },
239
+ }): Transformer);
324
240
 
325
- asset.symbols.ensure();
326
- for (let [k, v] of cssModulesList) {
327
- asset.symbols.set(k, v);
328
- }
329
- asset.symbols.set('default', 'default');
241
+ let cache = new Map();
330
242
 
331
- assets.push({
332
- type: 'js',
333
- content: code,
334
- env,
335
- });
243
+ function getTargets(browsers) {
244
+ if (browsers == null) {
245
+ return undefined;
336
246
  }
337
- return assets;
338
- }
339
247
 
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
- }
248
+ let cached = cache.get(browsers);
249
+ if (cached != null) {
250
+ return cached;
251
+ }
360
252
 
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
- }
253
+ let targets = browserslistToTargets(browserslist(browsers));
371
254
 
372
- get finalSource() {
373
- return '';
374
- }
375
- };
255
+ cache.set(browsers, targets);
256
+ return targets;
376
257
  }