Martin Kadlec
Martin Kadlec

Reputation: 4975

How to access compiler/compilation/module from loader context in Webpack 4

I am trying to write a custom webpack loader.

Webpack documentation mentions that access to the _compiler, _compilation and _module properties on loader context is deprecated (https://webpack.js.org/api/loaders/#this_compilation) and should be avoided.

Is there a newer API that would allow me to access them?

Upvotes: 4

Views: 789

Answers (2)

Martin Kadlec
Martin Kadlec

Reputation: 4975

For anyone trying to figure out the same thing. I don't think there is any replacement API. The idea probably is that a loader shouldn't really need access to these objects and if you do need them you should use a plugin instead (or in addition).

In case the properties will get removed in future, here is how you can "add them back" using a plugin:

// custom-plugin.js
const NormalModule = require('webpack/lib/NormalModule')

class CustomPlugin {
  apply (compiler) {
    compiler.hooks.compilation.tap('CustomPlugin', (compilation) => {
      NormalModule.getCompilationHooks(compilation).loader.tap('CustomPlugin', (loader, module) => {
        loader._customPlugin = {
          module: module,
          compiler: compiler,
          compilation: compilation,
        }
      })
    });
  }
}

module.exports = CustomPlugin
// custom-loader.js
module.exports = function (source) {
  console.log({
    module: this._customPlugin.module === this._module, // true
    compiler: this._customPlugin.compiler === this._compiler, // true
    compilation: this._customPlugin.compilation === this._compilation, // true
  });
  return source;
}

Upvotes: 2

killertofus
killertofus

Reputation: 423

After you loaded the dependencies, you can access the generated module's source:

// Index the modules generated in the child compiler by raw request.
let byRawRequest = new Map
for (let asset of compilation.modules)
  byRawRequest.set(asset.rawRequest, asset)

// Replace the template requests with the result from modules generated in
// the child compiler.
for (let {node, request} of this._getAssetRequests()) {
  if (!byRawRequest.has(request))
    continue

  const ASSET = byRawRequest.get(request)
  const SOURCE = ASSET.originalSource().source()
  const NEW_REQUEST = execAssetModule(SOURCE)
  setResourceRequest(node, NEW_REQUEST)

  log(`Changed: ${prettyFormat({from: request, to: NEW_REQUEST})}`)
}

And execute the module's source with a VM:

function execAssetModule(code, path) {
  let script = new Script(code)
  let exports = {}
  let sandbox = {
    __webpack_public_path__: '',
    module: {exports},
    exports,
  }
  script.runInNewContext(sandbox)
  return sandbox.module.exports
}

Upvotes: 0

Related Questions