@parcel/transformer-postcss 2.0.0-beta.1 → 2.0.0-nightly.1002

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