@parcel/transformer-css 2.0.0-beta.3.1 → 2.0.0-nightly.1006

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,6 +5,16 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
+ function _hash() {
9
+ const data = require("@parcel/hash");
10
+
11
+ _hash = function () {
12
+ return data;
13
+ };
14
+
15
+ return data;
16
+ }
17
+
8
18
  function _sourceMap() {
9
19
  const data = _interopRequireDefault(require("@parcel/source-map"));
10
20
 
@@ -75,10 +85,23 @@ function _semver() {
75
85
  return data;
76
86
  }
77
87
 
88
+ function _path() {
89
+ const data = _interopRequireDefault(require("path"));
90
+
91
+ _path = function () {
92
+ return data;
93
+ };
94
+
95
+ return data;
96
+ }
97
+
78
98
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
79
99
 
80
- const URL_RE = /url\s*\("?(?![a-z]+:)/;
100
+ const URL_RE = /url\s*\(/;
81
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\./;
82
105
 
83
106
  function canHaveDependencies(filePath, code) {
84
107
  return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code);
@@ -100,13 +123,15 @@ var _default = new (_plugin().Transformer)({
100
123
  // to be filled in later. When the CSS transformer runs, it would pick that up and try to
101
124
  // resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do
102
125
  // it this way since the resulting CSS doesn't need to be re-parsed.
103
- 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) {
104
129
  return null;
105
130
  }
106
131
 
107
132
  let code = await asset.getCode();
108
133
 
109
- if (code != null && !canHaveDependencies(asset.filePath, code)) {
134
+ if (code != null && !canHaveDependencies(asset.filePath, code) && !isCSSModule) {
110
135
  return null;
111
136
  }
112
137
 
@@ -120,10 +145,13 @@ var _default = new (_plugin().Transformer)({
120
145
  },
121
146
 
122
147
  async transform({
123
- asset
148
+ asset,
149
+ resolve,
150
+ options
124
151
  }) {
125
152
  // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
126
153
  // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
154
+ let env = asset.env;
127
155
  asset.setEnvironment({
128
156
  context: 'browser',
129
157
  engines: {
@@ -131,28 +159,33 @@ var _default = new (_plugin().Transformer)({
131
159
  },
132
160
  shouldOptimize: asset.env.shouldOptimize,
133
161
  sourceMap: asset.env.sourceMap
134
- }); // When this asset is an bundle entry, allow that bundle to be split to load shared assets separately.
135
- // Only set here if it is null to allow previous transformers to override this behavior.
136
-
137
- if (asset.isSplittable == null) {
138
- asset.isSplittable = true;
139
- } // Check for `hasDependencies` being false here as well, as it's possible
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
140
164
  // another transformer (such as PostCSSTransformer) has already parsed an
141
165
  // ast and CSSTransformer's parse was never called.
142
166
 
143
-
144
167
  let ast = await asset.getAST();
145
168
 
146
- if (!ast || asset.meta.hasDependencies === false) {
169
+ if (!ast || asset.meta.hasDependencies === false && !isCSSModule) {
147
170
  return [asset];
148
171
  }
149
172
 
150
173
  let program = _postcss().default.fromJSON(ast.program);
151
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
+
152
185
  let originalSourceMap = await asset.getMap();
153
186
 
154
- let createLoc = (start, specifier, lineOffset, colOffset) => {
155
- let loc = (0, _utils().createDependencyLocation)(start, specifier, lineOffset, colOffset);
187
+ let createLoc = (start, specifier, lineOffset, colOffset, o) => {
188
+ let loc = (0, _utils().createDependencyLocation)(start, specifier, lineOffset, colOffset, o);
156
189
 
157
190
  if (originalSourceMap) {
158
191
  loc = (0, _utils().remapSourceLocation)(loc, originalSourceMap);
@@ -165,43 +198,40 @@ var _default = new (_plugin().Transformer)({
165
198
  program.walkAtRules('import', rule => {
166
199
  let params = (0, _postcssValueParser().default)(rule.params);
167
200
  let [name, ...media] = params.nodes;
168
- let moduleSpecifier;
201
+ let specifier;
169
202
 
170
203
  if (name.type === 'function' && name.value === 'url' && name.nodes.length) {
171
204
  name = name.nodes[0];
172
205
  }
173
206
 
174
- moduleSpecifier = name.value;
207
+ specifier = name.value;
175
208
 
176
- if (!moduleSpecifier) {
209
+ if (!specifier) {
177
210
  throw new Error('Could not find import name for ' + String(rule));
178
- }
179
-
180
- if ((0, _utils().isURL)(moduleSpecifier)) {
181
- name.value = asset.addURLDependency(moduleSpecifier, {
182
- loc: createLoc((0, _nullthrows().default)(rule.source.start), asset.filePath, 0, 8)
183
- });
184
- } else {
185
- // If this came from an inline <style> tag, don't inline the imported file. Replace with the correct URL instead.
186
- // TODO: run CSSPackager on inline style tags.
187
- // let inlineHTML =
188
- // this.options.rendition && this.options.rendition.inlineHTML;
189
- // if (inlineHTML) {
190
- // name.value = asset.addURLDependency(dep, {loc: rule.source.start});
191
- // rule.params = params.toString();
192
- // } else {
193
- media = _postcssValueParser().default.stringify(media).trim();
194
- let dep = {
195
- moduleSpecifier,
196
- // Offset by 8 as it does not include `@import `
197
- loc: createLoc((0, _nullthrows().default)(rule.source.start), moduleSpecifier, 0, 8),
198
- meta: {
199
- media
200
- }
201
- };
202
- asset.addDependency(dep);
203
- rule.remove(); // }
204
- }
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(); // }
205
235
 
206
236
  isDirty = true;
207
237
  });
@@ -212,12 +242,15 @@ var _default = new (_plugin().Transformer)({
212
242
  parsed.walk(node => {
213
243
  if (node.type === 'function' && node.value === 'url' && node.nodes.length > 0 && !node.nodes[0].value.startsWith('#') // IE's `behavior: url(#default#VML)`
214
244
  ) {
215
- let url = asset.addURLDependency(node.nodes[0].value, {
216
- loc: createLoc((0, _nullthrows().default)(decl.source.start), node.nodes[0].value, 0, node.nodes[0].sourceIndex)
217
- });
218
- isDeclDirty = node.nodes[0].value !== url;
219
- node.nodes[0].value = url;
220
- }
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
+ }
221
254
  });
222
255
 
223
256
  if (isDeclDirty) {
@@ -233,7 +266,7 @@ var _default = new (_plugin().Transformer)({
233
266
  });
234
267
  }
235
268
 
236
- return [asset];
269
+ return assets;
237
270
  },
238
271
 
239
272
  async generate({
@@ -275,4 +308,123 @@ var _default = new (_plugin().Transformer)({
275
308
 
276
309
  });
277
310
 
278
- 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,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/transformer-css",
3
- "version": "2.0.0-beta.3.1",
3
+ "version": "2.0.0-nightly.1006+18b038d5",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,16 +17,20 @@
17
17
  "source": "src/CSSTransformer.js",
18
18
  "engines": {
19
19
  "node": ">= 12.0.0",
20
- "parcel": "^2.0.0-beta.1"
20
+ "parcel": "2.0.0-nightly.1004+18b038d5"
21
21
  },
22
22
  "dependencies": {
23
- "@parcel/plugin": "2.0.0-beta.3.1",
24
- "@parcel/source-map": "2.0.0-rc.1.0",
25
- "@parcel/utils": "2.0.0-beta.3.1",
23
+ "@parcel/hash": "2.3.3-nightly.2629+18b038d5",
24
+ "@parcel/plugin": "2.0.0-nightly.1006+18b038d5",
25
+ "@parcel/source-map": "^2.0.0",
26
+ "@parcel/utils": "2.0.0-nightly.1006+18b038d5",
26
27
  "nullthrows": "^1.1.1",
27
- "postcss": "^8.2.1",
28
- "postcss-value-parser": "^4.1.0",
29
- "semver": "^5.4.1"
28
+ "postcss": "^8.4.5",
29
+ "postcss-value-parser": "^4.2.0",
30
+ "semver": "^5.7.1"
30
31
  },
31
- "gitHead": "daece49d003ba804bbdaa3a7ed3d6aaf446f166d"
32
+ "devDependencies": {
33
+ "postcss-modules": "^4.3.0"
34
+ },
35
+ "gitHead": "18b038d57043965afb31077db1f46dadb11a6a78"
32
36
  }
@@ -1,22 +1,23 @@
1
1
  // @flow
2
2
 
3
3
  import type {Root} from 'postcss';
4
- import type {FilePath} from '@parcel/types';
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 {
9
- createDependencyLocation,
10
- isURL,
11
- remapSourceLocation,
12
- } from '@parcel/utils';
9
+ import {createDependencyLocation, remapSourceLocation} from '@parcel/utils';
13
10
  import postcss from 'postcss';
14
11
  import nullthrows from 'nullthrows';
15
12
  import valueParser from 'postcss-value-parser';
16
13
  import semver from 'semver';
14
+ import path from 'path';
17
15
 
18
- const URL_RE = /url\s*\("?(?![a-z]+:)/;
16
+ const URL_RE = /url\s*\(/;
19
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\./;
20
21
 
21
22
  function canHaveDependencies(filePath: FilePath, code: string) {
22
23
  return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code);
@@ -34,12 +35,19 @@ export default (new Transformer({
34
35
  // to be filled in later. When the CSS transformer runs, it would pick that up and try to
35
36
  // resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do
36
37
  // it this way since the resulting CSS doesn't need to be re-parsed.
37
- 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) {
38
42
  return null;
39
43
  }
40
44
 
41
45
  let code = await asset.getCode();
42
- if (code != null && !canHaveDependencies(asset.filePath, code)) {
46
+ if (
47
+ code != null &&
48
+ !canHaveDependencies(asset.filePath, code) &&
49
+ !isCSSModule
50
+ ) {
43
51
  return null;
44
52
  }
45
53
 
@@ -54,9 +62,10 @@ export default (new Transformer({
54
62
  };
55
63
  },
56
64
 
57
- async transform({asset}) {
65
+ async transform({asset, resolve, options}) {
58
66
  // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
59
67
  // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
68
+ let env = asset.env;
60
69
  asset.setEnvironment({
61
70
  context: 'browser',
62
71
  engines: {
@@ -66,28 +75,36 @@ export default (new Transformer({
66
75
  sourceMap: asset.env.sourceMap,
67
76
  });
68
77
 
69
- // When this asset is an bundle entry, allow that bundle to be split to load shared assets separately.
70
- // Only set here if it is null to allow previous transformers to override this behavior.
71
- if (asset.isSplittable == null) {
72
- asset.isSplittable = true;
73
- }
78
+ let isCSSModule =
79
+ asset.meta.cssModulesCompiled !== true &&
80
+ MODULE_BY_NAME_RE.test(asset.filePath);
74
81
 
75
82
  // Check for `hasDependencies` being false here as well, as it's possible
76
83
  // another transformer (such as PostCSSTransformer) has already parsed an
77
84
  // ast and CSSTransformer's parse was never called.
78
85
  let ast = await asset.getAST();
79
- if (!ast || asset.meta.hasDependencies === false) {
86
+ if (!ast || (asset.meta.hasDependencies === false && !isCSSModule)) {
80
87
  return [asset];
81
88
  }
82
89
 
83
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
+
84
100
  let originalSourceMap = await asset.getMap();
85
- let createLoc = (start, specifier, lineOffset, colOffset) => {
101
+ let createLoc = (start, specifier, lineOffset, colOffset, o) => {
86
102
  let loc = createDependencyLocation(
87
103
  start,
88
104
  specifier,
89
105
  lineOffset,
90
106
  colOffset,
107
+ o,
91
108
  );
92
109
  if (originalSourceMap) {
93
110
  loc = remapSourceLocation(loc, originalSourceMap);
@@ -99,7 +116,7 @@ export default (new Transformer({
99
116
  program.walkAtRules('import', rule => {
100
117
  let params = valueParser(rule.params);
101
118
  let [name, ...media] = params.nodes;
102
- let moduleSpecifier;
119
+ let specifier;
103
120
  if (
104
121
  name.type === 'function' &&
105
122
  name.value === 'url' &&
@@ -108,38 +125,35 @@ export default (new Transformer({
108
125
  name = name.nodes[0];
109
126
  }
110
127
 
111
- moduleSpecifier = name.value;
128
+ specifier = name.value;
112
129
 
113
- if (!moduleSpecifier) {
130
+ if (!specifier) {
114
131
  throw new Error('Could not find import name for ' + String(rule));
115
132
  }
116
133
 
117
- if (isURL(moduleSpecifier)) {
118
- name.value = asset.addURLDependency(moduleSpecifier, {
119
- loc: createLoc(nullthrows(rule.source.start), asset.filePath, 0, 8),
120
- });
121
- } else {
122
- // If this came from an inline <style> tag, don't inline the imported file. Replace with the correct URL instead.
123
- // TODO: run CSSPackager on inline style tags.
124
- // let inlineHTML =
125
- // this.options.rendition && this.options.rendition.inlineHTML;
126
- // if (inlineHTML) {
127
- // name.value = asset.addURLDependency(dep, {loc: rule.source.start});
128
- // rule.params = params.toString();
129
- // } else {
130
- media = valueParser.stringify(media).trim();
131
- let dep = {
132
- moduleSpecifier,
133
- // Offset by 8 as it does not include `@import `
134
- loc: createLoc(nullthrows(rule.source.start), moduleSpecifier, 0, 8),
135
- meta: {
136
- media,
137
- },
138
- };
139
- asset.addDependency(dep);
140
- rule.remove();
141
- // }
142
- }
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
+ // }
143
157
  isDirty = true;
144
158
  });
145
159
 
@@ -155,16 +169,23 @@ export default (new Transformer({
155
169
  node.nodes.length > 0 &&
156
170
  !node.nodes[0].value.startsWith('#') // IE's `behavior: url(#default#VML)`
157
171
  ) {
158
- let url = asset.addURLDependency(node.nodes[0].value, {
159
- loc: createLoc(
160
- nullthrows(decl.source.start),
161
- node.nodes[0].value,
162
- 0,
163
- node.nodes[0].sourceIndex,
164
- ),
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
+ ),
165
184
  });
166
- isDeclDirty = node.nodes[0].value !== url;
167
- node.nodes[0].value = url;
185
+ isDeclDirty = urlNode.value !== url;
186
+ urlNode.type = 'string';
187
+ urlNode.quote = '"';
188
+ urlNode.value = url;
168
189
  }
169
190
  });
170
191
 
@@ -182,7 +203,7 @@ export default (new Transformer({
182
203
  });
183
204
  }
184
205
 
185
- return [asset];
206
+ return assets;
186
207
  },
187
208
 
188
209
  async generate({asset, ast, options}) {
@@ -217,3 +238,139 @@ export default (new Transformer({
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
+ }