preface
This is the sequel to WebPack 1.
This long article is to learn cheng Liufeng teacher opened “play webpack” column practice notes, and column is not the same, my actual combat source code is based on Webpack5, its configuration and source code implementation and Webpack4 have many different places, interested students can combine with me in the above released source code warehouse for learning, I believe there will be no small harvest.
After reading this long article, you will learn:
- Flexible configuration of WebPack according to project requirements.
- Understand the principles of Tree Shaking, Scope, etc.
- It can package multi-page application, component library and base library, SSR, etc.
- Write a maintainable Webpack configuration, control code quality with unit tests, smoke tests, and so on, and use Travis for continuous integration.
- Know how to optimize build speed and build resource volume.
- Master webpack packaging principle through source code
- Write loader and plugin.
I put the source code in my repository, which can be read against the document. In addition, due to the word limit, I can only be divided into two articles. For a better reading experience, you can go to my blog. Source address: play webpack
Webpack builds speed and volume optimization strategies
Primary analysis: Use STATS
In webpack5, you can get a sense of how each phase of the build is handled, how much time is spent, and how much cache is used.
module.exports = {
stats: 'verbose'.// Output all information. Normal: standard information. Errors-only: outputs information only when errors occur
};
Copy the code
Stats. json is generated in the root directory, containing the build information.
{
"scripts": {
"analyze:stats": "webpack --config config/webpack.prod.js --json stats.json"}}Copy the code
Speed analysis: usespeed-measure-webpack-plugin
This plugin is no longer used in webpack5 and can be replaced with the built-in stats.
Function:
- Analyze the total packaging time
- Time consumption per plug-in and loader
yarn add speed-measure-webpack-plugin -D
Copy the code
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();
const webpackConfig = smp.wrap({
plugins: [new MyPlugin(), new MyOtherPlugin()],
});
Copy the code
Volume analysis: Webpack-bundle-Analyzer
Analysis:
- Size of dependency
- Size of business component code
yarn add webpack-bundle-analyzer
Copy the code
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [new BundleAnalyzerPlugin()],
};
Copy the code
http://127.0.0.1:8888/ will be automatically opened after the execution, as shown in the following figure:
Use later versions of WebPack and NodeJS
The webpack4 and NodeJS versions are better than before:
- Optimizations brought by V8: for of instead of forEach; Replaces the Object Map/Set. Includes replaces indexOf.
- Md5 → MD4 algorithm.
- Use string methods instead of regular expressions.
Main optimizations and features of webpack5:
- Persistent caching. You can set up temporary memory-based caches and persistent file system-based caches.
- Once enabled, caching Settings for other plug-ins are ignored.
- Tree Shaking
- Added export trace for nested modules to find module properties that are nested in the innermost layer and not used.
- Added static analysis of CJS module code.
- The Webpack 5 build output is much richer and more complete, and gives a good picture of how the build phases are processed, time consumed, and cache usage.
- Module Federation has been added to change the running process of microfront-end builds.
- Runtime Modules are optimized for the production code.
- Optimized work queues for processing modules.
- Added the stage option to the lifecycle.
Multi-process/multi-instance build
Options:
- thread-loader
- parallel-webpack
- Some plug-in built-in parallel parameter (such as TerserWebpackPlugin CssMinimizerWebpackPlugin HtmlMinimizerWebpackPlugin)
- HappyPack (author no longer maintains)
thread-loader
How it works: Each time webPack parses a module, Thread-Loader assigns it and its dependencies to worker threads.
module.exports = {
module: {
rules: [{test: /\.jsx? $/,
use: [
{
loader: 'thread-loader'.options: {
workder: 3,}},'babel-loader'.'eslint-loader',],},],},};Copy the code
Parallel compression
Plug-ins that can be configured for parallel compression:
- terser-webpack-plugin
- css-minimizer-webpack-plugin
- html-minimizer-webpack-plugin
module.exports = {
optimization: {
minimize: true.minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin({ parallel: 2 }),
'... ',].}};Copy the code
Further subcontracting: Precompiled resource modules (DLLS)
Review the previous ideas of subcontracting:
Use the SplitChunkPlugin to split react, React – DOM and other base libraries into separate chunks.
The disadvantage is that the base package is still parsed and compiled each time it is packaged. A better way is to precompile the resource module via DLLPlugin, DllReferencePlugin.
Precompile the resource module
React, react-dom, redux, react-Redux, react-Redux, react-redux, react-redux, react-redux, react-redux, react-redux, react-redux, react-redux, react-redux, etc.
Method: The DLLPlugin is used for subcontracting, and the DllReferencePlugin references manifest.json.
First define a config/webpack.dll. Js to separate the base libraries:
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'production'.entry: {
library: ['react'.'react-dom'],},output: {
filename: '[name]_[chunkhash].dll.js'.path: path.resolve(__dirname, '.. /build/library'),
library: '[name]',},plugins: [
new webpack.DllPlugin({
context: __dirname,
name: '[name]_[hash]'.path: path.join(__dirname, '.. /build/library/[name].json'),})]};Copy the code
Then introduce the precompiled resource module in webpack.common.js:
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('.. /build/library/library.json'),
scope: 'xyz'.sourceType: 'commonjs2',})]};Copy the code
Make full use of cache to improve secondary build speed
Objective: To improve the speed of secondary construction.
Cache idea:
- Webpack5’s built-in memory based temporary cache and file system-based persistent cache.
- Cache – loader.
- Terser-webpack-plugin enables caching.
File system-based persistent caching, node_modules generates.cache:
module.exports = {
cache: {
type: 'filesystem'.// memory Temporary memory based cache
// cacheDirectory: path.resolve(__dirname, '.temp_cache'),}};Copy the code
Narrow your Build goals
Purpose: Reduce the number of modules to parse.
- Babel-loader does not parse node_modules
Reduce file search scope:
- Resolve. modules Reduces the module search level by specifying the current node_modules.
- Resovle. mainFields Specifies the entry file.
- Resolve. Extension Specifies the file suffix algorithm to resolve for references that do not have a specified suffix.
- Use aliases wisely, referencing the built version of a three-party dependency.
module.exports = {
resolve: {
alias: {
react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'),},modules: [path.resolve(__dirname, './node_modules')].extensions: ['.js'.'.jsx'.'.json'].mainFields: ['main'],}};Copy the code
Tree Shaking Erases useless CSS
We’ve already seen how to use Tree Shaking to wipe out useless js, which is already built into webpack5. This section explains how to wipe out useless CSS.
- PurifyCSS: Iterates through the code to identify the CSS classes that have been used.
- Uncss: HTML needs to be loaded via jsDOM, all styles are parsed through PostCSS, and document.querySelector identifies non-existing selectors in HTML files.
Using PurifyCSS in webpack:
- Using purgecss webpack — the plugin
- Use with mini-CSS-extract-plugin
const PurgeCSSPlugin = require('purgecss-webpack-plugin');
const glob = require('glob');
const PATHS = { src: path.resolve('.. /src')};module.exports = {
plugins: [
new PurgeCSSPlugin({
paths: glob.sync(`${PATHS.src}/ * * / * `, { nodir: true})}),]};Copy the code
Image compression
yarn add image-minimizer-webpack-plugin
Copy the code
Lossless compression is recommended using the following dependencies:
yarn add imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo
Copy the code
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
module.exports = {
plugins: [
new ImageMinimizerPlugin({
minimizerOptions: {
plugins: [['jpegtran', { progressive: true}]],},}),],};Copy the code
Use the dynamic Polyfill service
For volume optimization.
Polyfill-service: only return the required polyfill to the User. Some domestic browsers may not be able to recognize the User Agent, so an elegant degradation scheme can be adopted.
Principle of polyfill-service: identify User Agent, deliver different polyfills, and load the required polyfills as required.
polyfill.io
Volume optimization strategy summary
- Scope Hoisting
- Tree Shaking
- Separation of common resources
- SplitChunks
- Precompile the resource module
- Image compression
- Dynamic Polyfill
Master webpack packaging principle through source code
Webpack startup process analysis
Get started: Start with the Webpack command line.
- Run Webpack through NPM Scripts
- Development environment: NPM run dev
- Production environment: NPM Run build
- Run directly from WebPack
- webpack entry.js bundle.js
What happens to this process?
After executing the above command, NPM tells the command line to look for webpack.js in node_modules/.bin, execute if it exists, and throw an error if it doesn’t.
. Under the bin directory of the file is actually a soft link, webpack. Js really point to the file is a node_modules/webpack/bin/webpack. Js.
Analyze the entry file for Webpack: webpack.js
Recognize a few key functions:
- RunCommand -> Run the command
- IsInstalled -> checks whether a package isInstalled
- RunCli -> Run webpack-cli
Execution process:
- Check whether webpack-CLI exists.
- If it does not exist, an exception is thrown. If it does, go straight to step 6.
- Determine whether the package manager in use is YARN/NPM/PNPM.
- Automatically install webpack-cil (runCommand ->
yarn webpack-cli -D
) - Run runCli after the installation is successful
- Run runCli (run
webpack-cli/bin/cli.js
)
Summary: Webpack eventually finds the webpack-cli package and executes webpack-cli.
Webpack – CLI source to read
Webpack – CLI things to do:
- Introduce Commander. Js to customize the command line.
- Analyze command line parameters and convert them to compile configuration items.
- Reference WebPack, compile and build from the generated configuration items.
Command line toolkitCommander.js
introduce
Complete nodeJS command-line solution.
- Provides command and grouping parameters.
- Dynamically generate help information.
Specific execution process:
- Follow up with the webpack-CLI implementation from the previous section, that is, execute
node_modules/webpack-cli/bin/cli.js
. - Check if there are
webpack
If so, executerunCli
What it does is basically instantiatenode_modules/webpack-cli/webpack-cli.js
And then call itsrun
Methods. - use
Commander.js
Customize command line arguments. - Parses command line arguments, or if they are built-in
createCompiler
, the main thing you want to do is pass the parameters you get towebpack
To generate instantiated objectscompiler
.
Summary: Webpack-CLI converts command line parameters, eventually generates configuration item parameter Options, passes the options to the Webpack object, and executes the build process (finally determines whether there are listening parameters, if so, executes the listening action).
Tapable plug-in architecture with Hooks design
The nature of WebPack: WebPack can be thought of as an event-based (publish-subscribe) programming paradigm, with a series of plug-ins running.
Compiler and Compilation both inherit from Tapable, so what is Tapable?
Tapable is a nodejs-like EventEmitter library that controls publishing and subscribing of hook functions and the plugin system for WebPack.
- Tapable exposes a number of Hook classes that provide hooks for plug-ins to mount.
Tapable hooks type:
Tapable provides methods for binding hooks synchronously and asynchronously, and both have methods for binding events and executing events.
Implement a Car class with hooks that contain hooks for accelerating, braking, computing, and so on.
console.time('cost');
class Car {
constructor() {
this.hooks = {
acclerate: new SyncHook(['newspped']), / / to accelerate
brake: new SyncHook(), / / brake
calculateRoutes: new AsyncSeriesHook(['source'.'target'.'routes']), // Calculate the path}; }}const myCar = new Car();
// Bind the sync hook
myCar.hooks.brake.tap('WarningLmapPlugin'.() = > {
console.log('WarningLmapPlugin');
});
// Bind the sync hook and pass the parameter
myCar.hooks.acclerate.tap('LoggerPlugin'.(newSpeed) = > {
console.log(`accelerating spped to ${newSpeed}`);
});
// Bind an asynchronous promise
myCar.hooks.calculateRoutes.tapPromise(
'calculateRoutes tabPromise'.(params) = > {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
console.log(`tapPromise to ${params}`);
resolve();
}, 1000); }); });// Trigger the sync hook
myCar.hooks.brake.call();
// Triggers the sync hook and passes in arguments
myCar.hooks.acclerate.call(120);
// Trigger the asynchronous hook
myCar.hooks.calculateRoutes
.promise(['Async'.'hook'.'demo'])
.then(() = > {
console.timeEnd('cost');
})
.catch((err) = > {
console.error(err);
console.timeEnd('cost');
});
Copy the code
How does Tapable relate to Webpack
CreateCompiler in webpack-cli.js passes the resulting options to the webpack method and generates the compiler object. I’ve posted the source code for createCompiler to make it easier to understand:
const createCompiler = (rawOptions) = > {
const options = getNormalizedWebpackOptions(rawOptions);
applyWebpackOptionsBaseDefaults(options);
const compiler = new Compiler(options.context); // Instantiate compiler
compiler.options = options;
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging,
}).apply(compiler);
if (Array.isArray(options.plugins)) {
// Iterate over and invoke the plug-in
for (const plugin of options.plugins) {
if (typeof plugin === 'function') {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
applyWebpackOptionsDefaults(options);
// hooks that trigger listening
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
new WebpackOptionsApply().process(options, compiler); // Inject internal plug-ins
compiler.hooks.initialize.call();
return compiler; // Return the instance
};
Copy the code
From the code above, you can draw two conclusions about the plug-in:
- Plug-ins listen for hooks on compiler objects.
- Executing the plug-in requires calling the plug-in’s Apply method and passing in the Compiler object as a parameter.
webpack.js
:
- Webpack also has the createCompiler method, which is instantiated first
Compiler
Object to generate compiler instances. - At the heart of Compiler is the mounting of a number of hooks inherited from Tapable. Elsewhere, Compiler instances can be used to register and fire events, which fire different hooks at different stages of WebPack construction.
options.plugins
In createCompiler, if the compiler instance is generatedoptions.plugins
Is an array type, it is iterated over and is passed in compiler of the formplugin.apply(compiler)
, some hooks events on the internal binding compiler.
Simple simulation Compiler and plug-in implementation:
// Compiler object, which mounts some hooks
const { SyncHook, AsyncSeriesHook } = require('tapable');
module.exports = class Compiler {
constructor() {
this.hooks = {
acclerate: new SyncHook(['newspped']),
brake: new SyncHook(),
calculateRoutes: new AsyncSeriesHook(['source'.'target'.'routesList']),}; }run() {
this.acclerate(100);
this.brake();
this.calculateRoutes('Async'.'hook'.'demo');
}
acclerate(speed) {
this.hooks.acclerate.call(speed);
}
brake() {
this.hooks.brake.call();
}
calculateRoutes(. params) {
this.hooks.calculateRoutes.promise(... params).then(() = > {},
(err) = > {
console.log(err); }); }};Copy the code
The Webpack plugin selectively listens for some hooks, depending on the compiler object passed in:
const Compiler = require('./compiler');
class MyPlugin {
apply(compiler) {
// Bind events
compiler.hooks.acclerate.tap('Print speed'.(newSpeed) = >
console.log(`speed acclerating to ${newSpeed}`)); compiler.hooks.brake.tap('Brake Warning'.() = > console.log('Braking'));
compiler.hooks.calculateRoutes.tapPromise(
'Compute path'.(source, target, routesList) = >
new Promise((resolve, reject) = > {
setTimeout(() = > {
console.log('Computing path:${source} ${target} ${routesList}`);
resolve();
}, 1000); })); }}// Simulate plug-in execution
const compiler = new Compiler();
const myPlugin = new MyPlugin();
// Simulate plugins configuration for webpack.config.js
const options = { plugins: [myPlugin] };
for (const plugin of options.plugins) {
if (typeof plugin === 'function') {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler); // Bind events
}
}
compiler.run(); // Trigger the event
Copy the code
Webpack Process Chapter: Preparation Phase
The packaging process for WebPack can be divided into three stages:
- Prepare: initialize the parameters and inject the plug-in for the corresponding parameters
- Module compilation and packaging
- Module optimization, code generation, and output to disk.
Webpack is compiled in the order in which the hooks are called:
- Entry-option: initializes the option.
- Run: Starts compiling.
- Make: Recursively analyzes dependencies from entry and builds each dependency module.
- Before-resolve: Resolves the module position.
- Build-module: Starts building a module.
- Normal-module-loader: compiles modules loaded by the loader to generate an AST tree.
- Program: Iterates through the AST, collecting dependencies when it encounters some call expression such as require.
- Seal: All dependencies build complete and optimization begins.
- Emit: Output to the dist directory.
entry-option
First, query the location of the entryOption string in the directory:
grep "\.entryOption\." -rn ./node_modules/webpack
Copy the code
The results are as follows:
As you can see, the hook is bound in EntryOptionPlugin and DllPlugin, and the hook is triggered in WebpackOptionsApply.
WebpackOptionsApply
- Convert all configuration options parameters to webPack internal plug-ins.
Such as:
- Options. externals corresponds to ExternalsPlugin.
- Options.output. clean corresponds to CleanPlugin.
- Options. The experiments. The corresponding WebAssemblyModulesPlugin syncWebAssembly.
- Bind the entryOption Hook and trigger it.
The final preparation phase ends with a relatively complete flow chart:
Webpack process chapter: Module building and Chunk generation phases
Related to the hook
Process related:
- (before-)run
- (before-/after-)compile
- make
- (after-)emit
- done
Monitoring related:
- watch-run
- watch-close
Compilation
Compiler calls the Compilation lifecycle methods:
- addEntry -> addModuleChain
- Finish (Module error reported)
- seal
ModuleFactory
Compiler creates two factory functions, NormalModuleFactory and ContextModuleFactory, which inherit from the ModuleFactory.
- NormalModuleFactory: Normal module name import.
- ContextModuleFactory: Module imported as a path.
NormalModule
Build-build phase:
- Use loader-Runner to run the loaders parsing module to generate JS code.
- Parsing dependencies through Parser (internally using acron)
- ParserPugins adds dependencies Once all dependency parsing is complete, the make phase is complete.
Specific Build process
- Compiler.compile: hooks.compile -> hooks. Make -> compiler.addentry
Look at the bindings and where hooks. Make is triggered:
-
After the module is built, trigger hook.finishMake -> compilation.finish -> compilation.seal -> links.aftercompile, Finally, the code generated by loaders (loader-runner) parsing is obtained.
-
Take NormalModule construction as an example.
- Build -> doBuild -> runLoaders
- Parse (Use acron to parse the compiled loader code and add dependencies).
- Store the resulting results to compiler.modules.
- Hook. FinishMake completes the build.
Chunk generation algorithm
- Webpack first generates a chunk for each module in an entry.
- Iterate through the dependency list of modules and add the dependent modules to chunk.
- If a dependent module is dynamically imported, a new chunk is created based on the module and the dependency continues to be traversed.
- Repeat the process until all chunks are generated.
Webpack Process chapter: File generation
After the build is complete, execute hooks. Seal, hooks. Optimize the build results, and emit hooks.
Start writing a simple Webpack
Modularity: Improves code readability and maintainability
Closures + execute functions now → AngularJS dependency injection → NodeJS CommonJS -> AMD -> ES2015 ESModule
- The shift from traditional Web development to Web App development.
- Code complexity is increasing.
- Separate JS files/modules for easy maintenance of subsequent code.
- You want to optimize your code for multiple HTTP requests at deployment time.
There are several common ways of modularization:
// esmodule
// Static analysis, not dynamic, can only be imported at the top of the file.
import * as largeNumber from 'large-number';
largeNumber('99');
Copy the code
// commonjs
// Nodejs is a default specification that supports dynamic import
const largeNumber = require('large-number');
largeNumber('99');
Copy the code
// AMD
// Take a page from commonjs, which is often used in browsers
require(['large-number'].function (largeNumber) {
largeNumber.add('99');
});
Copy the code
AST Basics
AST (Abstract syntax tree) is a tree representation of the abstract syntax structure of source code.
The AST usage scenarios are as follows:
- The template engine is implemented in two ways
- Regular match
- AST
- Es6 → ES5 or TS → JS
AST Online parsing engine
Module mechanism for Webpack
- What comes out of the package is an IIFE (anonymous closure function).
- Modules is an array, and each item is a module initialization function.
__webpack_require__
Used to load the module, returnsmodule.exports
.- through
WEBPACK_REQUIRE_METHOD(0)
Start the program.
A simple WebPack needs to support the following features:
- Es6 can be converted to ES5.
- through
parse
Generate AST. - through
transformFromAstSync
Rebuild the AST to generate es5 source code.
- through
- Dependencies between modules can be analyzed.
- through
traverse
的ImportDeclaration
Method to get a dependency property.
- through
- The resulting JS file can be run in a browser.
Write the steps
- write
minipack.config.js
. - write
parser.js
, the implementation converts ES6 code into an AST, then analyzes the dependencies and converts the AST into ES5 code. - write
compiler.js
To start building, build modules, and output the results to disk.
Implementation of the source code I put here, click to view
Write loaders and plug-ins
Loader chain calls and execution order
The simplest loader code structure
A loader is a js module that is exported as a function:
module.exports = function (source) {
return source;
};
Copy the code
Sequence of multiple Loaders
- Serial execution: The result of the previous execution is passed to the later loader.
- Execute from right to left.
module.exports = {
module: {
rules: [{test: /\.less/,
use: ['style-loader'.'css-loader'.'less-loader'],},],},};Copy the code
Two cases of combinations of functions
- In the Unix pipline
- Compose
const compose =
(f, g) = >
(. args) = >f(g(... args));Copy the code
Verify loader sequential execution
The source address
You can run yarn Build to view the printing sequence of Loader logs.
Use loader-runner to debug loader efficiently
When verifying the execution sequence of loader in the previous section, webpack webpack-CLI needs to be installed first, then webpack.config.js is compiled, and loader is imported into the corresponding configuration file. This process is quite tedious. More efficient loader-runner can be used for loader development and debugging. It allows you to run the Loader without installing webPack.
Loader-runner functions:
- It is used in WebPack to execute the loader as a dependency of WebPack.
- Develop and debug loader.
Write raw-loader, use loader-runner to run
Implement raw – loader:
module.exports = function rawLoader(source) {
const str = JSON.stringify(source)
.replace(/\u2028/g.'\\u2028')
.replace(/\u2029/g.'\\u2029'); // There is a security problem with template strings
return `export default ${str}`; // Convert file contents to modules
};
Copy the code
Call raw-loader in loader-runner:
const path = require('path');
const { runLoaders } = require('loader-runner');
const fs = require('fs');
runLoaders(
{
resource: path.join(__dirname, './src/info.txt'),
loaders: [path.join(__dirname, './loaders/raw-loader.js')].context: { minimize: true }, // The received context
readResource: fs.readFile.bind(fs), // How to read files
},
(err, result) = > {
if (err) {
console.log(err);
} else {
console.log(result); }});Copy the code
After executing node run-loader.js, the following output is displayed:
More complex loader development scenarios
Obtain loader parameters
Obtain the value by using the getOptions method of loader-utils.
Modify run-loader.js to pass arguments:
runLoaders(
{
resource: path.join(__dirname, './src/info.txt'),
loaders: [{loader: path.join(__dirname, './loaders/raw-loader.js'),
options: { name: 'ywhoo' }, // The name argument is passed},].context: { minimize: true },
readResource: fs.readFile.bind(fs),
},
(err, result) = > {
if (err) {
console.log(err);
} else {
console.log(result); }});Copy the code
Use this parameter in raw-loader.js:
const loaderUtils = require('loader-utils');
module.exports = function rawLoader(source) {
const { name } = loaderUtils.getOptions(this); // Introduce parameters
console.log(name, 'name');
const str = JSON.stringify(source)
.replace(/\u2028/g.'\\u2028')
.replace(/\u2029/g.'\\u2029');
return `export default ${str}`;
};
Copy the code
Loader exception handling
Synchronous:
- throw
- this.callback
- Return processing result
- An exception is thrown
- Return more values
// throw
throw new Error('error');
// this.callback
this.callback(err: Error | null.content: string| Buffer, sourceMap? : SourceMap, meta? :any);
Copy the code
Asynchronous processing
During loader development, you may need to deal with asynchrony, such as asynchronously reading files and returning the contents after reading.
This can be implemented with this.async() to modify the example in raw-loader to add asynchronous file reading:
// ...
module.exports = function rawLoader(source) {
const callback = this.async();
fs.readFile(
path.join(__dirname, '.. /src/async.txt'),
'utf-8'.(err, data) = > {
callback(null, data); }); };Copy the code
Use caching in the Loader
- Loader caching is enabled by default in Webpack
- use
this.cacheable(false)
Turn off the default cache
- use
- Cache condition: The loader result has a certain output with the same input
- A dependent loader cannot use the cache
Turn off caching:
module.exports = function () {
this.cacheable(false);
};
Copy the code
Output files to disk in loader
Use this.emitfile for file writing.
TXT file in loader-a.js, and finally generate demo. TXT file in dist:
module.exports = function (source) {
console.log('loader a is running');
this.emitFile('demo.txt'.'Prints the file in loader. ');
return source;
};
Copy the code
Or use loader-utils, which in this case generates index.js under dist:
const loaderUtils = require('loader-utils');
module.exports = function (source) {
console.log('loader a is running');
// Match the file name that matches the rule, as in index.js
const filename = loaderUtils.interpolateName(this.'[name].[ext]', source);
this.emitFile(filename, source);
return source;
};
Copy the code
Actual combat development of an automatic synthesis of Sprite map loader
Sprite image application can reduce the number of HTTP requests, effectively improve the speed of page loading.
To achieve multiple pictures of a composite, support the following effects:
How to combine two images into one image
Use spritesmith.
Realize the Sprite – loader:
const path = require('path');
const Spritesmith = require('spritesmith');
const fs = require('fs');
module.exports = function (source) {
const callback = this.async();
const regex = /url\((\S*)\? __sprite\S*\)/g;
let imgs = source.match(regex); // [ "url('./images/girl.jpg?__sprite", "url('./images/glasses.jpg?__sprite" ]
imgs = imgs.map((img) = > {
const imgPath = img.match(/\/(images\/\S*)\? /) [1];
return path.join(__dirname, '.. /src', imgPath);
});
Spritesmith.run({ src: imgs }, function handleResult(err, result) {
// Write the generated image to dist/sprites.jpg
// In webpack, emitFile should be used to write files
fs.writeFileSync(
path.join(process.cwd(), 'dist/sprites.jpg'),
result.image
);
const code = source.replace(regex, (match) = > "url('./sprites.jpg')");
/ / output index. The CSS
fs.writeFileSync(path.join(process.cwd(), 'dist/index.css'), code);
callback(null, code);
});
return source;
};
Copy the code
Use sprite-loader in loader-runner:
const path = require('path');
const { runLoaders } = require('loader-runner');
const fs = require('fs');
runLoaders(
{
resource: path.join(__dirname, './src/index.css'),
loaders: [{loader: path.join(__dirname, './loaders/sprites-loader.js'),},],context: { minimize: true },
readResource: fs.readFile.bind(fs),
},
(err, result) = > {
if (err) {
console.log(err);
} else {
console.log(result); }});Copy the code
Execute node run-sprites-loader.js and the result is as follows:
body {
background: url('./sprites.jpg');
}
.banner {
background: url('./sprites.jpg');
}
Copy the code
Introduction to the basic structure of plug-ins
Loader is responsible for processing resources, that is, resources as modules to process. Plug-ins can intervene in the webPack build lifecycle.
- Plug-ins do not have a separate runtime environment (loader-runner) like loaders.
- Can only run inside webPack.
Example:
module.exports = class MyPlugin {
constructor(options) {
console.log(options);
}
apply(compiler) {
console.log('perform my - plugin'); }};Copy the code
const path = require('path');
const MyPlugin = require('./plugins/my-plugin'); // Custom plug-in
module.exports = {
entry: path.join(__dirname, './src/index.js'),
output: {
filename: 'bundle.js'.path: path.resolve(__dirname, './dist'),},plugins: [new MyPlugin({ name: 'ywhoo'})]};Copy the code
More complex plug-in development scenarios
Error handling of plug-ins
throw new Error('error');
.- Received through warnings and errors of the compilation object.
compilation.warnings.push('warning');
compilation.errors.push('error');
Write files through Compilation
The emit phase of the file generation allows you to listen for the emit and then retrieve the compilation object.
Compilation assets can be used to write files
- You can set the ZIP resource bundle to the compilation.assets object.
Use the webpack-sources library for file writing. Example:
const { RawSource } = require('webpack-sources');
module.exports = class DemoPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
const { name } = this.options;
compiler.compilation.hooks.emit.tap('emit'.(compilation, cb) = > {
compilation.assets[name] = new RawSource('demo'); cb(); }); }};Copy the code
Plug-in extensions: Plug-ins that write plug-ins
Plugins can also extend themselves by exposing hooks. In the case of htML-webpack-plugin, this supports hooks:
- HTML – webpack – plugin – after – chunks (sync)
- HTML – webpack – plugin – before – HTML – generation (async)
- HTML – webpack – plugin – after – asset – tags (async)
- HTML – webpack – plugin – after – HTML – processing (async)
- HTML – webpack – plugin – after – emit (async)
Practical development of a compressed build resources for zip package plug-in
Requirements:
- The name of the generated ZIP package file can be passed in through the plug-in.
- You need to use hooks on the Compiler object for resource generation.
Prepare knowledge
Create and edit zip packages using Jszip in NodeJS.
Review: The hooks in Compiler responsible for file generation
Emit, an AsyncSeriesHook
During the emit generation phase, the compilation.assets object was read.
- Set the ZIP resource bundle to the compilation.assets object.
Realize the zip – plugin. Js:
const JSZip = require('jszip');
const path = require('path');
const { Compilation, sources } = require('webpack');
module.exports = class ZipPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
const { filename } = this.options;
// listen for hooks from compilation
compiler.hooks.compilation.tap('ZipPlugin'.(compilation) = > {
// Listen to processAssets hook to get static resources while processing build resources
compilation.hooks.processAssets.tapPromise(
{
name: 'ZipPlugin'.stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
(assets) = > {
return new Promise((resolve, reject) = > {
const zip = new JSZip();
// Create the zip package
const folder = zip.folder(filename);
Object.entries(assets).forEach(([fname, source]) = > {
// Add the packaged resource file to the compressed package
folder.file(fname, source.source());
});
zip.generateAsync({ type: 'nodebuffer' }).then(function (content) {
// /Users/yewei/Project/source-code-realize/play-webpack/source/mini-plugin/dist/ywhoo.zip
const outputPath = path.join(
compilation.options.output.path,
`${filename}.zip`
);
// Relative path ywhoo.zip
const relativeOutputPath = path.relative(
compilation.options.output.path,
outputPath
);
// Transfer buffer to raw source
// Add the zip package to the compilation's build resources
compilation.emitAsset(
relativeOutputPath,
new sources.RawSource(content)
);
resolve();
});
}).catch((e) = > {
console.log(e, 'e'); }); }); }); }};Copy the code
Using plug-ins:
const path = require('path');
const ZipPlugin = require('./plugins/zip-plugin');
module.exports = {
entry: path.join(__dirname, './src/index.js'),
output: {
filename: 'bundle.js'.path: path.resolve(__dirname, './dist'),},plugins: [new ZipPlugin({ filename: 'ywhoo'})]};Copy the code
After yarn Build is run, ywhoo.zip is generated in the dist directory.
conclusion
If you follow the practice notes here, I believe you are no longer so unfamiliar with Webpack, and have a certain degree of understanding of the principle, can write loader to handle static resources, write plug-ins to control every stage of the WebPack construction process. Finally, I would like to say a word with you. The journey is difficult, but you can see the dawn at that moment.