1、接着上次的代码
index.js:
import '@babel/polyfill';
import React, { Component } from 'react';
import ReactDom from 'react-dom';
class App extends Component {
render() {
return hello world
}
}
ReactDom.render( , document.getElementById('root'));
实际上当前版本的Webpack比较新,如果在webpack.config.js里面配置了babel-loader的相关内容(其实我们已经把它移到.babelrc这个文件中了),如果我们对presets下的useBuiltIns设置了usage这样的一个配置参数的话。那么我们index.js文件中不去引入babel/polyfill也是可以的。
2、
目录:
index.js:
import {add} from './math.js';
add(1, 2);
math.js:
export const add = (a, b) => {
console.log(a + b);
}
export const minus = (a, b) => {
console.log(a - b);
}
执行npx webpack,控制台打印出3。但是我们去main.js里面发现我们没有import的minus方法也打包了。这是没有必要的,因为我们的业务代码只引入了add方法,将minus也打包会导致打包后的main.js过大。最理想的打包方式就是我们引入什么就打包什么。解决办法:
webpack在2.0版本以后提出了Tree shaking这个概念:实际上就是把一个模块里没用的东西都摇晃(shaking表示摇晃)掉。一个模块可以理解成一个树,比如说math.js文件是一个模块,里面会导出很多的内容,这些内容可以理解成一个小的树形结构。而我们在index.js中只引入树的一部分,只需要把引入的那一部分打包即可。
注意: Tree-shaking只支持ES module模块引入的方式引入。
配置Webpack.config.js:
mode设置成 'development'的时候是没有Tree shanking这个功能的,所以我们需要自己配置:
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',
port: 10000,
hot: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader:'babel-loader',
},
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}, {
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(),
new webpack.HotModuleReplacementPlugin()
],
optimization: { //在开发环境中配置Tree shaking
usedExports: true //意思就是去打包引入的模块
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
package.json:
小知识:
{
"name": "webpackDemo",
"sideEffects": false,
"version": "1.0.0",
"description": "",
"main": "postcss.config.js",
"dependencies": {
"@babel/polyfill": "^7.4.4",
"@babel/runtime-corejs2": "^7.5.4",
"autoprefixer": "^9.6.1",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^3.1.4",
"css-loader": "^3.0.0",
"file-loader": "^4.0.0",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.12.0",
"postcss-loader": "^3.0.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"url-loader": "^2.0.1",
"webpack": "^4.35.3",
"webpack-cli": "^3.3.5",
"webpack-dev-server": "^3.7.2"
},
"private": true,
"devDependencies": {
"@babel/core": "^7.5.4",
"@babel/plugin-transform-runtime": "^7.5.0",
"@babel/preset-env": "^7.5.4",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.6"
},
"scripts": {
"start": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC"
}
"sideEffects": false, 意思是比如index.js中引入了babel/polyfill文件,由于它没有导出任何东西,用了Tree-shaking后导致打包会忽略它。因为当前index.js中没有引入像babel/polyfill这样的包,所以设置成false。表示没有需要特殊处理的东西。
重新执行打包,发现还是把minus给打包了,这是因为在开发环境中生成的代码需要做一些调试,如果Tree-shaking把一些代码删除掉的话
,在做调试的时候,代码对应的sourcemap的行数就都错了。所以Tree-shaking还是会保留这些代码。不过从下图可以看出,它已经提示我们了导出的只用了add这个模块。
production环境下:此时Tree-shaking就会生效了。配置如下:
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: 'production',
devtool: 'cheap-module-source-map',
entry: {
main: './src/index.js'
},
devServer: {
contentBase: './dist',
port: 10000,
hot: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader:'babel-loader',
},
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}, {
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(),
new webpack.HotModuleReplacementPlugin()
],
//该模式下Tree-shaking的配置自动就会写好了,我们不用自己配webpack.config.js
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
}
}
再次执行打包后发现Tree-shaking就生效了:
1、当我们项目开发完毕,就需要使用 production模式打包上线。两种打包模式的差异:
2、我们切换两种模式的时候,需要经常修改webpack.config.js文件,这样很麻烦。解决办法:
webpack.dev.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
port: 10000
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}, {
test: /\.jpg$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
}, {
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()
],
optimization: { //在开发环境中配置Tree shaking
usedExports: true //意思就是去打包引入的模块
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
webpack.prod.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
devtool: 'cheap-module-source-map',
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}, {
test: /\.jpg$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
}, {
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()
],
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
package.json:
dev: 如果要启动devserver进行开发的话,那么就是使用开发环境的webpack配置
build: 如果打包线上文件的话,就使用webpack.prod.js这个配置
此时运行npm run dev,进行的是开发环境的webpack打包配置。运行npm run build,执行的是生产环境的打包配置。
3、上面的两种打包方案的文件中存在大量的重复代码,优化方案:
webpack.common.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
entry: './src/index.js',
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}, {
test: /\.jpg$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
}, {
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()
],
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
webpack.dev.js:
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
port: 10000
},
optimization: { //在开发环境中配置Tree shaking
usedExports: true //意思就是去打包引入的模块
}
}
module.exports = merge(commonConfig, devConfig); //将自有的和公共的做一个结合
webpack.prod.js:
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map'
}
module.exports = merge(commonConfig, prodConfig);
此时能够正常进行两种不同方案的打包。
注意:如果将三个文件都放在一个文件夹下面,此时package.json文件的命令路径需要修改,比如在build文件夹下:
"dev": "webpack-dev-server --config ./build/webpack.dev.js"
此时应该修改output配置:
Code Splitting:指的是代码分割。
此时的目录结构:
因为我们希望查看打包后的文件代码,所以不能用npm run dev来打包,添加命令:
1、例子
import _ from 'lodash';
console.log(_.join(['a', 'b', 'c'])); //a,b,c
打包后打印出a,b,c。
假如我们index.js的业务逻辑很多,打包的话会把工具库和业务逻辑都打包到main.js文件中。此时虽然也能正常运行,但是main.js会比较大,导致加载时间很长。还有就是假如我们修改了业务代码,用户要重新去加载main.js,此时又要等很长的时间,这样就导致用户体验很差。
解决办法:
import _ from 'lodash';
window._ = _; //加载了一个lodash,又将lodash挂载到了全局的下划线上面,这样的话我们在其它地方就可以是使用下划线这个变量了。
console.log(_.join(['a', 'b', 'c'])); //a,b,c
entry: {
lodash: './src/lodash.js',
main: './src/index.js'
}
再次打包,运行正常。
此时打包后分成了两个js文件,这带来的好处就是当我们修改了业务代码,用户只需要加载打包后的业务js代码即可,不用加载库的代码。
在项目中对代码公用部分进行拆分来提升项目运行的速度,也就是Code Splitting。
2、上面的例子我们是自己做的公用代码拆分,它不够智能。webpack通过它自带的一些插件可以智能地帮助我们做一些拆分工作。
import _ from 'lodash';
console.log(_.join(['a', 'b', 'c'])); //a,b,c
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}, {
test: /\.jpg$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
}, {
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()
],
optimization: {
splitChunks: {
chunks: 'all' //意思就是我要帮你去做代码分割了
}
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].js'
}
}
此时执行npm run dev-build打包命令,发现dist文件夹下面打包了两个js文件,一个是业务逻辑js,还有一个是类库js。
这种代码分割配置适合上面这种同步模块的分割。
3、webpack中的代码分割不仅仅适合上面这种同步模块的分割。异步的代码也能进行分割,而且不需要添加上面的那种配置就会自动进行。
index.js:
function getComponent() {
return import('lodash').then(( { default: _ } ) => {
var element = document.createElement('div');
element.innerHTML = _.join(['z', 't'], '-');
return element;
})
};
getComponent().then( element => {
document.body.appendChild(element);
});
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}, {
test: /\.jpg$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
}, {
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()
],
// optimization: {
// splitChunks: {
// chunks: 'all' //意思就是我要帮你去做代码分割了
// }
// },
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].js'
}
}
执行打包命令,此时webpack对于这种异步的代码也能智能地进行分割。
webpack的代码分割底层使用了SplitChunksPlugin这个插件。
上面打包之后的dist文件夹:
1、 我们希望把 0.js 改一个名字
function getComponent() {
//下面的意思就是异步地引入lodash这样一个库,当我做代码分割的时候,单独给lodash进行打包的时候,给它起个名字叫lodash
return import(/* webpackChunkName: "lodash" */'lodash').then(( { default: _ } ) => {
var element = document.createElement('div');
element.innerHTML = _.join(['z', 't'], '-');
return element;
})
};
getComponent().then( element => {
document.body.appendChild(element);
});
{
"presets":[
[
"@babel/preset-env",
{
"useBuiltIns": "usage"
}
],
"@babel/preset-react"
],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
执行 npm run dev-build 命令后,
打包的lodash.js文件名却不是我们给的那个,修改webpack.common.js配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}, {
test: /\.jpg$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
}, {
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()
],
optimization: {
splitChunks: {
chunks: 'all', //意思就是我要帮你去做代码分割了
cacheGroups: {
vendors: false,
default: false
}
}
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].js'
}
}
执行打包命令后,dist文件夹下:
2、SplitChunks配置介绍:
splitChunks: {
chunks: "async", //在做代码分割的时候,只对异步代码生效。
minSize: 30000, //发现引入的块大于30000个字节的话,就会帮我们做代码分割
minChunks: 1, //当一个Chunk被至少用了多少次的时候,才对他进行代码分割
maxAsyncRequests: 5, //同时加载的模块数,最多是5个
maxInitialRequests: 3, //整个网站首页进行加载的时候,或者说入口文件进行加载的时候,入口文件可能会引入其它的js文件,入口文件如果做代码分割,最多只能分割出3个。此处一般不修改
automaticNameDelimiter: '~', //组和文件名之间连接的符号
name: true, //打包生成的名字通过cacheGroup设置有效。此处一般不变
cacheGroups: { //如果都符合下面俩个组的要求,那么谁的priority值大,就用谁的
vendors: { //vendors组
test: /[\\/]node_modules[\\/]/, //如果是从node_modules引入的
priority: -10
},
default: { //这个组里面没有test,意思就是所有的模块都符合要求
minChunks: 2,
priority: -20,
reuseExistingChunk: true //如果一个模块已经被打包了,再打包就会忽略这个模块,直接使用之前使用的那么模块就可以
}
}
}
对同步的代码进行分割:
splitChunks: {
chunks: "all", //1 同步和异步代码都会做分割,initial表示对同步代码做分割
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: { //2
test: /[\\/]node_modules[\\/]/,
priority: -10,
filename: 'vendors.js' //打包后的名字不带main,只有vendors
},
default: false
}
}
1、实现一个懒加载的行为
index.js:
function getComponent() {
return import(/* webpackChunkName: "lodash" */'lodash').then(( { default: _ } ) => {
var element = document.createElement('div');
element.innerHTML = _.join(['z', 't'], '-');
return element;
})
};
document.addEventListener('click', () => {
getComponent().then( element => {
document.body.appendChild(element);
});
})
webpack.common.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}, {
test: /\.jpg$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
}, {
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()
],
optimization: {
splitChunks: {
chunks: 'all', //意思就是我要帮你去做代码分割了
cacheGroups: {
vendors: false,
default: false
}
}
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].js'
}
}
执行打包命令,此时控制台的network只加载了main.js:
再页面点击之后,才加载lodash.js文件:
上面这种做法的好处是能够让页面的加载速度更快,只加载该页面用到的js代码。
可以使用async函数来实现上面的效果,index.js:
async function getComponent() {
const { default: _ } = await import(/* webpackChunkName: "lodash" */'lodash');
const element = document.createElement('div');
element.innerHTML = _.join(['z', 't'], '-');
return element;
}
document.addEventListener('click', () => {
getComponent().then( element => {
document.body.appendChild(element);
});
})
2、Chunk
Webpack打包过后生成了几个js文件,每个js文件我们都把它叫做一个Chunk。
1、打包分析:当我们使用Webpack进行打包之后,我们可以借助打包分析的工具来对我们的打包文件进行一定的分析,然后看打包是否合理。
打包分析网址
上面网址中的教程:
除了这个分析方法之外,还有很多别的方法,可以去webpack的文档中查看。
import './style.css';
body {
background-color: green;
}
执行打包后发现虽然dist目录下没有css文件,但是页面却又效果。这是因为webpack在做打包的时候会把Css文件直接打包到js里面。
1、希望实现Css文件单独打包进dist文件夹下面。
借助 MiniCssExtractPlugin 这个插件来进来css代码分割。
npm install --save-dev mini-css-extract-plugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}, {
test: /\.jpg$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
limit: 2048
}
}
}, {
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader, //1
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] //2
}]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({})
],
optimization: {
splitChunks: {
chunks: 'all'
}
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name].js'
}
}
此时执行打包命令后发现dist目录下成功打包出了css文件。
2、做Css代码的压缩
const TerserJSPlugin = require('terser-webpack-plugin');
// 安装这两个插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
// 配置压缩
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};