webpack
makes its usage more flexible through the plugin
mechanism to adapt to various application scenarios. Of course, this greatly increases the complexity of webpack
. During the lifecycle of webpack
, many events will be broadcast, and plugin
can hook into these events to change the output results in the processing process through the APIs provided by webpack
at the appropriate time.
webpack
is a modern static module bundler for JavaScript applications. When webpack
processes an application, it recursively builds a dependency graph containing every module the application needs, and then bundles all of these modules into one or more bundles.
Using webpack
as a front-end build tool can usually achieve the following:
TypeScript
to JavaScript
, compiling SCSS
to CSS
, and so on.JavaScript
, CSS
, HTML
code, compressing and merging images, and so on.In a webpack
application, there are two core components:
js
modules.webpack
build process to change the build result or do what you want.This article is about writing a simple webpack
plugin. Imagine a simple scenario: if we have implemented a multi-page Vue
application, each packed page will share a common header and footer, which are the top navigation bar and bottom footer. Since frameworks like Vue
load the header and footer at runtime, this part of the code can actually be developed as a standalone common sub-project, without the need to reference the component on every page of the multi-page application for the framework to parse the component. Additionally, when navigating between pages of a multi-page application, if a header component is written to be referenced within each page component, it is easy to experience navbar flashing due to the longer time needed to load and parse the JS
.
To address the issues mentioned above, one solution is to use static page fragments. During the webpack
packing process, we can inject the header and footer of the pages into the html
pages to be packed. This approach not only saves some JS
overhead for framework parsing of components, but also improves SEO
performance. Although it's only a header and footer with not much information, in a large amount of repeated CPU
tasks in an SSR
scenario, even a small improvement can have a significant impact overall, just like the line drawing algorithm in computer graphics, which can't stand too many computational iterations. Furthermore, this approach effectively solves the problem of component header flashing because it is returned with the HTML
and can be immediately rendered on the page without the need for JS
loading and parsing. Similarly, this approach can also be used for loading the skeleton screen. All the code mentioned in this article is available at https://github.com/WindrunnerMax/webpack-simple-environment
.
To explore webpack
, let's start by setting up a simple webpack
environment. First, initialize and install dependencies.
You can first try packaging with webpack
. webpack
can be packaged with zero configuration. The directory structure is as follows:
Then, add a packaging command.
Run npm run build
, which will call the webpack
command under node_modules/.bin
by default. Internally, webpack-cli
will parse user parameters for packaging, and by default, it will use src/index.js
as the entry file.
After the execution is completed, a warning will appear, reminding us that the default mode
is production
. At this point, you can see the appearance of the dist
folder, which is the final packaged result. Inside, there is a main.js
file. Webpack
will perform some syntax analysis and optimization, and you can see the structure of the completed package.
Of course, we generally don't use zero configurations for packaging, so we first create a file webpack.config.js
. Since webpack
says the default mode
is production
, let's solve this issue by configuring it. Because it's just a simple webpack
environment, we won't differentiate between webpack.dev.js
and webpack.prod.js
for configuration. Simply use process.env.NODE_ENV
to differentiate in webpack.config.js
. Here, we mainly care about the files after the dist
package. We won't handle the dev
environment or set up webpack-dev-server
here. cross-env
is a plugin used to configure environment variables.
However, according to the above requirements, we not only need to handle js
files but also need to handle html
files, for which we need to use the html-webpack-plugin
plugin.
Then configure it in webpack.config.js
, simply configure the relevant input/output and compression information. Additionally, if you want to delete the dist
folder every time you package, you can consider using the clean-webpack-plugin
plugin.
Next, when it comes to the main body, we need to write a plugin to handle the requirements mentioned earlier. Specifically, what we need to do is first leave a comment tag similar to <!-- inject:name="head" -->
in the html
. Then after webpack
bundles the html
file, we need to perform a regular match to replace the information related to the comment with a page slice, distinguishing which page slice to load by the name
. Additionally, personally, when actually writing webpack
plugins, it's best to first refer to the implementations of webpack
plugins written by others. It's a bit costly to go through the documents and look up various hooks
on your own.
For this plugin, we will directly create a static-page-slice.js
in the root directory. The plugin is instantiated by a constructor function, and the constructor function defines the apply
method. When webpack
processes the plugin, the apply
method will be called once by the webpack compiler
. The apply
method can receive a reference to the webpack compiler
object, so that we can access the compiler
object in the callback function. The structure of the most basic Plugin
is similar to the following:
When developing a plugin
, the two most commonly used objects are compiler
and compilation
. They are the bridge between the plugin
and webpack
. The meanings of compiler
and compilation
are as follows:
compiler
object contains all the configuration information of the webpack
environment, including options
, loaders
, and plugins
. This object is instantiated when webpack
is started and is globally unique. It can be simply understood as the webpack
instance.compilation
object contains the current module resources, compiled generated resources, and changed files. When webpack
is running in development mode, each time a file change is detected, a new compilation
will be created. The compilation
object also provides many event callbacks for plugins to extend, and the compiler
object can also be read through the compilation
.The difference between compiler
and compilation
is that compiler
represents the entire life cycle of webpack
from start to finish, while compilation
only represents a new compilation. The related information can be referred to at https://webpack.docschina.org/api/compiler-hooks/
.
Webpack
is like a production line. The source files must go through a series of processing before they can be converted into output results. The responsibilities of each processing step on this production line are single, and there are dependencies between multiple steps. Only after the current processing is completed can it be handed over to the next step for processing. Plugins are like features inserted into the production line, processing resources on the production line at specific times. Webpack
organizes this complex production line through tapeable
at https://github.com/webpack/tapable
.
Here, we choose to process resource files during the emit
period of the compiler
hook, which means executing before outputting the asset
to the output
directory. At this time, it is important to note that emit
is an AsyncSeriesHook
, which means it is an asynchronous hook
, so we need to use tapAsync
or tapPromise
from Tapeable
. If a synchronous hook
is chosen, tap
can be used instead.
Next, let's officially start processing the logic. First, we need to determine the type of this file. We only need to handle html
files, so we need to check if it's an html
file. Then it's a process of regular expression matching. After matching the comment information, we will replace it with a page fragment. We will directly simulate an asynchronous process using Promise
here for the page fragment. After that, we can reference it in webpack
and successfully bundle it.
You can then see the difference between the html
files before and after packaging.
Webpack5
has made an update to the hooks
. Using the plugin above will prompt:
Therefore, we can process the resources in advance according to its prompt to achieve the same effect.