@parcel/transformer-css 2.1.1 → 2.3.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
 
@@ -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,22 +159,33 @@ var _default = new (_plugin().Transformer)({
131
159
  },
132
160
  shouldOptimize: asset.env.shouldOptimize,
133
161
  sourceMap: asset.env.sourceMap
134
- }); // 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
135
164
  // another transformer (such as PostCSSTransformer) has already parsed an
136
165
  // ast and CSSTransformer's parse was never called.
137
166
 
138
167
  let ast = await asset.getAST();
139
168
 
140
- if (!ast || asset.meta.hasDependencies === false) {
169
+ if (!ast || asset.meta.hasDependencies === false && !isCSSModule) {
141
170
  return [asset];
142
171
  }
143
172
 
144
173
  let program = _postcss().default.fromJSON(ast.program);
145
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
+
146
185
  let originalSourceMap = await asset.getMap();
147
186
 
148
- let createLoc = (start, specifier, lineOffset, colOffset) => {
149
- 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);
150
189
 
151
190
  if (originalSourceMap) {
152
191
  loc = (0, _utils().remapSourceLocation)(loc, originalSourceMap);
@@ -203,12 +242,15 @@ var _default = new (_plugin().Transformer)({
203
242
  parsed.walk(node => {
204
243
  if (node.type === 'function' && node.value === 'url' && node.nodes.length > 0 && !node.nodes[0].value.startsWith('#') // IE's `behavior: url(#default#VML)`
205
244
  ) {
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
- }
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
+ }
212
254
  });
213
255
 
214
256
  if (isDeclDirty) {
@@ -224,7 +266,7 @@ var _default = new (_plugin().Transformer)({
224
266
  });
225
267
  }
226
268
 
227
- return [asset];
269
+ return assets;
228
270
  },
229
271
 
230
272
  async generate({
@@ -266,4 +308,123 @@ var _default = new (_plugin().Transformer)({
266
308
 
267
309
  });
268
310
 
269
- 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.1.1",
3
+ "version": "2.3.1",
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.1.1"
20
+ "parcel": "^2.3.1"
21
21
  },
22
22
  "dependencies": {
23
- "@parcel/plugin": "^2.1.1",
23
+ "@parcel/hash": "2.3.1",
24
+ "@parcel/plugin": "2.3.1",
24
25
  "@parcel/source-map": "^2.0.0",
25
- "@parcel/utils": "^2.1.1",
26
+ "@parcel/utils": "2.3.1",
26
27
  "nullthrows": "^1.1.1",
27
- "postcss": "^8.3.0",
28
- "postcss-value-parser": "^4.1.0",
28
+ "postcss": "^8.4.5",
29
+ "postcss-value-parser": "^4.2.0",
29
30
  "semver": "^5.7.1"
30
31
  },
31
- "gitHead": "f53ffe772a8259d94ca2937d02fde149454112f3"
32
+ "devDependencies": {
33
+ "postcss-modules": "^4.3.0"
34
+ },
35
+ "gitHead": "699f0b24c38eabcdad0960c62c03bd2f2902b19e"
32
36
  }
@@ -1,8 +1,9 @@
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
9
  import {createDependencyLocation, remapSourceLocation} from '@parcel/utils';
@@ -10,9 +11,13 @@ import postcss from 'postcss';
10
11
  import nullthrows from 'nullthrows';
11
12
  import valueParser from 'postcss-value-parser';
12
13
  import semver from 'semver';
14
+ import path from 'path';
13
15
 
14
- const URL_RE = /url\s*\("?(?![a-z]+:)/;
16
+ const URL_RE = /url\s*\(/;
15
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\./;
16
21
 
17
22
  function canHaveDependencies(filePath: FilePath, code: string) {
18
23
  return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code);
@@ -30,12 +35,19 @@ export default (new Transformer({
30
35
  // to be filled in later. When the CSS transformer runs, it would pick that up and try to
31
36
  // resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do
32
37
  // it this way since the resulting CSS doesn't need to be re-parsed.
33
- 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) {
34
42
  return null;
35
43
  }
36
44
 
37
45
  let code = await asset.getCode();
38
- if (code != null && !canHaveDependencies(asset.filePath, code)) {
46
+ if (
47
+ code != null &&
48
+ !canHaveDependencies(asset.filePath, code) &&
49
+ !isCSSModule
50
+ ) {
39
51
  return null;
40
52
  }
41
53
 
@@ -50,9 +62,10 @@ export default (new Transformer({
50
62
  };
51
63
  },
52
64
 
53
- async transform({asset}) {
65
+ async transform({asset, resolve, options}) {
54
66
  // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
55
67
  // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
68
+ let env = asset.env;
56
69
  asset.setEnvironment({
57
70
  context: 'browser',
58
71
  engines: {
@@ -62,22 +75,36 @@ export default (new Transformer({
62
75
  sourceMap: asset.env.sourceMap,
63
76
  });
64
77
 
78
+ let isCSSModule =
79
+ asset.meta.cssModulesCompiled !== true &&
80
+ MODULE_BY_NAME_RE.test(asset.filePath);
81
+
65
82
  // Check for `hasDependencies` being false here as well, as it's possible
66
83
  // another transformer (such as PostCSSTransformer) has already parsed an
67
84
  // ast and CSSTransformer's parse was never called.
68
85
  let ast = await asset.getAST();
69
- if (!ast || asset.meta.hasDependencies === false) {
86
+ if (!ast || (asset.meta.hasDependencies === false && !isCSSModule)) {
70
87
  return [asset];
71
88
  }
72
89
 
73
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
+
74
100
  let originalSourceMap = await asset.getMap();
75
- let createLoc = (start, specifier, lineOffset, colOffset) => {
101
+ let createLoc = (start, specifier, lineOffset, colOffset, o) => {
76
102
  let loc = createDependencyLocation(
77
103
  start,
78
104
  specifier,
79
105
  lineOffset,
80
106
  colOffset,
107
+ o,
81
108
  );
82
109
  if (originalSourceMap) {
83
110
  loc = remapSourceLocation(loc, originalSourceMap);
@@ -142,19 +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, {
172
+ let urlNode = node.nodes[0];
173
+ let url = asset.addURLDependency(urlNode.value, {
146
174
  loc:
147
175
  decl.source &&
148
176
  decl.source.start &&
149
177
  createLoc(
150
178
  decl.source.start,
151
- node.nodes[0].value,
179
+ urlNode.value,
180
+ 0,
181
+ decl.source.start.offset + urlNode.sourceIndex + 1,
152
182
  0,
153
- node.nodes[0].sourceIndex,
154
183
  ),
155
184
  });
156
- isDeclDirty = node.nodes[0].value !== url;
157
- node.nodes[0].value = url;
185
+ isDeclDirty = urlNode.value !== url;
186
+ urlNode.type = 'string';
187
+ urlNode.quote = '"';
188
+ urlNode.value = url;
158
189
  }
159
190
  });
160
191
 
@@ -172,7 +203,7 @@ export default (new Transformer({
172
203
  });
173
204
  }
174
205
 
175
- return [asset];
206
+ return assets;
176
207
  },
177
208
 
178
209
  async generate({asset, ast, options}) {
@@ -207,3 +238,139 @@ export default (new Transformer({
207
238
  };
208
239
  },
209
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
+ }