1. Core Concepts of webpack#
- entry: The entry point of an executable module or library. Defines the entry file after packaging.
- output: Indicates how and where webpack should output the files.
path: The absolute path where the packaged files are stored
publicPath: The access path during the website's runtime
filename: The name of the packaged file - module: In webpack, everything is a module, and a module corresponds to a file. webpack starts from the configured entry to recursively find all dependent modules.
- chunk: A
chunk
is composed of multiplemodules
. Executable modules and their dependent modules can be combined into a chunk, which is the packaging process. - loader: A module transformer used to convert the original content of a module into new content as needed. For example: converting es6 to es5, scss to css, etc.
- plugin: Plugins that extend the functionality of webpack. They add extension hooks at various lifecycle nodes of the webpack build process to add features.
2. webpack Build Process#
- Initialize Parameters: Parse the webpack configuration parameters, merge the parameters passed from the shell and those in the webpack.config.js file, forming the final configuration result.
- Start Compilation: The parameters obtained in the previous step initialize the compiler object, register all configured plugins, and the plugins listen to the events of the webpack build lifecycle, responding accordingly and executing the run method of the object to start the compilation.
- Determine Entry: The configured entry starts parsing the file to build the AST syntax tree, finding dependencies and recursively going deeper.
- Compile Modules: Based on the file type and loader configuration, call all configured loaders to transform the files, then find the dependent modules of that module, and recursively process until all entry-dependent files have been handled in this step.
- Complete Module Compilation and Output: After recursion, obtain the results of each file, which includes each module and their dependencies, and generate code chunks based on the entry configuration.
- Output Completion: Output all chunks to the file system.
3. Common Loaders#
- babel-loader: Converts es6 to es5;
- css-loader: Loads css, supports modularization, compression, file imports, etc.;
- style-loader: Injects css code into js, loading css through DOM manipulation;
- eslint-loader: Checks js code using Eslint;
- image-loader: Loads and compresses images;
- file-loader: Outputs files to a folder, referenced in code via relative URLs;
- url-loader: Similar to file-loader, can inject file content into code in base64 format when the file is small.
- source-map-loader: Loads additional source map files for easier debugging.
4. Business Scenarios and Corresponding Solutions#
1. Single Page Application#
A single page application needs to configure an entry to specify the execution entry. The WebPlugin in web-webpack-plugin can automatically complete this work: webpack will generate a chunk containing all the dependency files for the entry, but an HTML file is still needed to load the js generated by the chunk, and if css is also extracted, the extracted css needs to be included in the HTML file.
A simple example of a webpack configuration file:
const { WebPlugin } = require('web-webpack-plugin');
module.exports = {
entry: {
app: './src/doc/index.js',
home: './src/doc/home.js'
},
plugins: [
// One WebPlugin corresponds to generating one html file
new WebPlugin({
// The name of the output html file
filename: 'index.html',
// The `entry` that this html depends on
requires: ['app','home'],
}),
],
};
Note: require: ['app', 'home'] specifies which entries this html depends on, and the js and css generated by the entry will be automatically injected into the html.
It also supports configuring these resource injection methods, supporting the following properties:
- _dist resources that are only included in the production environment;
- _dev resources that are only included in the development environment;
- _inline injects the content of the resource into the html;
- _ie resources that are only needed for IE browsers.
These properties can be configured in js, here’s a simple example:
new WebPlugin({
filename: 'index.html',
requires: {
app:{
_dist:true,
_inline:false,
}
},
}),
These properties can also be set in the template, the benefit of using a template is that it allows flexible control over the injection points of resources.
new WebPlugin({
filename: 'index.html',
template: './template.html',
}),
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<link rel="stylesheet" href="app?_inline">
<script src="ie-polyfill?_ie"></script>
</head>
<body>
<div id="react-body"></div>
<script src="app"></script>
</body>
</html>
The WebPlugin plugin draws on the ideas of fis3, filling in the missing functionality of webpack for HTML entry. For more features of WebPlugin, see the documentation.
2. A Project Managing Multiple Single Pages#
A project may contain multiple single page applications. Although multiple single page applications can be combined into one, doing so would cause parts that the user does not access to load as well, especially if there are many single page applications in the project. Configuring an entry and WebPlugin for each single page application? If a new one is added, the webpack configuration needs to be updated, which is cumbersome. At this point, the AutoWebPlugin method in the web-webpack-plugin can solve these problems.
module.exports = {
plugins: [
// The entry directory for all pages
new AutoWebPlugin('./src/'),
]
};
Analysis:
AutoWebPlugin
will treat each folder under the ./src/ directory as an entry for a single page, automatically configuring aWebPlugin
for all page entries to output the corresponding html.- To add a new page, simply create a new folder under
./src/
containing the code dependencies for that single page application, andAutoWebPlugin
will automatically generate an html file named after the folder.
3. Code Splitting Optimization#
Good code splitting greatly enhances the browser's first screen performance.
Most Common React System:
- First, extract the basic libraries react, react-dom, redux, and react-redux into a separate file instead of packaging them together with other files. The benefit of this approach is that as long as you do not upgrade their versions, this file will never be refreshed. If you package these basic libraries with business code into one file, any change in business code will cause the file hash to change, leading to cache invalidation and the browser re-downloading the code containing the basic libraries. Therefore, package the basic libraries into one file.
// vender.js file extracts basic libraries into a separate file to prevent them from being refreshed with business code
// Third-party libraries that all pages depend on
// React basics
import 'react';
import 'react-dom';
import 'react-redux';
// Redux basics
import 'redux';
import 'redux-thunk';
// Webpack configuration
{
entry: {
vendor: './path/to/vendor.js',
},
}
- The CommonsChunkPlugin can extract code that is common to multiple chunks into a separate chunk. In scenarios where an application has multiple pages, extracting all common code reduces the code for individual pages, and when switching between different pages, the common code has already been loaded and does not need to be reloaded. Thus, the CommonsChunkPlugin can extract code that multiple chunks depend on into a separate chunk.
4. Building Server-Side Rendering#
Server-side rendering code must run in a nodejs environment, which differs from the browser in that server-side rendering code needs to use the commonjs specification and should not include non-js files like css.
The webpack configuration is as follows:
module.exports = {
target: 'node',
entry: {
'server_render': './src/server_render',
},
output: {
filename: './dist/server/[name].js',
libraryTarget: 'commonjs2',
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
},
{
test: /\.(scss|css|pdf)$/,
loader: 'ignore-loader',
},
]
},
};
Analysis:
target: 'node'
indicates that the built code is to run in a node environment.libraryTarget: 'commonjs2'
specifies that the output code should follow the commonjs specification.{test: /\.(scss|css|pdf)$/,loader: 'ignore-loader'}
is to prevent files that cannot be executed in node or are not needed for server-side rendering from being packaged.
5. Migrating from fis3 to webpack#
fis3 and webpack have many similarities and differences. Similarities: both use the commonjs specification. Differences: the way to import css and other non-js resources.
fis3 uses @require './index.scss', while webpack uses require('./index.scss').
If you want to smoothly migrate from fis3 to webpack, you can use comment-require-loader.
For example, if you want to use a module that adopted the fis3 method in webpack:
loaders:[{
test: /\.js$/,
loaders: ['comment-require-loader'],
include: [path.resolve(__dirname, 'node_modules/imui'),]
}]
5. Custom webpack Extensions#
If you cannot find a solution for your application scenario in the community, you will need to write your own loader or plugin.
Before you write a custom webpack extension, you need to clarify whether you want to create a loader or a plugin. You can determine this as follows:
If your extension is intended to transform individual files, then write a loader; everything else is a plugin.
Transforming files can include:
- babel-loader converts es6 to es5;
- file-loader replaces files with corresponding URLs;
- raw-loader injects the content of text files into code.
1. Writing a webpack Loader#
Writing a loader is very simple, taking comment-require-loader as an example:
module.exports = function (content) {
return replace(content);
};
The entry of the loader needs to export a function, and this function's job is to transform the content of a file.
The function receives the parameter content, which is the string representation of a file before transformation, and needs to return a new string representation as the result after transformation. All files imported through modularization will go through the loader. From this, it can be seen that loaders can only process individual files and cannot handle code blocks. You can refer to the official documentation.
2. Writing a webpack Plugin#
Plugins have a wide range of application scenarios, so they are slightly more complex. Taking end-webpack-plugin as an example:
class EndWebpackPlugin {
constructor(doneCallback, failCallback) {
this.doneCallback = doneCallback;
this.failCallback = failCallback;
}
apply(compiler) {
// Listen to events in the webpack lifecycle and handle them accordingly
compiler.plugin('done', (stats) => {
this.doneCallback(stats);
});
compiler.plugin('failed', (err) => {
this.failCallback(err);
});
}
}
module.exports = EndWebpackPlugin;
The entry of the loader needs to export a class. When new EndWebpackPlugin() is called, the parameters required by the plugin are passed through the constructor. When webpack starts, it first instantiates the plugin and then calls the plugin's apply method. The plugin listens to events in the webpack lifecycle in the apply function and handles them accordingly.
Two Core Concepts of webpack Plugins:
- compiler: There is only one Compiler from the start to the exit of webpack, which holds the webpack configuration.
- compilation: Due to webpack's automatic compilation mechanism for listening to file changes, compilation represents a single compilation.
Both Compiler and Compilation broadcast a series of events. There are many events in the webpack lifecycle.
The above is just a very simple demo; for more complex examples, you can refer to how to write a plugin or check out web-webpack-plugin.
Reference article: https://www.cnblogs.com/chengxs/p/11022842.html