@parcel/transformer-css 2.3.2 → 2.5.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,118 +35,76 @@ 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"));
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); }
90
89
 
91
- _path = function () {
92
- return data;
93
- };
94
-
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
110
  // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
@@ -158,273 +116,200 @@ var _default = new (_plugin().Transformer)({
158
116
  browsers: asset.env.engines.browsers
159
117
  },
160
118
  shouldOptimize: asset.env.shouldOptimize,
119
+ shouldScopeHoist: asset.env.shouldScopeHoist,
161
120
  sourceMap: asset.env.sourceMap
162
121
  });
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.
122
+ let [code, originalMap] = await Promise.all([asset.getBuffer(), asset.getMap()]);
123
+ let targets = getTargets(asset.env.engines.browsers);
124
+ let res;
125
+
126
+ try {
127
+ if (asset.meta.type === 'attr') {
128
+ res = (0, _css().transformStyleAttribute)({
129
+ code,
130
+ analyzeDependencies: true,
131
+ targets
132
+ });
133
+ } else {
134
+ var _config$cssModules;
135
+
136
+ res = (0, _css().transform)({
137
+ filename: _path().default.relative(options.projectRoot, asset.filePath),
138
+ code,
139
+ cssModules: asset.meta.type !== 'tag' && ((_config$cssModules = config === null || config === void 0 ? void 0 : config.cssModules) !== null && _config$cssModules !== void 0 ? _config$cssModules : asset.meta.cssModulesCompiled == null && /\.module\./.test(asset.filePath)),
140
+ analyzeDependencies: asset.meta.hasDependencies !== false,
141
+ sourceMap: !!asset.env.sourceMap,
142
+ drafts: config === null || config === void 0 ? void 0 : config.drafts,
143
+ pseudoClasses: config === null || config === void 0 ? void 0 : config.pseudoClasses,
144
+ targets
145
+ });
146
+ }
147
+ } catch (err) {
148
+ var _err$data;
166
149
 
167
- let ast = await asset.getAST();
150
+ err.filePath = asset.filePath;
151
+ let diagnostic = (0, _diagnostic().errorToDiagnostic)(err, {
152
+ origin: '@parcel/transformer-css'
153
+ });
154
+
155
+ if (((_err$data = err.data) === null || _err$data === void 0 ? void 0 : _err$data.type) === 'AmbiguousUrlInCustomProperty' && err.data.url) {
156
+ let p = '/' + (0, _utils().relativePath)(options.projectRoot, _path().default.resolve(_path().default.dirname(asset.filePath), err.data.url), false);
157
+ diagnostic[0].hints = [`Replace with: url(${p})`];
158
+ diagnostic[0].documentationURL = 'https://parceljs.org/languages/css/#url()';
159
+ }
168
160
 
169
- if (!ast || asset.meta.hasDependencies === false && !isCSSModule) {
170
- return [asset];
161
+ throw new (_diagnostic().default)({
162
+ diagnostic
163
+ });
171
164
  }
172
165
 
173
- let program = _postcss().default.fromJSON(ast.program);
166
+ asset.setBuffer(res.code);
174
167
 
175
- let assets = [asset];
168
+ if (res.map != null) {
169
+ let vlqMap = JSON.parse(res.map.toString());
170
+ let map = new (_sourceMap().default)(options.projectRoot);
171
+ map.addVLQMap(vlqMap);
176
172
 
177
- if (isCSSModule) {
178
- assets = await compileCSSModules(asset, env, program, resolve, options);
179
- }
173
+ if (originalMap) {
174
+ map.extends(originalMap);
175
+ }
180
176
 
181
- if (asset.meta.hasDependencies === false) {
182
- return assets;
177
+ asset.setMap(map);
183
178
  }
184
179
 
185
- let originalSourceMap = await asset.getMap();
180
+ if (res.dependencies) {
181
+ for (let dep of res.dependencies) {
182
+ let loc = dep.loc;
186
183
 
187
- let createLoc = (start, specifier, lineOffset, colOffset, o) => {
188
- let loc = (0, _utils().createDependencyLocation)(start, specifier, lineOffset, colOffset, o);
184
+ if (originalMap) {
185
+ loc = (0, _utils().remapSourceLocation)(loc, originalMap);
186
+ }
189
187
 
190
- if (originalSourceMap) {
191
- loc = (0, _utils().remapSourceLocation)(loc, originalSourceMap);
188
+ if (dep.type === 'import' && !res.exports) {
189
+ asset.addDependency({
190
+ specifier: dep.url,
191
+ specifierType: 'url',
192
+ loc,
193
+ meta: {
194
+ // For the glob resolver to distinguish between `@import` and other URL dependencies.
195
+ isCSSImport: true,
196
+ media: dep.media
197
+ }
198
+ });
199
+ } else if (dep.type === 'url') {
200
+ asset.addURLDependency(dep.url, {
201
+ loc,
202
+ meta: {
203
+ placeholder: dep.placeholder
204
+ }
205
+ });
206
+ }
192
207
  }
208
+ }
193
209
 
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;
210
+ let assets = [asset];
202
211
 
203
- if (name.type === 'function' && name.value === 'url' && name.nodes.length) {
204
- name = name.nodes[0];
212
+ if (res.exports != null) {
213
+ let exports = res.exports;
214
+ asset.symbols.ensure();
215
+ asset.symbols.set('default', 'default');
216
+ let dependencies = new Map();
217
+ let locals = new Map();
218
+ let c = 0;
219
+ let depjs = '';
220
+ let js = '';
221
+
222
+ for (let key in exports) {
223
+ locals.set(exports[key].name, key);
205
224
  }
206
225
 
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(); // }
226
+ let seen = new Set();
235
227
 
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
- });
255
-
256
- if (isDeclDirty) {
257
- decl.value = parsed.toString();
258
- isDirty = true;
228
+ let add = key => {
229
+ if (seen.has(key)) {
230
+ return;
259
231
  }
260
- }
261
- });
262
232
 
263
- if (isDirty) {
264
- asset.setAST({ ...ast,
265
- program: program.toJSON()
266
- });
267
- }
233
+ seen.add(key);
234
+ let e = exports[key];
235
+ let s = `module.exports[${JSON.stringify(key)}] = \`${e.name}`;
268
236
 
269
- return assets;
270
- },
237
+ for (let ref of e.composes) {
238
+ s += ' ';
271
239
 
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();
240
+ if (ref.type === 'local') {
241
+ add((0, _nullthrows().default)(locals.get(ref.name)));
242
+ s += '${' + `module.exports[${JSON.stringify((0, _nullthrows().default)(locals.get(ref.name)))}]` + '}';
243
+ } else if (ref.type === 'global') {
244
+ s += ref.name;
245
+ } else if (ref.type === 'dependency') {
246
+ let d = dependencies.get(ref.specifier);
291
247
 
292
- if (result.map != null) {
293
- map = new (_sourceMap().default)(options.projectRoot);
294
- map.addVLQMap(result.map.toJSON());
248
+ if (d == null) {
249
+ d = `dep_${c++}`;
250
+ depjs += `import * as ${d} from ${JSON.stringify(ref.specifier)};\n`;
251
+ dependencies.set(ref.specifier, d);
252
+ }
295
253
 
296
- if (originalSourceMap) {
297
- map.extends(originalSourceMap.toBuffer());
298
- }
299
- } else {
300
- map = originalSourceMap;
301
- }
254
+ s += '${' + `${d}[${JSON.stringify(ref.name)}]` + '}';
255
+ }
256
+ }
302
257
 
303
- return {
304
- content: result.css,
305
- map
306
- };
307
- }
258
+ s += '`;\n'; // If the export is referenced internally (e.g. used @keyframes), add a self-reference
259
+ // to the JS so the symbol is retained during tree-shaking.
308
260
 
309
- });
261
+ if (e.isReferenced) {
262
+ s += `module.exports[${JSON.stringify(key)}];\n`;
263
+ }
310
264
 
311
- exports.default = _default;
265
+ js += s;
266
+ };
312
267
 
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
- });
268
+ for (let key in exports) {
269
+ asset.symbols.set(key, exports[key].name);
270
+ add(key);
340
271
  }
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
272
 
381
- asset.symbols.ensure();
273
+ if (res.dependencies) {
274
+ for (let dep of res.dependencies) {
275
+ if (dep.type === 'import') {
276
+ // TODO: Figure out how to treeshake this
277
+ let d = `dep_$${c++}`;
278
+ depjs += `import * as ${d} from ${JSON.stringify(dep.url)};\n`;
279
+ js += `for (let key in ${d}) { if (key in module.exports) module.exports[key] += ' ' + ${d}[key]; else module.exports[key] = ${d}[key]; }\n`;
280
+ asset.symbols.set('*', '*');
281
+ }
282
+ }
283
+ }
382
284
 
383
- for (let [k, v] of cssModulesList) {
384
- asset.symbols.set(k, v);
285
+ assets.push({
286
+ type: 'js',
287
+ content: depjs + js,
288
+ dependencies: [],
289
+ env
290
+ });
385
291
  }
386
292
 
387
- asset.symbols.set('default', 'default');
388
- assets.push({
389
- type: 'js',
390
- content: code,
391
- env
392
- });
293
+ return assets;
393
294
  }
394
295
 
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
296
+ });
411
297
 
298
+ exports.default = _default;
299
+ let cache = new Map();
412
300
 
413
- if (rootRelativePath.startsWith(root)) {
414
- rootRelativePath = rootRelativePath.substr(root.length);
415
- }
301
+ function getTargets(browsers) {
302
+ if (browsers == null) {
303
+ return undefined;
304
+ }
416
305
 
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
- }
306
+ let cached = cache.get(browsers);
424
307
 
425
- get finalSource() {
426
- return '';
427
- }
308
+ if (cached != null) {
309
+ return cached;
310
+ }
428
311
 
429
- };
312
+ let targets = (0, _css().browserslistToTargets)((0, _browserslist().default)(browsers));
313
+ cache.set(browsers, targets);
314
+ return targets;
430
315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/transformer-css",
3
- "version": "2.3.2",
3
+ "version": "2.5.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.5.0"
21
21
  },
22
22
  "dependencies": {
23
- "@parcel/hash": "2.3.2",
24
- "@parcel/plugin": "2.3.2",
23
+ "@parcel/css": "^1.8.1",
24
+ "@parcel/diagnostic": "2.5.0",
25
+ "@parcel/plugin": "2.5.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.5.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": "5cfb846d742eb86b1232e531be6e0e513551d838"
36
32
  }
@@ -1,68 +1,26 @@
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');
29
- },
30
-
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;
43
- }
44
-
45
- let code = await asset.getCode();
46
- if (
47
- code != null &&
48
- !canHaveDependencies(asset.filePath, code) &&
49
- !isCSSModule
50
- ) {
51
- return null;
52
- }
53
-
54
- return {
55
- type: 'postcss',
56
- version: '8.2.1',
57
- program: postcss
58
- .parse(code, {
59
- from: asset.filePath,
60
- })
61
- .toJSON(),
62
- };
17
+ async loadConfig({config, options}) {
18
+ let conf = await config.getConfigFrom(options.projectRoot + '/index', [], {
19
+ packageKey: '@parcel/transformer-css',
20
+ });
21
+ return conf?.contents;
63
22
  },
64
-
65
- async transform({asset, resolve, options}) {
23
+ async transform({asset, config, options}) {
66
24
  // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
67
25
  // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
68
26
  let env = asset.env;
@@ -72,305 +30,214 @@ export default (new Transformer({
72
30
  browsers: asset.env.engines.browsers,
73
31
  },
74
32
  shouldOptimize: asset.env.shouldOptimize,
33
+ shouldScopeHoist: asset.env.shouldScopeHoist,
75
34
  sourceMap: asset.env.sourceMap,
76
35
  });
77
36
 
78
- let isCSSModule =
79
- asset.meta.cssModulesCompiled !== true &&
80
- MODULE_BY_NAME_RE.test(asset.filePath);
37
+ let [code, originalMap] = await Promise.all([
38
+ asset.getBuffer(),
39
+ asset.getMap(),
40
+ ]);
41
+
42
+ let targets = getTargets(asset.env.engines.browsers);
43
+ let res;
44
+ try {
45
+ if (asset.meta.type === 'attr') {
46
+ res = transformStyleAttribute({
47
+ code,
48
+ analyzeDependencies: true,
49
+ targets,
50
+ });
51
+ } else {
52
+ res = transform({
53
+ filename: path.relative(options.projectRoot, asset.filePath),
54
+ code,
55
+ cssModules:
56
+ asset.meta.type !== 'tag' &&
57
+ (config?.cssModules ??
58
+ (asset.meta.cssModulesCompiled == null &&
59
+ /\.module\./.test(asset.filePath))),
60
+ analyzeDependencies: asset.meta.hasDependencies !== false,
61
+ sourceMap: !!asset.env.sourceMap,
62
+ drafts: config?.drafts,
63
+ pseudoClasses: config?.pseudoClasses,
64
+ targets,
65
+ });
66
+ }
67
+ } catch (err) {
68
+ err.filePath = asset.filePath;
69
+ let diagnostic = errorToDiagnostic(err, {
70
+ origin: '@parcel/transformer-css',
71
+ });
72
+ if (err.data?.type === 'AmbiguousUrlInCustomProperty' && err.data.url) {
73
+ let p =
74
+ '/' +
75
+ relativePath(
76
+ options.projectRoot,
77
+ path.resolve(path.dirname(asset.filePath), err.data.url),
78
+ false,
79
+ );
80
+ diagnostic[0].hints = [`Replace with: url(${p})`];
81
+ diagnostic[0].documentationURL =
82
+ 'https://parceljs.org/languages/css/#url()';
83
+ }
81
84
 
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];
85
+ throw new ThrowableDiagnostic({
86
+ diagnostic,
87
+ });
88
88
  }
89
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
- }
90
+ asset.setBuffer(res.code);
95
91
 
96
- if (asset.meta.hasDependencies === false) {
97
- return assets;
98
- }
92
+ if (res.map != null) {
93
+ let vlqMap = JSON.parse(res.map.toString());
94
+ let map = new SourceMap(options.projectRoot);
95
+ map.addVLQMap(vlqMap);
99
96
 
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);
97
+ if (originalMap) {
98
+ map.extends(originalMap);
111
99
  }
112
- return loc;
113
- };
114
100
 
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];
101
+ asset.setMap(map);
102
+ }
103
+
104
+ if (res.dependencies) {
105
+ for (let dep of res.dependencies) {
106
+ let loc = dep.loc;
107
+ if (originalMap) {
108
+ loc = remapSourceLocation(loc, originalMap);
109
+ }
110
+
111
+ if (dep.type === 'import' && !res.exports) {
112
+ asset.addDependency({
113
+ specifier: dep.url,
114
+ specifierType: 'url',
115
+ loc,
116
+ meta: {
117
+ // For the glob resolver to distinguish between `@import` and other URL dependencies.
118
+ isCSSImport: true,
119
+ media: dep.media,
120
+ },
121
+ });
122
+ } else if (dep.type === 'url') {
123
+ asset.addURLDependency(dep.url, {
124
+ loc,
125
+ meta: {
126
+ placeholder: dep.placeholder,
127
+ },
128
+ });
129
+ }
126
130
  }
131
+ }
132
+
133
+ let assets = [asset];
127
134
 
128
- specifier = name.value;
135
+ if (res.exports != null) {
136
+ let exports = res.exports;
137
+ asset.symbols.ensure();
138
+ asset.symbols.set('default', 'default');
129
139
 
130
- if (!specifier) {
131
- throw new Error('Could not find import name for ' + String(rule));
140
+ let dependencies = new Map();
141
+ let locals = new Map();
142
+ let c = 0;
143
+ let depjs = '';
144
+ let js = '';
145
+
146
+ let jsDeps = [];
147
+
148
+ for (let key in exports) {
149
+ locals.set(exports[key].name, key);
132
150
  }
133
151
 
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
- },
152
+ let seen = new Set();
153
+ let add = key => {
154
+ if (seen.has(key)) {
155
+ return;
156
+ }
157
+ seen.add(key);
158
+
159
+ let e = exports[key];
160
+ let s = `module.exports[${JSON.stringify(key)}] = \`${e.name}`;
161
+
162
+ for (let ref of e.composes) {
163
+ s += ' ';
164
+ if (ref.type === 'local') {
165
+ add(nullthrows(locals.get(ref.name)));
166
+ s +=
167
+ '${' +
168
+ `module.exports[${JSON.stringify(
169
+ nullthrows(locals.get(ref.name)),
170
+ )}]` +
171
+ '}';
172
+ } else if (ref.type === 'global') {
173
+ s += ref.name;
174
+ } else if (ref.type === 'dependency') {
175
+ let d = dependencies.get(ref.specifier);
176
+ if (d == null) {
177
+ d = `dep_${c++}`;
178
+ depjs += `import * as ${d} from ${JSON.stringify(
179
+ ref.specifier,
180
+ )};\n`;
181
+ dependencies.set(ref.specifier, d);
182
+ }
183
+ s += '${' + `${d}[${JSON.stringify(ref.name)}]` + '}';
184
+ }
185
+ }
186
+
187
+ s += '`;\n';
188
+
189
+ // If the export is referenced internally (e.g. used @keyframes), add a self-reference
190
+ // to the JS so the symbol is retained during tree-shaking.
191
+ if (e.isReferenced) {
192
+ s += `module.exports[${JSON.stringify(key)}];\n`;
193
+ }
194
+
195
+ js += s;
153
196
  };
154
- asset.addDependency(dep);
155
- rule.remove();
156
- // }
157
- isDirty = true;
158
- });
159
197
 
160
- program.walkDecls(decl => {
161
- if (URL_RE.test(decl.value)) {
162
- let parsed = valueParser(decl.value);
163
- let isDeclDirty = false;
198
+ for (let key in exports) {
199
+ asset.symbols.set(key, exports[key].name);
200
+ add(key);
201
+ }
164
202
 
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;
203
+ if (res.dependencies) {
204
+ for (let dep of res.dependencies) {
205
+ if (dep.type === 'import') {
206
+ // TODO: Figure out how to treeshake this
207
+ let d = `dep_$${c++}`;
208
+ depjs += `import * as ${d} from ${JSON.stringify(dep.url)};\n`;
209
+ js += `for (let key in ${d}) { if (key in module.exports) module.exports[key] += ' ' + ${d}[key]; else module.exports[key] = ${d}[key]; }\n`;
210
+ asset.symbols.set('*', '*');
189
211
  }
190
- });
191
-
192
- if (isDeclDirty) {
193
- decl.value = parsed.toString();
194
- isDirty = true;
195
212
  }
196
213
  }
197
- });
198
214
 
199
- if (isDirty) {
200
- asset.setAST({
201
- ...ast,
202
- program: program.toJSON(),
215
+ assets.push({
216
+ type: 'js',
217
+ content: depjs + js,
218
+ dependencies: jsDeps,
219
+ env,
203
220
  });
204
221
  }
205
222
 
206
223
  return assets;
207
224
  },
208
-
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
- });
222
-
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());
230
- }
231
- } else {
232
- map = originalSourceMap;
233
- }
234
-
235
- return {
236
- content: result.css,
237
- map,
238
- };
239
- },
240
225
  }): Transformer);
241
226
 
242
- async function compileCSSModules(asset, env, program, resolve, options) {
243
- let cssModules;
227
+ let cache = new Map();
244
228
 
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
- });
229
+ function getTargets(browsers) {
230
+ if (browsers == null) {
231
+ return undefined;
273
232
  }
274
233
 
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
- });
234
+ let cached = cache.get(browsers);
235
+ if (cached != null) {
236
+ return cached;
336
237
  }
337
- return assets;
338
- }
339
238
 
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
- }
239
+ let targets = browserslistToTargets(browserslist(browsers));
371
240
 
372
- get finalSource() {
373
- return '';
374
- }
375
- };
241
+ cache.set(browsers, targets);
242
+ return targets;
376
243
  }