Loader is like a translator, it can convert the source file into a new output, and a file can be chained through multiple translators.

Take handling SCSS files as an example:

  1. SCSS source code will be first handed to Sass-Loader to convert SCSS to CSS;
  2. The CSS output by the Ass-Loader is handed to the CSS-Loader for processing, and the CSS is found to be dependent on resources and compressed.
  3. The CSS output by CSS-loader is handed to style-loader for processing and converted into JavaScript code loaded by scripts.

It can be seen that the preceding processes need to be executed in sequence, namely, sas-loader, CSS-loader, and style-loader. The Webpack configuration is as follows:

module.exports = {
  module: {
    rules: [{// Add support for SCSS files
        test: /\.scss/.// SCSS files are processed in the sequence of sass-loader, CSs-loader, and style-loader
        use: [
          'style-loader',
          {
            loader:'css-loader'.// Pass the configuration item to csS-loader
            options:{
              minimize:true,}},'sass-loader'],},]},};Copy the code

The duties of a Loader

As can be seen from the above example, a Loader has a single responsibility and only needs to complete one conversion. If a source file needs to go through a multi-step conversion to work properly, use multiple Loaders to convert it. When multiple Loaders are called to convert a file, each Loader will execute it in chained order. The first Loader will get the original content to be processed, and the results of the previous Loader will be passed to the next Loader for subsequent processing. The final Loader returns the processed final result to the Webpack.

So, when you develop a Loader, keep its responsibilities simple and you only care about the inputs and outputs.

Loader basis

Since Webpack runs on Node.js, a Loader is essentially a Node.js module that needs to export a function. The job of this exported function is to get the original content before processing, perform processing on the original content, and return the processed content.

A simple Loader source is as follows:

module.exports = function(source) {
  // source is the original content of a file passed to the Loader by the Compiler
  // This function returns the processed content. For simplicity, this function returns the original content
  return source;
};
Copy the code

Since Loader runs in Node.js, you can call any node.js API, or install a third-party module to call:

const sass = require('node-sass');
module.exports = function(source) {
  return sass(source);
};
Copy the code

Loader advanced

The above is just the simplest Loader. Webpack also provides some apis for Loader to call.

Get Loader options

In the top Webpack configuration that processes SCSS files, the options parameter is passed to csS-Loader to control csS-Loader. How to obtain the options passed by the user in the Loader written by myself? Need to do this:

const loaderUtils = require('loader-utils');
module.exports = function(source) {
  // Get the options passed by the user to the current Loader
  const options = loaderUtils.getOptions(this);
  return source;
};
Copy the code

Return other results

All of the above loaders only return the converted content, but some scenarios require returning something other than the content.

For example, using Babel-Loader to convert ES6 code, it also needs to output the corresponding Source Map of the converted ES5 code to facilitate debugging of the Source code. To return the Source Map to Webpack along with the ES5 code, write:

module.exports = function(source) {
  // This. Callback tells Webpack the result returned
  this.callback(null, source, sourceMaps);
  // When you use this.callback to return the content, the Loader must return undefined,
  // To let Webpack know that the result returned by the Loader is in this.callback, not return
  return;
};
Copy the code

The this.callback is an API injected by Webpack into Loader to facilitate communication between Loader and Webpack. This. Callback can be used as follows:

this.callback(
    // Returns an Error to Webpack when the original content cannot be converted
    err: Error | null.// The original content is converted to the content
    content: string | Buffer,
    // Use the Source Map to derive the converted content from the original content for easy debuggingsourceMap? : SourceMap,// If an AST syntax tree is generated for this conversion, you can return the AST,
    // The AST can be reused by the Loader that needs the AST to avoid repeated AST generation and improve performanceabstractSyntaxTree? : AST );Copy the code

Source Map generation is time consuming and is usually done in development environments and not in other environments to speed up builds. For this reason Webpack provides the this.sourceMap API to Loader to tell Loader whether the user needs the Source Map in the current build environment. If you’re writing a Loader that generates a Source Map, take this into account.

Synchronous and asynchronous

Loaders can be synchronous or asynchronous. The loaders described above are synchronous because their conversion processes are synchronous and the conversion results are returned after completion. But there are some scenarios where the transformation step can only be done asynchronously, such as when you need to make a network request to get the result, which would block the entire build and cause it to be very slow if you do it synchronously.

When the transition step is asynchronous, you can do this:

module.exports = function(source) {
    // tell Webpack that the conversion is asynchronous and Loader will callback the result in callback
    var callback = this.async();
    someAsyncOperation(source, function(err, result, sourceMaps, ast) {
        // Callback returns the result of asynchronous execution
        callback(err, result, sourceMaps, ast);
    });
};
Copy the code

Working with binary data

By default, Webpack passes the original content to Loader in UTF-8 format. However, in some scenarios, the Loader processes binary files instead of text files. For example, file-loader, Webpack needs to import binary data to the Loader. To do this, you need to write Loader like this:

module.exports = function(source) {
    // At exports.raw === true, the source that Webpack passes to Loader is of type Buffer
    source instanceof Buffer === true;
    // The type returned by the Loader can also be a Buffer
    / / in exports raw! If == true, the Loader can also return the result of type Buffer
    return source;
};
// Tells Webpack whether the Loader needs binary data via the exports.raw property
module.exports.raw = true;
Copy the code

The most critical line above is the last line of module.exports.raw = true; Loader can only get the string without the line.

Cache acceleration

In some cases, some transformations are computationally expensive and time-consuming, and if the transformations are repeated with each build, the build will be slow. For this reason, Webpack caches the results of all Loader processes by default, which means that the corresponding Loader is not re-invoked to perform the conversion operation if the file to be processed or its dependent file has not changed.

If you want Webpack not to cache the Loader’s results, you can do this:

module.exports = function(source) {
  // Disable the caching function of the Loader
  this.cacheable(false);
  return source;
};
Copy the code

Other Loader API

In addition to the Webpack API mentioned above that can be called in the Loader, there are the following common apis:

  • This. context: the directory where the current Loader is processing the file. If the current Loader is processing the file/SRC /main.js, this.context equals/SRC.

  • This.resource: The full request path to the file currently being processed, including queryString, such as/SRC /main.js? Name = 1.

  • This.resourcepath: the path to the current processing file, for example, / SRC /main.js.

  • This.resourcequery: QueryString for the file currently being processed.

  • This. target: is the target in the Webpack configuration. For details, see 2-7 Other configuration item – target.

  • LoadModule: this. LoadModule (Request: string, callback: this. LoadModule (request: string, callback: this. Function (err, source, sourceMap, module)) to get the result of the request file.

  • Resolve (context: string, request: string, callback: function(err, result: String)).

  • This.adddependency: Adds a dependency file to the current processing file so that Loader will be called to process the file again if the dependency file changes. AddDependency (file: string) is used.

  • Enclosing addContextDependency: similar to addDependency, but addContextDependency was added to the currently working with the entire directory file dependency. AddContextDependency (Directory: String) is used.

  • This.cleardependencies: Clears all dependencies of the file being processed using clearDependencies().

  • Enclosing emitFile: output to a file, use the method of emitFile (name: string, content: Buffer | string, sourceMap: {… }).

Other apis not mentioned can be found on the Webpack website.

Loading a local Loader

In the process of Loader development, in order to test whether the Loader written can work properly, the Loader may be invoked only after it is configured in Webpack. In the preceding sections, loaders are installed through Npm, and the Loader name is directly used to use Loader. The code is as follows:

module.exports = {
  module: {
    rules: [{test: /\.css/.use: ['style-loader'],},]},};Copy the code

Using a locally developed Loader in the same way is a hassle because you need to make sure that the source code for the Loader is in node_modules. To do this, you need to publish the Loader you wrote to the Npm repository and then install it to the local project.

There are two convenient ways to solve the above problems, which are as follows:

Npm link

Npm link is used to develop and debug local Npm modules. It can link the source code of a local module under development to the node_modules directory of the project without releasing the module, so that the project can directly use the local Npm module. Because it is implemented by soft link, the Npm module code is edited locally, and the edited code can be used in the project.

The steps to complete Npm Link are as follows:

  1. Ensure that the local Npm module under development (that is, the Loader under developmentpackage.jsonThe configuration is correct.
  2. Execute in the root directory of the local Npm modulenpm linkTo register local modules globally.
  3. Execute in the project root directorynpm link loader-nameLink the local Npm module registered globally in step 2 to the project’snode_moduelsBelow, among themloader-nameIs in step 1package.jsonThe module name configured in the file.

After linking the Loader to the project, you can use the local Loader as if it were a real Npm module.

ResolveLoader

ResolveLoader is used to configure how Webpack finds the Loader, as described in other configuration items 2-7. By default, only node_modules is searched, and resolveloader.modules needs to be modified in order for Webpack to load the Loader placed in the local project.

If the local Loader is in./loaders/loader-name in the project directory, the following configuration is required:

module.exports = {
  resolveLoader: {// Go to the directory to find Loader in order
    modules: ['node_modules'.'./loaders/'].}}Copy the code

After the above configuration is added, Webpack will first look for Loader in node_modules, and if it can’t find Loader, it will look for Loader in./loaders/.

In actual combat

The above mentioned many theories, the next from the actual, to write a Loader to solve the actual problem.

The Loader named comment-require-loader is used to add comment syntax to JavaScript code

// @require '.. /style/index.css'
Copy the code

Converted to

require('.. /style/index.css');
Copy the code

The Loader is used to correctly load JavaScript written for Fis3 that contains annotated loading dependent CSS files.

The usage of Loader is as follows:

module.exports = {
  module: {
    loaders: [{test: /\.js$/.loaders: ['comment-require-loader'].// For JavaScript files with fis3 CSS import syntax, use comment-require-loader to convert
        include: [path.resolve(__dirname, 'node_modules/imui']}]}};Copy the code

The Loader implementation is very simple, the complete code is as follows:

function replace(source) {
    // use the regex to call // @require '.. /style/index.css' convert to require('.. /style/index.css');
    return source.replace(/(\/\/ *@require) +(('|").+('|")).*/.'require($2); ');
}

module.exports = function (content) {
    return replace(content);
};
Copy the code

This example provides the complete code for the project

Easy to Understand Webpack online reading link

Read the original