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
./node_modules/webpack/bin
npx webpack
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:
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
- 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