@parcel/transformer-css 2.0.1 → 2.2.1

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