@parcel/transformer-postcss 2.0.0-nightly.148 → 2.0.0-nightly.1482

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,29 +4,26 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = loadExternalPlugins;
7
-
8
7
  async function loadExternalPlugins(plugins, relative, options) {
9
8
  if (Array.isArray(plugins)) {
10
- return Promise.all(plugins.map(p => loadPlugin(p, relative, null, options.packageManager)).filter(Boolean));
9
+ return Promise.all(plugins.map(p => loadPlugin(p, relative, null, options.packageManager, options.shouldAutoInstall)).filter(Boolean));
11
10
  } else if (typeof plugins === 'object') {
12
- let mapPlugins = await Promise.all(Object.keys(plugins).map(p => loadPlugin(p, relative, plugins[p], options.packageManager)));
11
+ let mapPlugins = await Promise.all(Object.keys(plugins).map(p => loadPlugin(p, relative, plugins[p], options.packageManager, options.shouldAutoInstall)));
13
12
  return mapPlugins.filter(Boolean);
14
13
  } else {
15
14
  return [];
16
15
  }
17
16
  }
18
-
19
- async function loadPlugin(pluginArg, relative, options = {}, packageManager) {
17
+ async function loadPlugin(pluginArg, relative, options = {}, packageManager, shouldAutoInstall) {
20
18
  if (typeof pluginArg !== 'string') {
21
19
  return pluginArg;
22
20
  }
23
-
24
- let plugin = await packageManager.require(pluginArg, relative);
21
+ let plugin = await packageManager.require(pluginArg, relative, {
22
+ shouldAutoInstall
23
+ });
25
24
  plugin = plugin.default || plugin;
26
-
27
25
  if (options != null && typeof options === 'object' && Object.keys(options).length > 0) {
28
26
  plugin = plugin(options);
29
27
  }
30
-
31
28
  return plugin.default || plugin;
32
29
  }
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "@parcel/transformer-postcss",
3
- "version": "2.0.0-nightly.148+da3cf2a5",
3
+ "version": "2.0.0-nightly.1482+cd447dc4e",
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.1480+cd447dc4e"
17
21
  },
18
22
  "dependencies": {
19
- "@parcel/plugin": "2.0.0-nightly.148+da3cf2a5",
20
- "@parcel/utils": "2.0.0-nightly.148+da3cf2a5",
21
- "css-modules-loader-core": "^1.1.0",
23
+ "@parcel/diagnostic": "2.0.0-nightly.1482+cd447dc4e",
24
+ "@parcel/plugin": "2.0.0-nightly.1482+cd447dc4e",
25
+ "@parcel/rust": "2.11.1-nightly.3105+cd447dc4e",
26
+ "@parcel/utils": "2.0.0-nightly.1482+cd447dc4e",
27
+ "clone": "^2.1.1",
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-value-parser": "^4.2.0",
30
+ "semver": "^7.5.2"
31
+ },
32
+ "devDependencies": {
33
+ "postcss": "^8.4.5",
34
+ "postcss-modules": "^4.3.1"
26
35
  },
27
- "gitHead": "da3cf2a5386d71d25eae5418ebef5d9bfb9e5d62"
36
+ "gitHead": "cd447dc4edaa3a6ff6b40fbfd41998656dad3515"
28
37
  }
@@ -1,212 +1,295 @@
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/rust';
6
+ import {glob} from '@parcel/utils';
6
7
  import {Transformer} from '@parcel/plugin';
7
- import FileSystemLoader from 'css-modules-loader-core/lib/file-system-loader';
8
8
  import nullthrows from 'nullthrows';
9
9
  import path from 'path';
10
- 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';
16
+ import {md, generateJSONCodeHighlights} from '@parcel/diagnostic';
15
17
 
16
18
  const COMPOSES_RE = /composes:.+from\s*("|').*("|')\s*;?/;
17
19
  const FROM_IMPORT_RE = /.+from\s*(?:"|')(.*)(?:"|')\s*;?/;
20
+ const LEGACY_MODULE_RE = /@value|:export|(:global|:local|:import)(?!\s*\()/i;
18
21
  const MODULE_BY_NAME_RE = /\.module\./;
19
22
 
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'},
23
+ export default (new Transformer({
24
+ loadConfig({config, options, logger}) {
25
+ return load({config, options, logger});
26
+ },
27
+
28
+ canReuseAST({ast}) {
29
+ return (
30
+ ast.type === 'postcss' && semver.satisfies(ast.version, POSTCSS_RANGE)
32
31
  );
32
+ },
33
+
34
+ async parse({asset, config, options}) {
35
+ let isLegacy = await isLegacyCssModule(asset);
36
+ if (!config && !isLegacy) {
37
+ return;
38
+ }
39
+
40
+ const postcss = await loadPostcss(options, asset.filePath);
41
+
42
+ return {
43
+ type: 'postcss',
44
+ version: '8.2.1',
45
+ program: postcss
46
+ .parse(await asset.getCode(), {
47
+ from: asset.filePath,
48
+ })
49
+ .toJSON(),
50
+ };
51
+ },
33
52
 
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': {},
53
+ async transform({asset, config, options, resolve, logger}) {
54
+ asset.type = 'css';
55
+ let isLegacy = await isLegacyCssModule(asset);
56
+ if (isLegacy && !config) {
57
+ config = {
58
+ raw: {},
59
+ filePath: '',
60
+ hydrated: {
61
+ plugins: [],
62
+ from: asset.filePath,
63
+ to: asset.filePath,
64
+ modules: {},
40
65
  },
41
66
  };
42
- }
43
67
 
44
- if (configFile == null) {
45
- return;
68
+ // TODO: warning?
46
69
  }
47
70
 
48
- if (typeof configFile !== 'object') {
49
- throw new Error('PostCSS config should be an object.');
71
+ if (!config) {
72
+ return [asset];
50
73
  }
51
74
 
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
- }
75
+ const postcss: Postcss = await loadPostcss(options, asset.filePath);
76
+ let ast = nullthrows(await asset.getAST());
77
+ let program = postcss.fromJSON(ast.program);
59
78
 
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
- }
79
+ let plugins = [...config.hydrated.plugins];
80
+ let cssModules: ?{|[string]: string|} = null;
81
+ if (config.hydrated.modules) {
82
+ asset.meta.cssModulesCompiled = 'postcss';
71
83
 
72
- let plugins = await loadPlugins(configFilePlugins, asset.filePath, options);
84
+ let code = asset.isASTDirty() ? null : await asset.getCode();
85
+ if (
86
+ Object.keys(config.hydrated.modules).length === 0 &&
87
+ code &&
88
+ !isLegacy &&
89
+ !LEGACY_MODULE_RE.test(code)
90
+ ) {
91
+ let filename = path.basename(config.filePath);
92
+ let message;
93
+ let configKey;
94
+ let hint;
95
+ if (config.raw.modules) {
96
+ message = md`The "modules" option in __${filename}__ can be replaced with configuration for @parcel/transformer-css to improve build performance.`;
97
+ configKey = '/modules';
98
+ hint = md`Remove the "modules" option from __${filename}__`;
99
+ } else {
100
+ message = md`The "postcss-modules" plugin in __${filename}__ can be replaced with configuration for @parcel/transformer-css to improve build performance.`;
101
+ configKey = '/plugins/postcss-modules';
102
+ hint = md`Remove the "postcss-modules" plugin from __${filename}__`;
103
+ }
104
+ if (filename === 'package.json') {
105
+ configKey = `/postcss${configKey}`;
106
+ }
73
107
 
74
- if (originalModulesConfig || configFile.modules) {
108
+ let hints = [
109
+ 'Enable the "cssModules" option for "@parcel/transformer-css" in your package.json',
110
+ ];
111
+ if (plugins.length === 0) {
112
+ message += md` Since there are no other plugins, __${filename}__ can be deleted safely.`;
113
+ hints.push(md`Delete __${filename}__`);
114
+ } else {
115
+ hints.push(hint);
116
+ }
117
+
118
+ let codeFrames;
119
+ if (path.extname(filename) !== '.js') {
120
+ let contents = await asset.fs.readFile(config.filePath, 'utf8');
121
+ codeFrames = [
122
+ {
123
+ language: 'json',
124
+ filePath: config.filePath,
125
+ code: contents,
126
+ codeHighlights: generateJSONCodeHighlights(contents, [
127
+ {
128
+ key: configKey,
129
+ type: 'key',
130
+ },
131
+ ]),
132
+ },
133
+ ];
134
+ } else {
135
+ codeFrames = [
136
+ {
137
+ filePath: config.filePath,
138
+ codeHighlights: [
139
+ {
140
+ start: {line: 1, column: 1},
141
+ end: {line: 1, column: 1},
142
+ },
143
+ ],
144
+ },
145
+ ];
146
+ }
147
+
148
+ logger.warn({
149
+ message,
150
+ hints,
151
+ documentationURL:
152
+ 'https://parceljs.org/languages/css/#enabling-css-modules-globally',
153
+ codeFrames,
154
+ });
155
+ }
156
+
157
+ // TODO: should this be resolved from the project root?
75
158
  let postcssModules = await options.packageManager.require(
76
159
  'postcss-modules',
77
160
  asset.filePath,
161
+ {
162
+ range: '^4.3.0',
163
+ saveDev: true,
164
+ shouldAutoInstall: options.shouldAutoInstall,
165
+ },
78
166
  );
79
167
 
80
168
  plugins.push(
81
169
  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,
170
+ getJSON: (filename, json) => (cssModules = json),
171
+ Loader: await createLoader(asset, resolve, options),
172
+ generateScopedName: (name, filename) =>
173
+ `${name}_${hashString(
174
+ path.relative(options.projectRoot, filename),
175
+ ).substr(0, 6)}`,
176
+ ...config.hydrated.modules,
87
177
  }),
88
178
  );
89
- }
90
179
 
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
-
102
- async parse({asset, config}) {
103
- if (!config) {
104
- return;
105
- }
106
-
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
- }
180
+ if (code == null || COMPOSES_RE.test(code)) {
181
+ program.walkDecls(decl => {
182
+ let [, importPath] = FROM_IMPORT_RE.exec(decl.value) || [];
183
+ if (decl.prop === 'composes' && importPath != null) {
184
+ let parsed = valueParser(decl.value);
120
185
 
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,
186
+ parsed.walk(node => {
187
+ if (node.type === 'string') {
188
+ asset.addDependency({
189
+ specifier: importPath,
190
+ specifierType: 'url',
191
+ loc: {
192
+ filePath: asset.filePath,
193
+ start: decl.source.start,
194
+ end: {
195
+ line: decl.source.start.line,
196
+ column: decl.source.start.column + importPath.length,
197
+ },
138
198
  },
139
- },
140
- });
141
- }
142
- });
143
- }
144
- });
199
+ });
200
+ }
201
+ });
202
+ }
203
+ });
204
+ }
145
205
  }
146
206
 
147
- let {messages, root} = await postcss(config.plugins).process(
148
- ast.program,
149
- config,
207
+ // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
208
+ let {messages, root} = await postcss(plugins).process(
209
+ program,
210
+ config.hydrated,
150
211
  );
151
- ast.program = root;
152
- ast.isDirty = true;
212
+ asset.setAST({
213
+ type: 'postcss',
214
+ version: '8.2.1',
215
+ program: root.toJSON(),
216
+ });
153
217
  for (let msg of messages) {
154
218
  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
- });
219
+ asset.invalidateOnFileChange(msg.file);
220
+ } else if (msg.type === 'dir-dependency') {
221
+ let pattern = `${msg.dir}/${msg.glob ?? '**/*'}`;
222
+ let files = await glob(pattern, asset.fs, {onlyFiles: true});
223
+ for (let file of files) {
224
+ asset.invalidateOnFileChange(path.normalize(file));
225
+ }
226
+ asset.invalidateOnFileCreate({glob: pattern});
166
227
  }
167
228
  }
168
229
 
169
230
  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);
231
+ if (cssModules) {
232
+ // $FlowFixMe
233
+ let cssModulesList = (Object.entries(cssModules): Array<
234
+ [string, string],
235
+ >);
236
+ let deps = asset.getDependencies().filter(dep => dep.priority === 'sync');
237
+ let code: string;
173
238
  if (deps.length > 0) {
174
239
  code = `
175
240
  module.exports = Object.assign({}, ${deps
176
- .map(dep => `require(${JSON.stringify(dep.moduleSpecifier)})`)
177
- .join(', ')}, ${code});
241
+ .map(dep => `require(${JSON.stringify(dep.specifier)})`)
242
+ .join(', ')}, ${JSON.stringify(cssModules, null, 2)});
178
243
  `;
179
244
  } else {
180
- code = `module.exports = ${code};`;
245
+ code = cssModulesList
246
+ .map(
247
+ // This syntax enables shaking the invidual statements, so that unused classes don't even exist in JS.
248
+ ([className, classNameHashed]) =>
249
+ `module.exports[${JSON.stringify(className)}] = ${JSON.stringify(
250
+ classNameHashed,
251
+ )};`,
252
+ )
253
+ .join('\n');
254
+ }
255
+
256
+ asset.symbols.ensure();
257
+ for (let [k, v] of cssModulesList) {
258
+ asset.symbols.set(k, v);
181
259
  }
260
+ asset.symbols.set('default', 'default');
182
261
 
183
262
  assets.push({
184
263
  type: 'js',
185
- filePath: asset.filePath + '.js',
186
- code,
264
+ content: code,
187
265
  });
188
266
  }
189
267
  return assets;
190
268
  },
191
269
 
192
- generate({asset}) {
193
- let ast = nullthrows(asset.ast);
270
+ async generate({asset, ast, options}) {
271
+ const postcss: Postcss = await loadPostcss(options, asset.filePath);
194
272
 
195
273
  let code = '';
196
- postcss.stringify(ast.program, c => {
274
+ postcss.stringify(postcss.fromJSON(ast.program), c => {
197
275
  code += c;
198
276
  });
199
277
 
200
278
  return {
201
- code,
279
+ content: code,
202
280
  };
203
281
  },
204
- });
282
+ }): Transformer);
205
283
 
206
- function createLoader(
284
+ async function createLoader(
207
285
  asset: MutableAsset,
208
286
  resolve: (from: FilePath, to: string) => Promise<FilePath>,
287
+ options: PluginOptions,
209
288
  ) {
289
+ let {default: FileSystemLoader} = await options.packageManager.require(
290
+ 'postcss-modules/build/css-loader-core/loader',
291
+ asset.filePath,
292
+ );
210
293
  return class ParcelFileSystemLoader extends FileSystemLoader {
211
294
  async fetch(composesPath, relativeTo) {
212
295
  let importPath = composesPath.replace(/^["']|["']$/g, '');
@@ -224,6 +307,7 @@ function createLoader(
224
307
  source,
225
308
  rootRelativePath,
226
309
  undefined,
310
+ // $FlowFixMe[method-unbinding]
227
311
  this.fetch.bind(this),
228
312
  );
229
313
  return exportTokens;
@@ -234,3 +318,20 @@ function createLoader(
234
318
  }
235
319
  };
236
320
  }
321
+
322
+ function loadPostcss(options: PluginOptions, from: FilePath): Promise<Postcss> {
323
+ return options.packageManager.require('postcss', from, {
324
+ range: POSTCSS_RANGE,
325
+ saveDev: true,
326
+ shouldAutoInstall: options.shouldAutoInstall,
327
+ });
328
+ }
329
+
330
+ async function isLegacyCssModule(asset: Asset | MutableAsset) {
331
+ if (!MODULE_BY_NAME_RE.test(asset.filePath)) {
332
+ return false;
333
+ }
334
+
335
+ let code = await asset.getCode();
336
+ return LEGACY_MODULE_RE.test(code);
337
+ }
@@ -0,0 +1,3 @@
1
+ // @flow
2
+
3
+ export const POSTCSS_RANGE = '^8.2.1';