从零开始学习搭建webpack


webpack 模块打包工具

一、安装webpack

npm install  webpack -D

####1. 创建项目

我们在合适位置新建一个文件夹wpk-test,用于存放我们的项目。
命令行中定位到webpack-test文件夹下,输入以下命令进行项目的初始化:

npm init

这里,要求设置很多选项,可以按项目情况配置也可以不填直接回车。完成后,我们发现文件夹中增加了package.json文件,它用于保存关于项目的信息

2.安装webpack-cli

我们在项目中本地安装webpack-cli:

npm install webpack-cli -D

二、打包

全局安装webpack时,

global

webpack xx.js

局部安装(推荐局部)

local

扫描二维码关注公众号,回复: 11623147 查看本文章

npx webpack xx.js
npx能帮助在寻找该文件下的webpack

用script脚本执行

"script":{
    "build":"webpack"
} 
用脚本运行会自动先寻找本地的webpack

三、 手动配置webpack

默认配置文件的名字 webpack.config.js

scripts脚本中命令可用run 执行。“webpack --config webpack.config.js” 配置 运行webpack的文件名 (指定配置文件)

1.基础配置

entry 入口文件 output输出文件

可以有多个入口文件, [name] 指代前面的 main sub

   const path  = require("path");
module.exports = {
	mode: 'development',
	entry: {
		main: './src/index.js',
		sub: './src/index.js'
	},
	output: {
		publicPath: 'http://cdn.com.cn', //g给打包后引入html的js添加统一前缀
		filename: '[name].js', //入口文件的打包会走这里
        chunkFilename: '[name].chunk.js'//间接引入的文件打包会走这里,
		path: path.resolve(__dirname, 'dist') //输出到dist
	}
}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>html 模版</title>
</head>
<body>
	<div id='root'></div>
<script type="text/javascript" src="http://cdn.com.cn/main.js"></script>
<script type="text/javascript" src="http://cdn.com.cn/sub.js"></script>
</body>
</html>

performance:false 关闭性能提升的警告,

resolve 配置默认查询后缀
{
    resolve:{
        extensions:['js','jsx'] ,//当你引入模块时,如果省略掉后缀就会默认先去找js,然后查找是否存在jsx
        //当配置过多,会过多查找,导致性能降低
            mainFiles:['index','child'], //引入文件夹时,默认引入下面的文件  
              alias:{
                  @:path.resolve(_dirname,'./src')   //配置别名
              }
    }
}
devtool配置

none 无配置,可以加快打包效率

eval 打包最快,性能最佳,会直接将业务代码当成eval()执行并映射也能定位到原文件,但当文件过大时,提示可能不全面

source-map 映射,会将打包后文件报错的等信息,映射到打包前的文件,而不是打包后的文件,方便查找定位错误 会在dist目录下生成 .map文件

inline-source-map 效果一样,但不会生成文件,会被文件转换为base64字符,放在main.js的底部

cheap-inline-source-map 没加cheap会定位精确到那个字符,会过多消耗性能,我们只需要定位到行即可

cheap-module-inline-source-map 没加module 是不会定位第三方模块等报错信息,加了module就会定位

cheap-module-eval-source-map 提示最好,性能也不错, 在开发环境推荐使用

chap-module-source-map 线上推荐使用

source-map 前面的前缀都可以任意组合使用,无关顺序

module.exports = {
    devtool:'none'
}
webpackDevSever配置

这种打包是放在内存中而不会生成打包文件

我们可以简单在packge.json中使用 webpack --watch 来监听源文件的变化,然后自动更新打包,但是这样当你需要ajax的时候,这样就不行了,所以,我们需要借助webpackDevSever

安装 webpack-dev-server 开启本地服务

npm i webpack-dev-server -D

config.js

module.exports = {
    ...
    	devServer: {
		contentBase: './dist', //开启服务器的文件
		open: true, //自动打开浏览器
		port: 8080,
         overlay: true, //当检测出错误的时候会在浏览器出现弹出层
	},
    ...
}

packge.json 添加启动命令

  "scripts": {
          "start": "webpack-dev-server",
  },

webpack-dev-middleware 用node自己编写一个本地sever

安装

npm i webpack-dev-middleware -D

配置 node sever.js

这只是简单的完成了功能,修改后需要手动刷新,页面,自动需要更多的配置

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
// 在node中直接使用webpack
// 在命令行里使用webpack
const complier = webpack(config); //编译代码,文件发生改变,就会运行

const app = express();

app.use(webpackDevMiddleware(complier, {}));

app.listen(3000, () => {
	console.log('server is running');
});

pakge.json

"scripts": {
          "sever": "node sever.js",
  },
HotModule Replacement 热模块更新

当我们修改页面时,并不想刷新页面,只想修改的模块部分刷新,这个时候就可以使用热更新

webpack.config.js

const webpack = require('webpack');
module.exports = {
    ...
    	devServer: {
		contentBase: './dist', //开启服务器的文件
		open: true, //自动打开浏览器
		port: 8080,
          hot: true, //开启热更新
		hotOnly: true //即时热更新不执行,浏览器也不会刷新页面
	},
    	plugins: [
		new webpack.HotModuleReplacementPlugin() //热更新插件
	],
    ...
}

xx.js

js实现需要添加下面的代码,来实现热更新效果,js需要监听更新,自己修改

if(module.hot) { //如果有热更新
    //监听指定文件的更新,监听到了,就会执行回调函数
	module.hot.accept('./xx.js', () => {
		//对检测到更新后的处理
	})
}

css-loader底层已经实现了热更新,vue, react框架中也自己实现了。

proxy代理

这个代理只对本地有效,只是方便开发的时候使用

devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		proxy: {
			'/react/api': {//当匹配到字符时代理到下面地址
				target: 'https://www.baidu.com',
				secure: false, //当转发到https时需要配置false
				pathRewrite: { //将匹配到的字符重写
					'header.json': 'demo.json'
				},
				changeOrigin: true, //有些网站对origin做了限制,开启即可
				headers: {
					host: 'www.dbaidu.com',
				}
			}
		}
	},

默认是无法对/ 根路径进行代理 如果需要添加配置 index:’’ 或者false

多个路径代理

proxy:[{
    context:['/auth','/api'],
    target: 'http://localhost:3000',
}]

四、loader

什么是loader,loader就是打包处理模块,处理webpack不认识的资源

loader的配置位置
	module: {
         //loaderd的顺序,默认是从右向左执行 从下向上
       // 单一loader时,use为一个对象,
        // 当需要使用多个loader时,user是一个数组
		rules: [{
			test: /\.jpg$/,
			use: {
				loader: 'file-loader',
                options:{
                    name:'[name].[ext]' , //打包后的文件名和后缀不变
                    outputPath:'images/' //打包到imgage文件下
                }
			}
		},
            {
        test:/\.less$/,
        use:[
        'style-loader',   
      'css-loader', //@import 解析路径
      'less-loader' // 将less转换成css
    		]
            }
               ]
	},
占位符palceholders

类似下面的[name] [ext]

		 {
			test: /\.jpg$/,
			use: {
				loader: 'file-loader',
                options:{
                    //paceholder 占位符
                    name:'[name].[ext]'  //打包后的文件名和后缀不变
                }
			}
		},

[contenthash] : 根据内容生成hash值,内容不变,hash值不改变

###(一)使用loader打包静态资源

1. 使用loader打包图片

file-loader打包图片


			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'file-loader',
                options:{
                    //paceholder 占位符
                    name:'[name].[ext]' , //打包后的文件名和后缀不变
                    outputPath: 'images/',
                }
			}
		},

用url-lodaer优化file-loader

url-lodaer 会将图片打包成bas64字符放入js文件中,如果图片比较小,就能优化图片的加载,不需要在请求图片,

同时我们也可以设置超过一定大小的,图片,打包到dist包中,而不是打包成base64

	module: {
		rules: [{
			test: /\.(jpg|png|gif)$/,
			use: {
				loader: 'url-loader',
				options: {
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240  //超过10kb打包成文件,小于打包到js中base64
				}
			} 
		}]
	},

2.打包样式处理

style-loader //将css插入head标签底部

css-loader //处理css

less less-loader//处理less

node-sass sass-lodaer //处理sass

postcss-loader autoprefixer //添加浏览器前缀 需要在package.json 中配置browserslist

  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 6"
  ],
module: {
		rules: [{
			test: /\.scss$/,
			use: [
				'style-loader', 
				'css-loader', 
				'postcss-loader',
				'sass-loader',
			]
		}]
	},

添加配置文件postcss.config.js

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

处理引入的sass文件中引入其他sass的打包

示例

//b.scss文件

@import './a.scss'

正常情况下, 我们会先通过scss,在通过css编译,单这种再次引入的文件中的scss语法就有可能不经过sass-loader,直接css-lodaer,这时,我们需要配置importLoaders,来让文件中import的文件也通过其他loader

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

css样式打包模块化 option配置modules:true

将scss打包成模块来使用,这样样式影响的就是局部,而不是全局

引用方式

import  ''./index.scss"  全局引入

变成模块引入   import style from './index.scss';

css模块化使用示例

index.scss

body {
	.imgStyle {
		width: 150px;
		height: 150px;
		transform: translate(100px, 100px);
	}
}

createImg.js

import avatar from './a.jpg';
import style from './index.scss';

function createImg() {
	var img = new Image();
	img.src = imgSrc;
	img.classList.add(style.imgStyle);

	var root = document.getElementById('root');
	root.append(img);
}

export default createAvatar;

index.js

import imgSrc from './a.jpg';
import style from './index.scss';
import createImg from './createImg';

createImg();

var img = new Image();
img.src = imgSrc;
img.classList.add(style.imgStyle);

var root = document.getElementById('root');
root.append(img);

3. 对于字体文件的处理

例如iconfont,webpack对于eot ttf等文件结尾并不认识,所以需要使用lodaer来打包,这里使用file-loader

在这里插入图片描述

 {
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader'
			} 
		},

五、plugin 插件

plugin 有点像生命周期,会在webpack运行到某一时刻的时候,帮你干点事情

安装

npm i html-webpack-plugin clean-webpack-plugin -D

html-webpapck-plugin 会在打包文件中自动生成html文件,同时将打包后的js,css文件注入到 html中

clean-webpack-plugin 帮我们打包前先自动清除dist目录下的东西

plugin使用示例

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'
	},
	module: {
	...loader
	},
	plugins: [new HtmlWebpackPlugin({
		template: 'src/index.html'
	}), new CleanWebpackPlugin(['dist'])],
}

六、 Babel处理Es6语法

文档

安装

npm install --save-dev babel-loader @babel/core
npm install @babel/preset-env --save-dev

babel/core babel的核心库,帮助babel识别js代码转换成AST抽象语法树

babel-loader 只是类似作为webpack与babel直接通信的桥梁,并不能将es6语法转换成es5

@babel/preset-env 将es6的语法转换为es5

config

module.exports ={
    ...
    module: {
      rules: [
        { test: /\.js$/, 
         exclude: /node_modules/, //排除  与下面相反
         include:path.resolve(_dirname,'../src'), //只有在src下的才执行
         loader: "babel-loader" ,
         options:{
      "presets": ["@babel/preset-env"]
    }
        }
      ]
    }
}

实现低版本语法兼容

npm install --save-dev @babel/polyfill

有些语法在低版本浏览器中不存在,这个时候,就需要这个包来自己实现,pormise,等语法

在js入口的顶部导入polyfill,以确保首先加载polyfill

import "@babel/polyfill";

这样还有个问题,这样会将一些没有用的到的,语法一起编译实现,会让打包文件变大

我们只想,实现用到的语法,没用到的不需要打包到文件中,(当配置usage,babel会默认引入,所以就不需要,自己再引入"@babel/polyfill")

添加配置项

module: {
  rules: [
    { test: /\.js$/, 
     exclude: /node_modules/,
     loader: "babel-loader" ,
     options:{
  	presets: [
      [
      "@babel/preset-env",
      {useBuiltIns:'usage'}
             ]
  ]
}
    }
  ]
}

这种编译注入,是全局变量,容易造成全局污染,不适合,写类库

类库的转换配置方式

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

npm install --save @babek/runtime-corejs2   //如果没有默认安装,手动安装

这样,帮助转换低版本语法时,会以闭包的形式注入

module: {
  rules: [
    { test: /\.js$/, 
     exclude: /node_modules/,
     loader: "babel-loader" ,
     options:{
  		  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": 2,
        "helpers": true,
        "regenerator": true,
        "useESModules": false,
      }
    ]
  ]
}
    }
  ]
}

config中的options,内容可以提取到根目录 .babelrc 文件中, config中的options就可以删除掉

例:

.babelrc

{
  		  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "absoluteRuntime": false,
        "corejs": 2,
        "helpers": true,
        "regenerator": true,
        "useESModules": false,
      }
    ]
  ]
}

七、webpack高级概念

1.Tree Shaking (剔除模块中未引入的代码)

只支持 ES Module

将模块比作树,我只引入一部分枝叶,没有引入的部分,不需要打包

简单就是当我们引入一个模块,我们只希望引入导出的,而不是模块整个的代码

在webpack中 mode ‘development’ 下默认是没有Tree Shaking的, 如果需要使用tree Shaking 需要配置

但是已经不会删除未引入的代码,因为本地需要调试,但是在打包文件中会用注释提示你已使用的部分,

config

module: {
 	optimization: {
		usedExports: true
	}
}

而在mode 'production’下,默认是开启了, 所以无需上面的配置,单需要下面的,同时打包后的文件是会删除多余代码

package.json

{
     "sideEffects": false, //表示所有文件都需要使用Tree Shaking
}
// 但是对于一些css等类似的没有导出的引入,tree Shaking就会判断为,未使用模块,因此打包会忽略
     @import  'style.css'
    import "@babel/polyfill";
  //  但是我们确实需要这些文件,因此我们需要配置那些文件不需要Tree Shaking,
    {
         "sideEffects": ['*.css', '@babel/polyfill'],  
    }
    

2. webpack 区分dev 和 pro 来管理

webpack的配置我们需要区分线上和开发模式,但2者也存在着共同的代码所以,我们提取出来公共部分,通过merge来合并,

根目录创建 build 文件 下面创建 build/webpack.dev.js,build/webpack.dev.js build/webpack.prod.js

安装

npm install webpack-merge -D

用于合并模块内容

packge.json

"scripts": {
    "dev-build": "webpack --config ./build/webpack.dev.js",
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.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|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'], {
			root: path.resolve(__dirname, '../')
		})
	],
	optimization: {
		splitChunks: {
      chunks: 'all'
    }
	},
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, '../dist')
	}
}

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);

webpack.dev.js

const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const devConfig = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin()
	],
	optimization: {
		usedExports: true
	}
}

module.exports = merge(commonConfig, devConfig);

第二种写法

packge.json

  "scripts": {
    "dev-build": "webpack --config ./build/webpack.common.js",
    "dev": "webpack-dev-server --config ./build/webpack.common.js",
    "build": "webpack --env.production --config ./build/webpack.common.js" //传人production
  },

webpack.prod.js

const prodConfig = {
    ....
}
module.exports = prodConfig;

webpack.dev.js

const devConfig = {
    ....
}
module.exports = devConfig;

webpack.common.js

const merge = require('webpack-merge');
const commomConfig = {
    ....
}
module.exports = (env) => {
	if(env && env.production) {
		return merge(commonConfig, prodConfig);
	}else {
		return merge(commonConfig, devConfig);
	}
}

3. Code Splitting 代码分割

当文件比如引入一个库,我们并不需要库,和业务逻辑都打包到main.js ,这个时候我们使用代码分割,可以将同步引入的库,与我们的业务逻辑做分离

[异步引入库](###4.lazy loading(懒加载))

webpack4底层默认添加了 SplitChunksPlugin插件,所以可以直接使用

开启代码分割 当 splitChunks为空对象时或者只配置 all,默认是如下配置

{
    ......
    optimization: {
            splitChunks: {
    chunks: "all",//async只对异步代码进行代码分割,如果需要同步改成initial,都需要all,同时如果是同步分割会走					到cacheGroups 》vendors的配置中
    minSize: 30000, //判断引入的库是否大于30000字节也就是30kb,大于才做代码分割
    maxSize:50000, //如果配置这一项,webpack会进一步尝试,再次分割成多个 最大50kb的文件,一般不配置
    minChunks: 1, //被使用多少次后,才进行代码分割
    maxAsyncRequests: 5, //同时加载的模块最多5个,超过5个就不会代码分割
    maxInitialRequests: 3,//入口文件代码分割最多3个,超过就不会进行代码分割
    automaticNameDelimiter: '~', //连接符
    name: true,
    cacheGroups: { //缓存组
        vendors: {   //当开启同步分割,webpack会如下配置会去检查引入的包是否在node_modules中,是否符合这个组 
            test: /[\\/]node_modules[\\/]/,
            priority: -10,//值越大,优先级越高,当同时满足组,优先使用优先级高的组
             filename:'vendor.js' //如果配置该项,那么分割的代码都会打包到vendor.js中
        },
    default: {  //当不满足上面的组时,默认走这里
            minChunks: 1,
            priority: -20,
            reuseExistingChunk: true,//如果一个模块已经被使用,就不在打包,而去使用已经打包后的文件
                 filenameL'common.js'
        }
    }
}
	},
}

4.lazy loading(懒加载)

下面2中,webpack带的异步加载,方式,实现的只有当点击页面时,才会加载lodash这个库,实现懒加载

async function getComponent() {
	const { default: _ } = await import(/* webpackChunkName:"lodash" */ 'lodash');
	const element = document.createElement('div');
	element.innerHTML = _.join(['Dell', 'Lee'], '-');
	return element;
}
// function getComponent() {
// 	return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
// 		var element = document.createElement('div');
// 		element.innerHTML = _.join(['Dell', 'Lee'], '-');
// 		return element;
// 	})
// }
document.addEventListener('click', () =>{
	getComponent().then(element => {
		document.body.appendChild(element);
	});
})

5. webpack打包分析

打包过程的描述

--profile --json > stats.json
"scripts": {
    "build": "webpack --env.production --profile --json > stats.json --config 					   ./build/webpack.common.js"
  },

可以借助 http://webpack.github.io/analyse/ 通过上传stats.json文件来分析,(要科学上网=-=)

安装插件 webpack-bundle-analyzer

文档

6. webpack 优化首屏

如何查看代码使用率

ctrl + shift + p 输入 show coverage

在这里插入图片描述

在首屏,中比如类似弹框等代码,开始并不需要,却要加载,会消耗首屏时间,这个时候,我们可以用webpack的写法

click.js

//提出不需要首屏加载的代码
function handleClick() {
	const element = document.createElement('div');
	element.innerHTML = 'hello;
	document.body.appendChild(element);
}

export default handleClick;

index.js

document.addEventListener('click', () =>{
	import(/* webpackPrefetch: true */ './click.js').then(({default: func}) => {
		func();
	})
});
//webpackPrefetch: true webpack 的写法,当核心代码加载完后,空余时间自动加载该代码
//  webpackPreload : true   同时加载,(不使用)

7.css的代码分割

在webpack中css会默认打包到js文件中,所以我们想单独生成css文件需要引入插件

安装

npm istall --save-dev mini-css-extract-plugin

改插件存在缺陷,不支持HMR(热更新),所以只用于线上打包,

css 压缩

npm istall --save-dev optimize-css-assets-webpack-plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');


const prodConfig = {
	mode: 'production',
	devtool: 'cheap-module-source-map',
	module: {
		rules:[{
			test: /\.scss$/,
			use: [
				MiniCssExtractPlugin.loader, 
				{
					loader: 'css-loader',
					options: {
						importLoaders: 2
					}
				},
				'sass-loader',
				'postcss-loader'
			]
		}, {
			test: /\.css$/,
			use: [
				MiniCssExtractPlugin.loader,
				'css-loader',
				'postcss-loader'
			]
		}]
	},
	optimization: {
		minimizer: [new OptimizeCSSAssetsPlugin({})]
	},
	plugins: [
		new MiniCssExtractPlugin({
			filename: '[name].css',
			chunkFilename: '[name].chunk.css'
		})
	]
}

module.exports = merge(commonConfig, prodConfig);

如果希望不管哪个入口的js引入的css 都打包到指定文件

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.css$/,
          chunks: 'all',
          enforce: true, //忽略掉一些默认配置
        },
      },
    },
  },
}

如果希望分入口切割

module.exports = {
  entry: {
    foo: path.resolve(__dirname, 'src/foo'),
    bar: path.resolve(__dirname, 'src/bar'),
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        fooStyles: {
          name: 'foo',
          test: (m, c, entry = 'foo') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
        barStyles: {
          name: 'bar',
          test: (m, c, entry = 'bar') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  }
}

8.webpack与浏览器缓存

当我们加载一个页面时,浏览器会帮我们缓存一些静态资源,所以如果我们打包的都叫main.js ,那么假设,用户在浏览网站,这个时候我们更新内容,如果用户只是普通的刷新,那么同样都叫main.js,这个时候浏览器发现内存中有,就会直接使用缓存,并不会获取更新的内容,所以我们需要如下改变

webpack.prod.js

	output: {
		filename: '[name].[contenthash].js',
		chunkFilename: '[name].[contenthash].js'
	}

这样就可以根据内容是否变化,来改变hash值,

在老版本中可能会一直都改变

需要配置

webpack.common.js

	optimization: {
		runtimeChunk: {
			name: 'runtime'
		},
    }

9.webpack shimming (垫片 解决代码或者打包兼容问题)

webpack 模块打包是区分模块的,所以模块与模块直接不存在耦合关系,例:

我在index.js 中引入了jquery, 和一个使用了jquery的库

banck.js

export function fnc() {
    $('body').css('background',green)
}

index.js

import $ from jquery;
import {fnc} from banck.js
fnc()

这样写并不会正常运行,$并不会和banck.js有关联,所以需要引入

但是如果是在nodemodule 中,我们不可能去改源码吧,所以我们可以借用providePlugin来实现,当webpack发现$时,就会自动引入jquery

	plugins: [
		new webpack.ProvidePlugin({
			$: 'jquery',
			_join: ['lodash', 'join'] //代表lodash中的join方法, 实际效果 _.join
		}),
	],

在模块中this通常都指向模块本身,如果想把模块的this都指向window,按如下配置

安装

npm install imports-loader --save-dev

config.js

rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/,
			use: [{
				loader: 'babel-loader'
			}, {
				loader: 'imports-loader?this=>window'
			}]
		},
        ]

八、一些实际运用

1.webpack 打包库

当我们打包一个库的时候,被人使用可能是 import ,也可能是require 也可能是script

module.exports = {
	mode: 'production',
	entry: './src/index.js',
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: 'library.js',
		library: 'root',
		libraryTarget: 'umd' //通用,可以是require 也可以是import
	}
}

有些时候,library 和 libraryTarget 是联动的,library生成一个叫library的变量, libraryTarget意思是挂载到哪里

module.exports = {
	mode: 'production',
	entry: './src/index.js',
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: 'library.js',
		library: 'library',
		libraryTarget: 'this' 
	}
}

有时候我们库里会引入一些其他库,当别人引入时,又再一次引入了库,就很有可能会导致打包出2份,所以们需要配置忽略,同时需要用户自己引入

{
    externals:['lodash']
}

2.PWA的打包d配置progressive Web Application

当服务器挂掉时,访问页面一般会出现找不到,但是PWA就就能简单实现,浏览器缓存页面,当你服务器挂掉时,依旧可以访问,缓存的页面 (下面简单的了解一下)

安装

npm i workbox-webpack-plugin -D
const WorkboxPlugin = require('workbox-webpack-plugin');

	plugins: [
		new WorkboxPlugin.GenerateSW({
			clientsClaim: true,
			skipWaiting: true
		})
	],

index.js

console.log('hello');

if ('serviceWorker' in navigator) {
	window.addEventListener('load', () => {
		navigator.serviceWorker.register('/service-worker.js')
			.then(registration => {
				console.log('service-worker registed');
			}).catch(error => {
				console.log('service-worker register error');
			})
	})
}

3. typeScript 的打包配置

安装

npm i ts-loader typescript --save-dev
	module: {
		rules: [{
			test: /\.tsx?$/,
			use: 'ts-loader',
			exclude: /node_modules/
		}]
	},

在根目录创建 tsconfig.json

{
	"compilerOpitons": {
		"outDir": "./dist",
		"module": "es6", //模块引入方式
		"target": "es5", //打包后的语法
		"allowJs": true, //允许引入js
	}
}

查询安装对应的typescript包类型文件 地址

只要存在就能通过 @type/loadash 类似安装对应的typescript提示,这样使用库时会存在语法提示

4.webpack中Eslint配置

安装

cnpm i eslint babel-eslint eslint-loader -D

配置

{
devServer: {
		overlay: true, //当检测出错误的时候会在浏览器出现弹出层
},
    modle:{
      rules: [
            { 
			test: /\.js$/, 
			exclude: /node_modules/, 
			use: ['babel-loader', {
                loader:'eslint-loader',
                options:{
                    fix:true
                },
                force:'pre' //强制优先执行
            }]
            }
      ]
    }
        
}

对eslint 配置 找到根目录下 的 .eslintrc.js

示例:

下面代码是在安装时默认选择使用Airbnb公司的代码规范, 如果不想配置webpack,可以在vscode编辑器中安装eslint插件,配合这个配置一样能实现eslint的功能,当出现报错提示时,如果感觉该规范,我不需要执行,复制代码提示中eslint()中的片段复制到 规则rules中设置为0即可

module.exports = {
	"extends": "airbnb",
  "parser": "babel-eslint",
  "rules": {
    "react/prefer-stateless-function": 0,
    "react/jsx-filename-extension": 0
  },
  globals: { //全局变量中禁止被覆盖
    document: false
  }
};

5.webpack打包速度优化

1.跟上技术的迭代(node,npm,yarn)

2.在尽可能少的模块上应用loader

3.尽量使用官方插件,尽可少使用插件

4.resolve参数合理配置

5.使用DllPlugins 提高打包速度

正常情况下,当我们引用一个库的时候,我我们打包编译时,每次都会去分析引入的库,当库引入的越多,时间就会越慢,单其实,库并不需要每次都编译,所以我们想库只打包一次,后面就都用这个打包后的库,

新建一个webpack.dll.js

const path = require('path');
const webpack = require('webpack');

module.exports = {
	mode: 'production',
	entry: {  //安装使用的库,可以分开也可以都放在vendors
		vendors: ['lodash'],
		react: ['react', 'react-dom'],
		jquery: ['jquery']
	},
	output: {
		filename: '[name].dll.js',
		path: path.resolve(__dirname, '../dll'),
		library: '[name]' // 将入口名作为全局变量暴露出去,
	},
	plugins: [
		new webpack.DllPlugin({ // 生成对应打包库的映射文件,当打包时,引入库的时候会先去查看是否有对应的映射,没有才去node_modlues
			name: '[name]',
			path: path.resolve(__dirname, '../dll/[name].manifest.json'),
		})
	]
}

webpack.common.js

const path = require('path');
const fs = require('fs');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
//因为我是分开生成的dll,所以需要遍历生成plugins
const plugins = [];

const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
	if(/.*\.dll.js/.test(file)) {
        //将库打包后,我们还需要手动在index.html中引入,下面的插件就起到添加静态资源的作用
		plugins.push(new AddAssetHtmlWebpackPlugin({
			filepath: path.resolve(__dirname, '../dll', file)
		}))
	}
	if(/.*\.manifest.json/.test(file)) {
        //当编译到引入库时,会先去查看是否有对应的映射文件,没有才去node_modlues中解析
		plugins.push(new webpack.DllReferencePlugin({
			manifest: path.resolve(__dirname, '../dll', file)
		}))
	}
})
module.exports = {
    ...
    plugin
    ...
}

package.json

添加一行命令用于打包前预先打包库文件,如果库没有更新,就无需再次执行

 "scripts": {
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js",
    "dll": "webpack --config ./build/webpack.dll.js"
  },

6.控制包文件大小 (tree Shakiing)

  1. thread-loader,parallel-webpak,happypack 多进程打包
  2. 合理使用sourceMap
  3. 结合stats分析打包结果
  4. 开发环境内存编译
  5. 开发饭局无用插件剔除

6.webpack多页面打包

如今框架vue,react等都是单页应用,如何变成多页呢

如图index.html为模板页面,detail,index,list 分别为一个页面(手动使用react库),

打包成多页,实际就是生成多个入口,同时使用hrmlWebpackPlugin 生成多个html

在这里插入图片描述

const path = require('path');
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');


const makePlugins = (configs) => {
	const plugins = [
		new CleanWebpackPlugin(['dist'], {
			root: path.resolve(__dirname, '../')
		})
	];
	Object.keys(configs.entry).forEach(item => {
		plugins.push(
			new HtmlWebpackPlugin({
				template: 'src/index.html',
				filename: `${item}.html`,
				chunks: ['runtime', 'vendors', item]
			})
		)
	});
	const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
	files.forEach(file => {
		if(/.*\.dll.js/.test(file)) {
			plugins.push(new AddAssetHtmlWebpackPlugin({
				filepath: path.resolve(__dirname, '../dll', file)
			}))
		}
		if(/.*\.manifest.json/.test(file)) {
			plugins.push(new webpack.DllReferencePlugin({
				manifest: path.resolve(__dirname, '../dll', file)
			}))
		}
	});
	return plugins;
}

const configs = {
	entry: {
		index: './src/index.js',
		list: './src/list.js',
		detail: './src/detail.js',
	},
	resolve: {
		extensions: ['.js', '.jsx'],
	},
	module: {
		rules: [{ 
			test: /\.jsx?$/, 
			include: path.resolve(__dirname, '../src'),
			use: [{
				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'
			} 
		}]
	},
	optimization: {
		runtimeChunk: {
			name: 'runtime'
		},
		usedExports: true,
		splitChunks: {
      chunks: 'all',
      cacheGroups: {
      	vendors: {
      		test: /[\\/]node_modules[\\/]/,
      		priority: -10,
      		name: 'vendors',
      	}
      }
    }
	},
	performance: false,
	output: {
		path: path.resolve(__dirname, '../dist')
	}
}

configs.plugins = makePlugins(configs);

module.exports = configs

猜你喜欢

转载自blog.csdn.net/marendu/article/details/108467816