WebPack学习实践笔记(一)

一、准备

nodejs安装教程:https://blog.csdn.net/FED_AF/article/details/105747632)

二、安装

(1)全局安装

:: 初始化npm
C:\Users\Administrator> npm init -y
package name: (administrator) webpack_test
::后面全部回车默认
::全局安装(webpack版本4.43.0,webpack-cli版本3.3.11)
C:\Users\Administrator> npm i webpack webpack-cli -g
::提供sw代码运行的服务器环境
C:\Users\Administrator> npm i serve -g

(2)项目安装

:: 初始化npm
项目根目录> npm init -y
package name: (webpack_test) 名称自己取,只要不和全局的一样就行
::后面全部回车默认
::安装webpack和webpack-cli
项目根目录> npm i webpack webpack-cli -D

(3)模块和插件安装

::安装webpack-dev-server
项目根目录> npm i webpack-dev-server -D
::用来打包css
项目根目录> npm i css-loader style-loader -D
::用来打包sass(这里是需要装css-loader和style-loader的,但前面已经装过就不再装)
项目根目录> npm i sass sass-loader -D
::用来打包html
项目根目录> npm i html-webpack-plugin -D
::用来打包css引入的图片
项目根目录> npm i url-loader file-loader -D
::用来打包html的img元素引入的图片
项目根目录> npm i html-loader -D


::用来提取css成单独文件
项目根目录> npm i mini-css-extract-plugin -D
::用来处理css兼容性
项目根目录> npm i postcss-loader postcss-preset-env -D
::用来压缩css代码
项目根目录> npm i optimize-css-assets-webpack-plugin -D
::用来检查规范js代码(airbnb风格)
项目根目录> npm i eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import -D
::用来处理js代码的兼容性
项目根目录> npm i babel-loader @babel/preset-env @babel/core @babel/polyfill core-js -D
::用来实现PWA技术
项目根目录> npm i workbox-webpack-plugin serve -D
::用来多进程打包
项目根目录> npm i thread-loader -D
::用来配合dll使用,功能是将某个文件打包输出去,并在html中自动引入
项目根目录> npm i add-asset-html-webpack-plugin -D
::优化生产环境代码压缩
项目根目录> npm i terser-webpack-plugin -D

(4)第三方库安装

::jquery
项目根目录> npm i jquery -D

三、结构

项目根目录
    源代码目录src
    构建后代码目录build
    webpack.config.js

四、配置

  • 项目根目录下的webpack.dll.js(初始化动态链接库):

/*
	使用dll技术,对某些库(第三方库:jquery、vue)进行单独打包
*/
const {resolve} = require('path');
const webpack = require('webpack');
module.exports = {
	entry: {
		// 最终打包生成的[name]
		// ['jquery']要打包的库是jquery
		jquery: ['jquery']
	},
	output: {
		filename: '[name].js',
		path: resolve(__dirname, 'dll'),
		// 打包的库向外暴露出去的内容叫什么名字
		library: '[name]_[hash]',
	},
	plugins: [
		// 打包生成一个manifest.json,提供和jquery的映射关系
		new webpack.DllPlugin({
			// 映射库的暴露的内容名称
			name: '[name]_[hash]',
			// 输出文件路径
			path: resolve(__dirname, 'dll/manifest.json')
		})
	],
	mode: 'production'
};
  • 项目根目录下的webpack.dev.js(开发环境):

// resolve用来拼接绝对路径的方法
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
/*
	样式文件可以使用HMR功能,因为style-loader内部实现了
	js文件默认不能使用HMR功能
	解决:需要修改js代码,添加支持HMR功能的代码,只能处理非入口js文件的其他文件
	html文件默认不能使用HMR功能(html不存在引用问题,没有优化的空间),同时会导致修改html文件后页面刷新无果
	解决:修改entry入口,将html文件引入
 */
// 设置node环境变量
process.env.NODE_ENV = 'development';
module.exports = { // webpack配置
	// 入口起点
	entry: ['./src/index.js', './src/index.html'],
	// 输出
	output: {
		// 输出文件名
		filename: 'built.js',
		// 输出路径
		// __dirname是nodejs的变量,代表当前文件的目录绝对路径
		path: resolve(__dirname, 'build')
	},
	// loader的配置
	module: {
		rules: [
            // 详细loader配置
            oneOf: [
				{
					// 匹配哪些文件
					test: /\.css$/,
					// 使用哪些loader进行处理,多个loader用use数组
					  use: [
						// use数组中loader的执行顺序:从右到左,从下到上,依次执行
						// 创建style标签,将js中的样式资源插入进去,添加到head中生效
						'style-loader',
						// 将css文件变成commonjs模块加载到js中,里面的内容是样式字符串
						'css-loader'
					]
				},
				{
					test: /\.sass$/,
					use: [
						'style-loader',
						'css-loader',
						// 将sass文件编译成css文件
						'sass-loader',
						'sass'
					]
				},
				{
					// 处理图片资源
					test: /\.(jpg|png|gif)$/,
					// 使用一个loader,直接用loader
					// 需要下载url-loader和file-loader
					loader: 'url-loader',
					options: {
						// 当发现图片大小小于8kb,就会被base64处理,转化为字符串
						// 优点:能够减少请求数量,减轻服务器压力
						// 缺点:图片体积会更大,文件请求速度更慢
						limit: 8 * 1024,
						// 问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
						// 解析时会出现问题:[object Module]
						// 解决:关闭url-loader的es6模块化,使用commonjs解析
						esModule: false
					}
				},
				{
					// 处理图片资源
					test: /\.html$/,
					// 处理html文件的img图片(负责引入img,从而能被url-loader处理)
					// 需要下载html-loader
					loader: 'html-loader'
				},
				{
					// 处理其他资源
					exclude: /\.(css|js|html|jpg|png|gif|sass|json)$/,
					loader: 'file-loader',
					options: {
						// 限制输出的文件名哈希值长度为10位,在加上本来的扩展名
						name: '[hash:10].[ext]'
					}
				}
			]	
		]
	},
	// plugins的配置
	plugins: [
		// 详细plugins的配置
		// html-webpack-plugin
		// 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
		// 需求:需要有结构的HTML文件
		new HtmlWebpackPlugin({
			// 将 './src/icons/icon.ico'文件作为网页标签的图标引入
			// favicon: './src/icons/icon.ico',
			// 复制 './src/index.html'文件,并自动引入打包输出的所有资源
			// 多个html需要多个HtmlWebpackPlugin,通过filename指定构建后的html路径和文件名
			template: './src/index.html'
		}),
		new HtmlWebpackPlugin({
			template: './src/inde.html',
			filename: './build/c/inde.html'
		}),
		// 告诉webpack哪些库不参与打包,同时使用时的名称也得改
		new webpack.DllReferencePlugin({
			manifest: resolve(__dirname, 'dll/manifest.json')
		}),
		// 将某个文件打包输出去,并在html中自动引入
		new AddAssetHtmlWebpackPlugin({
			filepath: resolve(__dirname, 'dll/jquery.js')
		})
	],
	// 模式
	// mode: 'production'
	mode: 'development',
	// 开发服务器devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器)
	// 特点:只会在内存中编译打包,不会有任何输出
	devServer: {
		contentBase: resolve(__dirname, 'build'),
		// 启动gzip压缩
		compress: true,
		// 自动打开浏览器
		// open: true,
		// 按如下配置即可在其他终端访问
		host: '0.0.0.0',
		// 开启热模块替换(HMR功能),省去不必要刷新
		hot: true,
		// 端口号
		port: 3000
	},
	devtool: "eval-source-map"
};
/*
	source-map是一种提供源代码到构建后代码映射的技术。用法如下:
	[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
	source-map:外部
		会提示错误代码准确信息和源代码的错误位置
	inline-source-map:内联,将source-map嵌入到入口js中,构建速度更快
		会提示错误代码准确信息和源代码的错误位置
	hidden-source-map:外部,将source-map输出到一个map文件中
		会提示错误代码错误原因但是没有错误位置,而且只能提示构建后代码的错误位置
	eval-source-map:内联,每一个文件都生成对应的source-map
		会提示错误代码准确信息和源代码的错误位置(只多了一个js文件的hash值)
	nosources-source-map:外部
		会提示错误代码准确信息,但是没有任何源代码信息
	cheap-source-map:外部
		会提示错误代码准确信息,但只能定位到行
	cheap-module-source-map:外部
		会提示错误代码准确信息,但只能定位到行


	速度快:eval-cheap-source-map>eval-source-map>inline-source-map>cheap-source-map>...
	调试友好:source-map>cheap-module-source-map>cheap-source-map>...

	开发环境通常选择eval-source-map或eval-cheap-module-source-map
	生产环境通常选择source-map或cheap-module-source-map

 */
  • 项目根目录下的webpack.pro.js(生产环境):

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
/*
	缓存:
	babel缓存:让第二次打包构建速度更快

	文件资源缓存:让代码上线运行更好使用。
	每次webpack构建时会生成一个唯一的hash值加入到文件名中,以此保证当文件修改时刷新缓存
	使用contenthash保证只对更改过的文件重新生成hash值
*/
/*
	tree shaking: 去除无用代码
	前提: 1.必须使用ES6模块化  2.开启production环境
	作用:减少代码体积
	在packaage.json中配置"sideEffects": ["*.css"]表示除css文件外所有代码都进行tree shaking
*/
/*
	code split: 代码分割
	将较大的js文件分割成多个较小的js文件,从而实现并行加载,速度更快
*/
/*
	PWA:渐进式网络开发应用程序(离线可访问)
	通过workbox实现PWA
 */
// 复用loader
const commonCssLoader = [
					// 提取js中的css成单独文件
					MiniCssExtractPlugin.loader,
					'css-loader',
					// css兼容性处理:postcss --> postcss-loader postcss-preset-env
					// 帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式
					/*	"browserslist": {
						// 开发环境 --> 设置node环境变量:process.env.NODE_ENV = development
						"development": [
						"last 1 chrome version",//兼容谷歌浏览器最近的一个版本
						"last 1 firefox version",
						"last 1 safari version"
						],
						// 生产环境:默认是生产环境
						"production": [
						">0.2%",//考虑99.8%的浏览器的兼容性
						"not dead",//不考虑不再使用的浏览器的兼容性
						"not op_mini all"//不考虑op_mini浏览器的全部版本的兼容性
						]
					}*/
					// 使用loader的默认配置
					// 'postcss-loader',
					// 修改loader的默认配置
					{
						loader: 'postcss-loader',
						options: {
							ident: 'postcss',
							plugins: () => [
								// postcss的插件
								require('postcss-preset-env')()
							]
						}
					}

];
// 设置node环境变量
// process.env.NODE_ENV = 'development';
module.exports = { 
	entry: './src/index.js',
	output: {
		filename: 'built.[contenthash:10].js',
		path: resolve(__dirname, 'build')
	},
	module: {
		rules: [
			/*{
				// 用eslint做js语法检查
				// 只检查自己写的源代码,第三方的库是不用检查的
				// 在package.json中的eslintConfig设置检查规则:
				// 使用airbnb风格
				test: /\.js$/,
				// 排除node模块
				exclude: /node_modules/,
				// 优先执行
				enforce: 'pre',
				loader: 'eslint-loader',
				options: {
					// 自动规范源js的语法
					fix: true
				}
			},*/
			{
				// 以下loader只会匹配一个,造成冲突的可以放到oneOf外面
				oneOf: [
					{
						test: /\.css$/,
						use: [...commonCssLoader]
					},
					{
						test: /\.sass$/,
						use: [...commonCssLoader, 'sass-loader', 'sass']
					},
					{
						test: /\.(jpg|png|gif)$/,
						loader: 'url-loader',
						options: {
							limit: 8 * 1024,
							name: '[hash:10].[ext]',
							outputPage: 'images',
							esModule: false
						}
					},
					{
						test: /\.html$/,
						loader: 'html-loader'
					},
					{
						exclude: /\.(css|js|html|jpg|png|gif|sass|json|ico)$/,
						loader: 'file-loader',
						options: {
							outputPage: 'media',
							name: '[hash:10].[ext]'
						}
					},
					{
						// 用babel做js兼容性处理
						test: /\.js$/,
						exclude: /node_modules/,
						use: [
							// 开启多进程打包。
							// 进程启动时间大概为600ms,进程通信也有开销。
							// 只有工作消耗时间比较长,才需要多进程打包
							{

								loader: 'thread-loader',
								options: {
									workers: 2 // 进程2个
								}
							},
							{
								loader: 'babel-loader',
								options: {
									// 预设:指示babel做怎么样的兼容性处理
									// @babel/preset-env:基本兼容性处理:只能转换基本语法,不能转换promise等
									// @babel/polyfill:全部兼容性处理(在入口js文件中引入即可:import '@babel/polyfill';):所有兼容性代码全部引入,代码体积太大
									// core-js:按需处理兼容性
									presets: [
										[
											'@babel/preset-env',
											{
												// 按需加载
												useBuiltIns: 'usage',
												corejs: {
													// 指定core-js的版本
													version: 3
												},
												// 指定兼容性做到哪个版本浏览器
												targets: {
													chrome: '60',
													firefox: '60',
													ie: '8',
													safari: '10',
													edge: '17'
												}
											}
										]
									],
									// 开启babel缓存
									// 第二次构建时,会读取之前的缓存
									cacheDirectory: true
								}
							}
						]
					}
				]
			}
		]
	},
	plugins: [
		new HtmlWebpackPlugin({
			// favicon: './src/icons/icon.ico',
			template: './src/index.html',
			// 压缩html代码
			minify: {
				// 移除空格
				collapseWhitespace: true,
				// 移除注释
				removeComments: true
			}
		}),
		new MiniCssExtractPlugin({
			// 对输出的文件重命名
			filename: 'css/main.[contenthash:10].css'
		}),
		// 压缩css
		new OptimizeCssAssetsWebpackPlugin(),
		// 压缩css
		new WorkboxWebpackPlugin.GenerateSW({
			// 帮助serviceworker快速启动
			// 删除旧的serviceworker
			// 生成一个serviceworker配置文件
			clientsClaim: true,
			skipWaiting: true,
		}),
		// 告诉webpack哪些库不参与打包,同时使用时的名称也得改
		new webpack.DllReferencePlugin({
			manifest: resolve(__dirname, 'dll/manifest.json')
		}),
		// 将某个文件打包输出去,并在html中自动引入
		new AddAssetHtmlWebpackPlugin({
			filepath: resolve(__dirname, 'dll/jquery.js')
		})
	],
	// 对于单页面应用,可以将node_module中代码单独打包一个chunk最终输出
	// 对于多页面应用,可以自动分析多入口trunk中有没有公共的文件。如果有会打包成单独一个chunk
	optimization: {
		splitChunks: {
			chunks: 'all'
		}
	},
	mode: 'production',
	// 下面这一行不是注释,是忽略eslint报的no-console警告
	// eslint-disable-next-line
	// mode: 'development',
	devtool: "source-map",
	externals: {
		// 忽略库名 -- npm包名
		jquery: 'jQuery'
	}
};
  • 项目根目录下的package.json:

{
  "name": "webpack_code",
  "version": "1.0.0",
  "main": "webpack.config.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@babel/core": "^7.9.6",
    "@babel/polyfill": "^7.8.7",
    "@babel/preset-env": "^7.9.6",
    "add-asset-html-webpack-plugin": "^3.1.3",
    "babel-loader": "^8.1.0",
    "core-js": "^3.6.5",
    "css-loader": "^3.5.3",
    "eslint": "^7.0.0",
    "eslint-config-airbnb-base": "^14.1.0",
    "eslint-loader": "^4.0.2",
    "eslint-plugin-import": "^2.20.2",
    "file-loader": "^6.0.0",
    "html-loader": "^1.1.0",
    "html-webpack-plugin": "^4.3.0",
    "jquery": "^3.5.1",
    "mini-css-extract-plugin": "^0.9.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "postcss-preset-env": "^6.7.0",
    "sass": "^1.26.5",
    "sass-loader": "^8.0.2",
    "serve": "^11.3.0",
    "style-loader": "^1.2.1",
    "thread-loader": "^2.1.3",
    "url-loader": "^4.1.0",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.11.0",
    "workbox-webpack-plugin": "^5.1.3"
  },
  "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  },
  "sideEffects": [
    "*.css",
    "*.sass"
  ]
}
  • 项目根目录下的server.js:

/*
	服务器代码
	启动服务器指令:node server.js
*/
const express = require('express');
const app = express();
// 缓存保留时长
app.use(express.static('build', {maxAge: 1000 * 3600}));
// 监听3000端口
app.listen(3000);
  • 项目入口js:

...
import './print.js';
...

//热模块替换
if (module.hot) { // 用于开发环境
	// 一旦module.hot为true,说明开启了HMR功能
		/* 
			1、accept方法会监听print.js的变化,一旦发生变化,默认不会重新打包构建其他js,而是会执行后面的回调函数
			2、每引入一个(自己写的)js,就要在if里面加一个用来监听的accept函数
		 */
	module.hot.accept('./print.js', function() {})
}

//代码分割、懒加载、预加载
用于生产环境,通过import动态导入语法能将某个文件单独打包成一个chunk
//通过/* webpackChunkName: 'test' */将输出的chunk设置为固定的名字
// 正常加载是并行加载(同时加载多个文件,文件越多加载速度越慢)
// 懒加载:当文件需要使用时才加载
// 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
// 懒加载或预加载都建立在代码分割的基础上
// 但是下面的语法eslint会报错,提示你import语句只能放在顶部,解决的方法,要么打包时关掉eslint,要么使用webpack.pro.js中的optimization实现按需加载
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
	console.log(mul(4, 5));
});

/*
	eslint不认识window、navigator全局变量
	解决:需要修改package.json中eslintConfig配置
    "env": { 
      "browser": true // 支持浏览器端全局变量
    }
    sw代码必须运行在服务器上
	npm i serve -g
	serve -s build	启动服务器,将build目录下所有资源作为静态资源暴露出去
	sw只能用https访问,本地除外
 */
// 注册serviceWorker
// 处理兼容性问题
if ('serviceWorker' in navigator) {
	window.addEventListener('load', () => {
		navigator.serviceWorker.register('/service-worker.js').then(() => {
			console.log('注册成功');
		}).catch(() => {
			console.log('注册失败');

		});
	});
}

五、性能优化

1、开发环境性能优化

侧重:

(1)优化打包构建速度

 - HMR

 - oneOf

(2)优化代码调试

 - source-map

2、生产环境性能优化

侧重:

(1)优化打包构建速度

 - oneOf

 - babel缓存

 - 多进程打包

(2)优化代码运行的性能

 - 文件资源缓存(hash-chunkhash-contenthash)

 - tree shaking

 - code split

 - 懒加载/预加载

 - pwa

 - externals

 - dll

六、使用

  • 手动打包

进入项目根目录,执行webpack命令,默认会将webpack.config.js作为配置文件,开发环境中,可以将webpack.dev,js复制为webpack.config.js。生产环境同理,并不是不能直接指定或修改配置文件,只是这样来得快。对于某些第三方库,可以先使用webpack.dll.js打包,以后每次打包再用webpack.pro.js

  • 开发环境实时监听打包

devServer配置:见webpack.dev.js

进入项目根目录,执行npx webpack-dev-server命令

  • 生产环境监听

服务器配置:见server.js

进入项目根目录,执行node server.js命令(只是创建了服务器,并不能自动打包和刷新页面)

运行servicework的服务器启动命令:serve -s build

你可能感兴趣的:(webpack)