Writing a webpack loader
, . Tagged
This article is the first in a series of articles about webpack. It will cover non-trivial configuration and extensions via custom loaders and plugins.
What are webpack loaders?
Webpack loaders are simple transformers that converts files, or node modules into other node modules.
You can think of it like commandline tools that you chain in a shell pipeline, like cat file | grep "pattern" | wc
-l
Yes I know, you don’t need to cat
into grep
.
For instance, let’s see how loaders are used for the following react component:
import React from 'react'; import logo from './logo.png'; const Logo = ({size}) => ( <a href="https://www.example.org"> <img src={logo} width={size} height={size}/> </a> ); Logo.defaultProps = { size: "100", }; export default Logo;
The line import logo from './logo.png'
has one main role for webpack: to indicate that our component’s file
depends on the png file. What webpack does of this dependency depends on its configuration.
If Webpack does not know about the file, you may have an error message like this one:
ERROR in ./src/components/logo/logo.png 1:0 Module parse failed: Unexpected character '' (1:0) You may need an appropriate loader to handle this file type. (Source code omitted for this binary file)
Otherwise, you may see logo.png
in your output folder, depending on the loader used.
A simple loader for Play i18n messages
In my work at Valwin we work on a stack based on playframework, with React for the frontend. The frontend is packaged with webpack.
Play has it’s own i18n module and translations that are used throughout the application, in the API and the emails
sent by the app.
The translations are stored in a messages
file, using the java MessageFormat
format.
event.name=Event event.name.tip=Enter the event's description. event.type=Type event.type.tip=Select the event type. event.place=Place event.place.tip=Enter the event's place. event.date=Date & time event.date.tip=Enter event's date and time. Format: aaaa-mm-dd hh:mm. event.noEvents=No events in sight... event.button.newEvent=New event event.form.updateEvent=Update event event.form.createEvent=Create a new event event.button.saveEvent=Save changes event.button.cancel=Cancel
In order to reuse these translations in the frontend, we have two options:
- Duplicate the translations in a json-y format, or
- Reuse the play translations directly.
The first option can be dangerous at times, because it introduces another source of truth (at least for the frontend). We can (and should) automate the synchronization between these two files, but we chose the second way, using a custom webpack loader to use the play translation directly.
A loader is a function that transforms the contents of a file, like in a bash pipeline.
For the first version of this loader, we do the following transformations:
- Split the input files by lines, and remove empty lines
- Extract the
key=value
for each line. - Output a CommonJS module that exports these
key=value
as a big object.
/** @format */ import _ from 'lodash/fp'; export default function(source) { const transformed = _.flow([ _.split('\n'), _.map(_.trim), _.filter(_.negate(_.isEmpty)), _.map(_.split('=')), _.map(_.take(2)), _.fromPairs, ])(source); return `module.exports = ${JSON.stringify(transformed)}`; }
This is a really simple loader. For more complicated messages, we could for instance use intl-messageformat for parsing messages with variables, and use another loader to interpolate the messages later in the chain.
For more examples, check out the [writing] guide.
Links
- [concept] Webpack loader concepts. https://webpack.js.org/concepts/loaders
- [writing] Writing a webpack loader. https://webpack.js.org/contribute/writing-a-loader