@parcel/transformer-css 2.0.0-beta.3.1 → 2.0.0-dev.1510

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,275 +4,387 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
-
7
+ function _path() {
8
+ const data = _interopRequireDefault(require("path"));
9
+ _path = function () {
10
+ return data;
11
+ };
12
+ return data;
13
+ }
8
14
  function _sourceMap() {
9
15
  const data = _interopRequireDefault(require("@parcel/source-map"));
10
-
11
16
  _sourceMap = function () {
12
17
  return data;
13
18
  };
14
-
15
19
  return data;
16
20
  }
17
-
18
21
  function _plugin() {
19
22
  const data = require("@parcel/plugin");
20
-
21
23
  _plugin = function () {
22
24
  return data;
23
25
  };
24
-
25
26
  return data;
26
27
  }
27
-
28
28
  function _utils() {
29
29
  const data = require("@parcel/utils");
30
-
31
30
  _utils = function () {
32
31
  return data;
33
32
  };
34
-
35
33
  return data;
36
34
  }
37
-
38
- function _postcss() {
39
- const data = _interopRequireDefault(require("postcss"));
40
-
41
- _postcss = function () {
35
+ function native() {
36
+ const data = _interopRequireWildcard(require("lightningcss"));
37
+ native = function () {
42
38
  return data;
43
39
  };
44
-
45
40
  return data;
46
41
  }
47
-
48
- function _nullthrows() {
49
- const data = _interopRequireDefault(require("nullthrows"));
50
-
51
- _nullthrows = function () {
42
+ function _browserslist() {
43
+ const data = _interopRequireDefault(require("browserslist"));
44
+ _browserslist = function () {
52
45
  return data;
53
46
  };
54
-
55
47
  return data;
56
48
  }
57
-
58
- function _postcssValueParser() {
59
- const data = _interopRequireDefault(require("postcss-value-parser"));
60
-
61
- _postcssValueParser = function () {
49
+ function _nullthrows() {
50
+ const data = _interopRequireDefault(require("nullthrows"));
51
+ _nullthrows = function () {
62
52
  return data;
63
53
  };
64
-
65
54
  return data;
66
55
  }
67
-
68
- function _semver() {
69
- const data = _interopRequireDefault(require("semver"));
70
-
71
- _semver = function () {
56
+ function _diagnostic() {
57
+ const data = _interopRequireWildcard(require("@parcel/diagnostic"));
58
+ _diagnostic = function () {
72
59
  return data;
73
60
  };
74
-
75
61
  return data;
76
62
  }
77
-
63
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
64
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
78
65
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
79
-
80
- const URL_RE = /url\s*\("?(?![a-z]+:)/;
81
- const IMPORT_RE = /@import/;
82
-
83
- function canHaveDependencies(filePath, code) {
84
- return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code);
85
- }
86
-
87
- var _default = new (_plugin().Transformer)({
88
- canReuseAST({
89
- ast
90
- }) {
91
- return ast.type === 'postcss' && _semver().default.satisfies(ast.version, '^8.2.1');
92
- },
93
-
94
- async parse({
95
- asset
66
+ const {
67
+ transform,
68
+ transformStyleAttribute,
69
+ browserslistToTargets
70
+ } = native();
71
+ var _default = exports.default = new (_plugin().Transformer)({
72
+ async loadConfig({
73
+ config,
74
+ options
96
75
  }) {
97
- // This is set by other transformers (e.g. Stylus) to indicate that it has already processed
98
- // all dependencies, and that the CSS transformer can skip this asset completely. This is
99
- // required because when stylus processes e.g. url() it replaces them with a dependency id
100
- // to be filled in later. When the CSS transformer runs, it would pick that up and try to
101
- // resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do
102
- // it this way since the resulting CSS doesn't need to be re-parsed.
103
- if (asset.meta.hasDependencies === false) {
104
- return null;
76
+ var _contents$cssModules, _contents$cssModules2, _contents$cssModules3, _contents$cssModules4;
77
+ let conf = await config.getConfigFrom(options.projectRoot + '/index', [], {
78
+ packageKey: '@parcel/transformer-css'
79
+ });
80
+ let contents = conf === null || conf === void 0 ? void 0 : conf.contents;
81
+ if (typeof (contents === null || contents === void 0 || (_contents$cssModules = contents.cssModules) === null || _contents$cssModules === void 0 ? void 0 : _contents$cssModules.include) === 'string') {
82
+ contents.cssModules.include = [(0, _utils().globToRegex)(contents.cssModules.include)];
83
+ } else if (Array.isArray(contents === null || contents === void 0 || (_contents$cssModules2 = contents.cssModules) === null || _contents$cssModules2 === void 0 ? void 0 : _contents$cssModules2.include)) {
84
+ contents.cssModules.include = contents.cssModules.include.map(include => typeof include === 'string' ? (0, _utils().globToRegex)(include) : include);
105
85
  }
106
-
107
- let code = await asset.getCode();
108
-
109
- if (code != null && !canHaveDependencies(asset.filePath, code)) {
110
- return null;
86
+ if (typeof (contents === null || contents === void 0 || (_contents$cssModules3 = contents.cssModules) === null || _contents$cssModules3 === void 0 ? void 0 : _contents$cssModules3.exclude) === 'string') {
87
+ contents.cssModules.exclude = [(0, _utils().globToRegex)(contents.cssModules.exclude)];
88
+ } else if (Array.isArray(contents === null || contents === void 0 || (_contents$cssModules4 = contents.cssModules) === null || _contents$cssModules4 === void 0 ? void 0 : _contents$cssModules4.exclude)) {
89
+ contents.cssModules.exclude = contents.cssModules.exclude.map(exclude => typeof exclude === 'string' ? (0, _utils().globToRegex)(exclude) : exclude);
111
90
  }
112
-
113
- return {
114
- type: 'postcss',
115
- version: '8.2.1',
116
- program: _postcss().default.parse(code, {
117
- from: asset.filePath
118
- }).toJSON()
119
- };
91
+ return contents;
120
92
  },
121
-
122
93
  async transform({
123
- asset
94
+ asset,
95
+ config,
96
+ options,
97
+ logger
124
98
  }) {
125
99
  // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
126
100
  // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
101
+ let env = asset.env;
127
102
  asset.setEnvironment({
128
103
  context: 'browser',
129
104
  engines: {
130
105
  browsers: asset.env.engines.browsers
131
106
  },
132
107
  shouldOptimize: asset.env.shouldOptimize,
108
+ shouldScopeHoist: asset.env.shouldScopeHoist,
133
109
  sourceMap: asset.env.sourceMap
134
- }); // When this asset is an bundle entry, allow that bundle to be split to load shared assets separately.
135
- // Only set here if it is null to allow previous transformers to override this behavior.
136
-
137
- if (asset.isSplittable == null) {
138
- asset.isSplittable = true;
139
- } // Check for `hasDependencies` being false here as well, as it's possible
140
- // another transformer (such as PostCSSTransformer) has already parsed an
141
- // ast and CSSTransformer's parse was never called.
142
-
143
-
144
- let ast = await asset.getAST();
145
-
146
- if (!ast || asset.meta.hasDependencies === false) {
147
- return [asset];
110
+ });
111
+ let [code, originalMap] = await Promise.all([asset.getBuffer(), asset.getMap(),
112
+ // $FlowFixMe native.default is the init function only when bundled for the browser build
113
+ process.browser && native().default()]);
114
+ let targets = getTargets(asset.env.engines.browsers);
115
+ let res;
116
+ try {
117
+ if (asset.meta.type === 'attr') {
118
+ res = transformStyleAttribute({
119
+ code,
120
+ analyzeDependencies: true,
121
+ errorRecovery: (config === null || config === void 0 ? void 0 : config.errorRecovery) || false,
122
+ targets
123
+ });
124
+ } else {
125
+ let cssModules = false;
126
+ if (asset.meta.type !== 'tag' && asset.meta.cssModulesCompiled == null) {
127
+ let cssModulesConfig = config === null || config === void 0 ? void 0 : config.cssModules;
128
+ let isCSSModule = /\.module\./.test(asset.filePath);
129
+ if (asset.isSource) {
130
+ var _cssModulesConfig$exc;
131
+ let projectRootPath = _path().default.relative(options.projectRoot, asset.filePath);
132
+ if (typeof cssModulesConfig === 'boolean') {
133
+ isCSSModule = true;
134
+ } else if (cssModulesConfig !== null && cssModulesConfig !== void 0 && cssModulesConfig.include) {
135
+ isCSSModule = cssModulesConfig.include.some(include => include.test(projectRootPath));
136
+ } else if (cssModulesConfig !== null && cssModulesConfig !== void 0 && cssModulesConfig.global) {
137
+ isCSSModule = true;
138
+ }
139
+ if (cssModulesConfig !== null && cssModulesConfig !== void 0 && (_cssModulesConfig$exc = cssModulesConfig.exclude) !== null && _cssModulesConfig$exc !== void 0 && _cssModulesConfig$exc.some(exclude => exclude.test(projectRootPath))) {
140
+ isCSSModule = false;
141
+ }
142
+ }
143
+ if (isCSSModule) {
144
+ if (cssModulesConfig !== null && cssModulesConfig !== void 0 && cssModulesConfig.dashedIdents && !asset.isSource) {
145
+ cssModulesConfig.dashedIdents = false;
146
+ }
147
+ cssModules = cssModulesConfig !== null && cssModulesConfig !== void 0 ? cssModulesConfig : true;
148
+ }
149
+ }
150
+ res = transform({
151
+ filename: (0, _utils().normalizeSeparators)(_path().default.relative(options.projectRoot, asset.filePath)),
152
+ code,
153
+ cssModules,
154
+ analyzeDependencies: asset.meta.hasDependencies !== false ? {
155
+ preserveImports: true
156
+ } : false,
157
+ sourceMap: !!asset.env.sourceMap,
158
+ drafts: config === null || config === void 0 ? void 0 : config.drafts,
159
+ pseudoClasses: config === null || config === void 0 ? void 0 : config.pseudoClasses,
160
+ errorRecovery: (config === null || config === void 0 ? void 0 : config.errorRecovery) || false,
161
+ targets
162
+ });
163
+ }
164
+ } catch (err) {
165
+ var _err$data;
166
+ err.filePath = asset.filePath;
167
+ let diagnostic = (0, _diagnostic().errorToDiagnostic)(err, {
168
+ origin: '@parcel/transformer-css'
169
+ });
170
+ if (((_err$data = err.data) === null || _err$data === void 0 ? void 0 : _err$data.type) === 'AmbiguousUrlInCustomProperty' && err.data.url) {
171
+ let p = '/' + (0, _utils().relativePath)(options.projectRoot, _path().default.resolve(_path().default.dirname(asset.filePath), err.data.url), false);
172
+ diagnostic[0].hints = [`Replace with: url(${p})`];
173
+ diagnostic[0].documentationURL = 'https://parceljs.org/languages/css/#url()';
174
+ }
175
+ throw new (_diagnostic().default)({
176
+ diagnostic
177
+ });
148
178
  }
149
-
150
- let program = _postcss().default.fromJSON(ast.program);
151
-
152
- let originalSourceMap = await asset.getMap();
153
-
154
- let createLoc = (start, specifier, lineOffset, colOffset) => {
155
- let loc = (0, _utils().createDependencyLocation)(start, specifier, lineOffset, colOffset);
156
-
157
- if (originalSourceMap) {
158
- loc = (0, _utils().remapSourceLocation)(loc, originalSourceMap);
179
+ if (res.warnings) {
180
+ for (let warning of res.warnings) {
181
+ logger.warn({
182
+ message: warning.message,
183
+ codeFrames: [{
184
+ filePath: asset.filePath,
185
+ codeHighlights: [{
186
+ start: {
187
+ line: warning.loc.line,
188
+ column: warning.loc.column + 1
189
+ },
190
+ end: {
191
+ line: warning.loc.line,
192
+ column: warning.loc.column + 1
193
+ }
194
+ }]
195
+ }]
196
+ });
159
197
  }
160
-
161
- return loc;
162
- };
163
-
164
- let isDirty = false;
165
- program.walkAtRules('import', rule => {
166
- let params = (0, _postcssValueParser().default)(rule.params);
167
- let [name, ...media] = params.nodes;
168
- let moduleSpecifier;
169
-
170
- if (name.type === 'function' && name.value === 'url' && name.nodes.length) {
171
- name = name.nodes[0];
198
+ }
199
+ if (res.map != null) {
200
+ let vlqMap = JSON.parse(Buffer.from(res.map).toString());
201
+ let map = new (_sourceMap().default)(options.projectRoot);
202
+ map.addVLQMap(vlqMap);
203
+ if (originalMap) {
204
+ map.extends(originalMap);
172
205
  }
173
-
174
- moduleSpecifier = name.value;
175
-
176
- if (!moduleSpecifier) {
177
- throw new Error('Could not find import name for ' + String(rule));
206
+ asset.setMap(map);
207
+ }
208
+ if (res.dependencies) {
209
+ for (let dep of res.dependencies) {
210
+ let loc = convertLoc(dep.loc);
211
+ if (originalMap) {
212
+ loc = (0, _utils().remapSourceLocation)(loc, originalMap);
213
+ }
214
+ if (dep.type === 'import' && !res.exports) {
215
+ asset.addDependency({
216
+ specifier: dep.url,
217
+ specifierType: 'url',
218
+ loc,
219
+ packageConditions: ['style'],
220
+ meta: {
221
+ // For the glob resolver to distinguish between `@import` and other URL dependencies.
222
+ isCSSImport: true,
223
+ media: dep.media,
224
+ placeholder: dep.placeholder
225
+ }
226
+ });
227
+ } else if (dep.type === 'url') {
228
+ asset.addURLDependency(dep.url, {
229
+ loc,
230
+ meta: {
231
+ placeholder: dep.placeholder
232
+ }
233
+ });
234
+ }
178
235
  }
179
-
180
- if ((0, _utils().isURL)(moduleSpecifier)) {
181
- name.value = asset.addURLDependency(moduleSpecifier, {
182
- loc: createLoc((0, _nullthrows().default)(rule.source.start), asset.filePath, 0, 8)
183
- });
184
- } else {
185
- // If this came from an inline <style> tag, don't inline the imported file. Replace with the correct URL instead.
186
- // TODO: run CSSPackager on inline style tags.
187
- // let inlineHTML =
188
- // this.options.rendition && this.options.rendition.inlineHTML;
189
- // if (inlineHTML) {
190
- // name.value = asset.addURLDependency(dep, {loc: rule.source.start});
191
- // rule.params = params.toString();
192
- // } else {
193
- media = _postcssValueParser().default.stringify(media).trim();
194
- let dep = {
195
- moduleSpecifier,
196
- // Offset by 8 as it does not include `@import `
197
- loc: createLoc((0, _nullthrows().default)(rule.source.start), moduleSpecifier, 0, 8),
198
- meta: {
199
- media
200
- }
201
- };
202
- asset.addDependency(dep);
203
- rule.remove(); // }
236
+ }
237
+ let assets = [asset];
238
+ let buffer = Buffer.from(res.code);
239
+ if (res.exports != null) {
240
+ var _asset$uniqueKey;
241
+ let exports = res.exports;
242
+ asset.symbols.ensure();
243
+ asset.symbols.set('default', 'default');
244
+ let dependencies = new Map();
245
+ let locals = new Map();
246
+ let c = 0;
247
+ let depjs = '';
248
+ let js = '';
249
+ let cssImports = '';
250
+ for (let key in exports) {
251
+ locals.set(exports[key].name, key);
204
252
  }
205
-
206
- isDirty = true;
207
- });
208
- program.walkDecls(decl => {
209
- if (URL_RE.test(decl.value)) {
210
- let parsed = (0, _postcssValueParser().default)(decl.value);
211
- let isDeclDirty = false;
212
- parsed.walk(node => {
213
- if (node.type === 'function' && node.value === 'url' && node.nodes.length > 0 && !node.nodes[0].value.startsWith('#') // IE's `behavior: url(#default#VML)`
214
- ) {
215
- let url = asset.addURLDependency(node.nodes[0].value, {
216
- loc: createLoc((0, _nullthrows().default)(decl.source.start), node.nodes[0].value, 0, node.nodes[0].sourceIndex)
253
+ (_asset$uniqueKey = asset.uniqueKey) !== null && _asset$uniqueKey !== void 0 ? _asset$uniqueKey : asset.uniqueKey = asset.id;
254
+ let seen = new Set();
255
+ let add = key => {
256
+ if (seen.has(key)) {
257
+ return;
258
+ }
259
+ seen.add(key);
260
+ let e = exports[key];
261
+ let s = `module.exports[${JSON.stringify(key)}] = \`${e.name}`;
262
+ for (let ref of e.composes) {
263
+ s += ' ';
264
+ if (ref.type === 'local') {
265
+ let exported = (0, _nullthrows().default)(locals.get(ref.name));
266
+ add(exported);
267
+ s += '${' + `module.exports[${JSON.stringify(exported)}]` + '}';
268
+ asset.addDependency({
269
+ specifier: (0, _nullthrows().default)(asset.uniqueKey),
270
+ specifierType: 'esm',
271
+ symbols: new Map([[exported, {
272
+ local: ref.name,
273
+ isWeak: false,
274
+ loc: null
275
+ }]])
276
+ });
277
+ } else if (ref.type === 'global') {
278
+ s += ref.name;
279
+ } else if (ref.type === 'dependency') {
280
+ let d = dependencies.get(ref.specifier);
281
+ if (d == null) {
282
+ d = `dep_${c++}`;
283
+ depjs += `import * as ${d} from ${JSON.stringify(ref.specifier)};\n`;
284
+ dependencies.set(ref.specifier, d);
285
+ cssImports += `@import "${ref.specifier}";\n`;
286
+ asset.addDependency({
287
+ specifier: ref.specifier,
288
+ specifierType: 'esm',
289
+ packageConditions: ['style']
217
290
  });
218
- isDeclDirty = node.nodes[0].value !== url;
219
- node.nodes[0].value = url;
220
291
  }
221
- });
222
-
223
- if (isDeclDirty) {
224
- decl.value = parsed.toString();
225
- isDirty = true;
292
+ s += '${' + `${d}[${JSON.stringify(ref.name)}]` + '}';
293
+ }
294
+ }
295
+ s += '`;\n';
296
+
297
+ // If the export is referenced internally (e.g. used @keyframes), add a self-reference
298
+ // to the JS so the symbol is retained during tree-shaking.
299
+ if (e.isReferenced) {
300
+ s += `module.exports[${JSON.stringify(key)}];\n`;
301
+ asset.addDependency({
302
+ specifier: (0, _nullthrows().default)(asset.uniqueKey),
303
+ specifierType: 'esm',
304
+ symbols: new Map([[key, {
305
+ local: exports[key].name,
306
+ isWeak: false,
307
+ loc: null
308
+ }]])
309
+ });
226
310
  }
311
+ js += s;
312
+ };
313
+
314
+ // It's possible that the exports can be ordered differently between builds.
315
+ // Sorting by key is safe as the order is irrelevant but needs to be deterministic.
316
+ for (let key of Object.keys(exports).sort()) {
317
+ asset.symbols.set(key, exports[key].name);
318
+ add(key);
227
319
  }
228
- });
229
-
230
- if (isDirty) {
231
- asset.setAST({ ...ast,
232
- program: program.toJSON()
320
+ if (res.dependencies) {
321
+ for (let dep of res.dependencies) {
322
+ if (dep.type === 'import') {
323
+ // TODO: Figure out how to treeshake this
324
+ let d = `dep_$${c++}`;
325
+ depjs += `import * as ${d} from ${JSON.stringify(dep.url)};\n`;
326
+ js += `for (let key in ${d}) { if (key in module.exports) module.exports[key] += ' ' + ${d}[key]; else module.exports[key] = ${d}[key]; }\n`;
327
+ asset.symbols.set('*', '*');
328
+ }
329
+ }
330
+ }
331
+ if (res.references != null) {
332
+ let references = res.references;
333
+ for (let symbol in references) {
334
+ let reference = references[symbol];
335
+ asset.addDependency({
336
+ specifier: reference.specifier,
337
+ specifierType: 'esm',
338
+ packageConditions: ['style'],
339
+ symbols: new Map([[reference.name, {
340
+ local: symbol,
341
+ isWeak: false,
342
+ loc: null
343
+ }]])
344
+ });
345
+ asset.meta.hasReferences = true;
346
+ cssImports += `@import "${reference.specifier}";\n`;
347
+ }
348
+ }
349
+ assets.push({
350
+ type: 'js',
351
+ content: depjs + js,
352
+ dependencies: [],
353
+ env
233
354
  });
234
- }
235
355
 
236
- return [asset];
237
- },
238
-
239
- async generate({
240
- asset,
241
- ast,
242
- options
243
- }) {
244
- let result = await (0, _postcss().default)().process(_postcss().default.fromJSON(ast.program), {
245
- from: undefined,
246
- to: options.projectRoot + '/index',
247
- map: {
248
- annotation: false,
249
- inline: false,
250
- sourcesContent: false
251
- },
252
- // Pass postcss's own stringifier to it to silence its warning
253
- // as we don't want to perform any transformations -- only generate
254
- stringifier: _postcss().default.stringify
255
- });
256
- let map = null;
257
- let originalSourceMap = await asset.getMap();
258
-
259
- if (result.map != null) {
260
- map = new (_sourceMap().default)(options.projectRoot);
261
- map.addVLQMap(result.map.toJSON());
262
-
263
- if (originalSourceMap) {
264
- map.extends(originalSourceMap.toBuffer());
356
+ // Prepend @import rules for each composes dependency so packager knows where to insert them.
357
+ if (cssImports.length > 0) {
358
+ buffer = Buffer.concat([Buffer.from(cssImports), buffer]);
265
359
  }
266
- } else {
267
- map = originalSourceMap;
268
360
  }
269
-
270
- return {
271
- content: result.css,
272
- map
273
- };
361
+ asset.setBuffer(buffer);
362
+ return assets;
274
363
  }
275
-
276
364
  });
277
-
278
- exports.default = _default;
365
+ let cache = new Map();
366
+ function getTargets(browsers) {
367
+ if (browsers == null) {
368
+ return undefined;
369
+ }
370
+ let cached = cache.get(browsers);
371
+ if (cached != null) {
372
+ return cached;
373
+ }
374
+ let targets = browserslistToTargets((0, _browserslist().default)(browsers));
375
+ cache.set(browsers, targets);
376
+ return targets;
377
+ }
378
+ function convertLoc(loc) {
379
+ return {
380
+ filePath: loc.filePath,
381
+ start: {
382
+ line: loc.start.line,
383
+ column: loc.start.column
384
+ },
385
+ end: {
386
+ line: loc.end.line,
387
+ column: loc.end.column + 1
388
+ }
389
+ };
390
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/transformer-css",
3
- "version": "2.0.0-beta.3.1",
3
+ "version": "2.0.0-dev.1510+a9bb85adf",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,16 +17,22 @@
17
17
  "source": "src/CSSTransformer.js",
18
18
  "engines": {
19
19
  "node": ">= 12.0.0",
20
- "parcel": "^2.0.0-beta.1"
20
+ "parcel": "^2.0.0-dev.1508+a9bb85adf"
21
21
  },
22
22
  "dependencies": {
23
- "@parcel/plugin": "2.0.0-beta.3.1",
24
- "@parcel/source-map": "2.0.0-rc.1.0",
25
- "@parcel/utils": "2.0.0-beta.3.1",
26
- "nullthrows": "^1.1.1",
27
- "postcss": "^8.2.1",
28
- "postcss-value-parser": "^4.1.0",
29
- "semver": "^5.4.1"
23
+ "@parcel/diagnostic": "2.0.0-dev.1510+a9bb85adf",
24
+ "@parcel/plugin": "2.0.0-dev.1510+a9bb85adf",
25
+ "@parcel/source-map": "^2.1.1",
26
+ "@parcel/utils": "2.0.0-dev.1510+a9bb85adf",
27
+ "browserslist": "^4.6.6",
28
+ "lightningcss": "^1.22.1",
29
+ "nullthrows": "^1.1.1"
30
30
  },
31
- "gitHead": "daece49d003ba804bbdaa3a7ed3d6aaf446f166d"
31
+ "devDependencies": {
32
+ "lightningcss-wasm": "^1.22.1"
33
+ },
34
+ "browser": {
35
+ "lightningcss": "lightningcss-wasm"
36
+ },
37
+ "gitHead": "a9bb85adf8f3b38631e178b3aacaa30c78696e36"
32
38
  }
@@ -1,219 +1,390 @@
1
- // @flow
1
+ // @flow strict-local
2
2
 
3
- import type {Root} from 'postcss';
4
- import type {FilePath} from '@parcel/types';
3
+ import type {SourceLocation} from '@parcel/types';
5
4
 
5
+ import path from 'path';
6
6
  import SourceMap from '@parcel/source-map';
7
7
  import {Transformer} from '@parcel/plugin';
8
8
  import {
9
- createDependencyLocation,
10
- isURL,
11
9
  remapSourceLocation,
10
+ relativePath,
11
+ globToRegex,
12
+ normalizeSeparators,
12
13
  } from '@parcel/utils';
13
- import postcss from 'postcss';
14
+ import {type SourceLocation as LightningSourceLocation} from 'lightningcss';
15
+ import * as native from 'lightningcss';
16
+ import browserslist from 'browserslist';
14
17
  import nullthrows from 'nullthrows';
15
- import valueParser from 'postcss-value-parser';
16
- import semver from 'semver';
18
+ import ThrowableDiagnostic, {errorToDiagnostic} from '@parcel/diagnostic';
17
19
 
18
- const URL_RE = /url\s*\("?(?![a-z]+:)/;
19
- const IMPORT_RE = /@import/;
20
-
21
- function canHaveDependencies(filePath: FilePath, code: string) {
22
- return !/\.css$/.test(filePath) || IMPORT_RE.test(code) || URL_RE.test(code);
23
- }
20
+ const {transform, transformStyleAttribute, browserslistToTargets} = native;
24
21
 
25
22
  export default (new Transformer({
26
- canReuseAST({ast}) {
27
- return ast.type === 'postcss' && semver.satisfies(ast.version, '^8.2.1');
28
- },
29
-
30
- async parse({asset}) {
31
- // This is set by other transformers (e.g. Stylus) to indicate that it has already processed
32
- // all dependencies, and that the CSS transformer can skip this asset completely. This is
33
- // required because when stylus processes e.g. url() it replaces them with a dependency id
34
- // to be filled in later. When the CSS transformer runs, it would pick that up and try to
35
- // resolve a dependency for the id which obviously doesn't exist. Also, it's faster to do
36
- // it this way since the resulting CSS doesn't need to be re-parsed.
37
- if (asset.meta.hasDependencies === false) {
38
- return null;
23
+ async loadConfig({config, options}) {
24
+ let conf = await config.getConfigFrom(options.projectRoot + '/index', [], {
25
+ packageKey: '@parcel/transformer-css',
26
+ });
27
+ let contents = conf?.contents;
28
+ if (typeof contents?.cssModules?.include === 'string') {
29
+ contents.cssModules.include = [globToRegex(contents.cssModules.include)];
30
+ } else if (Array.isArray(contents?.cssModules?.include)) {
31
+ contents.cssModules.include = contents.cssModules.include.map(include =>
32
+ typeof include === 'string' ? globToRegex(include) : include,
33
+ );
39
34
  }
40
-
41
- let code = await asset.getCode();
42
- if (code != null && !canHaveDependencies(asset.filePath, code)) {
43
- return null;
35
+ if (typeof contents?.cssModules?.exclude === 'string') {
36
+ contents.cssModules.exclude = [globToRegex(contents.cssModules.exclude)];
37
+ } else if (Array.isArray(contents?.cssModules?.exclude)) {
38
+ contents.cssModules.exclude = contents.cssModules.exclude.map(exclude =>
39
+ typeof exclude === 'string' ? globToRegex(exclude) : exclude,
40
+ );
44
41
  }
45
-
46
- return {
47
- type: 'postcss',
48
- version: '8.2.1',
49
- program: postcss
50
- .parse(code, {
51
- from: asset.filePath,
52
- })
53
- .toJSON(),
54
- };
42
+ return contents;
55
43
  },
56
-
57
- async transform({asset}) {
44
+ async transform({asset, config, options, logger}) {
58
45
  // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.
59
46
  // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.
47
+ let env = asset.env;
60
48
  asset.setEnvironment({
61
49
  context: 'browser',
62
50
  engines: {
63
51
  browsers: asset.env.engines.browsers,
64
52
  },
65
53
  shouldOptimize: asset.env.shouldOptimize,
54
+ shouldScopeHoist: asset.env.shouldScopeHoist,
66
55
  sourceMap: asset.env.sourceMap,
67
56
  });
68
57
 
69
- // When this asset is an bundle entry, allow that bundle to be split to load shared assets separately.
70
- // Only set here if it is null to allow previous transformers to override this behavior.
71
- if (asset.isSplittable == null) {
72
- asset.isSplittable = true;
73
- }
58
+ let [code, originalMap] = await Promise.all([
59
+ asset.getBuffer(),
60
+ asset.getMap(),
61
+ // $FlowFixMe native.default is the init function only when bundled for the browser build
62
+ process.browser && native.default(),
63
+ ]);
74
64
 
75
- // Check for `hasDependencies` being false here as well, as it's possible
76
- // another transformer (such as PostCSSTransformer) has already parsed an
77
- // ast and CSSTransformer's parse was never called.
78
- let ast = await asset.getAST();
79
- if (!ast || asset.meta.hasDependencies === false) {
80
- return [asset];
65
+ let targets = getTargets(asset.env.engines.browsers);
66
+ let res;
67
+ try {
68
+ if (asset.meta.type === 'attr') {
69
+ res = transformStyleAttribute({
70
+ code,
71
+ analyzeDependencies: true,
72
+ errorRecovery: config?.errorRecovery || false,
73
+ targets,
74
+ });
75
+ } else {
76
+ let cssModules = false;
77
+ if (
78
+ asset.meta.type !== 'tag' &&
79
+ asset.meta.cssModulesCompiled == null
80
+ ) {
81
+ let cssModulesConfig = config?.cssModules;
82
+ let isCSSModule = /\.module\./.test(asset.filePath);
83
+ if (asset.isSource) {
84
+ let projectRootPath = path.relative(
85
+ options.projectRoot,
86
+ asset.filePath,
87
+ );
88
+ if (typeof cssModulesConfig === 'boolean') {
89
+ isCSSModule = true;
90
+ } else if (cssModulesConfig?.include) {
91
+ isCSSModule = cssModulesConfig.include.some(include =>
92
+ include.test(projectRootPath),
93
+ );
94
+ } else if (cssModulesConfig?.global) {
95
+ isCSSModule = true;
96
+ }
97
+
98
+ if (
99
+ cssModulesConfig?.exclude?.some(exclude =>
100
+ exclude.test(projectRootPath),
101
+ )
102
+ ) {
103
+ isCSSModule = false;
104
+ }
105
+ }
106
+
107
+ if (isCSSModule) {
108
+ if (cssModulesConfig?.dashedIdents && !asset.isSource) {
109
+ cssModulesConfig.dashedIdents = false;
110
+ }
111
+
112
+ cssModules = cssModulesConfig ?? true;
113
+ }
114
+ }
115
+
116
+ res = transform({
117
+ filename: normalizeSeparators(
118
+ path.relative(options.projectRoot, asset.filePath),
119
+ ),
120
+ code,
121
+ cssModules,
122
+ analyzeDependencies:
123
+ asset.meta.hasDependencies !== false
124
+ ? {
125
+ preserveImports: true,
126
+ }
127
+ : false,
128
+ sourceMap: !!asset.env.sourceMap,
129
+ drafts: config?.drafts,
130
+ pseudoClasses: config?.pseudoClasses,
131
+ errorRecovery: config?.errorRecovery || false,
132
+ targets,
133
+ });
134
+ }
135
+ } catch (err) {
136
+ err.filePath = asset.filePath;
137
+ let diagnostic = errorToDiagnostic(err, {
138
+ origin: '@parcel/transformer-css',
139
+ });
140
+ if (err.data?.type === 'AmbiguousUrlInCustomProperty' && err.data.url) {
141
+ let p =
142
+ '/' +
143
+ relativePath(
144
+ options.projectRoot,
145
+ path.resolve(path.dirname(asset.filePath), err.data.url),
146
+ false,
147
+ );
148
+ diagnostic[0].hints = [`Replace with: url(${p})`];
149
+ diagnostic[0].documentationURL =
150
+ 'https://parceljs.org/languages/css/#url()';
151
+ }
152
+
153
+ throw new ThrowableDiagnostic({
154
+ diagnostic,
155
+ });
81
156
  }
82
157
 
83
- let program: Root = postcss.fromJSON(ast.program);
84
- let originalSourceMap = await asset.getMap();
85
- let createLoc = (start, specifier, lineOffset, colOffset) => {
86
- let loc = createDependencyLocation(
87
- start,
88
- specifier,
89
- lineOffset,
90
- colOffset,
91
- );
92
- if (originalSourceMap) {
93
- loc = remapSourceLocation(loc, originalSourceMap);
158
+ if (res.warnings) {
159
+ for (let warning of res.warnings) {
160
+ logger.warn({
161
+ message: warning.message,
162
+ codeFrames: [
163
+ {
164
+ filePath: asset.filePath,
165
+ codeHighlights: [
166
+ {
167
+ start: {
168
+ line: warning.loc.line,
169
+ column: warning.loc.column + 1,
170
+ },
171
+ end: {
172
+ line: warning.loc.line,
173
+ column: warning.loc.column + 1,
174
+ },
175
+ },
176
+ ],
177
+ },
178
+ ],
179
+ });
94
180
  }
95
- return loc;
96
- };
97
-
98
- let isDirty = false;
99
- program.walkAtRules('import', rule => {
100
- let params = valueParser(rule.params);
101
- let [name, ...media] = params.nodes;
102
- let moduleSpecifier;
103
- if (
104
- name.type === 'function' &&
105
- name.value === 'url' &&
106
- name.nodes.length
107
- ) {
108
- name = name.nodes[0];
181
+ }
182
+
183
+ if (res.map != null) {
184
+ let vlqMap = JSON.parse(Buffer.from(res.map).toString());
185
+ let map = new SourceMap(options.projectRoot);
186
+ map.addVLQMap(vlqMap);
187
+
188
+ if (originalMap) {
189
+ map.extends(originalMap);
109
190
  }
110
191
 
111
- moduleSpecifier = name.value;
192
+ asset.setMap(map);
193
+ }
112
194
 
113
- if (!moduleSpecifier) {
114
- throw new Error('Could not find import name for ' + String(rule));
195
+ if (res.dependencies) {
196
+ for (let dep of res.dependencies) {
197
+ let loc = convertLoc(dep.loc);
198
+ if (originalMap) {
199
+ loc = remapSourceLocation(loc, originalMap);
200
+ }
201
+
202
+ if (dep.type === 'import' && !res.exports) {
203
+ asset.addDependency({
204
+ specifier: dep.url,
205
+ specifierType: 'url',
206
+ loc,
207
+ packageConditions: ['style'],
208
+ meta: {
209
+ // For the glob resolver to distinguish between `@import` and other URL dependencies.
210
+ isCSSImport: true,
211
+ media: dep.media,
212
+ placeholder: dep.placeholder,
213
+ },
214
+ });
215
+ } else if (dep.type === 'url') {
216
+ asset.addURLDependency(dep.url, {
217
+ loc,
218
+ meta: {
219
+ placeholder: dep.placeholder,
220
+ },
221
+ });
222
+ }
115
223
  }
224
+ }
116
225
 
117
- if (isURL(moduleSpecifier)) {
118
- name.value = asset.addURLDependency(moduleSpecifier, {
119
- loc: createLoc(nullthrows(rule.source.start), asset.filePath, 0, 8),
120
- });
121
- } else {
122
- // If this came from an inline <style> tag, don't inline the imported file. Replace with the correct URL instead.
123
- // TODO: run CSSPackager on inline style tags.
124
- // let inlineHTML =
125
- // this.options.rendition && this.options.rendition.inlineHTML;
126
- // if (inlineHTML) {
127
- // name.value = asset.addURLDependency(dep, {loc: rule.source.start});
128
- // rule.params = params.toString();
129
- // } else {
130
- media = valueParser.stringify(media).trim();
131
- let dep = {
132
- moduleSpecifier,
133
- // Offset by 8 as it does not include `@import `
134
- loc: createLoc(nullthrows(rule.source.start), moduleSpecifier, 0, 8),
135
- meta: {
136
- media,
137
- },
138
- };
139
- asset.addDependency(dep);
140
- rule.remove();
141
- // }
226
+ let assets = [asset];
227
+ let buffer = Buffer.from(res.code);
228
+
229
+ if (res.exports != null) {
230
+ let exports = res.exports;
231
+ asset.symbols.ensure();
232
+ asset.symbols.set('default', 'default');
233
+
234
+ let dependencies = new Map();
235
+ let locals = new Map();
236
+ let c = 0;
237
+ let depjs = '';
238
+ let js = '';
239
+ let cssImports = '';
240
+
241
+ let jsDeps = [];
242
+
243
+ for (let key in exports) {
244
+ locals.set(exports[key].name, key);
142
245
  }
143
- isDirty = true;
144
- });
145
246
 
146
- program.walkDecls(decl => {
147
- if (URL_RE.test(decl.value)) {
148
- let parsed = valueParser(decl.value);
149
- let isDeclDirty = false;
150
-
151
- parsed.walk(node => {
152
- if (
153
- node.type === 'function' &&
154
- node.value === 'url' &&
155
- node.nodes.length > 0 &&
156
- !node.nodes[0].value.startsWith('#') // IE's `behavior: url(#default#VML)`
157
- ) {
158
- let url = asset.addURLDependency(node.nodes[0].value, {
159
- loc: createLoc(
160
- nullthrows(decl.source.start),
161
- node.nodes[0].value,
162
- 0,
163
- node.nodes[0].sourceIndex,
164
- ),
247
+ asset.uniqueKey ??= asset.id;
248
+
249
+ let seen = new Set();
250
+ let add = key => {
251
+ if (seen.has(key)) {
252
+ return;
253
+ }
254
+ seen.add(key);
255
+
256
+ let e = exports[key];
257
+ let s = `module.exports[${JSON.stringify(key)}] = \`${e.name}`;
258
+
259
+ for (let ref of e.composes) {
260
+ s += ' ';
261
+ if (ref.type === 'local') {
262
+ let exported = nullthrows(locals.get(ref.name));
263
+ add(exported);
264
+ s += '${' + `module.exports[${JSON.stringify(exported)}]` + '}';
265
+ asset.addDependency({
266
+ specifier: nullthrows(asset.uniqueKey),
267
+ specifierType: 'esm',
268
+ symbols: new Map([
269
+ [exported, {local: ref.name, isWeak: false, loc: null}],
270
+ ]),
165
271
  });
166
- isDeclDirty = node.nodes[0].value !== url;
167
- node.nodes[0].value = url;
272
+ } else if (ref.type === 'global') {
273
+ s += ref.name;
274
+ } else if (ref.type === 'dependency') {
275
+ let d = dependencies.get(ref.specifier);
276
+ if (d == null) {
277
+ d = `dep_${c++}`;
278
+ depjs += `import * as ${d} from ${JSON.stringify(
279
+ ref.specifier,
280
+ )};\n`;
281
+ dependencies.set(ref.specifier, d);
282
+ cssImports += `@import "${ref.specifier}";\n`;
283
+ asset.addDependency({
284
+ specifier: ref.specifier,
285
+ specifierType: 'esm',
286
+ packageConditions: ['style'],
287
+ });
288
+ }
289
+ s += '${' + `${d}[${JSON.stringify(ref.name)}]` + '}';
168
290
  }
169
- });
291
+ }
170
292
 
171
- if (isDeclDirty) {
172
- decl.value = parsed.toString();
173
- isDirty = true;
293
+ s += '`;\n';
294
+
295
+ // If the export is referenced internally (e.g. used @keyframes), add a self-reference
296
+ // to the JS so the symbol is retained during tree-shaking.
297
+ if (e.isReferenced) {
298
+ s += `module.exports[${JSON.stringify(key)}];\n`;
299
+ asset.addDependency({
300
+ specifier: nullthrows(asset.uniqueKey),
301
+ specifierType: 'esm',
302
+ symbols: new Map([
303
+ [key, {local: exports[key].name, isWeak: false, loc: null}],
304
+ ]),
305
+ });
174
306
  }
307
+
308
+ js += s;
309
+ };
310
+
311
+ // It's possible that the exports can be ordered differently between builds.
312
+ // Sorting by key is safe as the order is irrelevant but needs to be deterministic.
313
+ for (let key of Object.keys(exports).sort()) {
314
+ asset.symbols.set(key, exports[key].name);
315
+ add(key);
175
316
  }
176
- });
177
317
 
178
- if (isDirty) {
179
- asset.setAST({
180
- ...ast,
181
- program: program.toJSON(),
182
- });
183
- }
318
+ if (res.dependencies) {
319
+ for (let dep of res.dependencies) {
320
+ if (dep.type === 'import') {
321
+ // TODO: Figure out how to treeshake this
322
+ let d = `dep_$${c++}`;
323
+ depjs += `import * as ${d} from ${JSON.stringify(dep.url)};\n`;
324
+ js += `for (let key in ${d}) { if (key in module.exports) module.exports[key] += ' ' + ${d}[key]; else module.exports[key] = ${d}[key]; }\n`;
325
+ asset.symbols.set('*', '*');
326
+ }
327
+ }
328
+ }
184
329
 
185
- return [asset];
186
- },
330
+ if (res.references != null) {
331
+ let references = res.references;
332
+ for (let symbol in references) {
333
+ let reference = references[symbol];
334
+ asset.addDependency({
335
+ specifier: reference.specifier,
336
+ specifierType: 'esm',
337
+ packageConditions: ['style'],
338
+ symbols: new Map([
339
+ [reference.name, {local: symbol, isWeak: false, loc: null}],
340
+ ]),
341
+ });
187
342
 
188
- async generate({asset, ast, options}) {
189
- let result = await postcss().process(postcss.fromJSON(ast.program), {
190
- from: undefined,
191
- to: options.projectRoot + '/index',
192
- map: {
193
- annotation: false,
194
- inline: false,
195
- sourcesContent: false,
196
- },
197
- // Pass postcss's own stringifier to it to silence its warning
198
- // as we don't want to perform any transformations -- only generate
199
- stringifier: postcss.stringify,
200
- });
343
+ asset.meta.hasReferences = true;
344
+ cssImports += `@import "${reference.specifier}";\n`;
345
+ }
346
+ }
347
+
348
+ assets.push({
349
+ type: 'js',
350
+ content: depjs + js,
351
+ dependencies: jsDeps,
352
+ env,
353
+ });
201
354
 
202
- let map = null;
203
- let originalSourceMap = await asset.getMap();
204
- if (result.map != null) {
205
- map = new SourceMap(options.projectRoot);
206
- map.addVLQMap(result.map.toJSON());
207
- if (originalSourceMap) {
208
- map.extends(originalSourceMap.toBuffer());
355
+ // Prepend @import rules for each composes dependency so packager knows where to insert them.
356
+ if (cssImports.length > 0) {
357
+ buffer = Buffer.concat([Buffer.from(cssImports), buffer]);
209
358
  }
210
- } else {
211
- map = originalSourceMap;
212
359
  }
213
360
 
214
- return {
215
- content: result.css,
216
- map,
217
- };
361
+ asset.setBuffer(buffer);
362
+ return assets;
218
363
  },
219
364
  }): Transformer);
365
+
366
+ let cache = new Map();
367
+
368
+ function getTargets(browsers) {
369
+ if (browsers == null) {
370
+ return undefined;
371
+ }
372
+
373
+ let cached = cache.get(browsers);
374
+ if (cached != null) {
375
+ return cached;
376
+ }
377
+
378
+ let targets = browserslistToTargets(browserslist(browsers));
379
+
380
+ cache.set(browsers, targets);
381
+ return targets;
382
+ }
383
+
384
+ function convertLoc(loc: LightningSourceLocation): SourceLocation {
385
+ return {
386
+ filePath: loc.filePath,
387
+ start: {line: loc.start.line, column: loc.start.column},
388
+ end: {line: loc.end.line, column: loc.end.column + 1},
389
+ };
390
+ }