@parcel/transformer-postcss 2.0.0-nightly.97 → 2.0.0-nightly.982

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