Skip to content
This repository has been archived by the owner on Oct 17, 2020. It is now read-only.

How to write your own loader

Greg Hurrell edited this page Oct 16, 2020 · 3 revisions

⚠️ The contents of this wiki have been migrated to the liferay/liferay-frontend-projects monorepo and more specifically to the to the maintenance/projects/js-toolkit/docs directory. Development and updates will continue there, and this repo will be archived (ie. switched to read-only mode).


Since #303 the bundler supports webpack like loaders.

Though the mechanism is inspired in webpack, the differences between webpack's and Liferay bundler's models makes loaders' API incompatible so you cannot reuse webpack loaders with our bundler or vice versa.

What is a loader

A loader is a npm package that has a main module which exports just a default function with the following signature:

function(context, options) {
}

The first argument context is an object which contains the following fields:

  • content: a string with the contents of the processed file (it can be considered the main input of the loader).
  • filePath: the project-relative path of the file to which loader is being applied.
  • extraArtifacts: an object with project-relative paths as keys and strings as values of properties which may be used to output extra files in addition to the one being processed (it can be useful to generate source maps, for example).
  • log: a logger to dump execution information that will be written to the bundler's report file (see the PluginLogger class for information on its structure and API).

The second argument is an object which is directly taken from the options field of the loader's configuration (see How to configure rules and loaders ).

To finish with, the function may return nothing or a modified content. If something is returned, it will be copied on top of the context.content field and used to feed the next loader or write the final output file.

That means that returning 'something' or setting context.content = 'something' is fully equivalent.

However, there's one exotic case in which you cannot use the return syntax and need to explicitly assign to context.content. That's when you want no file to be generated at all. In that case you need to do context.content = undefined because returning undefined cannot be distinguished from no return at all. This in only useful if you want to make a loader that filters files preventing them from being generated, for example.

A loader example

So, let's say you want to write a loader that runs babel on .js files.

Because babel needs configuration, you may define that you want a rule configuration like:

{
  "rules": [
    {
      "test": "\\.js$",
      "exclude": "node_modules",
      "use": [
        {
          "loader": "babel-loader",
          "options": {
            "presets": ["env", "react"]
          }
        }
      ]
    }
  ]
}

So that you can specify babel options in the loader configuration.

Then, your loader will need to take the input content, pass it through babel and write the result and the source map file to the output directory.

So, your loader function may look something like this:

export default function(context, options) {
  // Get input parameters
  const { content, filePath, log, sourceMap } = context;

  // Run babel on content
  const result = babel.transform(content, options);

  // Create an extra .map file with source map next to source .js file
  context.extraArtifacts[`${filePath}.map`] = JSON.stringify(result.map);

  // Tell the user what we have done
  log.info("babel-loader", "Transpiled file");

  // Return the modified content
  return result.code;
}

Then you can write that function to an index.js file, put it inside a npm package and publish it so that you can then configure its name in the rules section of the .npmbundlerrc file.

Existing loaders

You can see an enumeration of all known loaders in the List of loaders.