@parcel/transformer-postcss 2.0.0-beta.3.1 → 2.0.0-dev.1515

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.
@@ -4,130 +4,95 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
-
7
+ function _rust() {
8
+ const data = require("@parcel/rust");
9
+ _rust = function () {
10
+ return data;
11
+ };
12
+ return data;
13
+ }
8
14
  function _utils() {
9
15
  const data = require("@parcel/utils");
10
-
11
16
  _utils = function () {
12
17
  return data;
13
18
  };
14
-
15
19
  return data;
16
20
  }
17
-
18
21
  function _plugin() {
19
22
  const data = require("@parcel/plugin");
20
-
21
23
  _plugin = function () {
22
24
  return data;
23
25
  };
24
-
25
- return data;
26
- }
27
-
28
- function _fileSystemLoader() {
29
- const data = _interopRequireDefault(require("css-modules-loader-core/lib/file-system-loader"));
30
-
31
- _fileSystemLoader = function () {
32
- return data;
33
- };
34
-
35
26
  return data;
36
27
  }
37
-
38
28
  function _nullthrows() {
39
29
  const data = _interopRequireDefault(require("nullthrows"));
40
-
41
30
  _nullthrows = function () {
42
31
  return data;
43
32
  };
44
-
45
33
  return data;
46
34
  }
47
-
48
35
  function _path() {
49
36
  const data = _interopRequireDefault(require("path"));
50
-
51
37
  _path = function () {
52
38
  return data;
53
39
  };
54
-
55
40
  return data;
56
41
  }
57
-
58
42
  function _semver() {
59
43
  const data = _interopRequireDefault(require("semver"));
60
-
61
44
  _semver = function () {
62
45
  return data;
63
46
  };
64
-
65
47
  return data;
66
48
  }
67
-
68
49
  function _postcssValueParser() {
69
50
  const data = _interopRequireDefault(require("postcss-value-parser"));
70
-
71
51
  _postcssValueParser = function () {
72
52
  return data;
73
53
  };
74
-
75
- return data;
76
- }
77
-
78
- function _postcssModules() {
79
- const data = _interopRequireDefault(require("postcss-modules"));
80
-
81
- _postcssModules = function () {
82
- return data;
83
- };
84
-
85
54
  return data;
86
55
  }
87
-
88
- function _loadConfig() {
89
- const data = require("./loadConfig");
90
-
91
- _loadConfig = function () {
56
+ var _loadConfig = require("./loadConfig");
57
+ var _constants = require("./constants");
58
+ function _diagnostic() {
59
+ const data = require("@parcel/diagnostic");
60
+ _diagnostic = function () {
92
61
  return data;
93
62
  };
94
-
95
63
  return data;
96
64
  }
97
-
98
65
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
99
-
100
66
  const COMPOSES_RE = /composes:.+from\s*("|').*("|')\s*;?/;
101
67
  const FROM_IMPORT_RE = /.+from\s*(?:"|')(.*)(?:"|')\s*;?/;
102
-
103
- var _default = new (_plugin().Transformer)({
68
+ const LEGACY_MODULE_RE = /@value|:export|(:global|:local|:import)(?!\s*\()/i;
69
+ const MODULE_BY_NAME_RE = /\.module\./;
70
+ var _default = exports.default = new (_plugin().Transformer)({
104
71
  loadConfig({
105
72
  config,
106
73
  options,
107
74
  logger
108
75
  }) {
109
- return (0, _loadConfig().load)({
76
+ return (0, _loadConfig.load)({
110
77
  config,
111
78
  options,
112
79
  logger
113
80
  });
114
81
  },
115
-
116
82
  canReuseAST({
117
83
  ast
118
84
  }) {
119
- return ast.type === 'postcss' && _semver().default.satisfies(ast.version, '^8.2.1');
85
+ return ast.type === 'postcss' && _semver().default.satisfies(ast.version, _constants.POSTCSS_RANGE);
120
86
  },
121
-
122
87
  async parse({
123
88
  asset,
124
89
  config,
125
90
  options
126
91
  }) {
127
- if (!config) {
92
+ let isLegacy = await isLegacyCssModule(asset);
93
+ if (!config && !isLegacy) {
128
94
  return;
129
95
  }
130
-
131
96
  const postcss = await loadPostcss(options, asset.filePath);
132
97
  return {
133
98
  type: 'postcss',
@@ -137,62 +102,139 @@ var _default = new (_plugin().Transformer)({
137
102
  }).toJSON()
138
103
  };
139
104
  },
140
-
141
105
  async transform({
142
106
  asset,
143
107
  config,
144
108
  options,
145
- resolve
109
+ resolve,
110
+ logger
146
111
  }) {
147
112
  asset.type = 'css';
113
+ let isLegacy = await isLegacyCssModule(asset);
114
+ if (isLegacy && !config) {
115
+ config = {
116
+ raw: {},
117
+ filePath: '',
118
+ hydrated: {
119
+ plugins: [],
120
+ from: asset.filePath,
121
+ to: asset.filePath,
122
+ modules: {}
123
+ }
124
+ };
125
+
126
+ // TODO: warning?
127
+ }
148
128
 
149
129
  if (!config) {
150
130
  return [asset];
151
131
  }
152
-
153
132
  const postcss = await loadPostcss(options, asset.filePath);
133
+ let ast = (0, _nullthrows().default)(await asset.getAST());
134
+ let program = postcss.fromJSON(ast.program);
154
135
  let plugins = [...config.hydrated.plugins];
155
136
  let cssModules = null;
156
-
157
137
  if (config.hydrated.modules) {
158
- plugins.push((0, _postcssModules().default)({
138
+ asset.meta.cssModulesCompiled = 'postcss';
139
+ let code = asset.isASTDirty() ? null : await asset.getCode();
140
+ if (Object.keys(config.hydrated.modules).length === 0 && code && !isLegacy && !LEGACY_MODULE_RE.test(code)) {
141
+ let filename = _path().default.basename(config.filePath);
142
+ let message;
143
+ let configKey;
144
+ let hint;
145
+ if (config.raw.modules) {
146
+ message = (0, _diagnostic().md)`The "modules" option in __${filename}__ can be replaced with configuration for @parcel/transformer-css to improve build performance.`;
147
+ configKey = '/modules';
148
+ hint = (0, _diagnostic().md)`Remove the "modules" option from __${filename}__`;
149
+ } else {
150
+ message = (0, _diagnostic().md)`The "postcss-modules" plugin in __${filename}__ can be replaced with configuration for @parcel/transformer-css to improve build performance.`;
151
+ configKey = '/plugins/postcss-modules';
152
+ hint = (0, _diagnostic().md)`Remove the "postcss-modules" plugin from __${filename}__`;
153
+ }
154
+ if (filename === 'package.json') {
155
+ configKey = `/postcss${configKey}`;
156
+ }
157
+ let hints = ['Enable the "cssModules" option for "@parcel/transformer-css" in your package.json'];
158
+ if (plugins.length === 0) {
159
+ message += (0, _diagnostic().md)` Since there are no other plugins, __${filename}__ can be deleted safely.`;
160
+ hints.push((0, _diagnostic().md)`Delete __${filename}__`);
161
+ } else {
162
+ hints.push(hint);
163
+ }
164
+ let codeFrames;
165
+ if (_path().default.extname(filename) !== '.js') {
166
+ let contents = await asset.fs.readFile(config.filePath, 'utf8');
167
+ codeFrames = [{
168
+ language: 'json',
169
+ filePath: config.filePath,
170
+ code: contents,
171
+ codeHighlights: (0, _diagnostic().generateJSONCodeHighlights)(contents, [{
172
+ key: configKey,
173
+ type: 'key'
174
+ }])
175
+ }];
176
+ } else {
177
+ codeFrames = [{
178
+ filePath: config.filePath,
179
+ codeHighlights: [{
180
+ start: {
181
+ line: 1,
182
+ column: 1
183
+ },
184
+ end: {
185
+ line: 1,
186
+ column: 1
187
+ }
188
+ }]
189
+ }];
190
+ }
191
+ logger.warn({
192
+ message,
193
+ hints,
194
+ documentationURL: 'https://parceljs.org/languages/css/#enabling-css-modules-globally',
195
+ codeFrames
196
+ });
197
+ }
198
+
199
+ // TODO: should this be resolved from the project root?
200
+ let postcssModules = await options.packageManager.require('postcss-modules', asset.filePath, {
201
+ range: '^4.3.0',
202
+ saveDev: true,
203
+ shouldAutoInstall: options.shouldAutoInstall
204
+ });
205
+ plugins.push(postcssModules({
159
206
  getJSON: (filename, json) => cssModules = json,
160
- Loader: createLoader(asset, resolve),
161
- generateScopedName: (name, filename) => `_${name}_${(0, _utils().md5FromString)(_path().default.relative(options.projectRoot, filename)).substr(0, 6)}`,
207
+ Loader: await createLoader(asset, resolve, options),
208
+ generateScopedName: (name, filename) => `${name}_${(0, _rust().hashString)(_path().default.relative(options.projectRoot, filename)).substr(0, 6)}`,
162
209
  ...config.hydrated.modules
163
210
  }));
164
- }
165
-
166
- let ast = (0, _nullthrows().default)(await asset.getAST());
167
- let program = postcss.fromJSON(ast.program);
168
- let code = asset.isASTDirty() ? null : await asset.getCode();
169
-
170
- if (code == null || COMPOSES_RE.test(code)) {
171
- program.walkDecls(decl => {
172
- let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
173
-
174
- if (decl.prop === 'composes' && importPath != null) {
175
- let parsed = (0, _postcssValueParser().default)(decl.value);
176
- parsed.walk(node => {
177
- if (node.type === 'string') {
178
- asset.addDependency({
179
- moduleSpecifier: importPath,
180
- loc: {
181
- filePath: asset.filePath,
182
- start: decl.source.start,
183
- end: {
184
- line: decl.source.start.line,
185
- column: decl.source.start.column + importPath.length
211
+ if (code == null || COMPOSES_RE.test(code)) {
212
+ program.walkDecls(decl => {
213
+ let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
214
+ if (decl.prop === 'composes' && importPath != null) {
215
+ let parsed = (0, _postcssValueParser().default)(decl.value);
216
+ parsed.walk(node => {
217
+ if (node.type === 'string') {
218
+ asset.addDependency({
219
+ specifier: importPath,
220
+ specifierType: 'url',
221
+ loc: {
222
+ filePath: asset.filePath,
223
+ start: decl.source.start,
224
+ end: {
225
+ line: decl.source.start.line,
226
+ column: decl.source.start.column + importPath.length
227
+ }
186
228
  }
187
- }
188
- });
189
- }
190
- });
191
- }
192
- });
193
- } // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
194
-
229
+ });
230
+ }
231
+ });
232
+ }
233
+ });
234
+ }
235
+ }
195
236
 
237
+ // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
196
238
  let {
197
239
  messages,
198
240
  root
@@ -202,47 +244,50 @@ var _default = new (_plugin().Transformer)({
202
244
  version: '8.2.1',
203
245
  program: root.toJSON()
204
246
  });
205
-
206
247
  for (let msg of messages) {
207
248
  if (msg.type === 'dependency') {
208
- asset.addIncludedFile(msg.file);
249
+ asset.invalidateOnFileChange(msg.file);
250
+ } else if (msg.type === 'dir-dependency') {
251
+ var _msg$glob;
252
+ let pattern = `${msg.dir}/${(_msg$glob = msg.glob) !== null && _msg$glob !== void 0 ? _msg$glob : '**/*'}`;
253
+ let files = await (0, _utils().glob)(pattern, asset.fs, {
254
+ onlyFiles: true
255
+ });
256
+ for (let file of files) {
257
+ asset.invalidateOnFileChange(_path().default.normalize(file));
258
+ }
259
+ asset.invalidateOnFileCreate({
260
+ glob: pattern
261
+ });
209
262
  }
210
263
  }
211
-
212
264
  let assets = [asset];
213
-
214
265
  if (cssModules) {
215
266
  // $FlowFixMe
216
267
  let cssModulesList = Object.entries(cssModules);
217
- let deps = asset.getDependencies().filter(dep => !dep.isURL);
268
+ let deps = asset.getDependencies().filter(dep => dep.priority === 'sync');
218
269
  let code;
219
-
220
270
  if (deps.length > 0) {
221
271
  code = `
222
- module.exports = Object.assign({}, ${deps.map(dep => `require(${JSON.stringify(dep.moduleSpecifier)})`).join(', ')}, ${JSON.stringify(cssModules, null, 2)});
272
+ module.exports = Object.assign({}, ${deps.map(dep => `require(${JSON.stringify(dep.specifier)})`).join(', ')}, ${JSON.stringify(cssModules, null, 2)});
223
273
  `;
224
274
  } else {
225
- code = cssModulesList.map( // This syntax enables shaking the invidual statements, so that unused classes don't even exist in JS.
275
+ code = cssModulesList.map(
276
+ // This syntax enables shaking the invidual statements, so that unused classes don't even exist in JS.
226
277
  ([className, classNameHashed]) => `module.exports[${JSON.stringify(className)}] = ${JSON.stringify(classNameHashed)};`).join('\n');
227
278
  }
228
-
229
279
  asset.symbols.ensure();
230
-
231
280
  for (let [k, v] of cssModulesList) {
232
281
  asset.symbols.set(k, v);
233
282
  }
234
-
235
283
  asset.symbols.set('default', 'default');
236
284
  assets.push({
237
285
  type: 'js',
238
- filePath: asset.filePath + '.js',
239
286
  content: code
240
287
  });
241
288
  }
242
-
243
289
  return assets;
244
290
  },
245
-
246
291
  async generate({
247
292
  asset,
248
293
  ast,
@@ -257,45 +302,46 @@ var _default = new (_plugin().Transformer)({
257
302
  content: code
258
303
  };
259
304
  }
260
-
261
305
  });
262
-
263
- exports.default = _default;
264
-
265
- function createLoader(asset, resolve) {
266
- return class extends _fileSystemLoader().default {
306
+ async function createLoader(asset, resolve, options) {
307
+ let {
308
+ default: FileSystemLoader
309
+ } = await options.packageManager.require('postcss-modules/build/css-loader-core/loader', asset.filePath);
310
+ return class extends FileSystemLoader {
267
311
  async fetch(composesPath, relativeTo) {
268
312
  let importPath = composesPath.replace(/^["']|["']$/g, '');
269
313
  let resolved = await resolve(relativeTo, importPath);
270
-
271
314
  let rootRelativePath = _path().default.resolve(_path().default.dirname(relativeTo), resolved);
272
-
273
- let root = _path().default.resolve('/'); // fixes an issue on windows which is part of the css-modules-loader-core
315
+ let root = _path().default.resolve('/');
316
+ // fixes an issue on windows which is part of the css-modules-loader-core
274
317
  // see https://github.com/css-modules/css-modules-loader-core/issues/230
275
-
276
-
277
318
  if (rootRelativePath.startsWith(root)) {
278
319
  rootRelativePath = rootRelativePath.substr(root.length);
279
320
  }
280
-
281
321
  let source = await asset.fs.readFile(resolved, 'utf-8');
282
322
  let {
283
323
  exportTokens
284
- } = await this.core.load(source, rootRelativePath, undefined, this.fetch.bind(this));
324
+ } = await this.core.load(source, rootRelativePath, undefined,
325
+ // $FlowFixMe[method-unbinding]
326
+ this.fetch.bind(this));
285
327
  return exportTokens;
286
328
  }
287
-
288
329
  get finalSource() {
289
330
  return '';
290
331
  }
291
-
292
332
  };
293
333
  }
294
-
295
334
  function loadPostcss(options, from) {
296
335
  return options.packageManager.require('postcss', from, {
297
- range: '^8.2.1',
336
+ range: _constants.POSTCSS_RANGE,
298
337
  saveDev: true,
299
338
  shouldAutoInstall: options.shouldAutoInstall
300
339
  });
340
+ }
341
+ async function isLegacyCssModule(asset) {
342
+ if (!MODULE_BY_NAME_RE.test(asset.filePath)) {
343
+ return false;
344
+ }
345
+ let code = await asset.getCode();
346
+ return LEGACY_MODULE_RE.test(code);
301
347
  }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.POSTCSS_RANGE = void 0;
7
+ const POSTCSS_RANGE = exports.POSTCSS_RANGE = '^8.2.1';
package/lib/loadConfig.js CHANGED
@@ -4,157 +4,159 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.load = load;
7
-
8
7
  function _path() {
9
8
  const data = _interopRequireDefault(require("path"));
10
-
11
9
  _path = function () {
12
10
  return data;
13
11
  };
14
-
15
12
  return data;
16
13
  }
17
-
18
- function _utils() {
19
- const data = require("@parcel/utils");
20
-
21
- _utils = function () {
14
+ function _diagnostic() {
15
+ const data = require("@parcel/diagnostic");
16
+ _diagnostic = function () {
22
17
  return data;
23
18
  };
24
-
25
19
  return data;
26
20
  }
27
-
28
21
  function _nullthrows() {
29
22
  const data = _interopRequireDefault(require("nullthrows"));
30
-
31
23
  _nullthrows = function () {
32
24
  return data;
33
25
  };
34
-
35
26
  return data;
36
27
  }
37
-
38
28
  function _clone() {
39
29
  const data = _interopRequireDefault(require("clone"));
40
-
41
30
  _clone = function () {
42
31
  return data;
43
32
  };
44
-
45
- return data;
46
- }
47
-
48
- function _loadPlugins() {
49
- const data = _interopRequireDefault(require("./loadPlugins"));
50
-
51
- _loadPlugins = function () {
52
- return data;
53
- };
54
-
55
33
  return data;
56
34
  }
57
-
35
+ var _constants = require("./constants");
36
+ var _loadPlugins = _interopRequireDefault(require("./loadPlugins"));
58
37
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
59
-
60
- const MODULE_BY_NAME_RE = /\.module\./;
61
-
62
- async function configHydrator(configFile, config, resolveFrom, options) {
63
- // Use a basic, modules-only PostCSS config if the file opts in by a name
64
- // like foo.module.css
65
- if (configFile == null && config.searchPath.match(MODULE_BY_NAME_RE)) {
66
- configFile = {
67
- plugins: {
68
- 'postcss-modules': {}
69
- }
70
- };
71
- resolveFrom = __filename;
72
- }
73
-
38
+ async function configHydrator(configFile, config, resolveFrom, options, logger) {
74
39
  if (configFile == null) {
75
40
  return;
76
- } // Load the custom config...
77
-
41
+ }
78
42
 
43
+ // Load the custom config...
79
44
  let modulesConfig;
80
45
  let configFilePlugins = (0, _clone().default)(configFile.plugins);
81
-
82
46
  if (configFilePlugins != null && typeof configFilePlugins === 'object' && configFilePlugins['postcss-modules'] != null) {
83
47
  modulesConfig = configFilePlugins['postcss-modules'];
84
48
  delete configFilePlugins['postcss-modules'];
85
49
  }
86
-
87
50
  if (!modulesConfig && configFile.modules) {
88
51
  modulesConfig = {};
89
52
  }
53
+ let plugins = await (0, _loadPlugins.default)(configFilePlugins, (0, _nullthrows().default)(resolveFrom), options);
90
54
 
91
- let plugins = await (0, _loadPlugins().default)(configFilePlugins, (0, _nullthrows().default)(resolveFrom), options); // contents is either:
55
+ // contents is either:
92
56
  // from JSON: { plugins: { 'postcss-foo': { ...opts } } }
93
57
  // from JS (v8): { plugins: [ { postcssPlugin: 'postcss-foo', ...visitor callback functions } ]
94
58
  // from JS (v7): { plugins: [ [Function: ...] ]
95
-
96
59
  let pluginArray = Array.isArray(configFilePlugins) ? configFilePlugins : Object.keys(configFilePlugins);
97
-
98
60
  for (let p of pluginArray) {
99
61
  if (typeof p === 'string') {
100
62
  config.addDevDependency({
101
- moduleSpecifier: p,
63
+ specifier: p,
102
64
  resolveFrom: (0, _nullthrows().default)(resolveFrom)
103
65
  });
104
66
  }
105
67
  }
106
-
107
- config.setResult({
68
+ let redundantPlugins = pluginArray.filter(p => p === 'autoprefixer' || p === 'postcss-preset-env');
69
+ if (redundantPlugins.length > 0) {
70
+ let filename = _path().default.basename(resolveFrom);
71
+ let isPackageJson = filename === 'package.json';
72
+ let message;
73
+ let hints = [];
74
+ if (!isPackageJson && redundantPlugins.length === pluginArray.length) {
75
+ message = (0, _diagnostic().md)`Parcel includes CSS transpilation and vendor prefixing by default. PostCSS config __${filename}__ contains only redundant plugins. Deleting it may significantly improve build performance.`;
76
+ hints.push((0, _diagnostic().md)`Delete __${filename}__`);
77
+ } else {
78
+ message = (0, _diagnostic().md)`Parcel includes CSS transpilation and vendor prefixing by default. PostCSS config __${filename}__ contains the following redundant plugins: ${[...redundantPlugins].map(p => _diagnostic().md.underline(p))}. Removing these may improve build performance.`;
79
+ hints.push((0, _diagnostic().md)`Remove the above plugins from __${filename}__`);
80
+ }
81
+ let codeFrames;
82
+ if (_path().default.extname(filename) !== '.js') {
83
+ let contents = await options.inputFS.readFile(resolveFrom, 'utf8');
84
+ let prefix = isPackageJson ? '/postcss' : '';
85
+ codeFrames = [{
86
+ language: 'json',
87
+ filePath: resolveFrom,
88
+ code: contents,
89
+ codeHighlights: (0, _diagnostic().generateJSONCodeHighlights)(contents, redundantPlugins.map(plugin => ({
90
+ key: `${prefix}/plugins/${plugin}`,
91
+ type: 'key'
92
+ })))
93
+ }];
94
+ } else {
95
+ codeFrames = [{
96
+ filePath: resolveFrom,
97
+ codeHighlights: [{
98
+ start: {
99
+ line: 1,
100
+ column: 1
101
+ },
102
+ end: {
103
+ line: 1,
104
+ column: 1
105
+ }
106
+ }]
107
+ }];
108
+ }
109
+ logger.warn({
110
+ message,
111
+ hints,
112
+ documentationURL: 'https://parceljs.org/languages/css/#default-plugins',
113
+ codeFrames
114
+ });
115
+ }
116
+ return {
108
117
  raw: configFile,
118
+ filePath: resolveFrom,
109
119
  hydrated: {
110
120
  plugins,
111
121
  from: config.searchPath,
112
122
  to: config.searchPath,
113
123
  modules: modulesConfig
114
124
  }
115
- });
125
+ };
116
126
  }
117
-
118
127
  async function load({
119
128
  config,
120
129
  options,
121
130
  logger
122
131
  }) {
123
- let configFile = await config.getConfig(['.postcssrc', '.postcssrc.json', '.postcssrc.js', 'postcss.config.js'], {
132
+ if (!config.isSource) {
133
+ return;
134
+ }
135
+ let configFile = await config.getConfig(['.postcssrc', '.postcssrc.json', '.postcssrc.js', '.postcssrc.cjs', '.postcssrc.mjs', 'postcss.config.js', 'postcss.config.cjs', 'postcss.config.mjs'], {
124
136
  packageKey: 'postcss'
125
137
  });
126
138
  let contents = null;
127
-
128
139
  if (configFile) {
129
140
  config.addDevDependency({
130
- moduleSpecifier: 'postcss',
131
- resolveFrom: config.searchPath
141
+ specifier: 'postcss',
142
+ resolveFrom: config.searchPath,
143
+ range: _constants.POSTCSS_RANGE
132
144
  });
133
145
  contents = configFile.contents;
134
- let isDynamic = configFile && _path().default.extname(configFile.filePath) === '.js';
135
-
146
+ let isDynamic = configFile && _path().default.extname(configFile.filePath).endsWith('js');
136
147
  if (isDynamic) {
137
148
  // We have to invalidate on startup in case the config is non-deterministic,
138
149
  // e.g. using unknown environment variables, reading from the filesystem, etc.
139
150
  logger.warn({
140
151
  message: 'WARNING: Using a JavaScript PostCSS config file means losing out on caching features of Parcel. Use a .postcssrc(.json) file whenever possible.'
141
152
  });
142
- config.shouldInvalidateOnStartup(); // Also add the config as a dev dependency so we attempt to reload in watch mode.
143
-
144
- config.addDevDependency({
145
- moduleSpecifier: (0, _utils().relativePath)(_path().default.dirname(config.searchPath), configFile.filePath),
146
- resolveFrom: config.searchPath
147
- });
148
153
  }
149
-
150
154
  if (typeof contents !== 'object') {
151
155
  throw new Error('PostCSS config should be an object.');
152
156
  }
153
-
154
157
  if (contents.plugins == null || typeof contents.plugins !== 'object' || Object.keys(contents.plugins).length === 0) {
155
158
  throw new Error('PostCSS config must have plugins');
156
159
  }
157
160
  }
158
-
159
- return configHydrator(contents, config, configFile === null || configFile === void 0 ? void 0 : configFile.filePath, options);
161
+ return configHydrator(contents, config, configFile === null || configFile === void 0 ? void 0 : configFile.filePath, options, logger);
160
162
  }
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = loadExternalPlugins;
7
-
8
7
  async function loadExternalPlugins(plugins, relative, options) {
9
8
  if (Array.isArray(plugins)) {
10
9
  return Promise.all(plugins.map(p => loadPlugin(p, relative, null, options.packageManager, options.shouldAutoInstall)).filter(Boolean));
@@ -15,20 +14,16 @@ async function loadExternalPlugins(plugins, relative, options) {
15
14
  return [];
16
15
  }
17
16
  }
18
-
19
17
  async function loadPlugin(pluginArg, relative, options = {}, packageManager, shouldAutoInstall) {
20
18
  if (typeof pluginArg !== 'string') {
21
19
  return pluginArg;
22
20
  }
23
-
24
21
  let plugin = await packageManager.require(pluginArg, relative, {
25
22
  shouldAutoInstall
26
23
  });
27
24
  plugin = plugin.default || plugin;
28
-
29
25
  if (options != null && typeof options === 'object' && Object.keys(options).length > 0) {
30
26
  plugin = plugin(options);
31
27
  }
32
-
33
28
  return plugin.default || plugin;
34
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/transformer-postcss",
3
- "version": "2.0.0-beta.3.1",
3
+ "version": "2.0.0-dev.1515+9d5643b9b",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,20 +17,21 @@
17
17
  "source": "src/PostCSSTransformer.js",
18
18
  "engines": {
19
19
  "node": ">= 12.0.0",
20
- "parcel": "^2.0.0-beta.1"
20
+ "parcel": "^2.0.0-dev.1513+9d5643b9b"
21
21
  },
22
22
  "dependencies": {
23
- "@parcel/plugin": "2.0.0-beta.3.1",
24
- "@parcel/utils": "2.0.0-beta.3.1",
23
+ "@parcel/diagnostic": "2.0.0-dev.1515+9d5643b9b",
24
+ "@parcel/plugin": "2.0.0-dev.1515+9d5643b9b",
25
+ "@parcel/rust": "2.12.1-dev.3138+9d5643b9b",
26
+ "@parcel/utils": "2.0.0-dev.1515+9d5643b9b",
25
27
  "clone": "^2.1.1",
26
- "css-modules-loader-core": "^1.1.0",
27
28
  "nullthrows": "^1.1.1",
28
- "postcss-modules": "^3.2.2",
29
- "postcss-value-parser": "^4.1.0",
30
- "semver": "^5.4.1"
29
+ "postcss-value-parser": "^4.2.0",
30
+ "semver": "^7.5.2"
31
31
  },
32
32
  "devDependencies": {
33
- "postcss": "^8.2.1"
33
+ "postcss": "^8.4.5",
34
+ "postcss-modules": "^4.3.1"
34
35
  },
35
- "gitHead": "daece49d003ba804bbdaa3a7ed3d6aaf446f166d"
36
+ "gitHead": "9d5643b9b5ba1a6ff638b4ae32024222e46b8133"
36
37
  }
@@ -1,21 +1,24 @@
1
1
  // @flow
2
2
 
3
- import type {FilePath, MutableAsset, PluginOptions} from '@parcel/types';
3
+ import type {FilePath, Asset, MutableAsset, PluginOptions} from '@parcel/types';
4
4
 
5
- import {md5FromString} from '@parcel/utils';
5
+ import {hashString} from '@parcel/rust';
6
+ import {glob} from '@parcel/utils';
6
7
  import {Transformer} from '@parcel/plugin';
7
- import FileSystemLoader from 'css-modules-loader-core/lib/file-system-loader';
8
8
  import nullthrows from 'nullthrows';
9
9
  import path from 'path';
10
10
  import semver from 'semver';
11
11
  import valueParser from 'postcss-value-parser';
12
- import postcssModules from 'postcss-modules';
13
12
  import typeof * as Postcss from 'postcss';
14
13
 
15
14
  import {load} from './loadConfig';
15
+ import {POSTCSS_RANGE} from './constants';
16
+ import {md, generateJSONCodeHighlights} from '@parcel/diagnostic';
16
17
 
17
18
  const COMPOSES_RE = /composes:.+from\s*("|').*("|')\s*;?/;
18
19
  const FROM_IMPORT_RE = /.+from\s*(?:"|')(.*)(?:"|')\s*;?/;
20
+ const LEGACY_MODULE_RE = /@value|:export|(:global|:local|:import)(?!\s*\()/i;
21
+ const MODULE_BY_NAME_RE = /\.module\./;
19
22
 
20
23
  export default (new Transformer({
21
24
  loadConfig({config, options, logger}) {
@@ -23,11 +26,14 @@ export default (new Transformer({
23
26
  },
24
27
 
25
28
  canReuseAST({ast}) {
26
- return ast.type === 'postcss' && semver.satisfies(ast.version, '^8.2.1');
29
+ return (
30
+ ast.type === 'postcss' && semver.satisfies(ast.version, POSTCSS_RANGE)
31
+ );
27
32
  },
28
33
 
29
34
  async parse({asset, config, options}) {
30
- if (!config) {
35
+ let isLegacy = await isLegacyCssModule(asset);
36
+ if (!config && !isLegacy) {
31
37
  return;
32
38
  }
33
39
 
@@ -44,56 +50,158 @@ export default (new Transformer({
44
50
  };
45
51
  },
46
52
 
47
- async transform({asset, config, options, resolve}) {
53
+ async transform({asset, config, options, resolve, logger}) {
48
54
  asset.type = 'css';
55
+ let isLegacy = await isLegacyCssModule(asset);
56
+ if (isLegacy && !config) {
57
+ config = {
58
+ raw: {},
59
+ filePath: '',
60
+ hydrated: {
61
+ plugins: [],
62
+ from: asset.filePath,
63
+ to: asset.filePath,
64
+ modules: {},
65
+ },
66
+ };
67
+
68
+ // TODO: warning?
69
+ }
70
+
49
71
  if (!config) {
50
72
  return [asset];
51
73
  }
52
74
 
53
75
  const postcss: Postcss = await loadPostcss(options, asset.filePath);
76
+ let ast = nullthrows(await asset.getAST());
77
+ let program = postcss.fromJSON(ast.program);
54
78
 
55
79
  let plugins = [...config.hydrated.plugins];
56
80
  let cssModules: ?{|[string]: string|} = null;
57
81
  if (config.hydrated.modules) {
82
+ asset.meta.cssModulesCompiled = 'postcss';
83
+
84
+ let code = asset.isASTDirty() ? null : await asset.getCode();
85
+ if (
86
+ Object.keys(config.hydrated.modules).length === 0 &&
87
+ code &&
88
+ !isLegacy &&
89
+ !LEGACY_MODULE_RE.test(code)
90
+ ) {
91
+ let filename = path.basename(config.filePath);
92
+ let message;
93
+ let configKey;
94
+ let hint;
95
+ if (config.raw.modules) {
96
+ message = md`The "modules" option in __${filename}__ can be replaced with configuration for @parcel/transformer-css to improve build performance.`;
97
+ configKey = '/modules';
98
+ hint = md`Remove the "modules" option from __${filename}__`;
99
+ } else {
100
+ message = md`The "postcss-modules" plugin in __${filename}__ can be replaced with configuration for @parcel/transformer-css to improve build performance.`;
101
+ configKey = '/plugins/postcss-modules';
102
+ hint = md`Remove the "postcss-modules" plugin from __${filename}__`;
103
+ }
104
+ if (filename === 'package.json') {
105
+ configKey = `/postcss${configKey}`;
106
+ }
107
+
108
+ let hints = [
109
+ 'Enable the "cssModules" option for "@parcel/transformer-css" in your package.json',
110
+ ];
111
+ if (plugins.length === 0) {
112
+ message += md` Since there are no other plugins, __${filename}__ can be deleted safely.`;
113
+ hints.push(md`Delete __${filename}__`);
114
+ } else {
115
+ hints.push(hint);
116
+ }
117
+
118
+ let codeFrames;
119
+ if (path.extname(filename) !== '.js') {
120
+ let contents = await asset.fs.readFile(config.filePath, 'utf8');
121
+ codeFrames = [
122
+ {
123
+ language: 'json',
124
+ filePath: config.filePath,
125
+ code: contents,
126
+ codeHighlights: generateJSONCodeHighlights(contents, [
127
+ {
128
+ key: configKey,
129
+ type: 'key',
130
+ },
131
+ ]),
132
+ },
133
+ ];
134
+ } else {
135
+ codeFrames = [
136
+ {
137
+ filePath: config.filePath,
138
+ codeHighlights: [
139
+ {
140
+ start: {line: 1, column: 1},
141
+ end: {line: 1, column: 1},
142
+ },
143
+ ],
144
+ },
145
+ ];
146
+ }
147
+
148
+ logger.warn({
149
+ message,
150
+ hints,
151
+ documentationURL:
152
+ 'https://parceljs.org/languages/css/#enabling-css-modules-globally',
153
+ codeFrames,
154
+ });
155
+ }
156
+
157
+ // TODO: should this be resolved from the project root?
158
+ let postcssModules = await options.packageManager.require(
159
+ 'postcss-modules',
160
+ asset.filePath,
161
+ {
162
+ range: '^4.3.0',
163
+ saveDev: true,
164
+ shouldAutoInstall: options.shouldAutoInstall,
165
+ },
166
+ );
167
+
58
168
  plugins.push(
59
169
  postcssModules({
60
170
  getJSON: (filename, json) => (cssModules = json),
61
- Loader: createLoader(asset, resolve),
171
+ Loader: await createLoader(asset, resolve, options),
62
172
  generateScopedName: (name, filename) =>
63
- `_${name}_${md5FromString(
173
+ `${name}_${hashString(
64
174
  path.relative(options.projectRoot, filename),
65
175
  ).substr(0, 6)}`,
66
176
  ...config.hydrated.modules,
67
177
  }),
68
178
  );
69
- }
70
179
 
71
- let ast = nullthrows(await asset.getAST());
72
- let program = postcss.fromJSON(ast.program);
73
- let code = asset.isASTDirty() ? null : await asset.getCode();
74
- if (code == null || COMPOSES_RE.test(code)) {
75
- program.walkDecls(decl => {
76
- let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
77
- if (decl.prop === 'composes' && importPath != null) {
78
- let parsed = valueParser(decl.value);
79
-
80
- parsed.walk(node => {
81
- if (node.type === 'string') {
82
- asset.addDependency({
83
- moduleSpecifier: importPath,
84
- loc: {
85
- filePath: asset.filePath,
86
- start: decl.source.start,
87
- end: {
88
- line: decl.source.start.line,
89
- column: decl.source.start.column + importPath.length,
180
+ if (code == null || COMPOSES_RE.test(code)) {
181
+ program.walkDecls(decl => {
182
+ let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
183
+ if (decl.prop === 'composes' && importPath != null) {
184
+ let parsed = valueParser(decl.value);
185
+
186
+ parsed.walk(node => {
187
+ if (node.type === 'string') {
188
+ asset.addDependency({
189
+ specifier: importPath,
190
+ specifierType: 'url',
191
+ loc: {
192
+ filePath: asset.filePath,
193
+ start: decl.source.start,
194
+ end: {
195
+ line: decl.source.start.line,
196
+ column: decl.source.start.column + importPath.length,
197
+ },
90
198
  },
91
- },
92
- });
93
- }
94
- });
95
- }
96
- });
199
+ });
200
+ }
201
+ });
202
+ }
203
+ });
204
+ }
97
205
  }
98
206
 
99
207
  // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
@@ -108,7 +216,14 @@ export default (new Transformer({
108
216
  });
109
217
  for (let msg of messages) {
110
218
  if (msg.type === 'dependency') {
111
- asset.addIncludedFile(msg.file);
219
+ asset.invalidateOnFileChange(msg.file);
220
+ } else if (msg.type === 'dir-dependency') {
221
+ let pattern = `${msg.dir}/${msg.glob ?? '**/*'}`;
222
+ let files = await glob(pattern, asset.fs, {onlyFiles: true});
223
+ for (let file of files) {
224
+ asset.invalidateOnFileChange(path.normalize(file));
225
+ }
226
+ asset.invalidateOnFileCreate({glob: pattern});
112
227
  }
113
228
  }
114
229
 
@@ -118,12 +233,12 @@ export default (new Transformer({
118
233
  let cssModulesList = (Object.entries(cssModules): Array<
119
234
  [string, string],
120
235
  >);
121
- let deps = asset.getDependencies().filter(dep => !dep.isURL);
236
+ let deps = asset.getDependencies().filter(dep => dep.priority === 'sync');
122
237
  let code: string;
123
238
  if (deps.length > 0) {
124
239
  code = `
125
240
  module.exports = Object.assign({}, ${deps
126
- .map(dep => `require(${JSON.stringify(dep.moduleSpecifier)})`)
241
+ .map(dep => `require(${JSON.stringify(dep.specifier)})`)
127
242
  .join(', ')}, ${JSON.stringify(cssModules, null, 2)});
128
243
  `;
129
244
  } else {
@@ -146,7 +261,6 @@ export default (new Transformer({
146
261
 
147
262
  assets.push({
148
263
  type: 'js',
149
- filePath: asset.filePath + '.js',
150
264
  content: code,
151
265
  });
152
266
  }
@@ -167,10 +281,15 @@ export default (new Transformer({
167
281
  },
168
282
  }): Transformer);
169
283
 
170
- function createLoader(
284
+ async function createLoader(
171
285
  asset: MutableAsset,
172
286
  resolve: (from: FilePath, to: string) => Promise<FilePath>,
287
+ options: PluginOptions,
173
288
  ) {
289
+ let {default: FileSystemLoader} = await options.packageManager.require(
290
+ 'postcss-modules/build/css-loader-core/loader',
291
+ asset.filePath,
292
+ );
174
293
  return class ParcelFileSystemLoader extends FileSystemLoader {
175
294
  async fetch(composesPath, relativeTo) {
176
295
  let importPath = composesPath.replace(/^["']|["']$/g, '');
@@ -188,6 +307,7 @@ function createLoader(
188
307
  source,
189
308
  rootRelativePath,
190
309
  undefined,
310
+ // $FlowFixMe[method-unbinding]
191
311
  this.fetch.bind(this),
192
312
  );
193
313
  return exportTokens;
@@ -201,8 +321,17 @@ function createLoader(
201
321
 
202
322
  function loadPostcss(options: PluginOptions, from: FilePath): Promise<Postcss> {
203
323
  return options.packageManager.require('postcss', from, {
204
- range: '^8.2.1',
324
+ range: POSTCSS_RANGE,
205
325
  saveDev: true,
206
326
  shouldAutoInstall: options.shouldAutoInstall,
207
327
  });
208
328
  }
329
+
330
+ async function isLegacyCssModule(asset: Asset | MutableAsset) {
331
+ if (!MODULE_BY_NAME_RE.test(asset.filePath)) {
332
+ return false;
333
+ }
334
+
335
+ let code = await asset.getCode();
336
+ return LEGACY_MODULE_RE.test(code);
337
+ }
@@ -0,0 +1,3 @@
1
+ // @flow
2
+
3
+ export const POSTCSS_RANGE = '^8.2.1';
package/src/loadConfig.js CHANGED
@@ -1,32 +1,36 @@
1
1
  // @flow
2
- import type {Config, FilePath, PluginOptions} from '@parcel/types';
3
- import type {PluginLogger} from '@parcel/logger';
2
+ import type {
3
+ Config,
4
+ FilePath,
5
+ PluginOptions,
6
+ PluginLogger,
7
+ } from '@parcel/types';
4
8
  import path from 'path';
5
- import {relativePath} from '@parcel/utils';
9
+ import {md, generateJSONCodeHighlights} from '@parcel/diagnostic';
6
10
  import nullthrows from 'nullthrows';
7
11
  import clone from 'clone';
12
+ import {POSTCSS_RANGE} from './constants';
8
13
 
9
14
  import loadExternalPlugins from './loadPlugins';
10
15
 
11
- const MODULE_BY_NAME_RE = /\.module\./;
16
+ type ConfigResult = {|
17
+ raw: any,
18
+ filePath: string,
19
+ hydrated: {|
20
+ plugins: Array<any>,
21
+ from: FilePath,
22
+ to: FilePath,
23
+ modules: any,
24
+ |},
25
+ |};
12
26
 
13
27
  async function configHydrator(
14
28
  configFile: any,
15
29
  config: Config,
16
- resolveFrom: ?FilePath,
30
+ resolveFrom: FilePath,
17
31
  options: PluginOptions,
18
- ) {
19
- // Use a basic, modules-only PostCSS config if the file opts in by a name
20
- // like foo.module.css
21
- if (configFile == null && config.searchPath.match(MODULE_BY_NAME_RE)) {
22
- configFile = {
23
- plugins: {
24
- 'postcss-modules': {},
25
- },
26
- };
27
- resolveFrom = __filename;
28
- }
29
-
32
+ logger: PluginLogger,
33
+ ): Promise<?ConfigResult> {
30
34
  if (configFile == null) {
31
35
  return;
32
36
  }
@@ -63,21 +67,82 @@ async function configHydrator(
63
67
  for (let p of pluginArray) {
64
68
  if (typeof p === 'string') {
65
69
  config.addDevDependency({
66
- moduleSpecifier: p,
70
+ specifier: p,
67
71
  resolveFrom: nullthrows(resolveFrom),
68
72
  });
69
73
  }
70
74
  }
71
75
 
72
- config.setResult({
76
+ let redundantPlugins = pluginArray.filter(
77
+ p => p === 'autoprefixer' || p === 'postcss-preset-env',
78
+ );
79
+ if (redundantPlugins.length > 0) {
80
+ let filename = path.basename(resolveFrom);
81
+ let isPackageJson = filename === 'package.json';
82
+ let message;
83
+ let hints = [];
84
+ if (!isPackageJson && redundantPlugins.length === pluginArray.length) {
85
+ message = md`Parcel includes CSS transpilation and vendor prefixing by default. PostCSS config __${filename}__ contains only redundant plugins. Deleting it may significantly improve build performance.`;
86
+ hints.push(md`Delete __${filename}__`);
87
+ } else {
88
+ message = md`Parcel includes CSS transpilation and vendor prefixing by default. PostCSS config __${filename}__ contains the following redundant plugins: ${[
89
+ ...redundantPlugins,
90
+ ].map(p =>
91
+ md.underline(p),
92
+ )}. Removing these may improve build performance.`;
93
+ hints.push(md`Remove the above plugins from __${filename}__`);
94
+ }
95
+
96
+ let codeFrames;
97
+ if (path.extname(filename) !== '.js') {
98
+ let contents = await options.inputFS.readFile(resolveFrom, 'utf8');
99
+ let prefix = isPackageJson ? '/postcss' : '';
100
+ codeFrames = [
101
+ {
102
+ language: 'json',
103
+ filePath: resolveFrom,
104
+ code: contents,
105
+ codeHighlights: generateJSONCodeHighlights(
106
+ contents,
107
+ redundantPlugins.map(plugin => ({
108
+ key: `${prefix}/plugins/${plugin}`,
109
+ type: 'key',
110
+ })),
111
+ ),
112
+ },
113
+ ];
114
+ } else {
115
+ codeFrames = [
116
+ {
117
+ filePath: resolveFrom,
118
+ codeHighlights: [
119
+ {
120
+ start: {line: 1, column: 1},
121
+ end: {line: 1, column: 1},
122
+ },
123
+ ],
124
+ },
125
+ ];
126
+ }
127
+
128
+ logger.warn({
129
+ message,
130
+ hints,
131
+ documentationURL: 'https://parceljs.org/languages/css/#default-plugins',
132
+ codeFrames,
133
+ });
134
+ }
135
+
136
+ return {
73
137
  raw: configFile,
138
+ filePath: resolveFrom,
74
139
  hydrated: {
75
140
  plugins,
76
141
  from: config.searchPath,
77
142
  to: config.searchPath,
78
143
  modules: modulesConfig,
79
144
  },
80
- });
145
+ };
81
146
  }
82
147
 
83
148
  export async function load({
@@ -88,21 +153,36 @@ export async function load({
88
153
  config: Config,
89
154
  options: PluginOptions,
90
155
  logger: PluginLogger,
91
- |}): Promise<void> {
156
+ |}): Promise<?ConfigResult> {
157
+ if (!config.isSource) {
158
+ return;
159
+ }
160
+
92
161
  let configFile: any = await config.getConfig(
93
- ['.postcssrc', '.postcssrc.json', '.postcssrc.js', 'postcss.config.js'],
162
+ [
163
+ '.postcssrc',
164
+ '.postcssrc.json',
165
+ '.postcssrc.js',
166
+ '.postcssrc.cjs',
167
+ '.postcssrc.mjs',
168
+ 'postcss.config.js',
169
+ 'postcss.config.cjs',
170
+ 'postcss.config.mjs',
171
+ ],
94
172
  {packageKey: 'postcss'},
95
173
  );
96
174
 
97
175
  let contents = null;
98
176
  if (configFile) {
99
177
  config.addDevDependency({
100
- moduleSpecifier: 'postcss',
178
+ specifier: 'postcss',
101
179
  resolveFrom: config.searchPath,
180
+ range: POSTCSS_RANGE,
102
181
  });
103
182
 
104
183
  contents = configFile.contents;
105
- let isDynamic = configFile && path.extname(configFile.filePath) === '.js';
184
+ let isDynamic =
185
+ configFile && path.extname(configFile.filePath).endsWith('js');
106
186
  if (isDynamic) {
107
187
  // We have to invalidate on startup in case the config is non-deterministic,
108
188
  // e.g. using unknown environment variables, reading from the filesystem, etc.
@@ -110,17 +190,6 @@ export async function load({
110
190
  message:
111
191
  'WARNING: Using a JavaScript PostCSS config file means losing out on caching features of Parcel. Use a .postcssrc(.json) file whenever possible.',
112
192
  });
113
-
114
- config.shouldInvalidateOnStartup();
115
-
116
- // Also add the config as a dev dependency so we attempt to reload in watch mode.
117
- config.addDevDependency({
118
- moduleSpecifier: relativePath(
119
- path.dirname(config.searchPath),
120
- configFile.filePath,
121
- ),
122
- resolveFrom: config.searchPath,
123
- });
124
193
  }
125
194
 
126
195
  if (typeof contents !== 'object') {
@@ -136,5 +205,11 @@ export async function load({
136
205
  }
137
206
  }
138
207
 
139
- return configHydrator(contents, config, configFile?.filePath, options);
208
+ return configHydrator(
209
+ contents,
210
+ config,
211
+ configFile?.filePath,
212
+ options,
213
+ logger,
214
+ );
140
215
  }