index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. let babel;
  2. try {
  3. babel = require("@babel/core");
  4. } catch (err) {
  5. if (err.code === "MODULE_NOT_FOUND") {
  6. err.message += "\n babel-loader@9 requires Babel 7.12+ (the package '@babel/core'). " + "If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7'.";
  7. }
  8. throw err;
  9. }
  10. // Since we've got the reverse bridge package at @babel/core@6.x, give
  11. // people useful feedback if they try to use it alongside babel-loader.
  12. if (/^6\./.test(babel.version)) {
  13. throw new Error("\n babel-loader@9 will not work with the '@babel/core@6' bridge package. " + "If you want to use Babel 6.x, install 'babel-loader@7'.");
  14. }
  15. const {
  16. version
  17. } = require("../package.json");
  18. const cache = require("./cache");
  19. const transform = require("./transform");
  20. const injectCaller = require("./injectCaller");
  21. const schema = require("./schema");
  22. const {
  23. isAbsolute
  24. } = require("path");
  25. const {
  26. promisify
  27. } = require("util");
  28. function subscribe(subscriber, metadata, context) {
  29. if (context[subscriber]) {
  30. context[subscriber](metadata);
  31. }
  32. }
  33. module.exports = makeLoader();
  34. module.exports.custom = makeLoader;
  35. function makeLoader(callback) {
  36. const overrides = callback ? callback(babel) : undefined;
  37. return function (source, inputSourceMap) {
  38. // Make the loader async
  39. const callback = this.async();
  40. loader.call(this, source, inputSourceMap, overrides).then(args => callback(null, ...args), err => callback(err));
  41. };
  42. }
  43. async function loader(source, inputSourceMap, overrides) {
  44. const filename = this.resourcePath;
  45. const logger = this.getLogger("babel-loader");
  46. let loaderOptions = this.getOptions(schema);
  47. if (loaderOptions.customize != null) {
  48. if (!isAbsolute(loaderOptions.customize)) {
  49. 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.");
  50. }
  51. if (overrides) {
  52. throw new Error("babel-loader's 'customize' option is not available when already " + "using a customized babel-loader wrapper.");
  53. }
  54. logger.debug(`loading customize override: '${loaderOptions.customize}'`);
  55. let override = require(loaderOptions.customize);
  56. if (override.__esModule) override = override.default;
  57. if (typeof override !== "function") {
  58. throw new Error("Custom overrides must be functions.");
  59. }
  60. logger.debug("applying customize override to @babel/core");
  61. overrides = override(babel);
  62. }
  63. let customOptions;
  64. if (overrides && overrides.customOptions) {
  65. logger.debug("applying overrides customOptions() to loader options");
  66. const result = await overrides.customOptions.call(this, loaderOptions, {
  67. source,
  68. map: inputSourceMap
  69. });
  70. customOptions = result.custom;
  71. loaderOptions = result.loader;
  72. }
  73. // Deprecation handling
  74. if ("forceEnv" in loaderOptions) {
  75. this.emitWarning(new Error("The option `forceEnv` has been removed in favor of `envName` in Babel 7."));
  76. }
  77. if (typeof loaderOptions.babelrc === "string") {
  78. 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"));
  79. }
  80. logger.debug("normalizing loader options");
  81. // Standardize on 'sourceMaps' as the key passed through to Webpack, so that
  82. // users may safely use either one alongside our default use of
  83. // 'this.sourceMap' below without getting error about conflicting aliases.
  84. if (Object.prototype.hasOwnProperty.call(loaderOptions, "sourceMap") && !Object.prototype.hasOwnProperty.call(loaderOptions, "sourceMaps")) {
  85. loaderOptions = Object.assign({}, loaderOptions, {
  86. sourceMaps: loaderOptions.sourceMap
  87. });
  88. delete loaderOptions.sourceMap;
  89. }
  90. const programmaticOptions = Object.assign({}, loaderOptions, {
  91. filename,
  92. inputSourceMap: inputSourceMap || loaderOptions.inputSourceMap,
  93. // Set the default sourcemap behavior based on Webpack's mapping flag,
  94. // but allow users to override if they want.
  95. sourceMaps: loaderOptions.sourceMaps === undefined ? this.sourceMap : loaderOptions.sourceMaps,
  96. // Ensure that Webpack will get a full absolute path in the sourcemap
  97. // so that it can properly map the module back to its internal cached
  98. // modules.
  99. sourceFileName: filename
  100. });
  101. // Remove loader related options
  102. delete programmaticOptions.customize;
  103. delete programmaticOptions.cacheDirectory;
  104. delete programmaticOptions.cacheIdentifier;
  105. delete programmaticOptions.cacheCompression;
  106. delete programmaticOptions.metadataSubscribers;
  107. logger.debug("resolving Babel configs");
  108. const config = await babel.loadPartialConfigAsync(injectCaller(programmaticOptions, this.target));
  109. if (config) {
  110. let options = config.options;
  111. if (overrides && overrides.config) {
  112. logger.debug("applying overrides config() to Babel config");
  113. options = await overrides.config.call(this, config, {
  114. source,
  115. map: inputSourceMap,
  116. customOptions
  117. });
  118. }
  119. if (options.sourceMaps === "inline") {
  120. // Babel has this weird behavior where if you set "inline", we
  121. // inline the sourcemap, and set 'result.map = null'. This results
  122. // in bad behavior from Babel since the maps get put into the code,
  123. // which Webpack does not expect, and because the map we return to
  124. // Webpack is null, which is also bad. To avoid that, we override the
  125. // behavior here so "inline" just behaves like 'true'.
  126. options.sourceMaps = true;
  127. }
  128. const {
  129. cacheDirectory = null,
  130. cacheIdentifier = "core" + transform.version + "," + "loader" + version,
  131. cacheCompression = true,
  132. metadataSubscribers = []
  133. } = loaderOptions;
  134. let result;
  135. if (cacheDirectory) {
  136. logger.debug("cache is enabled");
  137. const getFileTimestamp = promisify((path, cb) => {
  138. this._compilation.fileSystemInfo.getFileTimestamp(path, cb);
  139. });
  140. const hash = this.utils.createHash(this._compilation.outputOptions.hashFunction);
  141. result = await cache({
  142. source,
  143. options,
  144. transform,
  145. cacheDirectory,
  146. cacheIdentifier,
  147. cacheCompression,
  148. hash,
  149. getFileTimestamp,
  150. logger
  151. });
  152. } else {
  153. logger.debug("cache is disabled, applying Babel transform");
  154. result = await transform(source, options);
  155. }
  156. config.files.forEach(configFile => {
  157. this.addDependency(configFile);
  158. logger.debug(`added '${configFile}' to webpack dependencies`);
  159. });
  160. if (result) {
  161. if (overrides && overrides.result) {
  162. logger.debug("applying overrides result() to Babel transform results");
  163. result = await overrides.result.call(this, result, {
  164. source,
  165. map: inputSourceMap,
  166. customOptions,
  167. config,
  168. options
  169. });
  170. }
  171. const {
  172. code,
  173. map,
  174. metadata,
  175. externalDependencies
  176. } = result;
  177. externalDependencies?.forEach(([dep]) => {
  178. this.addDependency(dep);
  179. logger.debug(`added '${dep}' to webpack dependencies`);
  180. });
  181. metadataSubscribers.forEach(subscriber => {
  182. subscribe(subscriber, metadata, this);
  183. logger.debug(`invoked metadata subscriber '${String(subscriber)}'`);
  184. });
  185. return [code, map];
  186. }
  187. }
  188. // If the file was ignored, pass through the original content.
  189. return [source, inputSourceMap];
  190. }