I’ve read webpack articles and videos before, but I can’t remember them, so I decided to write a study note to summarize them, to ease my mind! Official Chinese version

1, Webpack understanding

1-1 What is webpack

Webpack is a module bundler Webapck Modern browsers already support the introduction of modules in ES6 ES Module, you can add type=" Module "attribute in <script> tag, and run the project on the server.

1-2 webpack installation

Generate packjson.json in your project via NPM init

NPM install webpack webpack-cli –save-dev to install local webpack

This way we can use WebPack for packaging in the project

  1. ./node_modules/webpack/bin
  2. npx webpack
  3. npm scripts

Detailed steps:Command Line Interface NPX webpack index.js can be packed with production dist/main.js, and index.html can be loaded with this JS and run successfullyDist file. Can the index.js file behind webpack not be specified, which brings us to the default configuration file

1-3 webpack configuration

// webpack.config.js default const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist'), }, };Copy the code

The entry is SRC /index.js, so the above example needs to specify the index.js of the sibling, and the output is dist/main.js. The entry here is a shorthand equivalent to:

// webpack.config.js
...
module.exports = {
    entry: {
        main: './src/index.js'
    },
    ...
}
Copy the code

1-4 to add

NPM scripts can be used to find the webpack under the project2. The default webpack configuration file is webpack.config.js, which can also be configured via webpack –confignpx webpack a.webpack.config.js

3. There are two webapck packaging modes: Production and development. The default is the former, that is, the code will be compressed, while development does not

This can be modified using the mode field:

Module. exports = {mode: 'production', entry: {main: './ SRC /index.js'},... }Copy the code

Get started with Webpack

What is 2-1 loader

Webpack recognizes js files by default, but for non-JS files you need to install the appropriate Loader to parse such files

file-loader

Using the file – loader to packaging picture files, for example, the result is dist/images/b417eb0f1c79e606d72c000762bdd6b7 JPG

// index.js
import img from './file.png'
console.log("img:",img)   //img:images/b417eb0f1c79e606d72c000762bdd6b7.jpg
let imgEle = new Image()
imgEle.src = img
document.body.appendChild(imgEle)
Copy the code
const path = require('path');

module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js'
	},
	module: {
		rules: [{
			test: /\.(jpe?g|png|gif)$/,
			use: {
				loader: 'file-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
                    // publicPath: 'dist/images/'
				}
			} 
		}]
	},
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist')
	}
}
Copy the code

Note the outputPath and publicPath. The former is the outputPath of the file, and the latter is the actual url path of the file, that is, the path of the return value

url-loader

const path = require('path');

module.exports = {
	...
	module: {
		rules: [{
			test: /\.(jpe?g|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
                    // publicPath: 'dist/images/',
                    limit: 10240
				}
			} 
		}]
	},
	...
}
Copy the code

Url-loader contains file-loader, and the limit attribute is used to set the threshold for transferring images to Base64. When the size is larger than the threshold, files will be generated. Otherwise, files will not be generated, but js files stored in base64 will be directly used (in performance optimization, small images can be generated in Base64. Reduce the number of HTTP requests, but it looks like base64 will increase the size of the image by 1/3.

style-loader

Load the CSS into the head

css-loader

Handle CSS reference relationships and merge them together

postcss-aloder

The Autoprefixer plug-in in postCSS-Loader automatically prefixes the browser vendor that the CSS3 attributes may require

sass-loader

SCSS files can be parsed

const path = require('path');

module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js'
	},
	module: {
		rules: [{
			test: /\.(jpe?g|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		},{
			test: /\.scss$/,
			use: [
				'style-loader', 
				'css-loader', 
				'sass-loader',
				'postcss-loader'
			]
		}]
	},
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist')
	}
}
Copy the code

Note that loaders are executed from right to left in the same order as CSS selectors are parsed

Postcss-loader requires a postcss.config.js file

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

css module

CSS modularity, because csS-loader is eventually inserted into style by style-loader, there may be style conflicts, you can configure modules:true in CSS-loader

const path = require('path');

module.exports = {
	...
	module: {
		rules: [...,{
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
                	loader: 'css-loader',
                    options: {
						importLoaders: 2,
						modules: true
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}]
	},
	...
}
Copy the code

Import stylecss from ‘./index.scss’ instead of importing from ‘./index.scss’

// index.js import stylecss from './index.scss' let div = document.createElement('div') div.innerhtml = 'I am a block' div.classList.add(stylecss.red) document.body.appendChild(div)Copy the code
// index.scss
body{
	.red{
    	color: red
    }
}
Copy the code

Dom structure after packaging:

I am a block

File-loader packages font files

Thanks to @font-face in CSS3, we can use any font we like on a web page. There are also dedicated websites (such as IconFont) that make font files out of ICONS.

If you use @font-face, then you need to specify webpack to pack these font files. As with images, font files can be packaged using file-loader

const path = require('path');

module.exports = {
	...
	module: {
		rules: [{
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}]
	},
	...
}
Copy the code

2-2 What are plugins

Plugins can do things for you at a certain point in the Webpack run (after the packaging has finished),

HtmlWebpackPlugin

After the package is completed, an HTML file is automatically generated, and the JS file generated by the package is automatically introduced into the HTML

const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'development', entry: { main: './src/index.js' }, module: { ... }, plugins: [new HtmlWebpackPlugin()], output: { ... }}Copy the code

But this only generates an empty template file

<! -- dist/index.html --> <! DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Webpack App</title> </head> <body> <script type="text/javascript" src="bundle.js"></script></body> </html>Copy the code

To meet this requirement, we can also specify an HTML template file

// webpack.config.js
module.exports = {
    ...
    plugins: [new HtmlWebpackPlugin({
        template: "src/index.html"
    })]
}
Copy the code

AddAssetHtmlWebpackPlugin

This will help us add some static resources to the generated HTML file.

// webpack.config.js
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
    ...
    plugins: [new HtmlWebpackPlugin({
        template: "src/index.html"
    }),
    new AddAssetHtmlWebpackPlugin({
        filepath: path.resolve(__dirname, 'assets/js/mj.js')
    })]
}
Copy the code

This will automatically introduce MJ.js to the generated index.html file

CleanWebpackPlugin

The dist file is automatically deleted before packaging

// webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
    ...
    plugins: [new HtmlWebpackPlugin({
        template: "src/index.html"
    }),
    new AddAssetHtmlWebpackPlugin({
        filepath: path.resolve(__dirname, 'assets/js/mj.js')
    }),
    new CleanWebpackPlugin(['dist'])]
}
Copy the code

2-3 Configuration of entry and Output

Entry mentioned earlier: ‘SRC /index.js’ equals

entry:{
	main:'src/index.js'
}
Copy the code

The packaged output file is dist/main.js

output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
Copy the code

So if we want multiple entrances, we can configure them this way

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js',
		sub-main: './src/index.js'
	},
	module: {
		...
	},
	plugins: [new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), new CleanWebpackPlugin(['dist'])],
	output: {
		publicPath: 'http://abc.com',
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
Copy the code

We can generate main.js and sub-main.js under dist, and HtmlWebpackPlugin will introduce both js files into HTML at the same time. If publicPath is configured: ‘http://abc.com’, specify the public domain name of the resource, then the imported JS file address will be http://abc.com/main.js http://abc.com/sub-main.js

Details: Entry output

2-4 SourceMap

When we write the JS file, we accidentally misspelled a grammatical word. When we package it, we will report the error of the packed JS file, such as the error of main.js1995 line, but we do not know which line of the source code

SourceMap is actually a mapping

You only need to configure devtool:value

  • Source-map: a. Map file is generated
  • Inline-source-map: inline-source-map: an additional map is mapped in JS to exactly which character is in which line
  • Cheap-inline-source-map: line-accurate, for your business code only
  • Cheap-module-inline-source-map: this applies to third party code
  • Eval: Fastest, best performance, but for more complex code, the hint may not be as complete

development devtool: 'cheap-module-eval-source-map'

production devtool: 'cheap-module-source-map'

Detailed steps: Devtool

2-5 WebpackDevServer

  • Listen for file changes –watch (file changes, automatic packaging, manual refresh, no local server)
  • DevServer (auto-pack, auto-update, start local server, vue, React)
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	entry: {
		main: './src/index.js'
	},
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
        proxy: {
        '/api': {
          target: 'http://localhost:3000',
          pathRewrite: {'^/api' : ''}
        }
    }
	},
	module: {
		rules: [{
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), new CleanWebpackPlugin(['dist'])],
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
Copy the code

Start test stop restart Run is not required

Webpackdevserver will not only help us with packaging, but also put the generated files into the computer memory to speed up the packaging

Details: DevServer

Development

2-6 Hot Module Replacement

When devServer is configured with HMR, it is mentioned that it will automatically package files and update files, but sometimes when we debug the style, the update data is lost, which is not convenient for debugging. We need to configure several options in DevServer

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	entry: {
		main: './src/index.js'
	},
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
        proxy: {
        '/api': {
          target: 'http://localhost:3000',
          pathRewrite: {'^/api' : ''}
        },
        hot: true,
        hotOnly: true
    }
	},
	module: {
		rules: [{
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), 
    new CleanWebpackPlugin(['dist']),
    new webpack.HotModuleReplacementPlugin()],
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
Copy the code

HotModuleReplacementPlugin webpack is bringing a plug-in. Hot: true means to start HRM, hotOnly: true means to only do HRM without automatic refresh.

After configuring the CSS, you can also see the effect of the change without refreshing, but the JS file still needs to use the API

// index.js import ABC from './abc.js' if (module.hot) {module.hot. Accept ('./ ABC. Function () {// callback when abc.js changes}); }Copy the code

CSS does not need to do this because CSS -loader already has this functionality built in, as does VUe-loader

Detailed steps: HMR

Detailed steps: HMR API

2-7 Babel

In this era of ES6, we are embarrassed to say that we know how to front-end ES6, because the new features of ES6 are very good, but some of the earlier versions of the browser do not support, so we need to use Babel to convert ES6 into ES5 or lower version of Babel official website

There is a difference between developing business code in Webpack and third-party libraries

  1. Business code way: PRESET -env + polyfill
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); T const CleanWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); module.exports = { ... module: { rules: [{ test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [['@babel/preset-env', { targets: { chrome: "67", }, useBuiltIns: 'usage' }]] } } }] } }Copy the code

Because Babel has too many configurations, you can extract them by creating. Babelrc configuration file

module.exports = {
	...
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/, 
            loader: 'babel-loader'
		}]
	}
}
Copy the code

Babelrc is created in the root directory

// .babelrc
{
	presets: [['@babel/preset-env', {
		targets: {
			chrome: "67",
		},
		useBuiltIns: 'usage'
		}]]
}
Copy the code

This will convert ES6 to ES5, but for promises, the browser may not implement them, so babel-polyfill is needed to implement the underlying implementation for the browser that doesn’t implement them

// index.js
import "@babel/polyfill";
function sleep(wait=2000){
	return new Promise((resolve,reject)=>{
    	setTimeout(()=>{
        	resolve()
        },wait)
    })
}
(async function(){
	await sleep(3000)
    console.log('let me exec')
})()

Copy the code

This allows me to write as much business code as I want, and because I’ve done all of the polyfills, the packaging volume is a little bit bigger, so in babelrc useBuiltIns: ‘usage’ means as needed, so I’m going to reduce the size of what I’m doing. Target :{chrome:”67″} indicates that the packaged file will run above Chrome67, at which point polyfill may not be implemented, as all above 67 May already be supported

2. Third-party library: transform-Runtime

npm install babel-loader @babel/core @babel/plugin-transform-runtime --save-dev
npm install --save @babel/runtime-corejs2
Copy the code
// .babelrc
{
	"plugins": [["@babel/plugin-transform-runtime", {
	"corejs": 2,
    "helpers": true,
    "regenerator": true,
    "useESModules": false
	}]]
}
Copy the code

ES5 code generated by polyfill is directly global and can pollute. The Runtime approach generates ES5 in a closure like form.

2-8 react packaging

JSX can be compiled with @babel/preset- React

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	entry: {
		main: './src/index.js'
	},
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true,
		hotOnly: true
	},
	module: {
		rules: [{ 
			test: /\.jsx?$/, 
			exclude: /node_modules/, 
			loader: 'babel-loader',
		}, {
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			} 
		}, {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		}, {
			test: /\.scss$/,
			use: [
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist']),
		new webpack.HotModuleReplacementPlugin()
	],
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	}
}
Copy the code
// .babelrc
{
	presets: [['@babel/preset-env', {
		targets: {
			chrome: "67",
		},
		useBuiltIns: 'usage'
		}],
     "@babel/preset-react"
    ]
}
Copy the code
// index.jsx import '@babel/polyfill' import React, {Component} from 'react' import ReactDom from 'react-dom' class App extends Component { render() { return <h1>React</h1>  } } ReactDom.render(<App/>, document.getElementById('app'))Copy the code

npm start ---> webpack-dev-server

React