@parcel/transformer-postcss 2.0.0-nightly.101 → 2.0.0-nightly.1014

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,277 @@ 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 _nullthrows() {
39
+ const data = _interopRequireDefault(require("nullthrows"));
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
+ _nullthrows = 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 _path() {
49
+ const data = _interopRequireDefault(require("path"));
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
+ _path = 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 _semver() {
59
+ const data = _interopRequireDefault(require("semver"));
60
60
 
61
- if (typeof configFile !== 'object') {
62
- throw new Error('PostCSS config should be an object.');
63
- }
61
+ _semver = 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 _postcssValueParser() {
69
+ const data = _interopRequireDefault(require("postcss-value-parser"));
71
70
 
72
- if (configFilePlugins != null && typeof configFilePlugins === 'object' && configFilePlugins['postcss-modules'] != null) {
73
- originalModulesConfig = configFilePlugins['postcss-modules']; // $FlowFixMe
71
+ _postcssValueParser = 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
+ var _loadConfig = require("./loadConfig");
79
79
 
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
- }
80
+ var _constants = require("./constants");
88
81
 
89
- return {
90
- plugins,
91
- from: asset.filePath,
92
- to: asset.filePath
93
- };
82
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
83
+
84
+ const COMPOSES_RE = /composes:.+from\s*("|').*("|')\s*;?/;
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\./;
88
+
89
+ var _default = new (_plugin().Transformer)({
90
+ loadConfig({
91
+ config,
92
+ options,
93
+ logger
94
+ }) {
95
+ return (0, _loadConfig.load)({
96
+ config,
97
+ options,
98
+ logger
99
+ });
94
100
  },
95
101
 
96
102
  canReuseAST({
97
103
  ast
98
104
  }) {
99
- 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);
100
106
  },
101
107
 
102
108
  async parse({
103
109
  asset,
104
- config
110
+ config,
111
+ options
105
112
  }) {
106
- if (!config) {
113
+ let isLegacy = await isLegacyCssModule(asset);
114
+
115
+ if (!config && !isLegacy) {
107
116
  return;
108
117
  }
109
118
 
119
+ const postcss = await loadPostcss(options, asset.filePath);
110
120
  return {
111
121
  type: 'postcss',
112
- version: '7.0.0',
113
- program: _postcss.default.parse((await asset.getCode()), {
122
+ version: '8.2.1',
123
+ program: postcss.parse(await asset.getCode(), {
114
124
  from: asset.filePath
115
- })
125
+ }).toJSON()
116
126
  };
117
127
  },
118
128
 
119
129
  async transform({
120
130
  asset,
121
- config
131
+ config,
132
+ options,
133
+ resolve
122
134
  }) {
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
+ }
148
+
123
149
  if (!config) {
124
150
  return [asset];
125
151
  }
126
152
 
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
145
- }
146
- }
147
- });
148
- }
149
- });
150
- }
153
+ const postcss = await loadPostcss(options, asset.filePath);
154
+ let ast = (0, _nullthrows().default)(await asset.getAST());
155
+ let program = postcss.fromJSON(ast.program);
156
+ let plugins = [...config.hydrated.plugins];
157
+ let cssModules = null;
158
+
159
+ if (config.hydrated.modules) {
160
+ asset.meta.cssModulesCompiled = true; // TODO: should this be resolved from the project root?
161
+
162
+ let postcssModules = await options.packageManager.require('postcss-modules', asset.filePath, {
163
+ range: '^4.3.0',
164
+ saveDev: true,
165
+ shouldAutoInstall: options.shouldAutoInstall
151
166
  });
152
- }
167
+ plugins.push(postcssModules({
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)}`,
171
+ ...config.hydrated.modules
172
+ }));
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
+ }
193
+ }
194
+ });
195
+ }
196
+ });
197
+ }
198
+ });
199
+ }
200
+ } // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
201
+
153
202
 
154
203
  let {
155
204
  messages,
156
205
  root
157
- } = await (0, _postcss.default)(config.plugins).process(ast.program, config);
158
- ast.program = root;
159
- ast.isDirty = true;
206
+ } = await postcss(plugins).process(program, config.hydrated);
207
+ asset.setAST({
208
+ type: 'postcss',
209
+ version: '8.2.1',
210
+ program: root.toJSON()
211
+ });
160
212
 
161
213
  for (let msg of messages) {
162
214
  if (msg.type === 'dependency') {
163
- // $FlowFixMe merely a convention
164
- msg = msg;
165
- asset.addIncludedFile({
166
- 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
167
230
  });
168
231
  }
169
232
  }
170
233
 
171
234
  let assets = [asset];
172
235
 
173
- if (asset.meta.cssModules) {
174
- let code = JSON.stringify(asset.meta.cssModules, null, 2);
175
- 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;
176
241
 
177
242
  if (deps.length > 0) {
178
243
  code = `
179
- 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)});
180
245
  `;
181
246
  } else {
182
- 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');
183
249
  }
184
250
 
251
+ asset.symbols.ensure();
252
+
253
+ for (let [k, v] of cssModulesList) {
254
+ asset.symbols.set(k, v);
255
+ }
256
+
257
+ asset.symbols.set('default', 'default');
185
258
  assets.push({
186
259
  type: 'js',
187
- filePath: asset.filePath + '.js',
188
- code
260
+ content: code
189
261
  });
190
262
  }
191
263
 
192
264
  return assets;
193
265
  },
194
266
 
195
- generate({
196
- asset
267
+ async generate({
268
+ asset,
269
+ ast,
270
+ options
197
271
  }) {
198
- let ast = (0, _nullthrows.default)(asset.ast);
272
+ const postcss = await loadPostcss(options, asset.filePath);
199
273
  let code = '';
200
-
201
- _postcss.default.stringify(ast.program, c => {
274
+ postcss.stringify(postcss.fromJSON(ast.program), c => {
202
275
  code += c;
203
276
  });
204
-
205
277
  return {
206
- code
278
+ content: code
207
279
  };
208
280
  }
209
281
 
@@ -211,15 +283,18 @@ var _default = new _plugin.Transformer({
211
283
 
212
284
  exports.default = _default;
213
285
 
214
- function createLoader(asset, resolve) {
215
- 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 {
216
291
  async fetch(composesPath, relativeTo) {
217
292
  let importPath = composesPath.replace(/^["']|["']$/g, '');
218
293
  let resolved = await resolve(relativeTo, importPath);
219
294
 
220
- let rootRelativePath = _path.default.resolve(_path.default.dirname(relativeTo), resolved);
295
+ let rootRelativePath = _path().default.resolve(_path().default.dirname(relativeTo), resolved);
221
296
 
222
- 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
223
298
  // see https://github.com/css-modules/css-modules-loader-core/issues/230
224
299
 
225
300
 
@@ -230,7 +305,8 @@ function createLoader(asset, resolve) {
230
305
  let source = await asset.fs.readFile(resolved, 'utf-8');
231
306
  let {
232
307
  exportTokens
233
- } = 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));
234
310
  return exportTokens;
235
311
  }
236
312
 
@@ -239,4 +315,21 @@ function createLoader(asset, resolve) {
239
315
  }
240
316
 
241
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);
242
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;
@@ -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.101+fa36fe82",
3
+ "version": "2.0.0-nightly.1014+ef16fad82",
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.1012+ef16fad82"
17
21
  },
18
22
  "dependencies": {
19
- "@parcel/plugin": "2.0.0-nightly.101+fa36fe82",
20
- "@parcel/utils": "2.0.0-nightly.101+fa36fe82",
21
- "css-modules-loader-core": "^1.1.0",
23
+ "@parcel/hash": "2.3.3-nightly.2637+ef16fad82",
24
+ "@parcel/plugin": "2.0.0-nightly.1014+ef16fad82",
25
+ "@parcel/utils": "2.0.0-nightly.1014+ef16fad82",
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": "fa36fe822604e5bc19789cd5c1e89b090c3a7471"
34
+ "gitHead": "ef16fad82001dd112c4dd46d79dce47968bb10b8"
28
35
  }
@@ -1,212 +1,220 @@
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 loadPlugins from './loadPlugins';
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;
18
20
  const MODULE_BY_NAME_RE = /\.module\./;
19
21
 
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
- );
22
+ export default (new Transformer({
23
+ loadConfig({config, options, logger}) {
24
+ return load({config, options, logger});
25
+ },
33
26
 
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
- }
27
+ canReuseAST({ast}) {
28
+ return (
29
+ ast.type === 'postcss' && semver.satisfies(ast.version, POSTCSS_RANGE)
30
+ );
31
+ },
43
32
 
44
- if (configFile == null) {
33
+ async parse({asset, config, options}) {
34
+ let isLegacy = await isLegacyCssModule(asset);
35
+ if (!config && !isLegacy) {
45
36
  return;
46
37
  }
47
38
 
48
- if (typeof configFile !== 'object') {
49
- throw new Error('PostCSS config should be an object.');
50
- }
39
+ const postcss = await loadPostcss(options, asset.filePath);
51
40
 
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');
41
+ return {
42
+ type: 'postcss',
43
+ version: '8.2.1',
44
+ program: postcss
45
+ .parse(await asset.getCode(), {
46
+ from: asset.filePath,
47
+ })
48
+ .toJSON(),
49
+ };
50
+ },
51
+
52
+ async transform({asset, config, options, resolve}) {
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?
58
66
  }
59
67
 
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'];
68
+ if (!config) {
69
+ return [asset];
70
70
  }
71
71
 
72
- let plugins = await loadPlugins(configFilePlugins, asset.filePath, options);
72
+ const postcss: Postcss = await loadPostcss(options, asset.filePath);
73
+ let ast = nullthrows(await asset.getAST());
74
+ let program = postcss.fromJSON(ast.program);
75
+
76
+ let plugins = [...config.hydrated.plugins];
77
+ let cssModules: ?{|[string]: string|} = null;
78
+ if (config.hydrated.modules) {
79
+ asset.meta.cssModulesCompiled = true;
73
80
 
74
- if (originalModulesConfig || configFile.modules) {
81
+ // TODO: should this be resolved from the project root?
75
82
  let postcssModules = await options.packageManager.require(
76
83
  'postcss-modules',
77
84
  asset.filePath,
85
+ {
86
+ range: '^4.3.0',
87
+ saveDev: true,
88
+ shouldAutoInstall: options.shouldAutoInstall,
89
+ },
78
90
  );
79
91
 
80
92
  plugins.push(
81
93
  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,
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)}`,
100
+ ...config.hydrated.modules,
87
101
  }),
88
102
  );
89
- }
90
-
91
- return {
92
- plugins,
93
- from: asset.filePath,
94
- to: asset.filePath,
95
- };
96
- },
97
-
98
- canReuseAST({ast}) {
99
- return ast.type === 'postcss' && semver.satisfies(ast.version, '^7.0.0');
100
- },
101
103
 
102
- async parse({asset, config}) {
103
- if (!config) {
104
- return;
105
- }
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);
106
110
 
107
- return {
108
- type: 'postcss',
109
- version: '7.0.0',
110
- program: postcss.parse(await asset.getCode(), {
111
- from: asset.filePath,
112
- }),
113
- };
114
- },
115
-
116
- async transform({asset, config}) {
117
- if (!config) {
118
- return [asset];
119
- }
120
-
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,
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
+ },
138
123
  },
139
- },
140
- });
141
- }
142
- });
143
- }
144
- });
124
+ });
125
+ }
126
+ });
127
+ }
128
+ });
129
+ }
145
130
  }
146
131
 
147
- let {messages, root} = await postcss(config.plugins).process(
148
- ast.program,
149
- config,
132
+ // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
133
+ let {messages, root} = await postcss(plugins).process(
134
+ program,
135
+ config.hydrated,
150
136
  );
151
- ast.program = root;
152
- ast.isDirty = true;
137
+ asset.setAST({
138
+ type: 'postcss',
139
+ version: '8.2.1',
140
+ program: root.toJSON(),
141
+ });
153
142
  for (let msg of messages) {
154
143
  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
- });
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});
166
152
  }
167
153
  }
168
154
 
169
155
  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);
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;
173
163
  if (deps.length > 0) {
174
164
  code = `
175
165
  module.exports = Object.assign({}, ${deps
176
- .map(dep => `require(${JSON.stringify(dep.moduleSpecifier)})`)
177
- .join(', ')}, ${code});
166
+ .map(dep => `require(${JSON.stringify(dep.specifier)})`)
167
+ .join(', ')}, ${JSON.stringify(cssModules, null, 2)});
178
168
  `;
179
169
  } else {
180
- 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');
181
179
  }
182
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
+
183
187
  assets.push({
184
188
  type: 'js',
185
- filePath: asset.filePath + '.js',
186
- code,
189
+ content: code,
187
190
  });
188
191
  }
189
192
  return assets;
190
193
  },
191
194
 
192
- generate({asset}) {
193
- let ast = nullthrows(asset.ast);
195
+ async generate({asset, ast, options}) {
196
+ const postcss: Postcss = await loadPostcss(options, asset.filePath);
194
197
 
195
198
  let code = '';
196
- postcss.stringify(ast.program, c => {
199
+ postcss.stringify(postcss.fromJSON(ast.program), c => {
197
200
  code += c;
198
201
  });
199
202
 
200
203
  return {
201
- code,
204
+ content: code,
202
205
  };
203
206
  },
204
- });
207
+ }): Transformer);
205
208
 
206
- function createLoader(
209
+ async function createLoader(
207
210
  asset: MutableAsset,
208
211
  resolve: (from: FilePath, to: string) => Promise<FilePath>,
212
+ options: PluginOptions,
209
213
  ) {
214
+ let {default: FileSystemLoader} = await options.packageManager.require(
215
+ 'postcss-modules/build/css-loader-core/loader',
216
+ asset.filePath,
217
+ );
210
218
  return class ParcelFileSystemLoader extends FileSystemLoader {
211
219
  async fetch(composesPath, relativeTo) {
212
220
  let importPath = composesPath.replace(/^["']|["']$/g, '');
@@ -224,6 +232,7 @@ function createLoader(
224
232
  source,
225
233
  rootRelativePath,
226
234
  undefined,
235
+ // $FlowFixMe[method-unbinding]
227
236
  this.fetch.bind(this),
228
237
  );
229
238
  return exportTokens;
@@ -234,3 +243,20 @@ function createLoader(
234
243
  }
235
244
  };
236
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';
@@ -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 (