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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,139 +5,185 @@ 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
+
107
+ var _default = new (_plugin().Transformer)({
108
+ loadConfig({
109
+ config,
110
+ options,
111
+ logger
112
+ }) {
113
+ return (0, _loadConfig.load)({
114
+ config,
115
+ options,
116
+ logger
117
+ });
94
118
  },
95
119
 
96
120
  canReuseAST({
97
121
  ast
98
122
  }) {
99
- return ast.type === 'postcss' && _semver.default.satisfies(ast.version, '^7.0.0');
123
+ return ast.type === 'postcss' && _semver().default.satisfies(ast.version, _constants.POSTCSS_RANGE);
100
124
  },
101
125
 
102
126
  async parse({
103
127
  asset,
104
- config
128
+ config,
129
+ options
105
130
  }) {
106
131
  if (!config) {
107
132
  return;
108
133
  }
109
134
 
135
+ const postcss = await loadPostcss(options, asset.filePath);
110
136
  return {
111
137
  type: 'postcss',
112
- version: '7.0.0',
113
- program: _postcss.default.parse((await asset.getCode()), {
138
+ version: '8.2.1',
139
+ program: postcss.parse(await asset.getCode(), {
114
140
  from: asset.filePath
115
- })
141
+ }).toJSON()
116
142
  };
117
143
  },
118
144
 
119
145
  async transform({
120
146
  asset,
121
- config
147
+ config,
148
+ options,
149
+ resolve
122
150
  }) {
151
+ asset.type = 'css';
152
+
123
153
  if (!config) {
124
154
  return [asset];
125
155
  }
126
156
 
127
- let ast = (0, _nullthrows.default)(asset.ast);
157
+ const postcss = await loadPostcss(options, asset.filePath);
158
+ let plugins = [...config.hydrated.plugins];
159
+ let cssModules = null;
128
160
 
129
- if (COMPOSES_RE.test((await asset.getCode()))) {
130
- ast.program.walkDecls(decl => {
161
+ if (config.hydrated.modules) {
162
+ plugins.push((0, _postcssModules().default)({
163
+ getJSON: (filename, json) => cssModules = json,
164
+ Loader: createLoader(asset, resolve),
165
+ generateScopedName: (name, filename) => `_${name}_${(0, _hash().hashString)(_path().default.relative(options.projectRoot, filename)).substr(0, 6)}`,
166
+ ...config.hydrated.modules
167
+ }));
168
+ }
169
+
170
+ let ast = (0, _nullthrows().default)(await asset.getAST());
171
+ let program = postcss.fromJSON(ast.program);
172
+ let code = asset.isASTDirty() ? null : await asset.getCode();
173
+
174
+ if (code == null || COMPOSES_RE.test(code)) {
175
+ program.walkDecls(decl => {
131
176
  let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
132
177
 
133
178
  if (decl.prop === 'composes' && importPath != null) {
134
- let parsed = (0, _postcssValueParser.default)(decl.value);
179
+ let parsed = (0, _postcssValueParser().default)(decl.value);
135
180
  parsed.walk(node => {
136
181
  if (node.type === 'string') {
137
182
  asset.addDependency({
138
- moduleSpecifier: importPath,
183
+ specifier: importPath,
184
+ specifierType: 'url',
139
185
  loc: {
140
- filePath: importPath,
186
+ filePath: asset.filePath,
141
187
  start: decl.source.start,
142
188
  end: {
143
189
  line: decl.source.start.line,
@@ -149,61 +195,85 @@ var _default = new _plugin.Transformer({
149
195
  });
150
196
  }
151
197
  });
152
- }
198
+ } // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
199
+
153
200
 
154
201
  let {
155
202
  messages,
156
203
  root
157
- } = await (0, _postcss.default)(config.plugins).process(ast.program, config);
158
- ast.program = root;
159
- ast.isDirty = true;
204
+ } = await postcss(plugins).process(program, config.hydrated);
205
+ asset.setAST({
206
+ type: 'postcss',
207
+ version: '8.2.1',
208
+ program: root.toJSON()
209
+ });
160
210
 
161
211
  for (let msg of messages) {
162
212
  if (msg.type === 'dependency') {
163
- // $FlowFixMe merely a convention
164
- msg = msg;
165
- asset.addIncludedFile({
166
- filePath: msg.file
213
+ asset.invalidateOnFileChange(msg.file);
214
+ } else if (msg.type === 'dir-dependency') {
215
+ var _msg$glob;
216
+
217
+ let pattern = `${msg.dir}/${(_msg$glob = msg.glob) !== null && _msg$glob !== void 0 ? _msg$glob : '**/*'}`;
218
+ let files = await (0, _utils().glob)(pattern, asset.fs, {
219
+ onlyFiles: true
220
+ });
221
+
222
+ for (let file of files) {
223
+ asset.invalidateOnFileChange(_path().default.normalize(file));
224
+ }
225
+
226
+ asset.invalidateOnFileCreate({
227
+ glob: pattern
167
228
  });
168
229
  }
169
230
  }
170
231
 
171
232
  let assets = [asset];
172
233
 
173
- if (asset.meta.cssModules) {
174
- let code = JSON.stringify(asset.meta.cssModules, null, 2);
175
- let deps = asset.getDependencies().filter(dep => !dep.isURL);
234
+ if (cssModules) {
235
+ // $FlowFixMe
236
+ let cssModulesList = Object.entries(cssModules);
237
+ let deps = asset.getDependencies().filter(dep => dep.priority === 'sync');
238
+ let code;
176
239
 
177
240
  if (deps.length > 0) {
178
241
  code = `
179
- module.exports = Object.assign({}, ${deps.map(dep => `require(${JSON.stringify(dep.moduleSpecifier)})`).join(', ')}, ${code});
242
+ module.exports = Object.assign({}, ${deps.map(dep => `require(${JSON.stringify(dep.specifier)})`).join(', ')}, ${JSON.stringify(cssModules, null, 2)});
180
243
  `;
181
244
  } else {
182
- code = `module.exports = ${code};`;
245
+ code = cssModulesList.map( // This syntax enables shaking the invidual statements, so that unused classes don't even exist in JS.
246
+ ([className, classNameHashed]) => `module.exports[${JSON.stringify(className)}] = ${JSON.stringify(classNameHashed)};`).join('\n');
183
247
  }
184
248
 
249
+ asset.symbols.ensure();
250
+
251
+ for (let [k, v] of cssModulesList) {
252
+ asset.symbols.set(k, v);
253
+ }
254
+
255
+ asset.symbols.set('default', 'default');
185
256
  assets.push({
186
257
  type: 'js',
187
- filePath: asset.filePath + '.js',
188
- code
258
+ content: code
189
259
  });
190
260
  }
191
261
 
192
262
  return assets;
193
263
  },
194
264
 
195
- generate({
196
- asset
265
+ async generate({
266
+ asset,
267
+ ast,
268
+ options
197
269
  }) {
198
- let ast = (0, _nullthrows.default)(asset.ast);
270
+ const postcss = await loadPostcss(options, asset.filePath);
199
271
  let code = '';
200
-
201
- _postcss.default.stringify(ast.program, c => {
272
+ postcss.stringify(postcss.fromJSON(ast.program), c => {
202
273
  code += c;
203
274
  });
204
-
205
275
  return {
206
- code
276
+ content: code
207
277
  };
208
278
  }
209
279
 
@@ -212,14 +282,14 @@ var _default = new _plugin.Transformer({
212
282
  exports.default = _default;
213
283
 
214
284
  function createLoader(asset, resolve) {
215
- return class extends _fileSystemLoader.default {
285
+ return class extends _fileSystemLoader().default {
216
286
  async fetch(composesPath, relativeTo) {
217
287
  let importPath = composesPath.replace(/^["']|["']$/g, '');
218
288
  let resolved = await resolve(relativeTo, importPath);
219
289
 
220
- let rootRelativePath = _path.default.resolve(_path.default.dirname(relativeTo), resolved);
290
+ let rootRelativePath = _path().default.resolve(_path().default.dirname(relativeTo), resolved);
221
291
 
222
- let root = _path.default.resolve('/'); // fixes an issue on windows which is part of the css-modules-loader-core
292
+ let root = _path().default.resolve('/'); // fixes an issue on windows which is part of the css-modules-loader-core
223
293
  // see https://github.com/css-modules/css-modules-loader-core/issues/230
224
294
 
225
295
 
@@ -230,7 +300,8 @@ function createLoader(asset, resolve) {
230
300
  let source = await asset.fs.readFile(resolved, 'utf-8');
231
301
  let {
232
302
  exportTokens
233
- } = await this.core.load(source, rootRelativePath, undefined, this.fetch.bind(this));
303
+ } = await this.core.load(source, rootRelativePath, undefined, // $FlowFixMe[method-unbinding]
304
+ this.fetch.bind(this));
234
305
  return exportTokens;
235
306
  }
236
307
 
@@ -239,4 +310,12 @@ function createLoader(asset, resolve) {
239
310
  }
240
311
 
241
312
  };
313
+ }
314
+
315
+ function loadPostcss(options, from) {
316
+ return options.packageManager.require('postcss', from, {
317
+ range: _constants.POSTCSS_RANGE,
318
+ saveDev: true,
319
+ shouldAutoInstall: options.shouldAutoInstall
320
+ });
242
321
  }
@@ -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,159 @@
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
+ const MODULE_BY_NAME_RE = /\.module\./;
55
+
56
+ async function configHydrator(configFile, config, resolveFrom, options) {
57
+ // Use a basic, modules-only PostCSS config if the file opts in by a name
58
+ // like foo.module.css
59
+ if (configFile == null && config.searchPath.match(MODULE_BY_NAME_RE)) {
60
+ configFile = {
61
+ plugins: {
62
+ 'postcss-modules': {}
63
+ }
64
+ };
65
+ resolveFrom = __filename;
66
+ }
67
+
68
+ if (configFile == null) {
69
+ return;
70
+ } // Load the custom config...
71
+
72
+
73
+ let modulesConfig;
74
+ let configFilePlugins = (0, _clone().default)(configFile.plugins);
75
+
76
+ if (configFilePlugins != null && typeof configFilePlugins === 'object' && configFilePlugins['postcss-modules'] != null) {
77
+ modulesConfig = configFilePlugins['postcss-modules'];
78
+ delete configFilePlugins['postcss-modules'];
79
+ }
80
+
81
+ if (!modulesConfig && configFile.modules) {
82
+ modulesConfig = {};
83
+ }
84
+
85
+ let plugins = await (0, _loadPlugins.default)(configFilePlugins, (0, _nullthrows().default)(resolveFrom), options); // contents is either:
86
+ // from JSON: { plugins: { 'postcss-foo': { ...opts } } }
87
+ // from JS (v8): { plugins: [ { postcssPlugin: 'postcss-foo', ...visitor callback functions } ]
88
+ // from JS (v7): { plugins: [ [Function: ...] ]
89
+
90
+ let pluginArray = Array.isArray(configFilePlugins) ? configFilePlugins : Object.keys(configFilePlugins);
91
+
92
+ for (let p of pluginArray) {
93
+ if (typeof p === 'string') {
94
+ config.addDevDependency({
95
+ specifier: p,
96
+ resolveFrom: (0, _nullthrows().default)(resolveFrom)
97
+ });
98
+ }
99
+ }
100
+
101
+ return {
102
+ raw: configFile,
103
+ hydrated: {
104
+ plugins,
105
+ from: config.searchPath,
106
+ to: config.searchPath,
107
+ modules: modulesConfig
108
+ }
109
+ };
110
+ }
111
+
112
+ async function load({
113
+ config,
114
+ options,
115
+ logger
116
+ }) {
117
+ if (!config.isSource) {
118
+ return;
119
+ }
120
+
121
+ let configFile = await config.getConfig(['.postcssrc', '.postcssrc.json', '.postcssrc.js', 'postcss.config.js'], {
122
+ packageKey: 'postcss'
123
+ });
124
+ let contents = null;
125
+
126
+ if (configFile) {
127
+ config.addDevDependency({
128
+ specifier: 'postcss',
129
+ resolveFrom: config.searchPath,
130
+ range: _constants.POSTCSS_RANGE
131
+ });
132
+ contents = configFile.contents;
133
+ let isDynamic = configFile && _path().default.extname(configFile.filePath) === '.js';
134
+
135
+ if (isDynamic) {
136
+ // We have to invalidate on startup in case the config is non-deterministic,
137
+ // e.g. using unknown environment variables, reading from the filesystem, etc.
138
+ logger.warn({
139
+ message: 'WARNING: Using a JavaScript PostCSS config file means losing out on caching features of Parcel. Use a .postcssrc(.json) file whenever possible.'
140
+ });
141
+ config.invalidateOnStartup(); // Also add the config as a dev dependency so we attempt to reload in watch mode.
142
+
143
+ config.addDevDependency({
144
+ specifier: (0, _utils().relativePath)(_path().default.dirname(config.searchPath), configFile.filePath),
145
+ resolveFrom: config.searchPath
146
+ });
147
+ }
148
+
149
+ if (typeof contents !== 'object') {
150
+ throw new Error('PostCSS config should be an object.');
151
+ }
152
+
153
+ if (contents.plugins == null || typeof contents.plugins !== 'object' || Object.keys(contents.plugins).length === 0) {
154
+ throw new Error('PostCSS config must have plugins');
155
+ }
156
+ }
157
+
158
+ return configHydrator(contents, config, configFile === null || configFile === void 0 ? void 0 : configFile.filePath, options);
159
+ }
@@ -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.1.0",
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.1.0"
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.1.0",
24
+ "@parcel/plugin": "^2.1.0",
25
+ "@parcel/utils": "^2.1.0",
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": "5a80fbe4af8797d0aab248a66f5254f42f00e83e"
28
37
  }
@@ -1,126 +1,82 @@
1
1
  // @flow
2
2
 
3
- import type {FilePath, MutableAsset} from '@parcel/types';
3
+ import type {FilePath, 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*;?/;
18
- const MODULE_BY_NAME_RE = /\.module\./;
19
-
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
21
 
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
- };
22
+ export default (new Transformer({
23
+ loadConfig({config, options, logger}) {
24
+ return load({config, options, logger});
96
25
  },
97
26
 
98
27
  canReuseAST({ast}) {
99
- return ast.type === 'postcss' && semver.satisfies(ast.version, '^7.0.0');
28
+ return (
29
+ ast.type === 'postcss' && semver.satisfies(ast.version, POSTCSS_RANGE)
30
+ );
100
31
  },
101
32
 
102
- async parse({asset, config}) {
33
+ async parse({asset, config, options}) {
103
34
  if (!config) {
104
35
  return;
105
36
  }
106
37
 
38
+ const postcss = await loadPostcss(options, asset.filePath);
39
+
107
40
  return {
108
41
  type: 'postcss',
109
- version: '7.0.0',
110
- program: postcss.parse(await asset.getCode(), {
111
- from: asset.filePath,
112
- }),
42
+ version: '8.2.1',
43
+ program: postcss
44
+ .parse(await asset.getCode(), {
45
+ from: asset.filePath,
46
+ })
47
+ .toJSON(),
113
48
  };
114
49
  },
115
50
 
116
- async transform({asset, config}) {
51
+ async transform({asset, config, options, resolve}) {
52
+ asset.type = 'css';
117
53
  if (!config) {
118
54
  return [asset];
119
55
  }
120
56
 
121
- let ast = nullthrows(asset.ast);
122
- if (COMPOSES_RE.test(await asset.getCode())) {
123
- ast.program.walkDecls(decl => {
57
+ const postcss: Postcss = await loadPostcss(options, asset.filePath);
58
+
59
+ let plugins = [...config.hydrated.plugins];
60
+ let cssModules: ?{|[string]: string|} = null;
61
+ if (config.hydrated.modules) {
62
+ plugins.push(
63
+ postcssModules({
64
+ getJSON: (filename, json) => (cssModules = json),
65
+ Loader: createLoader(asset, resolve),
66
+ generateScopedName: (name, filename) =>
67
+ `_${name}_${hashString(
68
+ path.relative(options.projectRoot, filename),
69
+ ).substr(0, 6)}`,
70
+ ...config.hydrated.modules,
71
+ }),
72
+ );
73
+ }
74
+
75
+ let ast = nullthrows(await asset.getAST());
76
+ let program = postcss.fromJSON(ast.program);
77
+ let code = asset.isASTDirty() ? null : await asset.getCode();
78
+ if (code == null || COMPOSES_RE.test(code)) {
79
+ program.walkDecls(decl => {
124
80
  let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
125
81
  if (decl.prop === 'composes' && importPath != null) {
126
82
  let parsed = valueParser(decl.value);
@@ -128,9 +84,10 @@ export default new Transformer({
128
84
  parsed.walk(node => {
129
85
  if (node.type === 'string') {
130
86
  asset.addDependency({
131
- moduleSpecifier: importPath,
87
+ specifier: importPath,
88
+ specifierType: 'url',
132
89
  loc: {
133
- filePath: importPath,
90
+ filePath: asset.filePath,
134
91
  start: decl.source.start,
135
92
  end: {
136
93
  line: decl.source.start.line,
@@ -144,64 +101,82 @@ export default new Transformer({
144
101
  });
145
102
  }
146
103
 
147
- let {messages, root} = await postcss(config.plugins).process(
148
- ast.program,
149
- config,
104
+ // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
105
+ let {messages, root} = await postcss(plugins).process(
106
+ program,
107
+ config.hydrated,
150
108
  );
151
- ast.program = root;
152
- ast.isDirty = true;
109
+ asset.setAST({
110
+ type: 'postcss',
111
+ version: '8.2.1',
112
+ program: root.toJSON(),
113
+ });
153
114
  for (let msg of messages) {
154
115
  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
- });
116
+ asset.invalidateOnFileChange(msg.file);
117
+ } else if (msg.type === 'dir-dependency') {
118
+ let pattern = `${msg.dir}/${msg.glob ?? '**/*'}`;
119
+ let files = await glob(pattern, asset.fs, {onlyFiles: true});
120
+ for (let file of files) {
121
+ asset.invalidateOnFileChange(path.normalize(file));
122
+ }
123
+ asset.invalidateOnFileCreate({glob: pattern});
166
124
  }
167
125
  }
168
126
 
169
127
  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);
128
+ if (cssModules) {
129
+ // $FlowFixMe
130
+ let cssModulesList = (Object.entries(cssModules): Array<
131
+ [string, string],
132
+ >);
133
+ let deps = asset.getDependencies().filter(dep => dep.priority === 'sync');
134
+ let code: string;
173
135
  if (deps.length > 0) {
174
136
  code = `
175
137
  module.exports = Object.assign({}, ${deps
176
- .map(dep => `require(${JSON.stringify(dep.moduleSpecifier)})`)
177
- .join(', ')}, ${code});
138
+ .map(dep => `require(${JSON.stringify(dep.specifier)})`)
139
+ .join(', ')}, ${JSON.stringify(cssModules, null, 2)});
178
140
  `;
179
141
  } else {
180
- code = `module.exports = ${code};`;
142
+ code = cssModulesList
143
+ .map(
144
+ // This syntax enables shaking the invidual statements, so that unused classes don't even exist in JS.
145
+ ([className, classNameHashed]) =>
146
+ `module.exports[${JSON.stringify(className)}] = ${JSON.stringify(
147
+ classNameHashed,
148
+ )};`,
149
+ )
150
+ .join('\n');
151
+ }
152
+
153
+ asset.symbols.ensure();
154
+ for (let [k, v] of cssModulesList) {
155
+ asset.symbols.set(k, v);
181
156
  }
157
+ asset.symbols.set('default', 'default');
182
158
 
183
159
  assets.push({
184
160
  type: 'js',
185
- filePath: asset.filePath + '.js',
186
- code,
161
+ content: code,
187
162
  });
188
163
  }
189
164
  return assets;
190
165
  },
191
166
 
192
- generate({asset}) {
193
- let ast = nullthrows(asset.ast);
167
+ async generate({asset, ast, options}) {
168
+ const postcss: Postcss = await loadPostcss(options, asset.filePath);
194
169
 
195
170
  let code = '';
196
- postcss.stringify(ast.program, c => {
171
+ postcss.stringify(postcss.fromJSON(ast.program), c => {
197
172
  code += c;
198
173
  });
199
174
 
200
175
  return {
201
- code,
176
+ content: code,
202
177
  };
203
178
  },
204
- });
179
+ }): Transformer);
205
180
 
206
181
  function createLoader(
207
182
  asset: MutableAsset,
@@ -224,6 +199,7 @@ function createLoader(
224
199
  source,
225
200
  rootRelativePath,
226
201
  undefined,
202
+ // $FlowFixMe[method-unbinding]
227
203
  this.fetch.bind(this),
228
204
  );
229
205
  return exportTokens;
@@ -234,3 +210,11 @@ function createLoader(
234
210
  }
235
211
  };
236
212
  }
213
+
214
+ function loadPostcss(options: PluginOptions, from: FilePath): Promise<Postcss> {
215
+ return options.packageManager.require('postcss', from, {
216
+ range: POSTCSS_RANGE,
217
+ saveDev: true,
218
+ shouldAutoInstall: options.shouldAutoInstall,
219
+ });
220
+ }
@@ -0,0 +1,3 @@
1
+ // @flow
2
+
3
+ export const POSTCSS_RANGE = '^8.2.1';
@@ -0,0 +1,160 @@
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
+ const MODULE_BY_NAME_RE = /\.module\./;
17
+
18
+ type ConfigResult = {|
19
+ raw: any,
20
+ hydrated: {|
21
+ plugins: Array<any>,
22
+ from: FilePath,
23
+ to: FilePath,
24
+ modules: any,
25
+ |},
26
+ |};
27
+
28
+ async function configHydrator(
29
+ configFile: any,
30
+ config: Config,
31
+ resolveFrom: ?FilePath,
32
+ options: PluginOptions,
33
+ ): Promise<?ConfigResult> {
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 && config.searchPath.match(MODULE_BY_NAME_RE)) {
37
+ configFile = {
38
+ plugins: {
39
+ 'postcss-modules': {},
40
+ },
41
+ };
42
+ resolveFrom = __filename;
43
+ }
44
+
45
+ if (configFile == null) {
46
+ return;
47
+ }
48
+
49
+ // Load the custom config...
50
+ let modulesConfig;
51
+ let configFilePlugins = clone(configFile.plugins);
52
+ if (
53
+ configFilePlugins != null &&
54
+ typeof configFilePlugins === 'object' &&
55
+ configFilePlugins['postcss-modules'] != null
56
+ ) {
57
+ modulesConfig = configFilePlugins['postcss-modules'];
58
+ delete configFilePlugins['postcss-modules'];
59
+ }
60
+
61
+ if (!modulesConfig && configFile.modules) {
62
+ modulesConfig = {};
63
+ }
64
+
65
+ let plugins = await loadExternalPlugins(
66
+ configFilePlugins,
67
+ nullthrows(resolveFrom),
68
+ options,
69
+ );
70
+
71
+ // contents is either:
72
+ // from JSON: { plugins: { 'postcss-foo': { ...opts } } }
73
+ // from JS (v8): { plugins: [ { postcssPlugin: 'postcss-foo', ...visitor callback functions } ]
74
+ // from JS (v7): { plugins: [ [Function: ...] ]
75
+ let pluginArray = Array.isArray(configFilePlugins)
76
+ ? configFilePlugins
77
+ : Object.keys(configFilePlugins);
78
+ for (let p of pluginArray) {
79
+ if (typeof p === 'string') {
80
+ config.addDevDependency({
81
+ specifier: p,
82
+ resolveFrom: nullthrows(resolveFrom),
83
+ });
84
+ }
85
+ }
86
+
87
+ return {
88
+ raw: configFile,
89
+ hydrated: {
90
+ plugins,
91
+ from: config.searchPath,
92
+ to: config.searchPath,
93
+ modules: modulesConfig,
94
+ },
95
+ };
96
+ }
97
+
98
+ export async function load({
99
+ config,
100
+ options,
101
+ logger,
102
+ }: {|
103
+ config: Config,
104
+ options: PluginOptions,
105
+ logger: PluginLogger,
106
+ |}): Promise<?ConfigResult> {
107
+ if (!config.isSource) {
108
+ return;
109
+ }
110
+
111
+ let configFile: any = await config.getConfig(
112
+ ['.postcssrc', '.postcssrc.json', '.postcssrc.js', 'postcss.config.js'],
113
+ {packageKey: 'postcss'},
114
+ );
115
+
116
+ let contents = null;
117
+ if (configFile) {
118
+ config.addDevDependency({
119
+ specifier: 'postcss',
120
+ resolveFrom: config.searchPath,
121
+ range: POSTCSS_RANGE,
122
+ });
123
+
124
+ contents = configFile.contents;
125
+ let isDynamic = configFile && path.extname(configFile.filePath) === '.js';
126
+ if (isDynamic) {
127
+ // We have to invalidate on startup in case the config is non-deterministic,
128
+ // e.g. using unknown environment variables, reading from the filesystem, etc.
129
+ logger.warn({
130
+ message:
131
+ 'WARNING: Using a JavaScript PostCSS config file means losing out on caching features of Parcel. Use a .postcssrc(.json) file whenever possible.',
132
+ });
133
+
134
+ config.invalidateOnStartup();
135
+
136
+ // Also add the config as a dev dependency so we attempt to reload in watch mode.
137
+ config.addDevDependency({
138
+ specifier: relativePath(
139
+ path.dirname(config.searchPath),
140
+ configFile.filePath,
141
+ ),
142
+ resolveFrom: config.searchPath,
143
+ });
144
+ }
145
+
146
+ if (typeof contents !== 'object') {
147
+ throw new Error('PostCSS config should be an object.');
148
+ }
149
+
150
+ if (
151
+ contents.plugins == null ||
152
+ typeof contents.plugins !== 'object' ||
153
+ Object.keys(contents.plugins).length === 0
154
+ ) {
155
+ throw new Error('PostCSS config must have plugins');
156
+ }
157
+ }
158
+
159
+ return configHydrator(contents, config, configFile?.filePath, options);
160
+ }
@@ -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 (