Webpack 入门与进阶


前言

一、初识 Webpack

1、Webpack 是什么

  • Webpack 官网地址:https://v4.webpack.docschina.org/concepts/
  • webpack 是一个 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图会映射项目所需的每个模块,并生成一个或多个 bundle

2、Webpack 环境搭建

(1)Webpack 安装

// 创建项目目录
mkdir webpack-demo
// 进入该项目目录
cd webpack-demo
  • 初始化项目
// 初始化项目
npm init
{
  "name": "webpack4.0",
  "version": "1.0.0",
  "description": "",
  "private": true,   // 表示是私有项目,不会发布到 npm 线上仓库
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "lgk",
  "license": "ISC"   // 如果想开源,可以改为 MIT
}
  • 安装 Webpack
// 全局安装 Webpack (不推荐)
// 如果两个项目都要 webpack 打包,且两个项目的版本不一样,就会导致有一个运行不起来
npm install webpack webpack-cli -g
// 查看版本
webpack --version

// 卸载 webpack webpack-cli
npm uninstall webpack webpack-cli -g

// 局部安装 Webpack
npm install webpack webpack-cli --save-dev
npm install webpack webpack-cli -D

// 查看版本
npx webpack -v

// 不知道一个包的某个版本是否存在
npm info webpack

(2)Webpack 的配置文件

  • webpack.config.js 文件
const path = require('path');

module.exports = {
	mode: 'production',  // 如果不配置,默认就是 production
	// entry: './src/index.js',  // 入口文件:项目从哪个文件开始打包
	entry: {
		main: './src/index.js'
	},
	output: {
		filename: 'bundle.js',  // 打包生成的文件名
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	}
}
  • package.json 文件
{
  "name": "webpack4.0",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "bundle": "webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack-cli": "^4.8.0"
  },
  "dependencies": {
  	"webpack": "^5.54.0"
  }
}

(3)运行 webpack 打包

  • webpack-cli 的作用是使得可以执行如下代码
  • 全局安装 webpack 后使用如下方式
webpack 入口文件
  • 局部安装 webpack
npx webpack 入口文件
  • npm scripts 方式:在 package.json 中 scripts 配置 “bundle”: “webpack” 后
npm run bundle

二、Webpack 核心知识

1、Loader

  • Webpack 默认只能打包 .js 文件,如果想要打包其它,如:.png、.jpg、.txt 等文件,需要安装引入对应的“加载器” Loader
  • Loader 是转换器,用来预处理除 JavaScript 之外的任何静态资源,方便 webpack 进行打包
  • 安装 Loader
npm install vue-loader --save-dev
  • 配置 Loader:同一个 rule 下,loader 的执行顺序是从后到前,从右到左
const path = require('path');

module.exports = {
	mode: 'production',  // 如果不配置,默认就是 production
	// entry: './src/index.js',  // 入口文件:项目从哪个文件开始打包
	entry: {
		main: './src/index.js'
	},
	output: {
		filename: 'bundle.js',  // 打包生成的文件名
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	]
	}
}

(1)图片

  • 安装 Loader
// file-loader
npm install file-loader --save-dev 

// url-loader
npm install url-loader --save-dev
  • 配置 Loader
const path = require('path');

module.exports = {
	
	module: {
		rules: [{
        	test: /\.(png|jpg|gif)$/,
        	use: [{
            	loader: 'file-loader',
            	options: {
            		outputPath: 'images/',  // 打包到 dist/images 下
            		name: '[name].[ext]',
            	},
          	}]
      	},
      	{
        	test: /\.(png|jpg|gif)$/,
        	use: [{
            	loader: 'url-loader',
            	options: {
            		limit: 10240
            	},
          	}]
      	},
      	]
	}
}
  • file-loader 和 url-loader 的区别:
    file-loader 返回的是图片的url
    url-loader 可以通过 limit 属性对图片分情况处理,当图片小于 limit(单位: byte )大小时转 base64 ,大于 limit 时调用 file-loader 对图片进行处理
    url-loader 封装了 file-loader,但 url-loader 并不依赖于 file-loader

(2)样式

  • 安装 Loader
// style-loader
npm install style-loader --save-dev 

// css-loader
npm install css-loader --save-dev

// sass-loader
npm install sass-loader --save-dev

// postcss-loader
npm install post-loader --save-dev
  • 配置 Loader
const path = require('path');

module.exports = {
	
	module: {
		rules: [
		{
        	test: /\.scss$/,
        	use: [{
            	loader: 'style-loader',
            	options: {},
          	},
          	{
          		loader: 'css-loader',
          		options: {
          			// 表示该loader引入之前还有引入两个loader
          			importLoaders: 2,
          			modules: true    // css 模块化
          		}
          	},
          	{
          		loader: 'sass-loader',
          		options: {}
          	},
          	{
          		loader: 'postcss-loader',  // 添加厂商前缀
          		options: {}
          	}]
      	}]
	}
}
  • 添加厂商前缀:使用 postcss-loader 时,安装 postcss-loader 的同时还需要安装插件:autoprefixer,并且在根目录下创建 postcss.config.js 文件并配置如下
npm install autoprefixer --save-dev
module.exports = {
 plugins: [
 	require('autoprefixer')
 ]
}
  • css 模块化:

(3)Webpack 打包字体文件

  • 首先下载字体文件到项目
  • 将 @font-face 中的 url 指向项目中的字体文件
  • 安装配置 file-loader 的规则为字体文件的后缀即可
const path = require('path');

module.exports = {
	
	module: {
		rules: [{
        	test: /\.(eot|ttf|svg)$/,
        	use: [{
            	loader: 'file-loader',
            	options: {},
          	}]
      	},
      	]
	}
}

2、Plugin

  • Plugin 译为"插件",Plugin 作用是扩展 webpack 的功能,让 webpack 具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果

(1)html-webpack-plugin

  • html-webpack-plugin 插件会以指定 html 文件为模版,在打包结束后,自动生成一个 html 文件,并把打包生成的 JS 自动引入到这个 html 文件中
  • 安装插件
npm install html-webpack-plugin --save-dev
  • 使用
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	mode: 'production',  // 如果不配置,默认就是 production
	// entry: './src/index.js',  // 入口文件:项目从哪个文件开始打包
	entry: {
		main: './src/index.js'
	},
	output: {
		filename: 'bundle.js',  // 打包生成的文件名
		filename: '[name].js',  // 多入口时
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	]
	},
	plugins: [new HtmlWebpackPlugin({
		template: 'src/index.html'
	})]
}

(2)clean-webpack-plugin

  • clean-webpack-plugin 插件会在打包运行前自动清空 dist 目录
  • 安装插件
npm install clean-webpack-plugin --save-dev
  • 使用
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'production',  // 如果不配置,默认就是 production
	// entry: './src/index.js',  // 入口文件:项目从哪个文件开始打包
	entry: {
		main: './src/index.js'
	},
	output: {
		filename: 'bundle.js',  // 打包生成的文件名
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	]
	},
	plugins: [
	new HtmlWebpackPlugin({
		template: 'src/index.html'
	}),
	new HtmlWebpackPlugin(['dist'])
	]
}

3、Entry 和 Output 的基础配置

  • 如果 Output 不指定 filename,Entry 也不指定名称,默认生成 main.js 文件
  • 如果 Output 不指定 filename,Entry 也指定名称,默认生成 Entry 指定名称的 .js 文件
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'production',  // 如果不配置,默认就是 production
	// entry: './src/index.js',  // 入口文件:项目从哪个文件开始打包
	entry: {  // 多入口
		main: './src/index.js',
		sub: './src/index.js'
	},
	output: {
		// publicPath: ‘’,  // 会在 html 引入打包 js 文件前加该地址 
		// filename: 'bundle.js',  // 单入口时打包生成的文件名
		filename: '[name].js',   // 多入口时的出口文件名与入口指定的相同   
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	]
	},
	plugins: [
	new HtmlWebpackPlugin({
		template: 'src/index.html'
	}),
	new HtmlWebpackPlugin(['dist'])
	]
}

4、SourceMap

  • source-map 是一个映射关系,它可以知道打包后 dist 目录下的 .js 文件与源文件的对应关系,对应关系在 .js.map 文件
  • 如果不使用 source-map,只能知道打包后的代码第几行出错,并不知道源代码的第几行出错;使用 source-map 后,可以知道打包后出错的代码对应源代码的位置
  • 在 webpack.config.js 文件中配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'production',  // 如果不配置,默认就是 production
	// entry: './src/index.js',  // 入口文件:项目从哪个文件开始打包
	// development devtool: 'cheap-module-eval-source-map',
	// production devtool: 'cheap-module-source-map',
	entry: {  // 多入口
		main: './src/index.js',
		sub: './src/index.js'
	},
	output: {
		// publicPath: ‘’,  // 会在 html 引入打包 js 文件前加该地址 
		// filename: 'bundle.js',  // 单入口时打包生成的文件名
		filename: '[name].js',   // 多入口时的出口文件名与入口指定的相同   
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	]
	},
	plugins: [
	new HtmlWebpackPlugin({
		template: 'src/index.html'
	}),
	new HtmlWebpackPlugin(['dist'])
	]
}
  • source-map 前加 inline:使用后不会单独生成 .map 文件了,而是变成 base64 字符串放在 .js 文件的末尾
  • source-map 前加 cheap:加 cheap 后,表示在报错时只会显示到第几行,不会显示到第几列
  • source-map 前加 module:加 module 表示不仅管项目业务代码的报错,还会管到第三方模块、loader等的错误
  • eval:使用 eval 的方式和使用 source-map 的方式不同,是通过 eval 这种执行形式生成 source-map 的对应关系。eval 这种方式打包最快,但是对于项目量比较大时,显示的错误可能不太全面
  • 在开发环境,devtool 使用 cheap-module-eval-source-map
  • 在线上环境,devtool 使用 cheap-module-source-map

5、WebpackDevServer

  • 安装 devServer
npm install webapck-dev-server --save-dev
  • 配置 devServer
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'production',  // 如果不配置,默认就是 production
	// entry: './src/index.js',  // 入口文件:项目从哪个文件开始打包
	// development devtool: 'cheap-module-eval-source-map',
	// production devtool: 'cheap-module-source-map',
	entry: {  // 多入口
		main: './src/index.js',
		sub: './src/index.js'
	},
	devServer: {
		contentBase: './dist',  // 服务器建在哪个目录下
		open: true,  // 表示在启动 webpack-dev-server 时,会自动打开浏览器,然后自动访问服务器的地址
		// proxy: {  // 跨域:当用户访问 localhost:8080/api 时,会自动转发到 localhost:3000 的地址
		//	'/api': 'http://localhost:3000'
		// },
		// port: 9000, // 默认是 8080 端口,如果要改的话可以自行设置
		
	},
	output: {
		// publicPath: ‘’,  // 会在 html 引入打包 js 文件前加该地址 
		// filename: 'bundle.js',  // 单入口时打包生成的文件名
		filename: '[name].js',   // 多入口时的出口文件名与入口指定的相同   
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	]
	},
	plugins: [
	new HtmlWebpackPlugin({
		template: 'src/index.html'
	}),
	new HtmlWebpackPlugin(['dist'])
	]
}
  • webpack:不会自动监听源代码的变化,每次都需要手动重新打包
  • webpack --watch:当源代码发生变化,webpack 就能监听到打包文件发生变化,然后就会重新打包
  • webpack-dev-server:不但会监听源代码的变化,还会自动的重新打开浏览器。webpack-dev-pack 打包后没有生成 dist 目录,打包生成的文件并不会放在 dist 目录下,而是放到电脑的内存中,这样可以有效的提升打包的速度
{
  "name": "webpack4.0",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
  	"bundle": "webpack",
    "watch": "webpack --watch",  // 当源代码发生变化,webpack 就能监听到打包文件发生变化,然后就会重新打包
    "start": "webpack-dev-server"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.54.0",
    "webpack-cli": "^4.8.0"
  }
}

6、Hot Module Replacement(热模块替换)

  • 热更新(HMR):它可以在应用运行的时候,不需要刷新页面,就可以直接替换、增删模块。
  • 在 devServer 中配置 hot 值为 true,表示让 webpack-dev-server 开启 热更新。如果编译报错,会抛出错误,重新改成正确的,这个时候又会触发重新编译,整个浏览器会重新刷新!
  • 在 devServer 中配置 hotOnly 值为 true,表示即使 HMR 的功能没有生效,也会让浏览器自动的重新刷新。如果编译报错,再改成正确的,重新编译,浏览器不会刷新!
  • 引入 webpack 自带的插件 HotModuleReplacementPlugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack')

module.exports = {
	mode: 'development',  // 如果不配置,默认就是 production
	devtool: 'cheap-module-eval-source-map',
	entry: {  // 多入口
		main: './src/index.js',
		sub: './src/index.js'
	},
	devServer: {
		contentBase: './dist',  // 服务器建在哪个目录下
		open: true,  // 表示在启动 webpack-dev-server 时,会自动打开浏览器,然后自动访问服务器的地址
		// proxy: {  // 跨域:当用户访问 localhost:8080/api 时,会自动转发到 localhost:3000 的地址
		//	'/api': 'http://localhost:3000'
		// },
		// port: 9000, // 默认是 8080 端口,如果要改的话可以自行设置
		hot: true,  // 表示让 webpack-dev-server 开启 热更新
		hotOnly: true  // 表示即使 HMR 的功能没有生效,也会让浏览器自动的重新刷新
	},
	output: {
		// publicPath: ‘’,  // 会在 html 引入打包 js 文件前加该地址 
		// filename: 'bundle.js',  // 单入口时打包生成的文件名
		filename: '[name].js',   // 多入口时的出口文件名与入口指定的相同   
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}),
		new HtmlWebpackPlugin(['dist']),
		new webpack.HotModuleReplacementPlugin()
	]
}
  • 对于更改 CSS 样式,css-loader 已经内置有监测 CSS 变化的 module.hot
  • 对于一些 JS 代码,还需要手动编写监测 JS 更新的代码
// 例如:监测 number.js 模块的更新
if(module.hot) {
	module.hot.accept('./number', () => {
		// 监测到 number.js 中代码发生改变后处理的业务
	})
}

7、Babel 处理 ES6 语法

  • 有些老版本的浏览器不支持 ES6 的语法,所以需要将 ES6 转为 ES5
  • 安装 babel-loader
// babel-loader 只是将 webpack 和 Babel 打通,实际上 babel-loader 并不会将 ES6 转为 ES5
npm install --save-dev babel-loader @babel/core

// 所以还需要借助其他模块做语法转换
npm install --save-dev @babel/preset-env

// 对于一些ES6 新增的像 map 等函数,preset-env 还是不够,所以需要补充
npm install --save @babel/polyfill
// 安装好 polyfill 之后只需要在所有需要转换的代码之前引入即可
// import "@babel/polyfill"
  • 使用 babel-loader
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack')

module.exports = {
	mode: 'development',  // 如果不配置,默认就是 production
	devtool: 'cheap-module-eval-source-map',
	entry: {  // 多入口
		main: './src/index.js',
		sub: './src/index.js'
	},
	devServer: {
		contentBase: './dist',  // 服务器建在哪个目录下
		open: true,  // 表示在启动 webpack-dev-server 时,会自动打开浏览器,然后自动访问服务器的地址
	},
	output: {
		// publicPath: ‘’,  // 会在 html 引入打包 js 文件前加该地址 
		// filename: 'bundle.js',  // 单入口时打包生成的文件名
		filename: '[name].js',   // 多入口时的出口文件名与入口指定的相同   
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	{ 
      		test: /\.js$/,
      		exclude: /node_modules/,
      		loader: "babel-loader",
      		options: {
      			presets: [["@babel/preset-env", {
      				useBuiltIns: 'usage',  // 按需打包 polyfill 
      				targets: {
      					chrome: "67"  // 对于已经支持ES6的浏览器,就不会转为ES5了
      				}
      			}]]
      		}
      	},
      	]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}),
		new HtmlWebpackPlugin(['dist']),
		new webpack.HotModuleReplacementPlugin()
	]
}
  • 可以将 babel-loader 中 options 的代码摘离出来放到 .babelrc 文件中,然后就可以不用在每个文件头部用 import @babel/polyfill 引入了
{
	presets: [
		"@babel/preset-env", {
			useBuiltIns: 'usage',  // 按需打包 polyfill 
      		targets: {
      			chrome: "67"  // 对于已经支持ES6的浏览器,就不会转为ES5了
      		}
		},
		"@babel/preset-react"
	]
}

8、Webpack 实现对 React 框架代码的打包

三、Webpack 进阶

1、Tree Shaking

  • Tree Shaking 指的是当引入一个模块时,不引入该模块所有的代码,只引入需要的代码,Tree Shaking 只支持 ES Module 的引入方式
  • 配置 Tree Shaking 需要在 webpack.config.js 文件中设置如下代码(开发环境,生产环境默认已经配置
module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		hot: true,
		hotOnly: true,
	},
	entry: {
		main: './src/index.js'
	},
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, 'dist')
	},
	module: {
		// loader
		rules: [{
			test: /\.js$/,
			exclude: /node_module/,
			loader: 'babel-loader'
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		})
	],
	optimization: {
		usedExports: true
	}
}
  • 在 package.json 文件中配置 sideEffects,如果某些文件不需要 Tree Shaking,可以在 sideEffects 中配置
{
	"name": "test",
	"sideEffects": false,
	// "sideEffects": ["*.css"]
	"version": "1.0.0",
	//等等
}

2、Development 和 Production 模式的区分打包

  • 开发环境:source-map 比较全面,可以在开发时快速定位问题;代码不希望压缩,可以明显看到打包生成的内容;
  • 生产环境:source-map 较为简洁;代码压缩,提高打包构建速度;

webpack.dev.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',  // 如果不配置,默认就是 production
	devtool: 'cheap-module-eval-source-map',
	entry: {  // 多入口
		main: './src/index.js'
	},
	devServer: {
		contentBase: './dist',  // 服务器建在哪个目录下
		open: true,  // 表示在启动 webpack-dev-server 时,会自动打开浏览器,然后自动访问服务器的地址
		port: 8080,
		hot: true,
		hotOnly: true
	},
	output: {
		// publicPath: ‘’,  // 会在 html 引入打包 js 文件前加该地址 
		// filename: 'bundle.js',  // 单入口时打包生成的文件名
		filename: '[name].js',   // 多入口时的出口文件名与入口指定的相同   
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [{
        	test: /\.(eot|ttf|svg)$/,
        	use: [{
            	loader: 'file-loader',
            	options: {
            		outputPath: 'images/',  // 打包到 dist/images 下
            		name: '[name].[ext]',
            	},
          	}]
      	},
      	{
        	test: /\.(png|jpg|gif)$/,
        	use: [{
            	loader: 'url-loader',
            	options: {
            		limit: 10240
            	},
          	}]
      	},
      	{
        	test: /\.css$/,
        	use: [
        		'style-loader',
        		'css-loader',
        		'postcss-loader'
          	]
      	},
      	{
        	test: /\.scss$/,
        	use: [{
            	loader: 'style-loader',
            	options: {},
          	},
          	{
          		loader: 'css-loader',
          		options: {
          			// 表示该loader引入之前还有引入两个loader
          			importLoaders: 2,
          			modules: true    // css 模块化
          		}
          	},
          	{
          		loader: 'sass-loader',
          		options: {}
          	},
          	{
          		loader: 'postcss-loader',  // 添加厂商前缀
          		options: {}
          	}]
      	},
      	{ 
      		test: /\.js$/,
      		exclude: /node_modules/,
      		loader: "babel-loader",
      		options: {}
      	},
      	]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}),
		new HtmlWebpackPlugin(['dist']),
		new webpack.HotModuleReplacementPlugin()
	],
	optimization: {
		usedExports: true
	}
}

webpack.prod.js 文件:

  • 生产环境不需要 devServer
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
	mode: 'production',  // 如果不配置,默认就是 production
	devtool: 'cheap-module-source-map',
	entry: {  // 多入口
		main: './src/index.js'
	},
	output: {
		// publicPath: ‘’,  // 会在 html 引入打包 js 文件前加该地址 
		// filename: 'bundle.js',  // 单入口时打包生成的文件名
		filename: '[name].js',   // 多入口时的出口文件名与入口指定的相同   
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [{
        	test: /\.(eot|ttf|svg)$/,
        	use: [{
            	loader: 'file-loader',
            	options: {
            		outputPath: 'images/',  // 打包到 dist/images 下
            		name: '[name].[ext]',
            	},
          	}]
      	},
      	{
        	test: /\.(png|jpg|gif)$/,
        	use: [{
            	loader: 'url-loader',
            	options: {
            		limit: 10240
            	},
          	}]
      	},
      	{
        	test: /\.css$/,
        	use: [
        		'style-loader',
        		'css-loader',
        		'postcss-loader'
          	]
      	},
      	{
        	test: /\.scss$/,
        	use: [{
            	loader: 'style-loader',
            	options: {},
          	},
          	{
          		loader: 'css-loader',
          		options: {
          			// 表示该loader引入之前还有引入两个loader
          			importLoaders: 2,
          			modules: true    // css 模块化
          		}
          	},
          	{
          		loader: 'sass-loader',
          		options: {}
          	},
          	{
          		loader: 'postcss-loader',  // 添加厂商前缀
          		options: {}
          	}]
      	},
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	{ 
      		test: /\.js$/,
      		exclude: /node_modules/,
      		loader: "babel-loader",
      		options: {}
      	},
      	]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}),
		new HtmlWebpackPlugin(['dist'])
	]
}

package.json 文件:

{
  "name": "webpack4.0",
  "sideEffects": false,
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
  	"dev": "webpack-dev-server --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js",
    "test": "webpack --config webpack.dev.js"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    
  },
  "dependencies": {
  	
  }
}

抽取公共配置 webpack.common.js 文件:

  • 合并配置文件需要安装第三方模块
npm install webpack-merge --save-dev
  • webpack.dev.js
const webpack = require('webpack');
const merge = require('webapck-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,   // 开启热更新
		// hotOnly: true  // 开启后编译出错不会自动刷新
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin()
	]
}

module.exports = merge(commonConfig, devConfig)

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

const prodConfig = {
	mode: 'production',
	devtool: 'cheap-module-source-map'
}

module.exports = merge(commonConfig, prodConfig)

  • 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'
	},
	output: { 
		// path 路径必须为绝对路径
		path: path.resolve(__dirname, 'dist')  
	},
	module: {
		rules: [{
        	test: /\.(eot|ttf|svg)$/,
        	use: [{
            	loader: 'file-loader',
            	options: {
            		outputPath: 'images/',  // 打包到 dist/images 下
            		name: '[name].[ext]',
            	},
          	}]
      	},
      	{
        	test: /\.(png|jpg|gif)$/,
        	use: [{
            	loader: 'url-loader',
            	options: {
            		limit: 10240
            	},
          	}]
      	},
      	{
        	test: /\.css$/,
        	use: [
        		'style-loader',
        		'css-loader',
        		'postcss-loader'
          	]
      	},
      	{
        	test: /\.scss$/,
        	use: [{
            	loader: 'style-loader',
            	options: {},
          	},
          	{
          		loader: 'css-loader',
          		options: {
          			// 表示该loader引入之前还有引入两个loader
          			importLoaders: 2,
          			modules: true    // css 模块化
          		}
          	},
          	{
          		loader: 'sass-loader',
          		options: {}
          	},
          	{
          		loader: 'postcss-loader',  // 添加厂商前缀
          		options: {}
          	}]
      	},
      	{
        	test: /\.vue$/,
        	use: [{
            	loader: 'vue-loader',
            	options: {},
          	}]
      	},
      	{ 
      		test: /\.js$/,
      		exclude: /node_modules/,
      		loader: "babel-loader",
      		options: {}
      	},
      	]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}),
		new HtmlWebpackPlugin(['dist'])
	],
	optimization: {
		useExports: true
	}
}

3、Code Splitting(代码分割)

  • lodash 是一个功能集合,里面提供了很多工具方法
npm install lodash --save
// 引入lodash: import _ from 'lodash';

不进行代码分割存在的问题:

  • 当引入第三方库和业务逻辑代码放在一起打包时,打包文件会很大,加载时间会很长
  • 当每次业务逻辑发生变化时,又要重新加载这个体积很大的包,会耗时较长

普通解决方案:

  • 可以把 lodash 等第三方库各自放在新建的 js 文件中引入,然后挂载在 window 对象上全局使用,同时在 entry 中配置入口指向该文件,这样就可以把第三方库与业务逻辑分开引入
// lodash.js 文件
import _ from 'lodash';

window._ = _;
entry: {
	main: './src/index.js',
	lodash: './src/lodash.js',
}
  • 浏览器可以并行加载文件,这样浏览器就能分开加载第三方库文件和业务逻辑文件
  • 当业务逻辑发生变化时,只需要加载业务文件即可,第三方库在缓存里就有

webpack 解决方案:

  • Code Splitting 对代码进行拆分,可以让代码执行的性能更高
  • Code Splitting 本质上和 webpack 没有关系,在没有 webpack 之前,可以像上面一样手动拆分代码
  • 在 webpack4.0 提供 splitChunksPlugin 插件与 webpack 捆绑,不用安装就能使用进行代码分割
  • webpack 中实现代码分割有两种方式:同步代码 和 异步代码

同步代码分割:

  • webpack 自带 splitChunksPlugin 可以实现自动分割,只需要在 webpack.common.js 中做 optimization 的配置即可
optmization: {
	splitChunksPlugins: {
		chunks: 'all',
		cacheGroups: {
			vendors: false,
			default: false
		}
	}
}

异步代码分割:

  • 指的是使用 import() 引入的异步组件或模块代码
  • 无需做任何配置,会自动进行异步代码分割,放置到新的文件中
  • 在异步加载时可以使用魔法注释指定生成的异步文件名
// 例如 lodash 的引入时
function getComponent() {
	return import(/* webpackChunkName: "lodash" */ 'lodash').then(({default: _}) => {
		// lodash 使用逻辑
	})
}
// 动态异步获取 若是不支持,可以安装如下插件
npm install @babel/plugin-syntax-dynamic-import --save-dev

// 然后在 .babelrc 文件中配置,然后 Babel 就能转以这种实验性质的语法
plugins: ["@babel/plugin-syntax-dynamic-import"]

splitChunksPlugin 配置参数:

  • 如果将 splitChunks 设置为空对象,则会使用默认配置,如下
optimization: {
    splitChunks: {
      chunks: 'async',  // all:对异步和同步都分割 async:只对异步分割
      minSize: 30000, // 如果打包后包大于30000字节(30Kb)就会做代码分割
      maxSize: 0,  // 如果分割后还大于maxSize,会尝试再次进行分割(maxSize 一般不设置)
      minChunks: 1,  // 当一个模块至少使用 minChunk 次才会代码分割
      maxAsyncRequests: 5,  // 同时加载模块数最多是 5,如果超过就不分割
      maxInitialRequests: 3,  // 入口文件做分割,最多生成 3 个 js 文件,超过则不再分割
      automaticNameDelimiter: '~', // 使用~作为下面的cacheGroups组名与文件名的连接符生成分割后的文件名
      name: true, // 打包后的名字使用cacheGroups里的名字有效
      cacheGroups: {  // 打包同步代码时,若符合上面的条件则会到这,执行完成后才会进行分割
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,  // 值越大,这个组的优先级越高
          filename: 'vendors.js'
        },
        default: {
          minChunks: 2,
          priority: -20, // -20 < -10,default 的优先级小于 vendors
          reuseExistingChunk: true, // 如果一个模块被打包过了,再打包时就忽略该模块,直接使用
          filename: common.js
        }
      }
    }
  }

output 中 filename 与 chunkFilename 的区别:

  • filename 是入口文件对应生成的输出文件名
  • chunkFilename 是入口文件间接引入生成的输出文件名

4、Webpack 懒加载(Lazy Loading),Preloading/Prefetching

  • 懒加载:就是异步的加载某个模块,当对象需要用到的时候再去加载
  • webpack 本质上与懒加载关系不大,只不过 webpack 能识别 import() 语法,然后对引入的模块进行代码分割而已
  • webpack 使用 Prefetching 和 Preloading 实现懒加载,使用的方式是在使用 import() 异步加载某个模块时,对加载的模块加上魔法注释,注释内容为:webpackPrefetch: true 或者 webpackPreload: true
import(/* webpackPrefetch: true */ 'lodash')
  • Prefetching/Preloading 实现懒加载主要区别是:prefetching 的代码会等到主要的 js 代码加载完,浏览器空闲时才会去加载(例如首页中的登录注册模块),preloading 代码会与主要的 js 代码一起加载

5、CSS 文件的代码分割

  • webpack 官网提供的 MiniCssExtractPlugin 插件对 CSS 进行代码分割,该插件暂时不支持热更新,一般用于生产环境,用在开发环境会降低开发效率
npm install --save-dev mini-css-extract-plugin
  • 在 webpack.config.js 中配置
  • 如果要使用该插件,还需要对 Loader 进行配置,之前最后一步是使用 style-loader 把 CSS 样式挂在 HTML 页面上,现在要将 style-loader 换成 MiniCssExtractPlugin.loader,同时修改 webpack.common.js 和 webpack.dev.js 中 Module 关于 sass 和 css 的代码
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const merge = require('webapck-merge');
const commonConfig = require('./webpack.common.js');

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

module.exports = merge(commonConfig, prodConfig)
  • 然后在 package.json 中将 sideEffects 的值设置为 ["*.css"]

CSS 代码压缩:

  • CSS 代码压缩可以使用 OptimizeCssAssetsPlugin 插件
npm install --save-dev optimize-css-assets-plugin
  • 在生产环境配置引入使用
optimization: {
	minimizer: [
		new OptimizeCssAssetsPlugin({})  // 别忘了加空对象
	]
}

6、Webpack 与浏览器缓存

  • 在开发环境,由于有热更新,每次对源代码修改都会重新打包,重新生成输出文件
  • 在生产环境,每次修改源代码生成的输出文件都同名,由于浏览器有缓存,对于同名的输出文件会直接使用缓存的内容,就会导致源代码的更新不生效
  • 所以在配置生产环境 output 的 filename 和 chunkFilename 时,需要加在 [contenthash]
// webpack.prod.js

output: {
	filename: '[name].[contenthash].js',
	chunkFilename: '[name].[contenthash].js'
}
  • 对于较老版本的 webpack4.0 ,对同一源代码再次打包,有可能生成的 hash 值不同,解决这个问题的方法是在 optimization 中 runtimeChunk
optimization: {
	runtimeChunk: {
		name: 'runtime'
	},
	usedExports: true,
	splitChunks: {
		chunks: 'all'
	}
}

7、Shimming 的作用

8、环境变量(env)的使用方法

四、配置实战案例

五、Webpack 底层原理及脚手架工具分析

六、Create-React-App 和 Vue-Cli 3.0 脚手架工具配置分析


总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值