@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.
@@ -5,32 +5,113 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
- var _sourceMap = _interopRequireDefault(require("@parcel/source-map"));
8
+ function _hash() {
9
+ const data = require("@parcel/hash");
9
10
 
10
- var _plugin = require("@parcel/plugin");
11
+ _hash = function () {
12
+ return data;
13
+ };
11
14
 
12
- var _utils = require("@parcel/utils");
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
- var _postcss = _interopRequireDefault(require("postcss"));
81
+ _semver = function () {
82
+ return data;
83
+ };
15
84
 
16
- var _postcssValueParser = _interopRequireDefault(require("postcss-value-parser"));
85
+ return data;
86
+ }
87
+
88
+ function _path() {
89
+ const data = _interopRequireDefault(require("path"));
17
90
 
18
- var _semver = _interopRequireDefault(require("semver"));
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*\("?(?![a-z]+:)/;
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, '^7.0.0');
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
- if (asset.meta.hasDependencies === false) {
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: '7.0.0',
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
- minify: asset.env.minify
75
- }); // When this asset is an bundle entry, allow that bundle to be split to load shared assets separately.
76
- // Only set here if it is null to allow previous transformers to override this behavior.
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
- ast.program.walkAtRules('import', rule => {
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 moduleSpecifier;
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
- moduleSpecifier = name.value;
102
-
103
- if (!moduleSpecifier) {
104
- throw new Error('Could not find import name for ' + rule);
105
- }
106
-
107
- if ((0, _utils.isURL)(moduleSpecifier)) {
108
- name.value = asset.addURLDependency(moduleSpecifier, {
109
- loc: (0, _utils.createDependencyLocation)(rule.source.start, asset.filePath, 0, 8)
110
- });
111
- } else {
112
- // If this came from an inline <style> tag, don't inline the imported file. Replace with the correct URL instead.
113
- // TODO: run CSSPackager on inline style tags.
114
- // let inlineHTML =
115
- // this.options.rendition && this.options.rendition.inlineHTML;
116
- // if (inlineHTML) {
117
- // name.value = asset.addURLDependency(dep, {loc: rule.source.start});
118
- // rule.params = params.toString();
119
- // } else {
120
- media = _postcssValueParser.default.stringify(media).trim();
121
- let dep = {
122
- moduleSpecifier,
123
- // Offset by 8 as it does not include `@import `
124
- loc: (0, _utils.createDependencyLocation)(rule.source.start, moduleSpecifier, 0, 8),
125
- meta: {
126
- media
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
- ast.program.walkDecls(decl => {
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
- let url = asset.addURLDependency(node.nodes[0].value, {
143
- loc: (0, _utils.createDependencyLocation)(decl.source.start, node.nodes[0].value)
144
- });
145
- isDeclDirty = node.nodes[0].value !== url;
146
- node.nodes[0].value = url;
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 [asset];
269
+ return assets;
162
270
  },
163
271
 
164
272
  async generate({
273
+ asset,
165
274
  ast,
166
275
  options
167
276
  }) {
168
- let root = ast.program; // $FlowFixMe
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.addRawMappings(result.map.toJSON());
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-beta.1",
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": ">= 10.0.0",
16
- "parcel": "^2.0.0-alpha.1.1"
19
+ "node": ">= 12.0.0",
20
+ "parcel": "2.0.0-nightly.1000+5530a6ef"
17
21
  },
18
22
  "dependencies": {
19
- "@parcel/plugin": "2.0.0-beta.1",
20
- "@parcel/source-map": "2.0.0-alpha.4.13",
21
- "@parcel/utils": "2.0.0-beta.1",
22
- "postcss": "^7.0.5",
23
- "postcss-value-parser": "^3.3.1",
24
- "semver": "^5.4.1"
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": "74335525be92e23bac4ed1bf30595443cfb238e3"
35
+ "gitHead": "5530a6eff8b619873353baeb0457ae4ec591e9fa"
27
36
  }
@@ -1,25 +1,31 @@
1
1
  // @flow
2
2
 
3
- import type {FilePath} from '@parcel/types';
4
- import type {Container, Node} from 'postcss';
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, isURL} from '@parcel/utils';
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*\("?(?![a-z]+:)/;
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, '^7.0.0');
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
- if (asset.meta.hasDependencies === false) {
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 (code != null && !canHaveDependencies(asset.filePath, code)) {
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: '7.0.0',
44
- program: postcss.parse(code, {
45
- from: asset.filePath,
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
- minify: asset.env.minify,
74
+ shouldOptimize: asset.env.shouldOptimize,
75
+ sourceMap: asset.env.sourceMap,
59
76
  });
60
77
 
61
- // When this asset is an bundle entry, allow that bundle to be split to load shared assets separately.
62
- // Only set here if it is null to allow previous transformers to override this behavior.
63
- if (asset.isSplittable == null) {
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
- ast.program.walkAtRules('import', rule => {
116
+ program.walkAtRules('import', rule => {
77
117
  let params = valueParser(rule.params);
78
118
  let [name, ...media] = params.nodes;
79
- let moduleSpecifier;
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
- moduleSpecifier = name.value;
128
+ specifier = name.value;
89
129
 
90
- if (!moduleSpecifier) {
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
- if (isURL(moduleSpecifier)) {
95
- name.value = asset.addURLDependency(moduleSpecifier, {
96
- loc: createDependencyLocation(
97
- rule.source.start,
98
- asset.filePath,
99
- 0,
100
- 8,
101
- ),
102
- });
103
- } else {
104
- // If this came from an inline <style> tag, don't inline the imported file. Replace with the correct URL instead.
105
- // TODO: run CSSPackager on inline style tags.
106
- // let inlineHTML =
107
- // this.options.rendition && this.options.rendition.inlineHTML;
108
- // if (inlineHTML) {
109
- // name.value = asset.addURLDependency(dep, {loc: rule.source.start});
110
- // rule.params = params.toString();
111
- // } else {
112
- media = valueParser.stringify(media).trim();
113
- let dep = {
114
- moduleSpecifier,
115
- // Offset by 8 as it does not include `@import `
116
- loc: createDependencyLocation(
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
- ast.program.walkDecls(decl => {
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 url = asset.addURLDependency(node.nodes[0].value, {
146
- loc: createDependencyLocation(
147
- decl.source.start,
148
- node.nodes[0].value,
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 = node.nodes[0].value !== url;
152
- node.nodes[0].value = url;
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(ast);
200
+ asset.setAST({
201
+ ...ast,
202
+ program: program.toJSON(),
203
+ });
165
204
  }
166
205
 
167
- return [asset];
206
+ return assets;
168
207
  },
169
208
 
170
- async generate({ast, options}) {
171
- let root = ast.program;
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.addRawMappings(result.map.toJSON());
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
+ }