From ebedae17bb514779691f9c1cf84118c6eddd4d96 Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Thu, 17 May 2018 10:50:08 -0700 Subject: [PATCH] Expose API to allow implementing custom loaders around babel-loader. (#619) --- README.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 49 ++++++++++++++++++++++++++------- 2 files changed, 115 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e1a7a5eb..09a1b466 100644 --- a/README.md +++ b/README.md @@ -205,4 +205,80 @@ In the case one of your dependencies is installing `babel` and you cannot uninst } ``` +## Customized Loader + +`babel-loader` exposes a loader-builder utility that allows users to add custom handling +of Babel's configuration for each file that it processes. + +`.custom` accepts a callback that will be called with the loader's instance of +`babel` so that tooling can ensure that it using exactly the same `@babel/core` +instance as the loader itself. + +### Example + +```js +module.exports = require("babel-loader").custom(babel => { + function myPlugin() { + return { + visitor: {}, + }; + } + + return { + // Passed the loader options. + customOptions({ opt1, opt2, ...loader }) { + return { + // Pull out any custom options that the loader might have. + custom: { opt1, opt2 }, + + // Pass the options back with the two custom options removed. + loader, + }; + }, + + // Passed Babel's 'PartialConfig' object. + config(cfg) { + if (cfg.hasFilesystemConfig()) { + // Use the normal config + return cfg.options; + } + + return { + ...cfg.options, + plugins: [ + ...(cfg.options.plugins || []), + + // Include a custom plugin in the options. + myPlugin, + ], + }; + }, + + result(result) { + return { + ...result, + code: result.code + "\n// Generated by some custom loader", + }; + }, + }; +}); +``` + +### `customOptions(options: Object): { custom: Object, loader: Object }` + +Given the loader's options, split custom options out of `babel-loader`'s +options. + + +### `config(cfg: PartialConfig): Object` + +Given Babel's `PartialConfig` object, return the `options` object that should +be passed to `babel.transform`. + + +### `result(result: Result): Result` + +Given Babel's result object, allow loaders to make additional tweaks to it. + + ## [License](http://couto.mit-license.org/) diff --git a/src/index.js b/src/index.js index b743905b..353fd4c8 100644 --- a/src/index.js +++ b/src/index.js @@ -14,19 +14,33 @@ function subscribe(subscriber, metadata, context) { } } -module.exports = function(source, inputSourceMap) { - // Make the loader async - const callback = this.async(); +module.exports = makeLoader(); +module.exports.custom = makeLoader; - loader - .call(this, source, inputSourceMap) - .then(args => callback(null, ...args), err => callback(err)); -}; +function makeLoader(callback) { + const overrides = callback ? callback(babel) : undefined; -async function loader(source, inputSourceMap) { + return function(source, inputSourceMap) { + // Make the loader async + const callback = this.async(); + + loader + .call(this, source, inputSourceMap, overrides) + .then(args => callback(null, ...args), err => callback(err)); + }; +} + +async function loader(source, inputSourceMap, overrides) { const filename = this.resourcePath; - const loaderOptions = loaderUtils.getOptions(this) || {}; + let loaderOptions = loaderUtils.getOptions(this) || {}; + + let customOptions; + if (overrides && overrides.customOptions) { + const result = await overrides.customOptions.call(this, loaderOptions); + customOptions = result.custom; + loaderOptions = result.loader; + } // Deprecation handling if ("forceEnv" in loaderOptions) { @@ -73,7 +87,13 @@ async function loader(source, inputSourceMap) { const config = babel.loadPartialConfig(programmaticOptions); if (config) { - const options = config.options; + let options = config.options; + if (overrides && overrides.config) { + options = await overrides.config.call(this, config, { + source, + customOptions, + }); + } const { cacheDirectory = null, @@ -105,6 +125,15 @@ async function loader(source, inputSourceMap) { } if (result) { + if (overrides && overrides.result) { + result = await overrides.result.call(this, result, { + source, + customOptions, + config, + options, + }); + } + const { code, map, metadata } = result; metadataSubscribers.forEach(subscriber => {