I. Webpack and Loader
Webpack aims to treat all dependent resources equally, smoothing over the processing differences between different types of resources:
Unlike most bundlers out there, the motivation behind Webpack is to gather all your dependencies (not just code, but other assets as well) and generate a dependency graph.
And the one responsible for smoothing over these differences is the Loader.
Webpack: No matter what you import, convert it to a JS Module for me, otherwise I'll act up (throw an error).
Loaders: Sure.
Configuration Structure
Entry
The entry point of the dependency graph.
entry: './src/index.js'
// Or multi-entry form
entry: {
main: './src/index.js'
}
Output
Output assets.
output: {
filename: 'main.js',
path: path.resolve('./build')
}
// Corresponding multi-entry form
output: {
filename: '[name].js',
path: path.resolve('./build')
}
Loaders
Dependency processors that intercept dependencies and perform preprocessing.
For example, in this scenario:
// index.js file
import helpers from '/helpers/main.js';
// Hey Webpack! I will need these styles:
import 'main.css';
Since Webpack doesn't recognize CSS (it cannot handle it directly), it needs to be processed by a Loader first (preprocessing).
Common Loader configuration:
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'less-loader' }
]
}
]
}
The application order of Loaders is less-loader, css-loader, style-loader.
P.S. In addition to specifying the relationship between Loaders and resource types through the configuration file, you can also add a loadername! prefix when importing resources, for example: import image from 'file-loader!./my-img.png'.
Plugins
When Loaders are insufficient, difficult to use, or unable to perform a task, use custom plugins for extension.
For example, extract-text-webpack-plugin is used to change the default behavior of style-loader where style rules are bundled, collecting CSS instead and referencing it as an external resource via the <link> tag:
var ExtractTextPlugin = require('extract-text-webpack-plugin');
plugins: [
new ExtractTextPlugin('main.css')
]
html-webpack-plugin is used to generate the entry HTML:
var HTMLWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HTMLWebpackPlugin()
]
The built-in DefinePlugin is used to distinguish between environments:
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(ENV)
})
The built-in UglifyJsPlugin is used to compress source code:
new webpack.optimize.UglifyJsPlugin()
Note that there is a similar-looking uglifyjs-webpack-plugin, which has slight version differences compared to the built-in UglifyJsPlugin; for details, see Is webpack.optimize.UglifyJsPlugin() UglifyJsPlugin() ?.
II. Loader
Loaders are mainly used to process non-JS resource dependencies:
webpack enables use of loaders to preprocess files. This allows you to bundle any static resource way beyond JavaScript.
Difference from Plugins
A Loader is only responsible for processing specific types of dependencies. "Processing" includes parsing, conversion, etc., turning things that Webpack doesn't recognize (various non-JS dependencies) into JS that can be bundled.
Plugins are more powerful; they can further process the output across Loaders and extend capabilities before, after, or during the bundling process.
Essence
A loader is a node module that exports a function. This function is called when a resource should be transformed by this loader. The given function will have access to the Loader API using the this context provided to it.
A Loader is a function used to transform dependent resources. This function can use the Loader API to access context information during the bundling process (such as the content of the target raw resource, the output of the previous loader, loader configuration options, etc.) and call internal Webpack methods (for example, processing secondary dependencies via this.resolve()).
Characteristics
-
Single responsibility
-
Composable
-
Stateless
Because Loaders are intended to be composable, they are required to have a single responsibility (allowing flexible configuration of processing steps) and be stateless (to avoid side effects affecting arbitrary combinations).
III. Chaining Loaders
Webpack 1 supports three ways to declare the application order of Loaders:
loaders: [
{ test: /\.scss$/, loader: 'style' },
{ test: /\.scss$/, loader: 'css' },
{
test: /\.scss$/, loader: 'autoprefixer',
query: { browsers: 'last 2 version' }
},
{
test: /\.scss$/, loader: 'sass',
query: { outputStyle: 'expanded' }
}
]
Or
loaders: [
{
test: /\.scss$/,
loaders: [
'style',
'css',
'autoprefixer?browsers=last 2 version',
'sass?outputStyle=expanded',
]
}
]
Or
loaders: [
{
test: /\.scss$/,
loader: 'style!css!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded',
},
]
Applied from right to left (opposite to the task definition order in Grunt/Gulp), equivalent to the function composition form style(css(file)), similar to a pipe. For example, last!second!first means:
The last Loader is applied first and receives the raw resource content.
The second Loader receives the return result of the previously executed Loader.
The first Loader is applied last and is required to return a JS Module and an optional source map.
Equivalent to echo $resource_content | first | second | last, taking the raw resource content as input and outputting a JS Module (CMD module or ES module), potentially flowing through 'n' Loaders in between.
In later versions, it was changed to:
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
{
loader: 'less-loader',
options: {
noIeCompat: true
}
}
]
However, for compatibility reasons, the query property is still retained and exists as an alias for options; for details, see UseEntry.
P.S. For more information about chaining loaders, please see Chaining loaders.
IV. Loader Example
For example, there is a scenario where comments in JSONC (JSON with JavaScript-style comments) need to be removed to load JSONC just like JSON, for example:
// settings.json
{
// Format a file on save. A formatter must be available, the file must not be auto-saved, and editor must not be shutting down.
"editor.formatOnSave": false
}
Default JSON dependency processing does not support comments:
Module build failed: SyntaxError: Unexpected token / in JSON at position 0
In this case, you can manually craft a simple loader (if you can't find an existing one):
import { getOptions } from 'loader-utils';
import { parse as parseJSONC, stripComments } from 'jsonc-parser';
export default function parse(source) {
const opts = getOptions(this);
let json = stripComments(source);
if (opts.extRule) {
const filePath = this.resourcePath;
const matched = opts.extRule.test(filePath);
if (!matched) {
let errors = [];
json = parseJSONC(source, errors, {
disallowComments: true
});
if (errors.length) {
throw new Error(`Unexpected COMMENTS at ${filePath}`);
}
}
}
return `export default ${json}`;
}
Using Microsoft's jsonc-parser to parse JSONC/JSON, it also supports a configuration option extRule for checking file extension rules:
{
test: /\.json$/,
use: {
loader: path.join(__dirname, './loaders/jsonc-loader.js'),
options: {
extRule: /.jsonc?$/i
}
}
}
P.S. In addition to synchronous Loaders, there are also callback-injected Asynchronous Loaders.
P.S. For more Loader APIs, please see The Loader Context.
V. Frequently Used Loaders
There are many classic combinations in actual application scenarios, such as:
-
style-loader!css-loader: Collects the CSS dependencies of the App and inserts them into the page via the<style>tag at runtime. -
file-loader: A way to generate multiple files (strangely, this task is handled by a Loader rather than the main configuration). -
file-loader!html-loader: Imports HTML, performs preprocessing such as template replacement, and then generates the output file.
The official documentation introduces seven categories of Loaders:
Files
-
raw-loader: Directly retrieves file content.
-
val-loader: Loads JS code, requiring the CMD module format.
-
url-loader: Similar to
file-loader, supporting data URL returns for small files. -
file-loader: Copies files to the
outputdirectory and returns a relative URL.
JSON
-
json-loader: Built-in by default, used to load JSON files.
-
json5-loader: Loads and transpiles JSON 5 files (ES5.1 JSON syntax).
-
cson-loader: Loads and transpiles CSON files.
Transpilation
-
script-loader: Executes JS files in the
globalenvironment (like ascripttag);requirecalls inside will not be transformed. -
babel-loader: Loads ES2015+ code and transpiles it to ES5 using Babel.
-
buble-loader: Loads ES2015+ code and transforms it to ES5 using Bublé.
-
traceur-loader: Loads ES2015+ code and transforms it to ES5 using Traceur.
-
ts-loader or awesome-typescript-loader: Loads TypeScript 2.0+ code.
-
coffee-loader: Loads CoffeeScript code.
Templates
-
html-loader: Exports HTML static resources referenced by
requireas strings. -
pug-loader: Loads Pug templates and returns a function.
-
jade-loader: Loads Jade templates and returns a function.
-
markdown-loader: Compiles Markdown into HTML.
-
react-markdown-loader: Compiles Markdown into React components using the
markdown-parseparser. -
posthtml-loader: Loads and transforms HTML files using PostHTML.
-
handlebars-loader: Compiles Handlebars into HTML.
-
markup-inline-loader: Embeds SVG/MathML file content into HTML; useful when using icon fonts or applying CSS animations to SVGs.
Styling
-
style-loader: Inserts module output into the DOM as a
style(tag). -
css-loader: Loads CSS files and returns CSS, supporting imports.
-
less-loader: Loads and compiles LESS files.
-
sass-loader: Loads and compiles SASS/SCSS files.
-
postcss-loader: Loads and transforms CSS/SSS files using PostCSS.
-
stylus-loader: Loads and compiles Stylus files.
Linting and Testing
-
mocha-loader: Performs tests in browser/Node.js environments using Mocha.
-
eslint-loader: A preloader that performs linting using ESLint.
-
jshint-loader: A preloader that performs linting using JSHint.
-
jscs-loader: A preloader that performs code style checking using JSCS.
-
coverjs-loader: A preloader that determines test coverage using CoverJS.
Frameworks
-
vue-loader: Loads and compiles Vue components.
-
polymer-loader: Processes HTML and CSS with configurable preprocessors, supporting
require()calls for Web Components just like regular modules. -
angular2-template-loader: Loads and compiles Angular components.
P.S. For more third-party Loaders, see awesome-webpack.
No comments yet. Be the first to share your thoughts.