babel-loader 9.2.0 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- > This README is for babel-loader v8/v9 with Babel v7
1
+ > This README is for babel-loader v8/v9/v10 with Babel v7
2
2
  > If you are using legacy Babel v6, see the [7.x branch](https://github.com/babel/babel-loader/tree/7.x) docs
3
3
 
4
4
  [![NPM Status](https://img.shields.io/npm/v/babel-loader.svg?style=flat)](https://www.npmjs.com/package/babel-loader)
@@ -6,7 +6,7 @@
6
6
 
7
7
  <div align="center">
8
8
  <a href="https://github.com/babel/babel">
9
- <img src="https://rawgit.com/babel/logo/master/babel.svg" alt="Babel logo" width="200" height="200">
9
+ <img src="https://raw.githubusercontent.com/babel/logo/master/babel.svg" alt="Babel logo" width="200" height="200">
10
10
  </a>
11
11
  <a href="https://github.com/webpack/webpack">
12
12
  <img src="https://webpack.js.org/assets/icon-square-big.svg" alt="webpack logo" width="200" height="200">
@@ -25,6 +25,7 @@ This package allows transpiling JavaScript files using [Babel](https://github.co
25
25
  > |:-:|:-:|:-:|:-:|
26
26
  > | 8.x | 4.x or 5.x | 7.x | >= 8.9 |
27
27
  > | 9.x | 5.x | ^7.12.0 | >= 14.15.0 |
28
+ > | 10.x | ^5.61.0 | ^7.12.0 \|\| ^8.0.0-alpha | ^18.20.0 \|\| ^20.10.0 \|\| >=22.0.0` |
28
29
 
29
30
 
30
31
  ```bash
@@ -46,8 +47,9 @@ module: {
46
47
  use: {
47
48
  loader: 'babel-loader',
48
49
  options: {
50
+ targets: "defaults",
49
51
  presets: [
50
- ['@babel/preset-env', { targets: "defaults" }]
52
+ ['@babel/preset-env']
51
53
  ]
52
54
  }
53
55
  }
@@ -71,10 +73,11 @@ module: {
71
73
  use: {
72
74
  loader: 'babel-loader',
73
75
  options: {
76
+ targets: "defaults",
74
77
  presets: [
75
- ['@babel/preset-env', { targets: "defaults" }]
78
+ ['@babel/preset-env']
76
79
  ],
77
- plugins: ['@babel/plugin-proposal-class-properties']
80
+ plugins: ['@babel/plugin-proposal-decorators', { version: "2023-11" }]
78
81
  }
79
82
  }
80
83
  }
@@ -88,20 +91,30 @@ This loader also supports the following loader-specific option:
88
91
 
89
92
  * `cacheDirectory`: Default `false`. When set, the given directory will be used to cache the results of the loader. Future webpack builds will attempt to read from the cache to avoid needing to run the potentially expensive Babel recompilation process on each run. If the value is set to `true` in options (`{cacheDirectory: true}`), the loader will use the default cache directory in `node_modules/.cache/babel-loader` or fallback to the default OS temporary file directory if no `node_modules` folder could be found in any root directory.
90
93
 
91
- * `cacheIdentifier`: Default is a string composed by
92
- - the `@babel/core`'s version and the `babel-loader`'s version
93
- - the [merged](https://babeljs.io/docs/configuration#how-babel-merges-config-items) [Babel config](https://babeljs.io/docs/config-files), including options passed to `babel-loader` and the contents of `babel.config.js` or `.babelrc` file if they exist
94
- - the value of the environment variable `BABEL_ENV` with a fallback to the `NODE_ENV` environment variable.
95
- This can be set to a custom value to force cache busting if the identifier changes.
94
+ * `cacheIdentifier`: Default is a string composed by the `@babel/core`'s version and the `babel-loader`'s version. The final cache id will be determined by the input file path, the [merged](https://babeljs.io/docs/configuration#how-babel-merges-config-items) Babel config via `Babel.loadPartialConfigAsync` and the `cacheIdentifier`. The merged Babel config will be determined by the `babel.config.js` or `.babelrc` file if they exist, or the value of the environment variable `BABEL_ENV` and `NODE_ENV`. `cacheIdentifier` can be set to a custom value to force cache busting if the identifier changes.
96
95
 
97
96
  * `cacheCompression`: Default `true`. When set, each Babel transform output will be compressed with Gzip. If you want to opt-out of cache compression, set it to `false` -- your project may benefit from this if it transpiles thousands of files.
98
97
 
99
98
  * `customize`: Default `null`. The path of a module that exports a `custom` callback [like the one that you'd pass to `.custom()`](#customized-loader). Since you already have to make a new file to use this, it is recommended that you instead use `.custom` to create a wrapper loader. Only use this if you _must_ continue using `babel-loader` directly, but still want to customize.
100
99
 
101
- * `metadataSubscribers`: Default `[]`. Takes an array of context function names. E.g. if you passed ['myMetadataPlugin'], you'd assign a subscriber function to `context.myMetadataPlugin` within your webpack plugin's hooks & that function will be called with `metadata`.
100
+ * `metadataSubscribers`: Default `[]`. Takes an array of context function names. E.g. if you passed ['myMetadataPlugin'], you'd assign a subscriber function to `context.myMetadataPlugin` within your webpack plugin's hooks & that function will be called with `metadata`. See [`./test/metadata.test.js`](./test/metadata.test.js) for an example.
102
101
 
103
102
  ## Troubleshooting
104
103
 
104
+ ### Enable debug mode logging
105
+
106
+ Specify the webpack option [`stats.loggingDebug`](https://webpack.js.org/configuration/stats/#statsloggingdebug) to output verbose debug logs.
107
+
108
+ ```js
109
+ // webpack.config.js
110
+ module.exports = {
111
+ // ...
112
+ stats: {
113
+ loggingDebug: ["babel-loader"]
114
+ }
115
+ }
116
+ ```
117
+
105
118
  ### babel-loader is slow!
106
119
 
107
120
  Make sure you are transforming as few files as possible. Because you are probably matching `/\.m?js$/`, you might be transforming the `node_modules` folder or other unwanted source.
@@ -291,10 +304,6 @@ For example, to change the environment targets passed to `@babel/preset-env` bas
291
304
 
292
305
  module.exports = api => {
293
306
  return {
294
- plugins: [
295
- "@babel/plugin-proposal-nullish-coalescing-operator",
296
- "@babel/plugin-proposal-optional-chaining"
297
- ],
298
307
  presets: [
299
308
  [
300
309
  "@babel/preset-env",
package/lib/cache.js CHANGED
@@ -10,7 +10,6 @@
10
10
  const os = require("os");
11
11
  const path = require("path");
12
12
  const zlib = require("zlib");
13
- const crypto = require("crypto");
14
13
  const {
15
14
  promisify
16
15
  } = require("util");
@@ -19,17 +18,15 @@ const {
19
18
  writeFile,
20
19
  mkdir
21
20
  } = require("fs/promises");
22
- const findCacheDirP = import("find-cache-dir");
21
+ const {
22
+ sync: findUpSync
23
+ } = require("find-up");
24
+ const {
25
+ env
26
+ } = process;
23
27
  const transform = require("./transform");
24
- // Lazily instantiated when needed
28
+ const serialize = require("./serialize");
25
29
  let defaultCacheDirectory = null;
26
- let hashType = "sha256";
27
- // use md5 hashing if sha256 is not available
28
- try {
29
- crypto.createHash(hashType);
30
- } catch {
31
- hashType = "md5";
32
- }
33
30
  const gunzip = promisify(zlib.gunzip);
34
31
  const gzip = promisify(zlib.gzip);
35
32
 
@@ -68,16 +65,38 @@ const write = async function (filename, compress, result) {
68
65
  *
69
66
  * @return {String}
70
67
  */
71
- const filename = function (source, identifier, options) {
72
- const hash = crypto.createHash(hashType);
73
- const contents = JSON.stringify({
74
- source,
75
- options,
76
- identifier
77
- });
78
- hash.update(contents);
68
+ const filename = function (source, identifier, options, hash) {
69
+ hash.update(serialize([options, source, identifier]));
79
70
  return hash.digest("hex") + ".json";
80
71
  };
72
+ const addTimestamps = async function (externalDependencies, getFileTimestamp) {
73
+ for (const depAndEmptyTimestamp of externalDependencies) {
74
+ try {
75
+ const [dep] = depAndEmptyTimestamp;
76
+ const {
77
+ timestamp
78
+ } = await getFileTimestamp(dep);
79
+ depAndEmptyTimestamp.push(timestamp);
80
+ } catch {
81
+ // ignore errors if timestamp is not available
82
+ }
83
+ }
84
+ };
85
+ const areExternalDependenciesModified = async function (externalDepsWithTimestamp, getFileTimestamp) {
86
+ for (const depAndTimestamp of externalDepsWithTimestamp) {
87
+ const [dep, timestamp] = depAndTimestamp;
88
+ let newTimestamp;
89
+ try {
90
+ newTimestamp = (await getFileTimestamp(dep)).timestamp;
91
+ } catch {
92
+ return true;
93
+ }
94
+ if (timestamp !== newTimestamp) {
95
+ return true;
96
+ }
97
+ }
98
+ return false;
99
+ };
81
100
 
82
101
  /**
83
102
  * Handle the cache
@@ -92,14 +111,21 @@ const handleCache = async function (directory, params) {
92
111
  cacheIdentifier,
93
112
  cacheDirectory,
94
113
  cacheCompression,
114
+ hash,
115
+ getFileTimestamp,
95
116
  logger
96
117
  } = params;
97
- const file = path.join(directory, filename(source, cacheIdentifier, options));
118
+ const file = path.join(directory, filename(source, cacheIdentifier, options, hash));
98
119
  try {
99
120
  // No errors mean that the file was previously cached
100
121
  // we just need to return it
101
122
  logger.debug(`reading cache file '${file}'`);
102
- return await read(file, cacheCompression);
123
+ const result = await read(file, cacheCompression);
124
+ if (!(await areExternalDependenciesModified(result.externalDependencies, getFileTimestamp))) {
125
+ logger.debug(`validated cache file '${file}'`);
126
+ return result;
127
+ }
128
+ logger.debug(`discarded cache file '${file}' due to changes in external dependencies`);
103
129
  } catch {
104
130
  // conitnue if cache can't be read
105
131
  logger.debug(`discarded cache as it can not be read`);
@@ -124,20 +150,16 @@ const handleCache = async function (directory, params) {
124
150
  // return it to the user asap and write it in cache
125
151
  logger.debug(`applying Babel transform`);
126
152
  const result = await transform(source, options);
127
-
128
- // Do not cache if there are external dependencies,
129
- // since they might change and we cannot control it.
130
- if (!result.externalDependencies.length) {
131
- try {
132
- logger.debug(`writing result to cache file '${file}'`);
133
- await write(file, cacheCompression, result);
134
- } catch (err) {
135
- if (fallback) {
136
- // Fallback to tmpdir if node_modules folder not writable
137
- return handleCache(os.tmpdir(), params);
138
- }
139
- throw err;
153
+ await addTimestamps(result.externalDependencies, getFileTimestamp);
154
+ try {
155
+ logger.debug(`writing result to cache file '${file}'`);
156
+ await write(file, cacheCompression, result);
157
+ } catch (err) {
158
+ if (fallback) {
159
+ // Fallback to tmpdir if node_modules folder not writable
160
+ return handleCache(os.tmpdir(), params);
140
161
  }
162
+ throw err;
141
163
  }
142
164
  return result;
143
165
  };
@@ -172,15 +194,18 @@ module.exports = async function (params) {
172
194
  if (typeof params.cacheDirectory === "string") {
173
195
  directory = params.cacheDirectory;
174
196
  } else {
175
- if (defaultCacheDirectory === null) {
176
- const {
177
- default: findCacheDir
178
- } = await findCacheDirP;
179
- defaultCacheDirectory = findCacheDir({
180
- name: "babel-loader"
181
- }) || os.tmpdir();
182
- }
197
+ defaultCacheDirectory ??= findCacheDir("babel-loader");
183
198
  directory = defaultCacheDirectory;
184
199
  }
185
200
  return await handleCache(directory, params);
186
- };
201
+ };
202
+ function findCacheDir(name) {
203
+ if (env.CACHE_DIR && !["true", "false", "1", "0"].includes(env.CACHE_DIR)) {
204
+ return path.join(env.CACHE_DIR, name);
205
+ }
206
+ const rootPkgJSONPath = path.dirname(findUpSync("package.json"));
207
+ if (rootPkgJSONPath) {
208
+ return path.join(rootPkgJSONPath, "node_modules", ".cache", name);
209
+ }
210
+ return os.tmpdir();
211
+ }
package/lib/index.js CHANGED
@@ -23,7 +23,9 @@ const schema = require("./schema");
23
23
  const {
24
24
  isAbsolute
25
25
  } = require("path");
26
- const validateOptions = require("schema-utils").validate;
26
+ const {
27
+ promisify
28
+ } = require("util");
27
29
  function subscribe(subscriber, metadata, context) {
28
30
  if (context[subscriber]) {
29
31
  context[subscriber](metadata);
@@ -42,14 +44,8 @@ function makeLoader(callback) {
42
44
  async function loader(source, inputSourceMap, overrides) {
43
45
  const filename = this.resourcePath;
44
46
  const logger = this.getLogger("babel-loader");
45
- let loaderOptions = this.getOptions();
46
- validateOptions(schema, loaderOptions, {
47
- name: "Babel loader"
48
- });
47
+ let loaderOptions = this.getOptions(schema);
49
48
  if (loaderOptions.customize != null) {
50
- if (typeof loaderOptions.customize !== "string") {
51
- throw new Error("Customized loaders must be implemented as standalone modules.");
52
- }
53
49
  if (!isAbsolute(loaderOptions.customize)) {
54
50
  throw new Error("Customized loaders must be passed as absolute paths, since " + "babel-loader has no way to know what they would be relative to.");
55
51
  }
@@ -78,10 +74,10 @@ async function loader(source, inputSourceMap, overrides) {
78
74
 
79
75
  // Deprecation handling
80
76
  if ("forceEnv" in loaderOptions) {
81
- console.warn("The option `forceEnv` has been removed in favor of `envName` in Babel 7.");
77
+ this.emitWarning(new Error("The option `forceEnv` has been removed in favor of `envName` in Babel 7."));
82
78
  }
83
79
  if (typeof loaderOptions.babelrc === "string") {
84
- console.warn("The option `babelrc` should not be set to a string anymore in the babel-loader config. " + "Please update your configuration and set `babelrc` to true or false.\n" + "If you want to specify a specific babel config file to inherit config from " + "please use the `extends` option.\nFor more information about this options see " + "https://babeljs.io/docs/core-packages/#options");
80
+ this.emitWarning(new Error("The option `babelrc` should not be set to a string anymore in the babel-loader config. " + "Please update your configuration and set `babelrc` to true or false.\n" + "If you want to specify a specific babel config file to inherit config from " + "please use the `extends` option.\nFor more information about this options see " + "https://babeljs.io/docs/#options"));
85
81
  }
86
82
  logger.debug("normalizing loader options");
87
83
  // Standardize on 'sourceMaps' as the key passed through to Webpack, so that
@@ -133,17 +129,17 @@ async function loader(source, inputSourceMap, overrides) {
133
129
  }
134
130
  const {
135
131
  cacheDirectory = null,
136
- cacheIdentifier = JSON.stringify({
137
- options,
138
- "@babel/core": transform.version,
139
- "@babel/loader": version
140
- }),
132
+ cacheIdentifier = "core" + transform.version + "," + "loader" + version,
141
133
  cacheCompression = true,
142
134
  metadataSubscribers = []
143
135
  } = loaderOptions;
144
136
  let result;
145
137
  if (cacheDirectory) {
146
138
  logger.debug("cache is enabled");
139
+ const getFileTimestamp = promisify((path, cb) => {
140
+ this._compilation.fileSystemInfo.getFileTimestamp(path, cb);
141
+ });
142
+ const hash = this.utils.createHash(this._compilation.outputOptions.hashFunction);
147
143
  result = await cache({
148
144
  source,
149
145
  options,
@@ -151,6 +147,8 @@ async function loader(source, inputSourceMap, overrides) {
151
147
  cacheDirectory,
152
148
  cacheIdentifier,
153
149
  cacheCompression,
150
+ hash,
151
+ getFileTimestamp,
154
152
  logger
155
153
  });
156
154
  } else {
@@ -178,7 +176,7 @@ async function loader(source, inputSourceMap, overrides) {
178
176
  metadata,
179
177
  externalDependencies
180
178
  } = result;
181
- externalDependencies?.forEach(dep => {
179
+ externalDependencies?.forEach(([dep]) => {
182
180
  this.addDependency(dep);
183
181
  logger.debug(`added '${dep}' to webpack dependencies`);
184
182
  });
package/lib/schema.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
+ "title": "Babel Loader options",
2
3
  "type": "object",
3
4
  "properties": {
4
5
  "cacheDirectory": {
5
- "oneOf": [
6
+ "anyOf": [
6
7
  {
7
8
  "type": "boolean"
8
9
  },
@@ -20,8 +21,18 @@
20
21
  "default": true
21
22
  },
22
23
  "customize": {
23
- "type": "string",
24
+ "anyOf": [
25
+ {
26
+ "type": "null"
27
+ },
28
+ {
29
+ "type": "string"
30
+ }
31
+ ],
24
32
  "default": null
33
+ },
34
+ "metadataSubscribers": {
35
+ "type": "array"
25
36
  }
26
37
  },
27
38
  "additionalProperties": true
@@ -0,0 +1,82 @@
1
+ var objToString = Object.prototype.toString;
2
+ var objKeys = Object.getOwnPropertyNames;
3
+
4
+ /**
5
+ * A custom Babel options serializer
6
+ *
7
+ * Intentional deviation from JSON.stringify:
8
+ * 1. Object properties are sorted before seralizing
9
+ * 2. The output is NOT a valid JSON: e.g.
10
+ * The output does not enquote strings, which means a JSON-like string '{"a":1}'
11
+ * will share the same result with an JS object { a: 1 }. This is not an issue
12
+ * for Babel options, but it can not be used for general serialization purpose
13
+ * 3. Only 20% slower than the native JSON.stringify on V8
14
+ *
15
+ * This function is a fork from https://github.com/nickyout/fast-stable-stringify
16
+ * @param {*} val Babel options
17
+ * @param {*} isArrayProp
18
+ * @returns serialized Babel options
19
+ */
20
+ function serialize(val, isArrayProp) {
21
+ var i, max, str, keys, key, propVal, toStr;
22
+ if (val === true) {
23
+ return "!0";
24
+ }
25
+ if (val === false) {
26
+ return "!1";
27
+ }
28
+ switch (typeof val) {
29
+ case "object":
30
+ if (val === null) {
31
+ return null;
32
+ } else if (val.toJSON && typeof val.toJSON === "function") {
33
+ return serialize(val.toJSON(), isArrayProp);
34
+ } else {
35
+ toStr = objToString.call(val);
36
+ if (toStr === "[object Array]") {
37
+ str = "[";
38
+ max = val.length - 1;
39
+ for (i = 0; i < max; i++) {
40
+ str += serialize(val[i], true) + ",";
41
+ }
42
+ if (max > -1) {
43
+ str += serialize(val[i], true);
44
+ }
45
+ return str + "]";
46
+ } else if (toStr === "[object Object]") {
47
+ // only object is left
48
+ keys = objKeys(val).sort();
49
+ max = keys.length;
50
+ str = "{";
51
+ i = 0;
52
+ while (i < max) {
53
+ key = keys[i];
54
+ propVal = serialize(val[key], false);
55
+ if (propVal !== undefined) {
56
+ if (str) {
57
+ str += ",";
58
+ }
59
+ str += '"' + key + '":' + propVal;
60
+ }
61
+ i++;
62
+ }
63
+ return str + "}";
64
+ } else {
65
+ return JSON.stringify(val);
66
+ }
67
+ }
68
+ case "function":
69
+ case "undefined":
70
+ return isArrayProp ? null : undefined;
71
+ case "string":
72
+ return val;
73
+ default:
74
+ return isFinite(val) ? val : null;
75
+ }
76
+ }
77
+ module.exports = function (val) {
78
+ var returnVal = serialize(val, false);
79
+ if (returnVal !== undefined) {
80
+ return "" + returnVal;
81
+ }
82
+ };
package/lib/transform.js CHANGED
@@ -15,7 +15,7 @@ module.exports = async function (source, options) {
15
15
 
16
16
  // We don't return the full result here because some entries are not
17
17
  // really serializable. For a full list of properties see here:
18
- // https://github.com/babel/babel/blob/main/packages/babel-core/src/transformation/index.js
18
+ // https://github.com/babel/babel/blob/main/packages/babel-core/src/transformation/index.ts
19
19
  // For discussion on this topic see here:
20
20
  // https://github.com/babel/babel-loader/pull/629
21
21
  const {
@@ -36,7 +36,7 @@ module.exports = async function (source, options) {
36
36
  metadata,
37
37
  sourceType,
38
38
  // Convert it from a Set to an Array to make it JSON-serializable.
39
- externalDependencies: Array.from(externalDependencies || [])
39
+ externalDependencies: Array.from(externalDependencies || [], dep => [dep]).sort()
40
40
  };
41
41
  };
42
42
  module.exports.version = babel.version;
package/package.json CHANGED
@@ -1,38 +1,35 @@
1
1
  {
2
2
  "name": "babel-loader",
3
- "version": "9.2.0",
3
+ "version": "10.0.0",
4
4
  "description": "babel module loader for webpack",
5
5
  "files": [
6
6
  "lib"
7
7
  ],
8
8
  "main": "lib/index.js",
9
9
  "engines": {
10
- "node": ">= 14.15.0"
10
+ "node": "^18.20.0 || ^20.10.0 || >=22.0.0"
11
11
  },
12
12
  "dependencies": {
13
- "find-cache-dir": "^4.0.0",
14
- "schema-utils": "^4.0.0"
13
+ "find-up": "^5.0.0"
15
14
  },
16
15
  "peerDependencies": {
17
16
  "@babel/core": "^7.12.0",
18
- "webpack": ">=5"
17
+ "webpack": ">=5.61.0"
19
18
  },
20
19
  "devDependencies": {
21
- "@ava/babel": "^1.0.1",
22
20
  "@babel/cli": "^7.23.0",
23
21
  "@babel/core": "^7.23.3",
24
22
  "@babel/eslint-parser": "^7.23.3",
25
23
  "@babel/preset-env": "^7.23.3",
26
- "ava": "^3.13.0",
27
- "c8": "^8.0.0",
24
+ "c8": "^10.1.2",
28
25
  "eslint": "^9.6.0",
29
26
  "eslint-config-prettier": "^9.1.0",
30
27
  "eslint-plugin-prettier": "^5.1.3",
31
28
  "globals": "^15.8.0",
32
- "husky": "^8.0.3",
33
- "lint-staged": "^13.2.3",
29
+ "husky": "^9.1.5",
30
+ "lint-staged": "^15.2.9",
34
31
  "prettier": "^3.0.0",
35
- "webpack": "^5.89.0"
32
+ "webpack": "^5.93.0"
36
33
  },
37
34
  "scripts": {
38
35
  "clean": "node ./scripts/rimraf.mjs lib",
@@ -43,7 +40,7 @@
43
40
  "prepublish": "yarn run clean && yarn run build",
44
41
  "preversion": "yarn run test",
45
42
  "test": "yarn run lint && yarn run build --source-maps && c8 yarn run test-only",
46
- "test-only": "ava"
43
+ "test-only": "node --test test/**/*.test.js"
47
44
  },
48
45
  "resolutions": {
49
46
  "minipass": "6.0.2"
@@ -78,18 +75,6 @@
78
75
  "sourceMap": false,
79
76
  "instrument": false
80
77
  },
81
- "ava": {
82
- "files": [
83
- "test/**/*.test.js",
84
- "!test/fixtures/**/*",
85
- "!test/helpers/**/*"
86
- ],
87
- "babel": {
88
- "compileAsTests": [
89
- "test/helpers/**/*"
90
- ]
91
- }
92
- },
93
78
  "lint-staged": {
94
79
  "scripts/*.js": [
95
80
  "prettier --trailing-comma es5 --write",