For those of you on the front end, WebPack should be familiar, and most projects are now built using WebPack. Many frameworks now provide scaffolding command-line tools that will generate template projects directly after execution. We don’t need to spend too much time on scaffolding to focus more on business writing, but we still need to know how WebPack works. Knowing this also makes it easier for us to transform or expand existing projects.
The following tutorial takes you from zero to complete the Webpack scaffolding, with a lot of whys and howls along the way. This article is roughly divided into the following parts:
- What is Webpack?
- The first step in the setup — NPM init
- Generate the directory structure for the project
- Dependencies of the installation base
- Install and configure Loader
- Configuration entry HTML — HTml-webpack-plugin
- Project optimization
What is the webpack
Essentially, WebPack is a static module packaging tool for modern JavaScript applications. Webpack simply packages modules into one or more files according to different rules based on dependencies. That’s what WebPack does.
The first step in the setup — NPM init
Create a folder, go to the folder directory, run the NPM init command, and then a package.json file is generated. At this time, the project initialization is complete.
- Why do you need package.json?
The package.json file stores information about your entire project, including dependencies, versions, etc., so that others can quickly learn about your project and make changes to it. 2. Can I do without the NPM init command? Yes, you can copy it from another project or create your own package.json file
Generate the directory structure for the project
I use React for my JS framework and LESS for my CSS framework. The same goes for other vue, sass, stylus, etc.
The directory structure is as follows:
Dependencies of the installation base
When we used WebPack as a build tool for our project, we needed to install the following packages: WebPack/webpack-CLI /webpack-dev-server
// Run the following command to install
npm install webpack webpack-cli webpack-dev-server --save-dev
Copy the code
The webpack-CLI is installed so that the webpack command can be used. Otherwise, the webpack command cannot be used
Next we modify package.json: add two commands to scripts:
Webpack and webpack-dev-server
Webpack is a module packager that compiles the files according to the dependencies of our code and outputs the compiled files to the ouput option path
Webpack-dev-server is webpack+ HttpServer which will put the compiled files in memory and start an HTTP service so that we can debug the code locally
If we use react, we need to install the react/react-dom packages:
// Run the following command to install
npm install react react-dom --save
Copy the code
OK, now that we have installed the underlying dependencies, we can go ahead and run the NPM run dev to get our project running.
Install and configure Loader
Install the loader
If not, you will get the following error after executing the previous command:
????? Are there a lot of question marks? Don’t be afraid of the following analysis:
ERROR in ./src/index.js 6:4
Module parse failed: Unexpected token (6:4)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|
| ReactDOM.render(
> <div>Hello World!</div>. |document.getElementById('root') |)Copy the code
This means that Webpack encountered a module conversion error while compiling our file, indicating that we needed a loader to parse the file, but we didn’t have one. First of all, what is a loader? Loader can preprocess our source code. In other words, it can process our source code into webPack-recognized code. We need to use a loader to convert JSX to webpack-recognized code. We need to use a loader to convert JSX to webpack-recognized code. We need to use a loader to convert JSX to webpack-recognized code.
// Install the dependencies required by Babel-Loader
npm install babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/plugin-transform-runtime @babel/runtime @babel/runtime-corejs3 --save-dev
Copy the code
@babel/core Babel’s core library @babel/preset-env converts ES6, ES7 syntax to ES5, babel7 and later versions only use this preset package to transform the syntax. The packages of PRESET -stage-0,perset-stage-1, and perset-stage-2 have been deprecated, but this package can’t convert some new features of ES6 and ES7 such as array.includes (), So we need to use @babel/ plugin-reansform-Runtime so by default, the preset env only converts syntax, if we want to convert built-in objects or instance methods, Use the useBuiltlns attribute to control how the @babel/preset-env is used to import the core of the polyfill. There are three values available (Entry, Usage,false)
- entry : As soon as we import a string of code like “core-js” into the package configuration entry or file entry, Babel will import the required polyfill according to the target browser configuration we configured (that is, whether you use it or not, as long as the browser does not support it).
import "core-js" function test(){ new Promise() } test() const arr = [1.3.4.5].map(item= >item*item) Copy the code
- Usage: Polyfill will be added as needed based on the target browser (BrowserSlit) and the features used in the code. To use this parameter, you need to pass a corejs version number. Core-js supports two versions 2/3. Flat and so on the latest method, version 2 does not have, recommended to use 3
- False: does not introduce polyfill
@babel/preset-react Convert react syntax to ES5
@babel/plugin-transform-runtime supports some new syntax for ES6 and ES7 to use a preset-env helper function in each file –> to use a Helper function in Babel-Runtime to reduce the package size
Babel/Runtime is a package that generates many helper functions when converting from ES6 to ES5. These functions are generated repeatedly for each file conversion. In this way, the helper functions introduced in each file refer directly to the files in the package, reducing the package size of each file
What is the relationship between @babel-Runtime and @babel/ plugin-tranform-Runtime? @babel-Runtime is a core, kind of helper function implementation, while @babel/ plugin-tranform-Runtime is like a butler for better reuse of @babel-Runtime
Configure the loader
Create a new build folder in the root directory and create a new webpack.config.js file to change the WebPack configuration. Since we are using the default webpack command, changing this requires specifying the configuration file in which the Webpack command is executed
// build/webpack.config.js
const path = require("path")
module.exports = {
// Specify the build environment
mode: "development"./ / the entry
entry: {app:'./src/index'
},
output: {path:path.resolve(".. /dist"),
filename:'js/[name].js'.publicPath:"/" // The prefix of the access path of the packaged resource
},
module: {rules:[
{
test:/\.jsx? $/,
exclude:/node_modules/.// The js/ JSX file in the node_modules folder does not need to use babel-Loader
loader:'babel-loader'
// The babel-loader parameter can also be configured in this way. We create a. Babelrc file to configure it
// use: {
// loader: 'babel-loader',
// options: {
// presets: ['@babel/preset-env']
/ /}
// }}}}]/ /. Babelrc file
{
"presets": [["@babel/preset-env", {"modules":false."targets": {"browsers": ["1%" >."last 2 versions"."not ie<=8"]}}],"@babel/preset-react"]."plugins": [["@babel/plugin-transform-runtime", {"corejs":3."useBUildIns":"usage"}}]]// package.json Modify the dev command in the scripts option
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"."dev": "webpack-dev-server --config build/webpack.config.js"."build": "webpack --config build/webpack.config.js" // --config refers to the configuration using the specified file. In this case, we are using the newly created webpack.config.js
},
// The important configuration has been commented, how to write in.babelrc please check the official documentation, what is the function of each plug-in has been mentioned before
Copy the code
If you only use JSX syntax, you are ready to compile and package
css-loader style-loader less-loader
If you use the syntax below congratulations, you will continue to report errors while compiling your code
The CSS loader is not available
Css-loader is designed to handle imports such as import require@import URL
Style-loader dynamically creates a style tag and adds styles to the page
Less-loader compiles less syntax
Execution order: If the loader options are an array, the execution order is from right to left loader: [‘style-loader’,’css-loader’] For multiple loaders, the execution order is from bottom to top
So we need to configure the loader for our less file as well
// The same needs to be installed first
npm install style-loader css-loader less-loader --save-dev
// build/webpack.config.js
module: {rules:[
{
test:/\.jsx? $/,
exclude:/node_modules/,
loader:'babel-loader'
},
{
test:/\.css$/,
use:[
{
loader:"style-loader"
},
{
loader:"css-loader"}]}, {test:/\.less$/,
use:[
{
loader:"style-loader"
},
{
loader:"css-loader"
},
{
loader:"less-loader"}]}]}Copy the code
Normally the NPM run dev project will run,
If the following error occurs:The less-loader version we installed is version 7.0.0, which means that this version does not contain the less package, so we need to install the less package separately
// Run the following command to install the following
npm install less --save-dev
Copy the code
At this point our project was running successfully and no errors were reported
Configuration entry HTML — HTml-webpack-plugin
When we open uphttp://localhost:8080Is this the case?Do you look blind? Don’t be afraid, because although we have the project running, we did not specify an entry HTML for it. It is the default page in webpack-dev-server, so we need to use the html-webpack-plugin to configure our entry HTML
// HTML -webpack-plugin
// We need to add plugins option to webpack.config.js to configure our entry HTML
// First, you need to install the html-webpack-plugin
npm install html-webpack-plugin --save-dev
// add plugins option to build/webpack.config.js
/ / install HTML - webpack - the plugin
const HtmlWebpackPlugin = require("html-webpack-plugin")
plugins: [new HtmlWebpackPlugin({
filename:path.resolve(__dirname,".. /dist/index.html"),
template:path.resolve(__dirname,".. /public/index.html"),
inject:true.// The injection option has four values: true,body(the script tag is at the bottom of the body),head,false(no js file inserted)
hash:true.// Add a random number to the js file in the script tag to prevent caching bundle.js? 22b9692e22e7be37b57e})]Copy the code
Run NPM run dev againhttp://localhost:8080You’ll see Hello World!Up to now, we have completed the infrastructure of the project. If it is a few simple pages, the process above is fully sufficient. However, some large and medium-sized projects still need to be optimized
Project optimization
In this section we will use some common plug-ins to optimize our build process, improve our build time and reduce the size of our build packages
Distinguish between different environments
There are several sets of environments in our daily work, such as test environment, pre-production environment, production environment and so on. Therefore, it is necessary to use different configuration items in different environments. For example, we do not need to compress our code in the test environment, so it is easier to debug and so on. So let’s see what we need to do to distinguish between different environments. Okay?
The first method is to use cross-env to set different environment variables, and then judge the environment variables to use different configurations
Cross-env allows you to set the value of a variable across different environmentsCopy the code
/ / install cross - env
npm install cross-env --save-dev
// Add test command to package.json
"build:test": "cross-env APP_ENV=test webpack --config build/webpack.config.js".// Add test code to webpack.config.js
// We will build js files in different folders depending on the value of APP_ENV
output: {path:process.env.APP_ENV === 'test' ? path.resolve(__dirname,".. /test"): path.resolve(__dirname,".. /dist"),
filename:'js/[name].js'.publicPath:"/" // The prefix of the access path of the packaged resource
},
Copy the code
After that, run the NPM run build:test command to generate a test folder, as shown in the figure below:In addition to being able to configure different output directories, you can also use the values of environment variables to determine whether additional plug-ins are needed and so on to achieve different environment, different configuration goals
The second method: generate different configuration files and execute the corresponding commands to execute the different configuration files
First, we copy webpack.config.js and name them webpack.dev.config.js and webpack.prod.config.js to match our configuration in the development and production environments, respectively. But we found that they had a lot of common configurations before, so we also pulled the common configurations out of the webpack.base.config.js file. They are then combined using the Webpack-merge plug-in. The specific implementation is as follows:
// Install dependencies on webpack-merge
npm install webpack-merge --save-dev
// webpack.base.config.js
const path = require("path")
module.exports = {
/ / the entry
entry: {app:'./src/index'
},
output: {path:path.resolve(__dirname,".. /dist"),
filename:'js/[name].js'.publicPath:"/" // The prefix of the access path of the packaged resource
},
module: {rules:[
{
test:/\.jsx? $/,
exclude:/node_modules/.// The js/ JSX file in the node_modules folder does not need to use babel-Loader
loader:'babel-loader'
// The babel-loader parameter can also be configured in this way. We create a. Babelrc file to configure it
// use: {
// loader: 'babel-loader',
// options: {
// presets: ['@babel/preset-env']
/ /}
// }
},
{
test:/\.css$/,
use:[
{
loader:"style-loader"
},
{
loader:"css-loader"}]}, {test:/\.less$/,
use:[
{
loader:"style-loader"
},
{
loader:"css-loader"
},
{
loader:"less-loader"}]},}// webpack.dev.config.js
const path = require("path")
/ / install HTML - webpack - the plugin
const HtmlWebpackPlugin = require("html-webpack-plugin")
const webpackBaseConfig = require("./webpack.base.config.js")
// Merge the two configuration items according to different rules,
const { merge } = require("webpack-merge")
module.exports = merge(webpackBaseConfig,{
// Specify the build environment
mode: "development".plugins: [new HtmlWebpackPlugin({
filename:path.resolve(__dirname,".. /dist/index.html"),
template:path.resolve(__dirname,".. /public/index.html"),
inject:true.// The injection option has four values: true,body(the script tag is at the bottom of the body),head,false(no js file inserted)})]})// webpack.prod.config.js
const path = require("path")
/ / install HTML - webpack - the plugin
const HtmlWebpackPlugin = require("html-webpack-plugin")
const webpackBaseConfig = require("./webpack.base.config.js")
const { merge } = require("webpack-merge")
module.exports = merge(webpackBaseConfig,{
// Specify the build environment
mode: "production".plugins: [new HtmlWebpackPlugin({
filename:path.resolve(__dirname,".. /dist/index.html"),
template:path.resolve(__dirname,".. /public/index.html"),
inject:true.// The injection option has four values: true,body(the script tag is at the bottom of the body),head,false(no js file inserted)
hash:true.// Add a random number to the js file in the script tag to prevent caching bundle.js? 22b9692e22e7be37b57e
// There are many options for compression. You can go to the NPM website to see the corresponding configuration
minify: {removeComments:true./ / to comment
collapseWhitespace:true./ / to space
removeAttributeQuotes:true.// Unquote attributes}})]})// Finally modify the package command configuration file in package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"."dev": "webpack-dev-server --config build/webpack.dev.config.js"."build": "webpack --config build/webpack.prod.config.js"
},
Copy the code
At this point we have separated the configuration files for the different environments so that it will be clearer when we add configurations later.
Compression js
We used the Uglifyjs-webpack-plugin to compress js. Terser-webpack-plugin is also a built-in plugin for WebPack 4 (that is, we don’t need to add this plugin ourselves to compress JS code). But if we don’t want to use the default configuration, we need to redefine it.
// Install the terser-webpack-plugin plugin
npm install terser-webpack-plugin --save-dev
// Since this plugin will only work in the production environment, we will write this configuration in webpack.prod.config.js
// Add optimization options to webpack.prod.config.js (and plugins/mode)
// Optimization is an option that overwrites webPack's built-in configurations such as compression, subcontracting, and so on
optimization: {minimizer: [new TerserWebpackPlugin({
parallel:true.sourceMap:false.exclude:/\/node_modules/,
extractComments: true.// This option, if true, generates an app.js.license.txt file to store the specific format of comments
terserOptions: {warnings:false.compress: {unused:true.drop_debugger:true.drop_console:true},}})]}Copy the code
If you run NPM Run build at this point, the commit will not change much since webPack4 already has the plugin built in and our code is compressed, but we still need to look at some configuration items of the plugin
Extract the CSS file or compress the CSS
Extracting CSS files
By default, WebPack will package CSS as a module into the corresponding JS file, but this will also cause our bundle to be large, so it is necessary to separate the CSS. In this case, we extracted the individual CSS files using the plugin mini-css-extract-plugin (again, this plugin only works in production)
The configuration is as follows:
// Install the plug-in
npm install mini-css-extract-plugin --save-dev
// Modify webpack.base.config.js to add mini-css-extract-plugin
plugins: [new MiniCssExtractPlugin({
filename:'css/[name].css'.chunkFilename:'css/[id].css'})].// Change the CSS /less loader at the same time
// Here is the code that needs to be modified in module --> rules
// Comment out the style-loader because the two will conflict, resulting in an error
{
test:/\.css$/,
use:[
{
loader:MiniCssExtractPlugin.loader,
options: {hmr:true.reloadAll:true}},/ / {
// loader:"style-loader"
// },
{
loader:"css-loader"}]}, {test:/\.less$/,
use:[
{
loader:MiniCssExtractPlugin.loader,
options: {hmr:true.reloadAll:true}},/ / {
// loader:"style-loader"
// },
{
loader:"css-loader"
},
{
loader:"less-loader"}}]Copy the code
Once we wrap it up again, we can see that an app.css file has been generated
Compress CSS
So if we open up the app.css file that we just packed and you can see that the content is not compressed,Let’s implement the optimize- CSS-assets -webpack-plugin to compress CSS:
// Install the plugin optimize-csS-assets webpack-plugin
npm install optimize-css-assets-webpack-plugin --save-dev
// Also modify the optimization option to override the default configuration
optimization: {minimizer: [new TerserWebpackPlugin({
parallel:true.sourceMap:false.exclude:/\/node_modules/,
extractComments: true.// This option, if true, generates an app.js.license.txt file to store the specific format of comments
terserOptions: {warnings:false.compress: {unused:true.drop_debugger:true.drop_console:true}}}),/ / compress CSS
new OptimizeCssAssetsPlugin({
cssProcessorOptions: {safe:true.discardComments: {removeAll:true}}}})]Copy the code
After repackaging, the code inside app.css has been compressed
Clear build directory/build package analysis
Clearing the build directory
Now that the app.js files generated by packaging do not have random numbers to prevent caching, we will add this function: we will add a configuration to our outputThen we can see something like this in our dist folder:In other words, none of the files we built before have been deleted
Webpack provides a plugin to help us remove the original dist directory: clean-Webpack-plugin doesn’t need any special configuration, just add it to the plugins optionThis will clear the dist directory before we rebuild
Build package analysis
After a build, we want to see what’s in our build package so we can optimize it later. This is where the package analysis plug-in comes in.
We use Webpack-Bundle-Analyzer to do this.
Add the same to the plugins option:When the package is finished, the browser automatically opens and displays the corresponding package size and contents:
Build package split
For details, see webpack4. Set splitChunks as default parameter :(splitChunks is a property in optimization)
splitChunks: {
// async means to split only modules loaded asynchronously (dynamic load import()) (will split modules loaded asynchronously by lazy loading, etc.)
// initial means only split from the entry module (the entry file contains react-dom in node_modules, but the plugins like Marterial loaded asynchronously in blog.js are not split into a package with the business code).
// all indicates both of the above
chunks: "async".minSize: 30000.// > 30K will be unpacked by WebPack
minChunks: 1.// If the number of references is greater than or equal to this number, split
// Import () the file itself counts as one
// Only js is counted, not CSS
// If there are two modules that meet the cacheGroup rules to be split, but the maxInitialRequests values allow only one more module to be split, then the larger module will be split
maxAsyncRequests: 5.// Maximum number of load on demand (asynchronous) requests
// The maximum number of initial load requests to limit the number of requests and not split into too many modules
// The entry file counts as one
// If the module is loaded asynchronously, it does not count
// Count js, not CSS
// Runtime split by runtimeChunk is not counted
// If two modules meet cacheGroup rules to be split, but the value in maxInitialRequests allows only one more module to be split, then the larger module will be split
maxInitialRequests: 3.automaticNameDelimiter: '~'.// Pack the delimiter
name:true.cacheGroups: {
// The default configuration
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
// The default configuration, vendors rules that don't hit, will hit here
default: {
minChunks: 2.// Reference modules more than twice -> default
priority: -20.reuseExistingChunk: true}},}Copy the code
We use chunks:”all” to split the chunks:
Static resource packaging
The static resources in our project do not need To be handled by WebPack. We just need to copy them to the corresponding build directory. We use a plugin called copy-webpack-plugin
Multipage application packaging configuration
Currently, our application is a single-page application. If it is a multi-page application, we need to add multiple entries and multiple exits, as well as the corresponding HTML template file. In other words, we can directly change the entry and add the HTML template
// First add our multi-page application directory structure as follows
// We split the files for different pages so that the post processing is easier and the structure is clearer
-src
- detail
- index.js
- index
- css
- index.css
- index.js
// Modify entry to add an entry
entry: {app:'./src/index/index'.detail:"./src/detail/index"
},
// Add the HTML template
plugins: [new HtmlWebpackPlugin({
filename:path.resolve(__dirname,".. /dist/index.html"),
template:path.resolve(__dirname,".. /public/index.html"),
inject:true.chunks: ['app'] // This corresponds to the key in the entry to indicate which dependent file the entry HTML needs to import
}),
new HtmlWebpackPlugin({
filename:path.resolve(__dirname,".. /dist/detail.html"),
template:path.resolve(__dirname,".. /public/index.html"),
inject:true.chunks: ['detail'"})"Copy the code
This is not smart enough for a large number of pages. In this case, we can use the glob plugin to get the file name directly under the page folder under our SRC folder, and then write functions to dynamically set the entry and HTML template
// Install the Glob plugin
npm install glob --save-dev
// Add the util.js file to the build folder
const path = require("path")
const glob = require("glob")
let chunksList = []
let entry = {}
function getModulesList() {
//[ '../src/pages/detail', '../src/pages/index' ]
let modulesList = glob.sync(path.resolve(__dirname,'.. /src/pages/*'))
for(let i = 0,len = modulesList.length; i<len; i++){let moduleName = modulesList[i].split('/').slice(-1).join()
chunksList.push(moduleName)
entry[moduleName] = path.resolve(__dirname,".. /src/pages/"+moduleName+'/index.js')
}
}
getModulesList()
module.exports = {
resolve:function (dir) {
return path.resolve(__dirname,dir)
},
entry:entry,
chunks:chunksList
}
Copy the code
Using the above function, you can get the entry, and then you can get the corresponding HTML template
Other optimization
Optimized packaging prompts
We have build information printed out when we pack, but we don’t care about these things, so we can just get rid of that information. The specific implementation is as follows:
conclusion
At this point we have implemented a relatively complete webPack-built project code. In addition to explaining how to configure it, there is also a lot of thinking about why. I hope you will not only know how to build a project after reading it, but also know why
thinking
-
Why is loader executed from right to left, bottom to top (hint: compose and pipe)
-
Terser-webpack-plugin and Uglifyjs-webpack-Plugin are different