@parcel/transformer-css 2.1.1 → 2.2.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,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 _fileSystemLoader() {
89
+ const data = _interopRequireDefault(require("css-modules-loader-core/lib/file-system-loader"));
90
+
91
+ _fileSystemLoader = 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
120
  const URL_RE = /url\s*\("?(?![a-z]+:)/;
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,11 +262,13 @@ 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)
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)
208
268
  });
209
- isDeclDirty = node.nodes[0].value !== url;
210
- node.nodes[0].value = url;
269
+ isDeclDirty = urlNode.value !== url;
270
+ urlNode.type = 'word';
271
+ urlNode.value = url;
211
272
  }
212
273
  });
213
274
 
@@ -224,7 +285,7 @@ var _default = new (_plugin().Transformer)({
224
285
  });
225
286
  }
226
287
 
227
- return [asset];
288
+ return assets;
228
289
  },
229
290
 
230
291
  async generate({
@@ -266,4 +327,115 @@ var _default = new (_plugin().Transformer)({
266
327
 
267
328
  });
268
329
 
269
- exports.default = _default;
330
+ exports.default = _default;
331
+
332
+ async function compileCSSModules(asset, env, program, resolve, options) {
333
+ let cssModules;
334
+ let code = asset.isASTDirty() ? null : await asset.getCode();
335
+
336
+ if (code == null || COMPOSES_RE.test(code)) {
337
+ program.walkDecls(decl => {
338
+ let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
339
+
340
+ if (decl.prop === 'composes' && importPath != null) {
341
+ let parsed = (0, _postcssValueParser().default)(decl.value);
342
+ let start = decl.source.start;
343
+ parsed.walk(node => {
344
+ if (node.type === 'string') {
345
+ asset.addDependency({
346
+ specifier: importPath,
347
+ specifierType: 'url',
348
+ loc: start ? {
349
+ filePath: asset.filePath,
350
+ start,
351
+ end: {
352
+ line: start.line,
353
+ column: start.column + importPath.length
354
+ }
355
+ } : undefined
356
+ });
357
+ }
358
+ });
359
+ }
360
+ });
361
+ }
362
+
363
+ let {
364
+ root
365
+ } = await (0, _postcss().default)([(0, _postcssModules().default)({
366
+ getJSON: (filename, json) => cssModules = json,
367
+ Loader: createLoader(asset, resolve),
368
+ generateScopedName: (name, filename) => `${name}_${(0, _hash().hashString)(_path().default.relative(options.projectRoot, filename)).substr(0, 6)}`
369
+ })]).process(program, {
370
+ from: asset.filePath,
371
+ to: asset.filePath
372
+ });
373
+ asset.setAST({
374
+ type: 'postcss',
375
+ version: '8.2.1',
376
+ program: root.toJSON()
377
+ });
378
+ let assets = [asset];
379
+
380
+ if (cssModules) {
381
+ // $FlowFixMe
382
+ let cssModulesList = Object.entries(cssModules);
383
+ let deps = asset.getDependencies().filter(dep => dep.priority === 'sync');
384
+ let code;
385
+
386
+ if (deps.length > 0) {
387
+ code = `
388
+ module.exports = Object.assign({}, ${deps.map(dep => `require(${JSON.stringify(dep.specifier)})`).join(', ')}, ${JSON.stringify(cssModules, null, 2)});
389
+ `;
390
+ } else {
391
+ code = cssModulesList.map( // This syntax enables shaking the invidual statements, so that unused classes don't even exist in JS.
392
+ ([className, classNameHashed]) => `module.exports[${JSON.stringify(className)}] = ${JSON.stringify(classNameHashed)};`).join('\n');
393
+ }
394
+
395
+ asset.symbols.ensure();
396
+
397
+ for (let [k, v] of cssModulesList) {
398
+ asset.symbols.set(k, v);
399
+ }
400
+
401
+ asset.symbols.set('default', 'default');
402
+ assets.push({
403
+ type: 'js',
404
+ content: code,
405
+ env
406
+ });
407
+ }
408
+
409
+ return assets;
410
+ }
411
+
412
+ function createLoader(asset, resolve) {
413
+ return class extends _fileSystemLoader().default {
414
+ async fetch(composesPath, relativeTo) {
415
+ let importPath = composesPath.replace(/^["']|["']$/g, '');
416
+ let resolved = await resolve(relativeTo, importPath);
417
+
418
+ let rootRelativePath = _path().default.resolve(_path().default.dirname(relativeTo), resolved);
419
+
420
+ let root = _path().default.resolve('/'); // fixes an issue on windows which is part of the css-modules-loader-core
421
+ // see https://github.com/css-modules/css-modules-loader-core/issues/230
422
+
423
+
424
+ if (rootRelativePath.startsWith(root)) {
425
+ rootRelativePath = rootRelativePath.substr(root.length);
426
+ }
427
+
428
+ let source = await asset.fs.readFile(resolved, 'utf-8');
429
+ let {
430
+ exportTokens
431
+ } = await this.core.load(source, rootRelativePath, undefined, // $FlowFixMe[method-unbinding]
432
+ this.fetch.bind(this));
433
+ return exportTokens;
434
+ }
435
+
436
+ get finalSource() {
437
+ return '';
438
+ }
439
+
440
+ };
441
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/transformer-css",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,16 +17,19 @@
17
17
  "source": "src/CSSTransformer.js",
18
18
  "engines": {
19
19
  "node": ">= 12.0.0",
20
- "parcel": "^2.1.1"
20
+ "parcel": "^2.2.0"
21
21
  },
22
22
  "dependencies": {
23
- "@parcel/plugin": "^2.1.1",
23
+ "@parcel/hash": "^2.2.0",
24
+ "@parcel/plugin": "^2.2.0",
24
25
  "@parcel/source-map": "^2.0.0",
25
- "@parcel/utils": "^2.1.1",
26
+ "@parcel/utils": "^2.2.0",
27
+ "css-modules-loader-core": "^1.1.0",
26
28
  "nullthrows": "^1.1.1",
27
29
  "postcss": "^8.3.0",
30
+ "postcss-modules": "^3.2.2",
28
31
  "postcss-value-parser": "^4.1.0",
29
32
  "semver": "^5.7.1"
30
33
  },
31
- "gitHead": "f53ffe772a8259d94ca2937d02fde149454112f3"
34
+ "gitHead": "4745cd3023f8d5a5adcf9e565d5b82d1418dc262"
32
35
  }
@@ -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 'css-modules-loader-core/lib/file-system-loader';
12
15
  import semver from 'semver';
16
+ import path from 'path';
13
17
 
14
18
  const URL_RE = /url\s*\("?(?![a-z]+:)/;
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,22 @@ 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 = 'word';
189
+ urlNode.value = url;
158
190
  }
159
191
  });
160
192
 
@@ -172,7 +204,7 @@ export default (new Transformer({
172
204
  });
173
205
  }
174
206
 
175
- return [asset];
207
+ return assets;
176
208
  },
177
209
 
178
210
  async generate({asset, ast, options}) {
@@ -207,3 +239,124 @@ export default (new Transformer({
207
239
  };
208
240
  },
209
241
  }): Transformer);
242
+
243
+ async function compileCSSModules(asset, env, program, resolve, options) {
244
+ let cssModules;
245
+
246
+ let code = asset.isASTDirty() ? null : await asset.getCode();
247
+ if (code == null || COMPOSES_RE.test(code)) {
248
+ program.walkDecls(decl => {
249
+ let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
250
+ if (decl.prop === 'composes' && importPath != null) {
251
+ let parsed = valueParser(decl.value);
252
+ let start = (decl.source.start: any);
253
+
254
+ parsed.walk(node => {
255
+ if (node.type === 'string') {
256
+ asset.addDependency({
257
+ specifier: importPath,
258
+ specifierType: 'url',
259
+ loc: start
260
+ ? {
261
+ filePath: asset.filePath,
262
+ start,
263
+ end: {
264
+ line: start.line,
265
+ column: start.column + importPath.length,
266
+ },
267
+ }
268
+ : undefined,
269
+ });
270
+ }
271
+ });
272
+ }
273
+ });
274
+ }
275
+
276
+ let {root} = await postcss([
277
+ postcssModules({
278
+ getJSON: (filename, json) => (cssModules = json),
279
+ Loader: createLoader(asset, resolve),
280
+ generateScopedName: (name, filename) =>
281
+ `${name}_${hashString(
282
+ path.relative(options.projectRoot, filename),
283
+ ).substr(0, 6)}`,
284
+ }),
285
+ ]).process(program, {from: asset.filePath, to: asset.filePath});
286
+ asset.setAST({
287
+ type: 'postcss',
288
+ version: '8.2.1',
289
+ program: root.toJSON(),
290
+ });
291
+
292
+ let assets = [asset];
293
+ if (cssModules) {
294
+ // $FlowFixMe
295
+ let cssModulesList = (Object.entries(cssModules): Array<[string, string]>);
296
+ let deps = asset.getDependencies().filter(dep => dep.priority === 'sync');
297
+ let code: string;
298
+ if (deps.length > 0) {
299
+ code = `
300
+ module.exports = Object.assign({}, ${deps
301
+ .map(dep => `require(${JSON.stringify(dep.specifier)})`)
302
+ .join(', ')}, ${JSON.stringify(cssModules, null, 2)});
303
+ `;
304
+ } else {
305
+ code = cssModulesList
306
+ .map(
307
+ // This syntax enables shaking the invidual statements, so that unused classes don't even exist in JS.
308
+ ([className, classNameHashed]) =>
309
+ `module.exports[${JSON.stringify(className)}] = ${JSON.stringify(
310
+ classNameHashed,
311
+ )};`,
312
+ )
313
+ .join('\n');
314
+ }
315
+
316
+ asset.symbols.ensure();
317
+ for (let [k, v] of cssModulesList) {
318
+ asset.symbols.set(k, v);
319
+ }
320
+ asset.symbols.set('default', 'default');
321
+
322
+ assets.push({
323
+ type: 'js',
324
+ content: code,
325
+ env,
326
+ });
327
+ }
328
+ return assets;
329
+ }
330
+
331
+ function createLoader(
332
+ asset: MutableAsset,
333
+ resolve: (from: FilePath, to: string) => Promise<FilePath>,
334
+ ) {
335
+ return class ParcelFileSystemLoader extends FileSystemLoader {
336
+ async fetch(composesPath, relativeTo) {
337
+ let importPath = composesPath.replace(/^["']|["']$/g, '');
338
+ let resolved = await resolve(relativeTo, importPath);
339
+ let rootRelativePath = path.resolve(path.dirname(relativeTo), resolved);
340
+ let root = path.resolve('/');
341
+ // fixes an issue on windows which is part of the css-modules-loader-core
342
+ // see https://github.com/css-modules/css-modules-loader-core/issues/230
343
+ if (rootRelativePath.startsWith(root)) {
344
+ rootRelativePath = rootRelativePath.substr(root.length);
345
+ }
346
+
347
+ let source = await asset.fs.readFile(resolved, 'utf-8');
348
+ let {exportTokens} = await this.core.load(
349
+ source,
350
+ rootRelativePath,
351
+ undefined,
352
+ // $FlowFixMe[method-unbinding]
353
+ this.fetch.bind(this),
354
+ );
355
+ return exportTokens;
356
+ }
357
+
358
+ get finalSource() {
359
+ return '';
360
+ }
361
+ };
362
+ }