Original address juejin.cn/post/684490…

preface

2020 is coming, in many front-end recruitment requirements,webpack, engineering these words are becoming more and more frequent. Everyday developers use scaffolding like vue-CLI and create-react-app to build our projects. But if you want to stand out on your team (stand out from the crowd) and get a better offer(pay off your mortgage), then you need to have a good understanding of the Webpack that we deal with a lot

This article is divided into three parts to get you up to speed on WebPack. It should take about 60 minutes to read. If there are any deficiencies, please correct them

This paper is written based on

  • Webpack 4.41.2version
  • Node: 10.15.3version

1 getting started (let’s familiarize you with webPack configuration with these little examples)

1.1 Initializing the Project

Create a new directory and initialize NPM

NPM init copies the codeCopy the code

Webpack is running in the Node environment and we need to install the following two NPM packages

NPM I -d webpack webpack- CLI Copy codeCopy the code
  • NPM I -d stands for NPM install –save-dev
  • NPM i-s stands for NPM install –save

Create a new folder SRC, then create a new file main.js, and write some code to test it out

Console. log(' Call me old yuan') copies the codeCopy the code

Configure the package.json command

perform

NPM run build copies the codeCopy the code

If you generate a dist folder with main.js inside it, you have successfully packaged it

1.2 Start our own configuration

Create a new build folder with webpack.config.js in it

// webpack.config.js const path = require('path'); Module. exports = {mode:'development', // Development mode entry: path.resolve(__dirname,'.. / SRC /main.js'), // import file output: {filename: 'output.js', // package filename path: path.resolve(__dirname,'.. /dist') // Packaged directory}} copy the codeCopy the code

Change our packaging command

NPM run build generates the following directory (image). The dist folder in main.js is the file we need to run in the browser

1.3 Configuring an HTML Template

The JS file is packaged, but it is not possible to manually import packaged JS into an HTML file every time

Some of you might think that the js file name is always fixed (output.js). So you don’t have to change the file name every time? In fact, in our daily development, we often have this configuration:

Module. exports = {// omit other configuration output: {filename: '[name].[hash:8].js', // filename path: path.resolve(__dirname,'.. /dist') // Packaged directory}} copy the codeCopy the code

The dist directory file generated at this point is as follows

For caching purposes, you’ll find that the name of the packaged JS file is different every time. Webpack packed JS files that we need to import into THE HTML, but every time we manually change the JS file name is cumbersome, so we need a plug-in to do this for us

NPM i-d html-webpack-plugin copies the codeCopy the code

Create a “public” folder of the same class as “build” and create an “index.html” configuration file

// webpack.config.js const path = require('path'); Const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = {mode:'development', // development mode entry: path.resolve(__dirname,'.. / SRC /main.js'), // import file output: {filename: '[name].[hash:8].js', // package filename path: path.resolve(__dirname,'.. Dist ')}, Plugins :[new HtmlWebpackPlugin({template:path.resolve(__dirname,'../public/index.html')})]Copy the code

The generated directory is as follows (picture)

You can see that the js file generated by the package has been automatically imported into the HTML file

1.3.1 How to develop multi-entry Files

Generate multiple instances of the HTML-Webpack-plugin to solve this problem

const path = require('path'); Const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = {mode:'development', // development mode entry: { main:path.resolve(__dirname,'.. /src/main.js'), header:path.resolve(__dirname,'.. / SRC /header.js')}, output: {filename: '[name].[hash:8].js', // filename path: path.resolve(__dirname,'.. Plugins :[new HtmlWebpackPlugin({template:path.resolve(__dirname,'../ /public/index.html'), Filename :'index.html', chunks:['main'] // module name corresponding to entry file}), new HtmlWebpackPlugin({ template:path.resolve(__dirname,'../public/header.html'), filename:'header.html', Chunks :['header'] // module name corresponding to the entry file}),]} copy codeCopy the code

The following directories are generated

[

](postimg.cc/4m9c76yW)

1.3.2 the clean – webpack – the plugin

Each time we execute NPM run build, we will find that the dist folder remains the last packed file. We recommend a plugin to help us clean the folder clean-webpack-plugin before packing the output

const {CleanWebpackPlugin} = require('clean-webpack-plugin') module.exports = { // ... Omit other configuration plugins:[new CleanWebpackPlugin()]} copy the codeCopy the code

1.4 refer to CSS

Our entry file is JS, so we import our CSS file in the entry JS

We also need some loaders to parse our CSS files

NPM i-d style-loader CSS-loader replicates the codeCopy the code

If we use less to build the style, we need to install two more

NPM I -d less less-loader Copies the codeCopy the code

The configuration file is as follows

// webpack.config.js module.exports = { // ... Omit the other configuration module: {rules: [{$/ test: / \. CSS, use: [' style - loader ', 'CSS - loader'] / / analytical principles from right to left}, {test: / \. Less $/, Use :['style-loader','css-loader','less-loader'] // Parse principles from right to left}]}} copy codeCopy the code

The browser opens the HTML as follows

1.4.1 Adding a Browser prefix to the CSS

NPM I -d postCSs-loader autoprefixer copies codeCopy the code

Configuration is as follows

// webpack.config.js module.exports = { module:{ rules:[ { test:/\.less$/, Use :['style-loader','css-loader',' postCSs-loader ','less-loader'] // Parse principles from right to left}]}} Copy codeCopy the code

Next, we also need to introduce Autoprefixer to make it work, in two ways

1. Create a postcss.config.js file in the root directory of the project and configure it as follows:

Module. exports = {plugins: [require('autoprefixer')]Copy the code

2. Configure it directly in webpack.config.js

// webpack.config.js module.exports = { //... {rules:[{test:/\. Less $/, use:['style-loader','css-loader',{loader:' postCSs-loader ', Options :{plugins:[require('autoprefixer')]}},'less-loader'] // From right to left parsing principle}]}} copy codeCopy the code

At this point we find that CSS is added to the HTML file as a style tag, but if there are many style files, adding them all to the HTML will be confusing. So what we want to do is we want to break out the CSS and introduce it into the CSS file as an external chain and how do we do that? This is where plug-ins help us

1.4.2 split CSS

NPM I-D mini-CSS-extract-plugin copies the codeCopy the code

Before WebPack 4.0, we used the extract-text-webpack-plugin to extract CSS styles from JS files into separate CSS files. After WebPackage 4.0, it is recommended to use the mini-CSs-extract-plugin to package CSS files

The configuration file is as follows

const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { //... Module: {rules: [{test: /\. Less $/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'less-loader' ], } ] }, plugins: [ new MiniCssExtractPlugin({ filename: "[name] [hash] CSS", chunkFilename: "[id]. CSS"})]} copy codeCopy the code

1.4.3 Splitting Multiple CSS

To be more specific, the mini-CSs-extract-plugin we used above consolidates all CSS styles into one CSS file. If you want to split into one-to-one CSS files, we need to use the extract-text-webpack-plugin, which is currently not supported by the Mini-CSS-extract-plugin. We need to install @next version of the extract-text-webpack-plugin

NPM I -d extract-text-webpack-plugin@next // webpack.config.js const path = require('path'); const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin') let indexLess = new ExtractTextWebpackPlugin('index.less'); let indexCss = new ExtractTextWebpackPlugin('index.css'); module.exports = { module:{ rules:[ { test:/\.css$/, use: indexCss.extract({ use: ['css-loader'] }) }, { test:/\.less$/, use: indexLess.extract({ use: [' csS-loader ','less-loader']})}]}, plugins:[indexLess, indexCss]} copy codeCopy the code

1.5 Pack images, fonts, media, and other files

File-loader is used to process files (mainly file names and paths, and file URLS) and move files to output directories. Url-loader is generally used with file-loader and has similar functions to file-loader. If the file size is smaller than the limit. Base64 encoding is returned, otherwise file-loader is used to move the file to the output directory

// webpack.config.js module.exports = {// omit other configurations... Module: {{rules: [/ /... test: / \ | PNG (jpe? G | GIF) $/, / / picture file use: [{loader: 'url - loader, the options: {limit: 10240, fallback: { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]' } } } } ] }, { test: / \. (mp4 | webm | | ogg mp3 | wav | flac | aac) (\? *)? $/, / / media file use: [{loader: 'url - loader, the options: {limit: 10240, fallback: { loader: 'file-loader', options: { name: 'media/[name].[hash:8].[ext]' } } } } ] }, { test: / \. (woff2? | eot | the vera.ttf | otf) (\? *)? $/ I / / font use: [{loader: 'url - loader, the options: {limit: 10240, fallback: {loader: 'file - loader' options: {name: 'fonts / [name] [8] hash: [ext]'}}}}}], copying code]}}Copy the code

1.6 Escape JS files with Babel

In order to make our JS code compatible with more environments we need to install dependencies

NPM I -d babel-loader @babel/preset-env @babel/core replicates the codeCopy the code
  • Pay attention tobabel-loaderwithbabel-coreVersion mapping of
  1. Babel-loader 8.x corresponds to babel-core 7.x

  2. Babel-loader 7.x Corresponds to babel-core 6.x

    // webpack.config.js module.exports = {// omit other configurations… module:{ rules:[ { test:/.js$/, use:{ loader:’babel-loader’, options:{ presets:[‘@babel/preset-env’] } }, Exclude :/node_modules/},]}} Duplicates the code

The above babel-loader will only convert ES6/7/8 syntax to ES5 syntax, but not for new apis such as Promise, Generator, Set, Maps, Proxy, etc. At this point we need to use Babel-Polyfill to help us convert

// webpack.config.js const path = require('path') module.exports = {entry: [" @ Babel/polyfill, "path. Resolve (__dirname, '.. / SRC/index. Js')], / / the entry file} copy codeCopy the code
  • It is more beneficial to read the following articles by typing the above demo once manually. It is recommended for beginners to type more than three times [

The above practice is that we have a preliminary understanding of the functions of Webpack, but in order to skillfully apply in development, we need a system of actual combat. Let’s get rid of the scaffolding and try to build a VUE development environment ourselves

2. Set up the VUE development environment

The small example above has helped us implement packaging CSS, images, JS, HTML, etc. But we also need the following configurations

2.1 Parsing. Vue files

NPM i-d VUe-loader vuE-template-compiler VUE-style-loader NPM i-s VUE copies the codeCopy the code

Vue-loader is used to parse the vue file vue-template-compiler is used to compile the template

const vueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { module:{ rules:[{ test:/\.vue$/, use:['vue-loader'] },] }, resolve:{ alias:{ 'vue$':'vue/dist/vue.runtime.esm.js', ' @':path.resolve(__dirname,'.. / SRC ')}, extensions: [' * 'and' js', 'json', 'vue']}, plugins: [new vueLoaderPlugin ()]} copy codeCopy the code

2.2 Configuring webpack-dev-server for hot Update

NPM i-d webpack-dev-server copies the codeCopy the code

Configuration is as follows

const Webpack = require('webpack') module.exports = { // ... DevServer :{port:3000, hot:true, contentBase:'.. / dist '}, plugins: [new Webpack. HotModuleReplacementPlugin ()]} copy codeCopy the code

The complete configuration is as follows

// webpack.config.js const path = require('path'); const {CleanWebpackPlugin} = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin') const vueLoaderPlugin = require('vue-loader/lib/plugin') const Webpack = require('webpack') module.exports = { mode:'development', // Entry: {main:path.resolve(__dirname,'.. . / SRC/main js')}, the output: {filename: '[name] [8] hash: js', / / package after the file name path: the path. The resolve (__dirname,'.. / dist ') / / directory} after packaging, the module: {rules: [{test: / \. Vue $/, use: [' vue - loader]}, {test: / \. Js $/, use:{ loader:'babel-loader', options:{ presets:[ ['@babel/preset-env'] ] } } }, { test:/\.css$/, use: ['vue-style-loader','css-loader',{ loader:'postcss-loader', options:{ plugins:[require('autoprefixer')] } }] }, { test:/\.less$/, use: ['vue-style-loader','css-loader',{ loader:'postcss-loader', options:{ plugins:[require('autoprefixer')] } },'less-loader'] } ] }, resolve:{ alias:{ 'vue$':'vue/dist/vue.runtime.esm.js', ' @':path.resolve(__dirname,'.. /src') }, extensions:['*','.js','.json','.vue'] }, devServer:{ port:3000, hot:true, contentBase:'.. /dist' }, plugins:[ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template:path.resolve(__dirname,'../public/index.html'), filename:'index.html' }), New vueLoaderPlugin (), new Webpack. HotModuleReplacementPlugin ()]} copy codeCopy the code

2.3 Configuring packaging Commands

Now that the package file is configured, let’s test it by creating a new main.js file from SRC

Create a new app.vue

Create a new public folder and create a new index.html folder inside it

NPM run dev if the browser displays the Vue development environment running successfully, congratulations, you have successfully taken the first step

2.4 Distinguish between development environment and production environment

In the actual application of the project, we need to distinguish the development environment and production environment, we added two files on the basis of the original webpack.config.js

  • Webpack.dev.js development environment configuration file

    The development environment mainly implements hot updates, no compression code, full sourceMap copy code

  • Webpack.prod. js Production environment configuration file

    Production environment is mainly implemented to compress the code, extract CSS files, reasonable sourceMap, segmentation code needs to install the following modules: NPM I -D webpack-merge copy-webpack-plugin optimize- CSS -assets-webpack-plugin uglifyjs-webpack-plugin

  • Webpack-merge Merges configurations

  • Copy-webpack-plugin copies static resources

  • Optimize the CSS – assets – webpack – the plugin CSS

  • Js uglifyjs webpack – plugin compression

Webpack Mode sets production to automatically compress JS code. In principle, there is no need to introduce uglifyjs-webpack-plugin to duplicate the work. But the optimize- CSS -assets-webpack-plugin will destroy the original JS compression at the same time, so here we introduce UglifyJS compression

Against 2.4.1 webpack. Config. Js

const path = require('path') const {CleanWebpackPlugin} = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const vueLoaderPlugin = require('vue-loader/lib/plugin') const MiniCssExtractPlugin = require("mini-css-extract-plugin") const devMode = process.argv.indexOf('--mode=production') === -1; module.exports = { entry:{ main:path.resolve(__dirname,'.. /src/main.js') }, output:{ path:path.resolve(__dirname,'.. /dist'), filename:'js/[name].[hash:8].js', chunkFilename:'js/[name].[hash:8].js' }, module:{ rules:[ { test:/\.js$/, use:{ loader:'babel-loader', options:{ presets:['@babel/preset-env'] } }, exclude:/node_modules/ }, { test:/\.vue$/, use:[{ loader:'vue-loader', options:{ compilerOptions:{ preserveWhitespace:false } } }] }, { test:/\.css$/, use:[{ loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader, options:{ publicPath:"../dist/css/", hmr:devMode } },'css-loader',{ loader:'postcss-loader', options:{ plugins:[require('autoprefixer')] } }] }, { test:/\.less$/, use:[{ loader:devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader, options:{ publicPath:"../dist/css/", hmr:devMode } },'css-loader','less-loader',{ loader:'postcss-loader', options:{ plugins:[require('autoprefixer')] } }] }, { test:/\.(jep?g|png|gif)$/, use:{ loader:'url-loader', options:{ limit:10240, fallback:{ loader:'file-loader', options:{ name:'img/[name].[hash:8].[ext]' } } } } }, { test:/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, use:{ loader:'url-loader', options:{ limit:10240, fallback:{ loader:'file-loader', options:{ name:'media/[name].[hash:8].[ext]' } } } } }, { test:/\.(woff2?|eot|ttf|otf)(\?.*)?$/i, use:{ loader:'url-loader', options:{ limit:10240, fallback:{ loader:'file-loader', options:{ name:'media/[name].[hash:8].[ext]' } } } } } ] }, resolve:{ alias:{ 'vue$':'vue/dist/vue.runtime.esm.js', ' @':path.resolve(__dirname,'.. /src') }, extensions:['*','.js','.json','.vue'] }, plugins:[ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template:path.resolve(__dirname,'../public/index.html') }), new vueLoaderPlugin(), new MiniCssExtractPlugin({ filename: devMode ? '[name].css' : '[name].[hash].css', chunkFilename: DevMode? '[id]. CSS' : '[id]. [hash] CSS'}})] duplicate codeCopy the code

Webpack 2.4.2. Dev. Js

const Webpack = require('webpack') const webpackConfig = require('./webpack.config.js') const WebpackMerge = require('webpack-merge') module.exports = WebpackMerge(webpackConfig,{ mode:'development', devtool:'cheap-module-eval-source-map', devServer:{ port:3000, hot:true, contentBase:'.. / dist '}, plugins: [new Webpack. HotModuleReplacementPlugin ()]}) duplicate codeCopy the code

2.4.3 webpack. Prod. Js

const path = require('path') const webpackConfig = require('./webpack.config.js') const WebpackMerge = require('webpack-merge') const CopyWebpackPlugin = require('copy-webpack-plugin') const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') const UglifyJsPlugin = require('uglifyjs-webpack-plugin') module.exports =  WebpackMerge(webpackConfig,{ mode:'production', devtool:'cheap-module-source-map', plugins:[ new CopyWebpackPlugin([{ from:path.resolve(__dirname,'../public'), to:path.resolve(__dirname,'../dist') }]), Optimization :{minimizer:[new UglifyJsPlugin({// compress JS cache:true, parallel:true, sourceMap:true}), optimization:{minimizer:[new UglifyJsPlugin({// compress JS cache:true, parallel:true, sourceMap:true}), new OptimizeCssAssetsPlugin({}) ], splitChunks:{ chunks:'all', cacheGroups:{ libs: { name: "chunk-libs", test: / / \ \ / node_modules / \ \ / /, priority: 10, chunks: "initial" / / packaging only initial rely on third-party}}}}}) duplicate codeCopy the code

2.5 Optimize the WebPack configuration

You may be tired of reading this, but if you want to get a better offer and a higher salary, you need to go further

[

](postimg.cc/ZCRcC9tS)

Optimizing the configuration is very practical for us, it is actually related to the size of the file you package, packaging speed and so on. Specific optimization can be divided into the following points:

2.5.1 Optimize packaging speed

Build speed refers to how quickly we update the code after each change and how quickly we package the files before release.

2.5.1.1 Properly Configure mode and Devtool parameters

If the mode parameter is not set, – Webpack4 sets mode default to Production tree shaking and Uglifyjs

2.5.1.2 Narrowing the search scope of files (Include Exclude alias noParse Extensions)

  • Alias: When import ‘vue’ appears in our code, Webpack does a recursive upward search to node_modules. To reduce the search area we can simply tell Webpack which path to look for. This is the configuration of aliases.

  • Include Exclude Also reduces the search conversion time of the WebPack Loader.

  • NoParse When we import jq from ‘jquery’ in our code, WebPack will parse whether the jQ library is dependent on other packages. However, we generally assume that we don’t refer to other packages (except for special ones, you can judge by yourself) for libraries such as jquery. Add a noParse attribute that tells WebPack not to parse to increase packaging speed.

  • Extensions WebPack looks for files based on the suffix defined by extensions (more frequent file types go first)

2.5.1.3 Enabling Multi-process Loader Conversion using HappyPack

Most of the time spent in webPack building is actually spent on loader parsing and conversion and code compression. In daily development, we need to use Loader to convert JS, CSS, pictures, fonts and other files, and the amount of file data to convert is also very large. Due to the single-threaded nature of JS, these conversion operations cannot process files concurrently, but need to process files individually. The basic principle of HappyPack is to reduce the total build time by splitting the task into several sub-processes, which then send the results to the main process

NPM i-d happypack copies the codeCopy the code

2.5.1.4 Use webpack-parallel-ugli-fi -plugin to enhance code compression

The above loader conversion has been optimized, so there is another difficulty is to optimize the compression time of the code.

NPM i-d webpack-parallel-ugli-fi -plugin copies the codeCopy the code

2.5.1.5 Removing a third-party Module

Statically dependent files that do not change often in a development project. Similar to our elementUi, Vue family bucket, etc. Since changes are rare, we don’t want these dependencies to be integrated into every build logic. The advantage of this is that every time I change the files of my native code, WebPack only needs to package the files of my project instead of compiling third-party libraries. In the future, webPack will not pack these libraries as long as we do not upgrade third-party packages, which can quickly improve the packaging speed.

Here, we use the DllPlugin DllReferencePlugin built in WebPack to extract it and create webpack.dll.config.js in the same directory as the webPack configuration file with the following code

// webpack.dll.config.js const path = require("path"); const webpack = require("webpack"); Module. exports = {// Array entry of modules you want to package: {vendor: ['vue','element-ui']}, output: {path: Path.resolve (__dirname, 'static/js'), // File output location filename: '[name].dll. '[name]_library' // this needs to be the same as' name: '[name]_library', 'in webpack.dllPlugin. }, plugins: [ new webpack.DllPlugin({ path: path.resolve(__dirname, '[name]-manifest.json'), name: '[name]_library', context: __dirname }) ] }; Copy the codeCopy the code

Configure the following command in package.json

"DLL" : "webpack - config build/webpack. DLL. Config. Js" duplicate codeCopy the code

Next add the following code to our webpack.config.js

module.exports = { plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: Require ('./vendor-manifest.json')}), new CopyWebpackPlugin([// copy the generated file to dist directory so that no manual CV {from: 'static', to:'static'} ]), ] }; Copy the codeCopy the code

perform

NPM Run DLL copies the codeCopy the code

We’ll find vendor.dll.js that generates the collection third place code we need to import this js file manually in the HTML file

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Initial-scale =1.0"> <meta http-equiv=" x-UA-compatible "content=" IE =edge" SRC = "static/js/vendor. DLL. Js" > < / script > < / head > < body > < div id = "app" > < / div > < / body > < / HTML > duplicate codeCopy the code

This way we don’t need NPM run DLLS if we don’t update third-party dependencies. When we directly execute NPM run dev NPM run build, we will find that our packaging speed has been significantly improved. This is because we have removed third-party dependencies through the dllPlugin.

2.5.1.6 Configuring cache

Every time we perform a build, we compile all the files repeatedly. Can this repeated work be cached? The answer is yes, most loaders provide cache configuration items. For example, in babel-loader, caching can be enabled by setting cacheDirectory. CacheDirectory =true writes the result of each compilation to a hard disk file (default: node_modules/. Cache /babel-loader at the root of the project, but you can also customize it)

But what if loader doesn’t support caching? We also have a method, we can use cache-loader, it is very simple to do, babel-loader after the cache is enabled, loader compile results to the hard disk cache. The next build will compare the files and use the cache directly if the files have not changed from the previous one. Use the loader as shown in the official demo. Add the Loader before other loaders that have high performance overhead

NPM i-d cache-loader copies the codeCopy the code

2.5.2 Optimizing the Volume of packed Files

We have optimized the packaging speed, but the file volume after packaging is very large, resulting in slow page loading and waste of traffic, etc. Next, let’s continue to optimize the file volume

2.5.2.1 Importing Webpack-bundle-Analyzer to analyze packaged files

Webpack-bundle-analyzer presents bundles of packaged content as an interactive, intuitive tree that lets us know what we’re really introducing into the packages we’re building

NPM I -D webpack-bundle-Analyzer copies codeCopy the code

Next configure the startup command in package.json

"Analyz ": "NODE_ENV=production npM_config_report =true NPM run build" Copy codeCopy the code

Windows Install NPM I -d cross-env

"Analyz ": "cross-env NODE_ENV=production npM_config_report =true NPM run build" Copy codeCopy the code

The NPM Run Analyz browser will then automatically open a page with the file dependency graph

2.5.2.3 externals

If we want to reference a library, but don’t want webpack, and don’t want to use it globally in programs like CMD, AMD, or Window /global, we can configure Externals. This function is mainly used to create a library, but we can also make full use of Externals in our project development. We remove these static resources that do not need to be packaged from the build logic, and use CDN to reference them.

Sometimes we want a library we’ve introduced through script, such as jquery with CDN, to be used in require, but we don’t want WebPack to compile it into a file. Here the official website case is clear enough, you can click to understand

The webpack official website case is as follows

< script SRC = "https://code.jquery.com/jquery-3.1.0.js" integrity = "sha256 - slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk =" Crossorigin ="anonymous"> </script> module. Exports = {// exports... externals: { jquery: 'jQuery' } }; Import $from 'jquery'; $('.my-element').animate(/* ... * /); Copy the codeCopy the code

2.5.2.3 Tree – shaking

Tree-shaking is mentioned separately because there is a pit. Tree-shaking is mostly used to weed out useless parts of code. Currently, tree-shaking is automatically enabled in webpack4 when we set mode to production. But for this to work, the generated code must be an ES6 module. You cannot use other types of modules such as CommonJS streams. There is a slight problem if Babel is used, because Babel’s preset preset presets any module type to CommonJS by default, which would cause tree-shaking to fail. Fixing this is as simple as setting modules: false in the.babelrc file or in the webpack.config.js file

//. Babelrc {"presets": [["@babel/preset-env", {"modules": false}]]Copy the code

or

// webpack.config.js module: { rules: [ { test: /\.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env', {modules: false}]}}, exclude: /(node_modules)/}]} copy the codeCopy the code

After the baptism of the above two series, we have become a qualified Webpack configuration engineer. But light screw, self substitutability is still very high, we will go into the principle of Webpack

3 Handwritten Webpack series

Having experienced the above two parts, we can skillfully use the relevant Loader and plugin to transform and parse our code. Next, we manually implement loader and plugin to make it more fun in the usual development.

3.1 Handwritten Webpack Loader

Loader is essentially a Node module. It is equivalent to a juicer giving it file codes of relevant types. According to the rules we set, the processed juice (code) will be returned to us after a series of processing.

Loader writing Principles

  • Single principle: everyLoaderDo only one thing;
  • Chain call:WebpackEach will be called in a sequential chainLoader;
  • Unity principle: FollowWebpackFormulate design rules and structure, input and output are strings, eachLoaderFully independent, plug and play;

In everyday development environments, we tend to include a lot of console printing for debugging purposes. But we don’t want printed values in production. So here we implement a loader ourselves to remove the console from the code

AST for knowledge popularization. AST in layman’s terms, suppose we have a file a.js and we do something to the 1000 lines in a.js, such as adding try catches to all await and so on, but the code in A.js is essentially a bunch of strings. So what we can do is convert it to an object with marked information (an abstract syntax tree) that we can add, delete, change and look up easily. This tagged object (abstract syntax tree) is the AST. Here is a good AST article AST quick start

NPM I -d @babel/parser @babel/traverse @babel/generator @babel/types Copy codeCopy the code
  • @babel/parserParse the source code intoAST
  • @babel/traverseASTThe node is recursively traversed to generate an easy to operate, transformpathobject
  • @babel/generatorASTDecoding generatedjscode
  • @babel/typesThrough the module for specificASTAdd, delete, change, and query nodes

The new drop – the console. Js

const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const generator = require('@babel/generator').default const t = require('@babel/types') module.exports=function(source){ const ast = parser.parse(source,{ sourceType: 'module'}) traverse(ast,{ CallExpression(path){ if(t.isMemberExpression(path.node.callee) && t.isIdentifier(path.node.callee.object, {name: "console"})){ path.remove() } } }) const output = generator(ast, {}, source); Return output.code} Copy codeCopy the code

How to use

const path = require('path') module.exports = { mode:'development', entry:path.resolve(__dirname,'index.js'), output:{ filename:'[name].[contenthash].js', path:path.resolve(__dirname,'dist') }, module:{ rules:[{ test:/\.js$/, Use :path.resolve(__dirname,'drop-console.js')}]}} Copy codeCopy the code

In fact, removing the console is already integrated in webpack4 and can be configured in minimizer to remove the console

How to write a loader

3.2 Handwritten Webpack Plugin

A number of events are broadcast during the life cycle of a Webpack run, and the Plugin can listen for these events and change the output when appropriate through the API provided by Webpack. In layman’s terms: A dish of delicious scrambled eggs with salted beans needs to go through the process of frying with oil and seasoning to the final loading, etc. Plugin can monitor every link and operate, for example, a plugin can be written to monitor the life cycle events exposed by Webpack (seasoning), and perform the operation of reducing pepper when seasoning. So what distinguishes it from Loader? Loader can only do one thing. For example, less-loader can only parse less files. Plugin can perform a wide range of tasks for the whole process.

A basic plugin plugin structure is as follows

class firstPlugin { constructor (options) { console.log('firstPlugin options', options) } apply (compiler) { compiler.plugin('done', Compilation => {console.log('firstPlugin'))}} module.exports = firstPluginCopy the code

Compiler, compilation what is?

  • compilerThe object containsWebpackAll configuration information for the environment. This object is starting upwebpackIs created once and configured with all actionable Settings, includingoptions.loaderplugin. When inwebpackWhen a plug-in is applied to the environment, the plug-in receives thiscompilerObject reference. You can use it to accesswebpackThe main environment of.
  • compilationObject contains the current module resources, build resources, changing files, and so on. When runningwebpackWhen developing environment middleware, each time a file change is detected, a new one is createdcompilationTo generate a new set of compile resources.compilationObject also provides a number of critical timing callbacks that plug-ins can choose to use when doing custom processing.

The difference between compiler and compilation is

  • Compiler represents the entire WebPack lifecycle from startup to shutdown, while compilation simply represents a new compilation process

  • Compiler and compilation expose a number of hooks that can be customized according to the actual scenarios we need

Compiler hook document

Compilation hook document

Let’s manually develop a simple requirement to automatically generate information about the size of the packaged file before generating it

Create a new webpack-firstplugin.js

class firstPlugin{ constructor(options){ this.options = options } apply(compiler){ compiler.plugin('emit',(compilation,callback)=>{ let str = '' for (let filename in compilation.assets){ str += ${compilation. Assets [filename]['size']()}\n '} [' filesize.md '] = {source:function(){return STR}, Size :function(){return str.length}} callback()})}} module.exports = firstPluginCopy the code

How to use

Const path = require('path') const firstPlugin = require(' webpack-firstplugin.js ') module.exports = {// omit other code Plugins :[new firstPlugin()]} Copy codeCopy the code

Execute NPM run build to see filesize.md generated in the dist folder with information about the packaged files

The above two loader and Plugin cases are only a guide. There are many aspects of loader and plugin to consider in the actual development requirements. It is recommended that you try more by yourself.

How to write a plugin on the official website

3.3 write a webpack

Because the length is too long, and the principle is more in-depth. In view of this quick start to practical development principle, we decided to write a new article that dissects webPack principles and implements a demo version. Once the format is calibrated, a link will be posted below

4 WebPackage 5.0 era

Both front-end frameworks and build tools are being updated faster than we ever imagined, and the jquery of a few years ago is gone forever. We want to embrace the constant iteration of Vue, React, Node, Serverless, Docker, K8S….

Not to be outdone, WebPack has recently released WebPack 5.0.0 Beta 10. Webpack5.0 is designed to reduce configuration complexity and make it easier to get started (as was said in webpack4), as well as some performance improvements

  • Use persistent caching to improve build performance;
  • Improve long-term caching with better algorithms and default values;
  • Clean up the internal structure without introducing any disruptive changes;
  • Introduce some breaking changes to use v5 for as long as possible.

Currently, updates from maintainers are frequent, and it won’t be long before webpack5.0 will embrace the masses. If you’re interested, you can install the beta version first. But before that, I suggest you to master Webpack4, so that the road behind will be more and more easy to go.

Author: Huang Xiaobug Link: juejin.cn/post/684490… The copyright belongs to the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.