webpack简介
webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部构建一个 依赖图(dependency graph),此依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle。
webpack 使用
- 初始化并安装
npm init -y 之后 npm install webpack webpack-cli --save-dev
- 配置打包文件
在项目的根目录下添加一个webpack.config.js文件,文件内以导出的方式书写配置文件.默认是由src/index.js ->输出到dist/main.js。
yarn webpack --mode 模式可以切换webpack的打包模式,可以在配置文件的配置对象中设置 mode:‘development’ 属性来配置
production模式会优化打包的结果
development**模式会优化打包的速度
none模式会运行最原始的webpack配置,不做任何额外的处理
使用内置的插件需要导入模块
const path = require('path')
module.exports = {
mode:'development',
entry: './src/main.js',//配置导入文件的路径
output: { //配置输出文件的信息
filename: 'bundle.js',
path: path.join(__dirname, 'output')//设置文件的路径,必须为绝对路径
}
}
- 执行打包命令
使用yarn webpack
命令
`
webpack 打包原理
webpack会将打包好的模块以立即执行函数的方式定义。调用时,给参数中传入一个数组,数组列表都是以参数相同的函数,函数对应源代码中的模块。所有的模块会包裹在函数中,从而实现模块的私有作用域.
webpack 资源模块加载
webpack可以使用loader来加载任何类型的资源。需要安装css-loader yarn add css-loader --dev
和style-loader (将css-loader转换的结果追加到页面上)yarn add style-loader --dev
,安装完成以后需要在webpack.config.js文件中添加配置。在module中添加rules属性。
module: {
rules: [
{
test: /.css$/, //利用正则表达式来匹配路径。(以.css文件结尾)
use: [ //在配置use对象时,加载顺序是从后向前执行,必须将css-loader放到最后先执行
'style-loader', //使用style-loader将转换好的css结果追加到页面上
'css-loader' //使用的css-loader 将css代码转换成js模块才可以进行打包
]
}
]
}
将css资源引入js文件中,满足代码所需要的条件而非应用将js和css相分离。
加载图片资源时也需要引入加载器file-loader 命令yarn add file-loader --dev
.webpack会将所有打包的结果放到网站的根目录下。在webpack的配置文件的导出(output属性)里添加publicPath:'实际的url'
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: 'dist/' //设置导出文件的位置
},
module: {
rules: [
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.png$/, //利用正则表达式确定图片文件位置
use: 'file-loader' //使用file-loader将图片文件打包
}
]
}
}
使用url-loader 将文件转化为Data URLs存放到代码中安装yarn add url-loader --dev
小文件使用Data URLs,减少请求次数 ;大文件单独提取存放,提高加载速度
use: {
loader: 'url-loader', //
options: {
limit: 10 * 1024 // 10 KB //设置10kb以下的文件通过 url-loader存放超过10KB的文件用文件单独存放
}
}
加载器类型
- 资源类加载器 css-loader
- 文件操作类加载器 file-loader
- 代码检查加载器 eslint-loader
配置bable-loader,
{ test: /.js$/,
use: {
loader: 'babel-loader', //配置设置为babel-loader
options: {
presets: ['@babel/preset-env']//此插件集合包含全部ES全新特性
}
}
}
打包html需要使用html-loader 插件,使用html-loader默认只会处理image标签的src属性,使用其他标签属性需要额外配置
{
test: /.html$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'a:href'] //处理a标签的herf属性
}
}
}
Webpack模块加载方式
- 遵循ES Module标准的import声明
- 遵循CommonJs标准的require函数
- 遵循AMD标准的define函数和require函数
- 样式代码中的@import指令和url函数
- HTML代码中图片标签的src属性
所有需要引用的资源都会被webpack找出根据配置交给不同的loader处理,将处理的结果整体打包到输出目录,通过这个特点实现项目的模块化
webpack工作原理
一个项目中会存在资源文件和代码集 webpack会根据配置找到一个js文件作为打包入口。沿着入口文件代码根据代码引入的import文件解析依赖的资源模块,最后根据依赖找到所有项目依赖关系的依赖树,webpack会递归遍历依赖树种每个节点所对应的资源文件。最后根据配置文件中的rules属性找到模块对应的加载器加载该模块,将加载出的结果放到bundle.js中。
yarn add marked --dev
插件使用
增强Webpack自动化能力使用Plugin解决其他自动化工作 等
Webpack自动清理输出目录插件
安装clean-webpack-plugin 插件yarn add clean-webpack-plugin --dev
。使用时需要导入插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')//导出插件
module.explorts = {
plugins: [ //配置插件时在module.explorts中书写一个plugins 属性
new CleanWebpackPlugin()
]
}
html 打包 插件
为使html文件能够参与构建,减少各资源文件位置引用错误,使用html-webpack-plugin插件进行构建。确保引用相对路径正确
安装插件 yarn add html-webpack-plugin --dev
默认html-webpack-plugin 导出为插件类型,无需解构
const HtmlWebpackPlugin = require('html-webpack-plugin')
//还需删除原来资源文件的默认导出路径 (publicpath的属性值)
plugins: [
new CleanWebpackPlugin(),
// 用于生成 index.html
new HtmlWebpackPlugin({
title: 'Webpack Plugin Sample', //设置html文件中titel标签中的内容
meta: {
viewport: 'width=device-width' //设置内mateb标签中content的属性值
},
template: './src/index.html'
}),
// 用于生成 about.html
new HtmlWebpackPlugin({
filename: 'about.html'
})
]
拷贝文件(上线前最后一次打包时使用)
利用copy-webpack-plugin --dev
安装。传递数组用于拷贝文件的路径
const CopyWebpackPlugin = require('copy-webpack-plugin')
plugins:[
new CopyWebpackPlugin([
// 'public/**'//将public目录中的所有文件打包拷贝
'public'
])
]
Webpack 插件原理
plugin通过在生命周期中的钩子中挂载函数实现扩展,必须是一个函数或者是包含apply()方法的 对象。一般声明一个类,在类中定义对象方法。
webpack 增强开发体验
使用webpack打包时,可以使用yarn webpack --watch
命令开启监听模式,代码修改后自动重新打包。使用 browser-sync插件可以自动编译后刷新 browser-sync dist --file "**/*"
但是编译读取会重复读取磁盘效率低下。使用webpack Dev Server提供Http Server集成自动编译和自动刷新功能。安装插件yarn add webpack-dev-server --dev
后运行命令yarn webpack-dev-server
会自动使用webpack打包应用,并且启用HTTP-serve运行打包结果,监听运行变化。Webpack Dev Server没有将打包结果写入磁盘,而放入内存,内部的HTTP-server从内存读取数据。使用yarn webpack-dev-server --open
用于自动唤起浏览器
Http-Server 默认将构建结果输出的文件构建为开发资源文件,静态资源被访问时需要额外为开发服务器指定查找资源目录serve。
//在配置文件中添加一个devServer属性
devServer: {
contentBase:'./public' //制定额外的静态资源路径,可以设置为字符串或者数组来配置一个或多个路径
}
代理API(处理跨域获取资源问题)
在生产环境可以直接访问API,但是在开发环境中访问网页使用的地址是localhost:8080 此时需要CORS跨越请求,但并非所有的浏览器都支持跨域请求,此时就需要将API服务代理到开发服务中。Webpack-Dev-Server 可以通过配置来提供代理服务。
devServer: {
contentBase: './public',
proxy: { //添加代理服务配置,内部的每个属性都是代理服务规则
'/api': { //
// http://localhost:8080/api/users -> https://api.github.com/api/users
target: 'https://api.github.com', // 将github资源代理到开发服务器
// http://localhost:8080/api/users -> https://api.github.com/users
pathRewrite: { //重写代理路径
'^/api': '' //将api目录替换成空(会以正则方式替换,所以^表示开头)
},
// 不能使用 localhost:8080 作为请求 GitHub 的主机名
changeOrigin: true //默认浏览器会以实际请求的主机名
}
}
}
Source Map
使用webpack打包过程中,会将文件进行转换,线上运行的代码和开发过程中代码不一致,导致难以排除bug。
Source Map文件是映射源代码和转换后的代码的关系,通过Sourse Map能够将转化后的代码逆向得到源代码。在webpack.config.js文件中可以通过devtool属性配置转换的模式。
- eval 模式:模块代码放到eval函数中执行并且通过sourse URL标识文件路径,只能定位出错文件不可定位行列
- eval-sourse-map:可定位行列,生产sourse-map
- cheap-eval-source-map :只定位行,无列信息。定位的源代码是ES6转化后的源代码
- cheap-module-eval-source-map:加module以后定位代码为编写时的源代码
- hidden-source-map:生成souce-map开发第三方包时候使用。生成source-map文件,但没有引用文件
- nosourse-source-map:可以看到错误位置信息,但无源代码
选择使用模式
开发:cheap-module-eval-sourse-map:经过loader转化之后的源代码差异较大,需要使用转化之前的。启动打包慢,重新打包速度的很快
生产:nosurce-source-map 或none:source-map会暴漏源代码
自动刷新问题
自动刷新导致页面状态的丢失。
解决方法:使用热替换(HMR)
webpack-dev-server --hot
或在配置文件中配置devServer中hot属性为true,将插件实例化。
const webpack = require('webpack') //要使用webpack内置的插件,先将插件导入
module.exports = {
...
devServer: {
// hot: true //用hot如果热替换失败就会回退自动刷新功能
hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading
},
...
}
plugin[
new webpack.HotModuleReplacementPlugin()
]
HMR需要手动处理模块热替换逻辑。样式文件时由css-loader处理,直接替换修改过的内容,而JS文件需要自己手动处理热更新之后的替换规则。
HMR API
HMR为更新修改内容提供了API,使用内置方法accept()
语法:module.hot.accept(更新文件的URL,处理函数)。
//main.js文件
module.hot.accept('./better.png', () => {
// 当 better.png 更新后执行
// 重写设置 src 会触发图片元素重新加载,从而局部更新图片
img.src = background
})
由于热更新失败默认采用自动刷新,使得热更新代码有错误,不容易发现,可以采用hotOnly:true
devServer: {
//hot: true
hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading
},
没有启动HMR,HMR API报错:由于module.hot对象是一由HMR插件提供。没有启动HMR但使用了HMR导致
if (module.hot) { //首先安段有没有开启,再使用
let hotEditor = editor
module.hot.accept('./editor.js', () => {
// 当 editor.js 更新,自动执行此函数
// 临时记录编辑器内容
const value = hotEditor.innerHTML
// 移除更新前的元素
document.body.removeChild(hotEditor)
// 创建新的编辑器
// 此时 createEditor 已经是更新过后的函数了
hotEditor = createEditor()
// 还原编辑器内容
hotEditor.innerHTML = value
// 追加到页面
document.body.appendChild(hotEditor)
})
在使用webpack打包的过程中,HRM相关配置代码会自动被删除。不会被引入到生产环境中
不同环境打包不同代码
方式1:配置文件根据环境不同导出不同的配置 (中小型项目)
通过导出一个函数来配置对象,函数提供两个参数env(环境名参数)和argv(所有参数)
if (env === 'production') {
config.mode = 'production'
config.devtool = false
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
}
return config
}
方式2:一个环境对应一个配置文件(大型项目)
配置时一般会有多个配置文件,用后面的配置覆盖掉公共属性的配置,并且在公共配置的基础上添加一些配置。使用webpack中的merge合并配置需求。安装webpack-merge使用命令yarn add webpack-merge --dev
const merge = require('webpack-merge')
module.exports = merge(common, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
})
webpack优化配置
DefinePlugin插件
给代码注入全局成员默认给程序中注入 process.env.NODE_ENV常量,用来为第三方模块提供判断运行环境的参数
const webapck = require('webpack')
plugins: [
new webpack.DefinPlugin({
API_BASE_URL:'"https://api.example.com"'
//如果传入一个值则使用json.stringify将值转化为表示值的代码片段。
// new webpack.DefinePlugin({
// 值要求的是一个代码片段
// API_BASE_URL: JSON.stringify('https://api.example.com')
})
]
Tree shaking
移除代码中未引用的代码,在生产模式下自动开启
解释:在生产模式下自动开启usedExports和concatenateModules属性导致自动
optimization: {
// 模块只导出被使用的成员
usedExports: true,//标记未引用的代码
// 尽可能合并每一个模块到一个函数中
concatenateModules: true,//删除被标记的代码
// 压缩输出结果
// minimize: true
}
concatenateMoudles属性将所有模块合并到一起。既提升效率有减少代码体积。
使用Tree-shaking前提是用ES Module来组织代码。webpack打包代码之前必须使用ESM。babel-loader转化时如果设置option属性为presets :[@babel/preset-env]
时,就会将ESmodule转化成commonjs。此时Treee-shaking就无法生效
sideEffects新特性 通过配置的方式表示代码是否有副作用。从而为Tree-shaking提供更大的 压缩空间。副作用是指模块执行是除了导出成员还有其他任务。设置sideEffects属性值为false其他没有用到的代码就不会被打包。
分包(代码分割)
- 多入口打包(适用于多页面程序)
配置entry定义为对象而不是数组,如果定义为数组会把多个文件打包在一起
module.exports = {
mode: 'none',
entry: {
index: './src/index.js',
album: './src/album.js'
},
output: {
filename: '[name].bundle.js' //输出文件名为index.budle.js和album.budle.js
},
...
}
以上配置会将所有模块引入到每个文件中,需要配置chunks属性
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: ‘Multi Entry’,
template: ‘./src/index.html’,
filename: ‘index.html’,
chunks: [‘index’] //每个打包入口形成独立的chunks
}),
new HtmlWebpackPlugin({
title: ‘Multi Entry’,
template: ‘./src/album.html’,
filename: ‘album.html’,
chunks: [‘album’]
})
]
- 动态导入
将公共模块提取到单独的模块中
output: {
filename: '[name].bundle.js'
},
optimization: {
splitChunks: {
// 自动提取所有公共模块到单独 bundle
chunks: 'all'
}
},
...
动态导入以后的名称是序号,通过魔法注释为打包后的文件命名
imput(/* WebpackChunkName:
components*/
./post
)`相同命名的文件会打包在一起
提取CSS到单独的文件(CSS按需加载)
miniCssExtractPlugin插件导入后直接放到plugin数组中就可使用
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin()
]
提取以后需要压缩输出使用optimize-css-assets-webpack-plugin插件。使用时需要将js压缩和css压缩插件都放在一起才可使用,安装webpack内置插件terser-webpack-plugin压缩js代码
optimization: {
minimizer: [
new TerserWebpackPlugin(),
new OptimizeCssAssetsWebpackPlugin()
]
}