CC 4.0 License

The content of this section is derived from the content of the following links and is subject to the CC BY 4.0 license.

The following contents can be assumed to be the result of modifications and deletions based on the original contents if not specifically stated.

Loader types

Rspack supports multiple loader types, including sync loader, async loader, ESM loader, Raw loader, and Pitching loader.

The following sections provide some basic examples of the different types of loaders.

Sync loader

Sync loaders are the most basic type of loader. They can synchronously return transformed content using either a return statement or the this.callback method:

sync-loader.js
module.exports = function (content, map, meta) {
  return someSyncOperation(content);
};

Compared to the return statement, the this.callback method offers more flexibility as it allows passing multiple parameters, including error information, source maps, and metadata:

sync-loader-with-callback.js
module.exports = function (content, map, meta) {
  this.callback(null, someSyncOperation(content), map, meta);

  // Return undefined when calling callback() to avoid return value conflicts
  return;
};
INFO
  • The map and meta parameters are optional, see this.callback for more details.
  • Rspack will internally convert loaders to async, regardless of whether it's a synchronous loader, for technical and performance reasons.

Async loader

When you need to perform async operations (such as file I/O, network requests, etc.), you should use an async loader. Call the this.async() method to get a callback function, informing Rspack that this loader requires async processing.

The callback of an async loader can also pass multiple parameters, including transformed content, source maps, and metadata:

async-loader.js
module.exports = function (content, map, meta) {
  // Get the async callback function
  const callback = this.async();

  // Perform async operation
  someAsyncOperation(content, function (err, result) {
    // Handle error case
    if (err) return callback(err);

    // Return the processing result on success
    callback(null, result, map, meta);
  });
};

ESM loader

Rspack supports ESM loaders, you can write loaders using ESM syntax and export the loader function using export default.

When writing ESM loaders, the file name needs to end with .mjs, or set type to module in the nearest package.json.

my-loader.mjs
export default function loader(content, map, meta) {
  // ...
}

If you need to export options like raw or pitch, you can use named exports:

my-loader.mjs
export default function loader(content) {
  // ...
}

// Set raw loader
export const raw = true;

// Add pitch function
export function pitch(remainingRequest, precedingRequest, data) {
  // ...
}
TIP

ESM loader and CommonJS loader have the same functionality, but use different module syntax. You can choose the format based on your project needs.

Write with TypeScript

If you write Rspack loader using TypeScript, you can import LoaderContext to add types to the loader:

my-loader.ts
import type { LoaderContext } from '@rspack/core';

// Declare the type of loader options
type MyLoaderOptions = {
  foo: string;
};

export default function myLoader(
  this: LoaderContext<MyLoaderOptions>,
  source: string,
) {
  const options = this.getOptions();
  console.log(options); // { foo: 'bar' }
  return source;
}

Raw loader

By default, Rspack converts file content into UTF-8 strings before passing it to loaders for processing. However, when handling binary files (such as images, audio, or font files), we need to work directly with the raw binary data rather than string representations.

By exporting raw: true in the loader file, a loader can receive the original Buffer object as input instead of a string.

  • CJS:
raw-loader.js
module.exports = function (content) {
  // Process binary content
  // Here content is a Buffer instance
  const processed = someBufferOperation(content);

  // Return the processed result
  return processed;
};

// Mark as Raw Loader
module.exports.raw = true;
  • ESM:
raw-loader.mjs
export default function loader(content) {
  // ...
}

export const raw = true;

When multiple loaders are chained together, each loader can choose to receive and pass processing results as either strings or Buffers. Rspack automatically handles the conversion between Buffer and string between different loaders, ensuring data is correctly passed to the next loader.

Raw Loaders are particularly useful in scenarios involving image compression, binary resource transformation, file encoding, etc. For example, when developing a loader to process images, direct manipulation of binary data is typically required to properly handle image formats.

Pitching loader

In Rspack's loader execution process, the default exported loader function is always called from right to left (called normal stage). However, sometimes a loader may only care about the request's metadata rather than the processing result of the previous loader. To address this need, Rspack provides a pitching stage — a special stage that each loader can define before its normal execution.

Contrary to normal execution, the pitch method exported in the loader file is called from left to right, before any loader's default function executes. This bidirectional processing mechanism provides developers with more flexible resource handling options.

For example, with the following configuration:

rspack.config.mjs
export default {
  //...
  module: {
    rules: [
      {
        //...
        use: ['a-loader', 'b-loader', 'c-loader'],
      },
    ],
  },
};

These steps would occur:

|- a-loader `pitch` |- b-loader `pitch` |- c-loader `pitch` |- requested module is picked up as a dependency |- c-loader normal execution |- b-loader normal execution |- a-loader normal execution

Normally, if it the loader is simple enough which only exports the normal stage hook:

module.exports = function (source) {};

Then, the pitching stage will be skipped.

So why might a loader take advantage of the pitching stage?

First, the data passed to the pitch method is exposed in the execution stage as well under this.data and could be useful for capturing and sharing information from earlier in the cycle.

module.exports = function (content) {
  return someSyncOperation(content, this.data.value);
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  data.value = 42;
};

Second, if a loader delivers a result in the pitch method, the process turns around and skips the remaining loaders. In our example above, if the b-loaders pitch method returned something:

module.exports = function (content) {
  return someSyncOperation(content);
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  if (someCondition()) {
    return (
      'module.exports = require(' +
      JSON.stringify('-!' + remainingRequest) +
      ');'
    );
  }
};

The steps above would be shortened to:

|- a-loader `pitch` |- b-loader `pitch` returns a module |- a-loader normal execution

For a real world example, style-loader leverages the second advantage to dispatch requests. Please visit style-loader for details.