1.webpack是什么?
- 最开始是一个基于node的js打包工具。
- 简单来说,webpack是一个(bundle)模块打包工具,处理模块之间的依赖同时生成对应模块的静态资源。
2.为什么使用webpack?
- 项目中html文件可能会引用十几个js文件,而且得按照一定的顺序,js文件之间存在依赖关系,同时对于es6+等语法,less,sass等css预处理都不能很好的解决。此时需要借助一些工具处理这些问题
打包例子如下:
// 初始化
npm init
// 安装webpack,webpack-cil的作用是可以在命令行内使用webpack指令或者npx webpack指令
npm install webpack webpack-cli --save-dev
- 文件目录如下:
- 在index.html引入打包后的main.js文件
<script src="../dist/main.js"></script> <!--这是打包之后默认生成的main.js文件-->
- header.js导出一个头部模块
function header() {
const el = document.createElement('div')
el.innerText = '这是导航模块'
document.body.appendChild(el)
}
module.exports = header
- content.js导出内容模块
function content() {
const el = document.createElement('div')
el.innerText = '这是内容模块'
document.body.appendChild(el)
}
module.exports = content
- 将这两个模块引入index.js
var Header = require('./header.js');
var Content = require('./content.js');
new Header()
new Content()
- 执行打包命令
// npx 是node 5.2版本以上自带的 并且查看局部版本号的
npx webpack src/index.js
// 直接使用webpack 需要全局安装webpack,且可以修改打包输出文件名
webpack src/index.js --output dist/bundle.js
-
页面效果如下
上述操作就相当于我们把header.js模块和content.js模块合并到了index.js模块,对这合并后的index.js模块打包成main.js,然后供index.html引用,这就是webpack的打包原理。
webpack打包原理
它会把项目当成一个整体,通过一个入口文件(index.js),从这个文件开始找到项目中所有依赖,然后在内部创建一个依赖图(dependencygraph),用于映射到项目需要的每个模块,最后打包成一个或多个浏览器可识别的js文件。
现在已经成功的使用webpack进行打包啦,但是在终端输入命令,感觉好烦哦,还好山人自有妙计,且往下看看。
3.通过配置文件使用webpack
- 在当前根目录下新建配置文件webpack.config.js,从入口和出口配置
// 输出路径,必须是绝对路径,这里需要使用node的核心模块path,调用模块的resolve方法
const path = require('path')
module.exports = {
entry:'./src/index.js', // 入口文件
// 输出文件
output:{
publicPath:'/', // 打包输出根路径
// publicPath:'gttp://cdn.com.cn',
filename:'[name].bundle.js',
path:path.resolve(__dirname,'dist')
},
}
- publicPath:项目发布时,配置对应的域名参数
- filename:打包输出文件名,可以是bundle.js默认生成的main.js,如果打包多个文件,可以改成占位符,这样打包出来的名字就是entry配置的
- path:打包出的文件放在哪个文件下面
- __dirname:node的变量,即webpck.config.js当前所在文件夹的目录,这个就是生成的bundle文件夹的路径,一般都是叫dist,生成的文件名字叫做bundle.js
-
配置完成,运行命令时会自动引用webpack.config.js文件中的配置选项,运行如下:
注意:如果配置的默认webpack.config.js名字修改为webpackconfig.js,此时运行命令
// 告诉webpack 按照webpackconfig.js里面的规则打包
npx webpack --config webpackconfig.js
- 通过pack.json 文件配置对应的命令
注意:package.json中的script会按你设置的命令名称来执行对应的命令
// 执行npm run build,会自动执行webpackj进行打包
"scripts": {
"build":"webpack"
},
总结(webpack三种使用方法)
- 全局安装执行命令
// index.js 是入口文件
webpack index.js
- 本地安装执行命令
npx webpack index.js
- 在pack.json 中配置命令并且借助配置文件间接执行webpack指令
// 在script 目录下使用webpack,会在当前的工程目录下找webpack,这就是为什么不用加npx
npm run build => webpack
4.Loaders介绍
1,开始之前,先看看下面的图片是否能正常显示?
- 在当前项目下新建img.js,引入一张下载并放到对应的目录文件下
import avater from '../assets/images/avater.jpg'
function Img(){
var img = new Image();
img.src=avater;
img.classList.add('avater');
document.body.append(img)
}
export default Img;
- 在index.js中引入img模块,并创建一个该构造函数的实例
import Img from './img'
new Img();
- 运行打包命令npm run build
- 结果报错:
出错原因是webpack只知道怎么打包js文件,不知道怎么打包其他类似jpg文件。那么webpack打包除了js以外的文件都需要借助loaders。
loaders是webpack最强大的功能之一,通过不同的loader,webpack有能力调用外部的脚本或工具,实现对不同格式的文件(如图片),字体图标的处理,例如把less转为css,将ES66、ES7等语法转化为当前浏览器能识别的语法等功能
Loaders需要单独安装并且需要在webpack.config.js中的modules配置项下进行配置,Loaders的配置包括以下几方面:
- test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)
- loader:loader的名称(必须)
- include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
- options:为loaders提供额外的设置选项(可选)
图片打包loader
- file-loader
- 安装对应的loader
npm install --save-dev file-loader
- 配置参数
module:{
rules:[{
test: /\.(png|jpe?g|gif)$/i, // 匹配图片格式名
use:[
{
loader:'file-loader', // 使用file-loader
options: {
name:'[name].[hash].[ext]' //配置打包名字和格式
}
}
]
}]
},
-
运行npm run build 后显示如下:
- url-loader
- 安装
npm install --save-dev url-loader
- 将上面的file-loader 替换成url-loader
- 运行 npm run build
- 结果如下:
url-loader
它会将图片转化成base64 图片,将图片打包到js文件中
虽然不用httpq请求图片,但是假如生成的js文件过大的情况下,会影响页面加载性能
url-loader的最佳使用方法:
给图片一个限制limit属性,当图片小于某一个值的时候,打包到js文件中,否则超出这个限制,则和file-loader一样,将图片打包成对应的图片,放到dist目录下
rules:[{
test: /\.(png|jpe?g|gif)$/i,
use:[
{
loader:'url-loader',
options: {
/**placeholder 占位符,
* [name] 名字是原来图片对应的名字
* [ext] 图片对应的格式
* outputPath:表示打包到dist对应的images下
* limit:2048 指的是2kb 204800 即200kb,如果小于200kb则会使用url-loader 打包成base64
* 到js文件中,大于200kb的则不会打包到js文件中,而是按照设置的目录打包成图片
* */
name:'[name]_[hash].[ext]',
outputPath:'images/',
limit:10240
}
}
]
运行打包结果,生成单独的图片
注意:虽然我们只需使用url-loader,但url-loader是依赖于file-loader的,需要两个一起安装
样式打包loader
如果我们要加载一个css文件,需要安装配置style-loader和css-loader:
npm install style-loader css-loader --save-dev
- 在项目里面配置一下规则
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
- 我们在src文件夹下新建css文件夹,该文件夹内新建index.css文件:
// index.css
body{
background: red;
}
- 此时运行结果会发现背景变成了红色
配置项目中常用的less
// 安装less 及对应的less-loader
npm install less less-loader --save-dev
- 增加less的rules
{
test: /\.less$/,
use: [
'style-loader',
{
loader:'css-loader',
options:{
importLoaders:2// 作用于 @import的资源之前」有多少个 loader 0 => 无 loader(默认); 1 => postcss-loader; 2 => postcss-loader, less-loader
// modules:true
}
},
'less-loader',
'postcss-loader'
], // compiles Less to CSS
},
- style-loader :会将对应的样式挂在在dom节点上
- less-loader:解析less语法
- postcss-loader:自动生成css3的浏览器前缀
loader 执行顺序:从下到上,从右到左,先执行less-loader文件进行翻译,然后给到css-loader,最后挂载到style-loader挂载到dom上
- importLoaders:保证不管在js文件中importless文件还是less文件中导入子less文件,都能执行下面的是在一个less文件导入另外的less文件,postcss-loader,less-loader在进行,然后给到css-loader
- modules:开启css模块化打包,类似与全局或者局部
- postcss-loader与autoprefixer配合自动补全css前缀
npm install autoprefixer postcss-loader --save-dev
- 具体查看postcss-loader
https://www.webpackjs.com/loaders/postcss-loader/
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')]
}
结果会在加上前缀
- 在css文件夹下创建avater.less
// avater.less
body{
.avater{
width: 200px;
height: auto;
border: 1px solid #ccc;
transform: translate(100px,100px);
}
}
- 在img.js中引入avater.less:
import './css/avater.less'
- 结果入下图
还有诸如字体图标loader、字体loader等就不一一列出来了,感兴趣的可前往webpack官网查看,都是一样的套路。
5.插件(Plugins)
插件(Plugins)是用来拓展Webpack功能的,它们会在整个构建过程中生效,执行相关的任务,类似与vue,react的生命周期概念,在构建过程中某些时刻,做些事情,让打包更加便捷。
插件的使用流程:
- 安装
- 引入
- 在plugins里面实列化
1.HtmlWebpackPlugin
功能:在打包结束后,会自动生成一个html文件,并把打包生成的js自动引入到这个html文件中
还可以通过配置模板属性,打包生成的html文件与src目录下的index.html文件一致
// 安装
npm install --save-dev html-webpack-plugin
- webpack.config.js中我们引入了HtmlWebpackPlugin插件,并配置了引用了我们设置的模板,如下:
const path = require('path')
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode:'development',
entry:'./src/index.js',
module:{
rules:[{
test: /\.(png|jpe?g|gif)$/i,
use:[
{
loader:'url-loader',
options: {
name:'[name].[hash].[ext]',
limit:2048 /*:2048 指的是2kb 204800 即200kb,如果小于200kb则会使用url-loader 打包成base64 到js文件中,
大于200kb的则不会打包到js文件中,而是按照设置的目录打包成图片*/
}
}
]
}, {
test: /\.css$/,
use: [ 'style-loader', 'css-loader']
},
{
test: /\.less$/,
use: [ 'style-loader', 'css-loader','less-loader','postcss-loader']
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'src/index.html' //根据自己的指定的模板文件来生成特定的 html 文件。这里的模板类型可以是任意你喜欢的模板,可以是 html, jade, ejs, hbs, 等等,但是要注意的是,使用自定义的模板文件时,需要提前安装对应的 loader, 否则webpack不能正确解析
})
],
output:{
filename:'[name].js',
path:path.resolve(__dirname,'dist')
}
}
- 先将将目录下dist文件删除然后我们使用npm run build进行打包,你会发现,dist文件夹和html文件都会自动生成,如下:
CleanWebpackPlugin
在打包的之前,清除上一次的dist文件夹下的打包生成的js为文件,也可以配置清理/dist文件夹
// 安装
npm install clean-webpack-plugin --save-dev
//引入清除文件插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
//实例化,参数为目录,这里面无参数,默认删除的是output.path!
我的output配置项里的路径就是dist目录。
ew CleanWebpackPlugin()
output:{
filename:'[name].js',
path:path.resolve(__dirname,'dist')
}
注意:webpack 4.32.2 以上的写法,
新版本的插件需要进行解构,默认删除的是index.html里面未引用的文件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
6.构建本地服务器
使用 webpack-dev-server能够根据代码的变化,自动打包并刷新页面效果,提升开发效率
webpack-dev-server配置本地服务器
npm i webpack-dev-server --save-dev
devServer作为webpack配置选项中的一项,以下是它的一些配置选项:
- contentBase :设置服务器所读取文件的目录,当前我们设置为"./dist"
- open:true 是否自动打开新的浏览器窗口
- port :设置端口号,如果省略,默认为8080
- inline :设置为true,当源文件改变时会自动刷新页面
- historyApiFallback:设置为true,所有的跳转将指向index.html
- 将这些配置加到webpack.config.js文件上,如下:
devServer:{ //
contentBase: path.join(__dirname, 'dist'),
open: true, // 自动打开浏览器
port: "8088", // 设置端口号为8088,默认是8080
hot:true, // 热模块
hotOnly:true, //html 热加载,html 失效的时候,如果是false ,会重新帮你刷新页面,一般也买你失效,不需要去刷新,所以这里值为true
proxy:{} // 设置代理
},
- 在package.json文件中scripts添加启动命令:
"dev":"webpack-dev-server"
- 启动项目
npm run dev
其他webpack配置本地服务器方法
- 在package.json 中 配置--watch 启动之后,监听打包的js文件发生变化就会自动执行打包,这个无法axios请求以及自动更新
"build": "webpack --watch",
- 利用node自己搭建一个服务器
- 安装webpack 的中间件 middleware
npm install express webpack-dev-middleware -D
- 在package.json 配置命令
"scripts": {
"build": "webpack",
"server": "node server.js"
}
- 在项目根目录下新建一个server.js文件
// server.js
const express = require('express');
const webpack= require('webpack');
const webpackDevMiddlewear = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
const complier = webpack(config); // 进行编译
// 在node中使用webpack
const app = express();
app.use(webpackDevMiddlewear(complier,{
publicPath:config.output.publicPath // 打包输出的路径就是config
}))
app.listen(3000,(()=> {
console.log('server is runnin')
}))
-
启动结果
总结:会发现这个没有webpack-dev-server智能,每次修改页面需要手动刷新页面才能更新,所以项目中一般用webpack-dev-server
Source Maps调试配置
作为开发,代码调试当然少不了,那么问题来了,经过打包后的文件,你是不容易找到出错的地方的,Source Map就是用来解决这个问题的。
通过如下配置,我们会在打包时生成对应于打包文件的.map文件,使得编译后的代码可读性更高,更易于调试
module.exports = {
mode:'production', // 改成production 则是生产环境,会打包压缩
devtool:'source-map', // development 环境下,会默认是关闭de,线上cheap-module-source-map
entry: {main:'./src/index.js'}
// 热加载配置
devServer:{
contentBase:'./dist',
open:false,
port: "8088", // 设置端口号为8088
hot:true, // 热模块
hotOnly:true //html 热加载,html 失效的时候,如果是false ,会重新帮你刷新页面,一般也买你失效,不需要去刷新,所以这里值为true
},
}
- 比如在index.js中写入错误代码
consoel.log('1111')
- 配置好后,我们再次运行npm run build进行打包,这时我们会发现在dist文件夹中多出了一个main.js.map文件如下:
- 并且执行npm run dev,SourceMap 会建立打包代码与源代码的一个映射概关系,它是一个映射 关系,他知道dist目录下main.js文件报错代码对映的实际上对于的是src目录下SourceMap/index.js文件中的第8行代码报错,结果如下
如果我们的代码有bug,在浏览器的调试工具中会提示错误出现的位置,这就是devtool: 'source-map'配置项的作用。
source-map配置优化
- source-map 参数,具体看官网,关闭用none
- source-map:会降低打包速度,打包过程中会生成main.bundle.js.map映射文件,降低打包速度
- inline-source-map:区别 对应的main.bundle.js.map文件会以base64格式打包到对应的js文件中,也就是不会单独生成.map文件
- inline-cheap-source-map:只要告诉第几行出错了,打包速度会提高,只管业务代码,他的打包速度比较快
- cheap-module-source-map:不止管业务代码里面的错误,还会管css,loader里面的错误,但是速度比较慢
- eval-source-map:这个速度比较快,eval并且会将代码的映射概关系打包到main.js中,但是在大型中可能不全。且不会生产map.js文件
- ==cheap-module-eval-source-map== 建议在开发环境下,打包速度比较快且比较全
- production 线上代码:==cheap-module-source-map== 提示更全面点
具体参数配置在官网:
打开官网
浏览器调式插件:source-map
热更新(HotModuleReplacementPlugin)
可以在我们修改代码后自动刷新预览效果
- 使用方法:
- devServer配置项中添加hot: true参数。
- 因为HotModuleReplacementPlugin是webpack模块自带的,所以引入webpack后,在plugins配置项中直接使用即可。
7.babel
Babel其实是一个编译JavaScript的平台,它可以编译代码帮你达到以下目的:
让你能使用最新的JavaScript代码(ES6,ES7...),而不用管新标准是否被当前使用的浏览器完全支持;
让你能使用基于JavaScript进行了拓展的语言,比如React的JSX;
// 安装es6语法
npm install --save-dev babel-loader @babel/core
npm install @babel/preset-env --save-dev
{ test: /\.js$/,
exclude: /node_modules/, // 检测到js文件,排除node_modules下js文件,进行语法转化
loader: "babel-loader" ,
options:{
"presets": ["@babel/preset-env"]
}
}
现在我们已经可以支持ES6语法转换ES5啦
// index.ja写下面代码
const str = '1223333'
let nameArr = ['violet','122','3333']
nameArr.map((el,idx) => {
console.log(el,idx)
})
// 执行打包命令 在目录中生成dist
webpack
(因为执行npm run dev 会将打包的dist目录放放置在内存中,隐藏起来,此时我们打开打包后的main.js,最末尾会看到语法转化),如下:
但是map函数或者对象在低版本浏览器里面是没有的,还需要将这些低版本浏览器里没有的函数或者对象补充进去,这就需要使用@babel/polyfill
npm install --save @babel/polyfill
- 在index.js中导入
import "@babel/polyfill";
执行webpack
你会发现这中全局注入的方式,会增大main.js的体积
注入@babel/polyfill前。main.js只有20.2kib
注入后。main.js 419kib
体积变大的原因是这种全局注入方式,会将所有的将函数或者变量在低版本的补充,包括promise, symbol 这类新增的内建对象的注入,因此如果能实现按需注入,就可以减小体积啦
@babel/polyfill优化方法:
可以设置useBuiltIns:'usage' 类似与根据你的业务代码进行替换,业务代码里面用到什么就注入@babel/polyfill,按需引入,优化打包体积
同时,使用这个属性webpack会在打包时自动引入@babel/polyfill,不需要手动引入
{ test: /\.js$/,
exclude: /node_modules/, // 检测到js文件,排除node_modules下js文件,进行语法转化
loader: "babel-loader" ,
options:{
"presets": [["@babel/preset-env",{
targets: {
edge: "17",
firefox: "60",
chrome: "67", // 项目会打包运行在chorme 版本67 的上面,在这个版本号或者以上版本是否支持es6,如果支持,则是不必要进行注入转换
safari: "11.1",
},
useBuiltIns:'usage' // 根据业务代码用到的函数,按需替换
}]]
}
}
打包后体积变成 main.js 45.9kib
此外还可以在设置targets的浏览器版本号,决定是否要转换代码,比如chorme 67 以上对Es6语法都支持所以打包后的体积,又精简成 20.3kib啦,如下图
@babel/runtime 配置
虽然polyfill是按需引入的,但是会污染全局命名空间,当你写的是公共库时,可能会与使用者本地的方法产生冲突。
当我们开发UI组件库或者在开发第三方库时就要需要使用@babel/runtime和@babel/plugin-transform-runtime,能够有效的避免,会以闭包的形式注入,不会污染全局的环境
npm install --save @babel/runtime-corejs2
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
配置代码
options:{
"plugins": [[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2, // 一般写2
"helpers": true,
"regenerator": true,
"useESModules": false
}
]]
// "presets": [["@babel/preset-env",{
// targets: {
// edge: "17",
// firefox: "60",
// chrome: "67", // 项目会打包运行在chorme 版本67 的上面,在这个版本号或者以上版本是否支持es6,如果支持,则是不必要进行注入转换
// safari: "11.1",
// },
// useBuiltIns:'usage' // 根据业务代码用到的函数,按需替换
// }]]
}
}
babele配置提取
- 在项目根目录下新建一个.babelrc文件
这里面的内容对应的就是babel-loader options里面的内容
// .babelrc 使用时把注释删掉,该文件不能添加注释
{
"plugins": [[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2, // 一般写2
"helpers": true,
"regenerator": true,
"useESModules": false
}
]]
}
更详细babel7的可以看这篇文章的介绍
link
8.webpack项目优化及拓展
1.环境模式区分打包配置
- 修改目录结构如下:
- 使用webapxk-merge将公共配置提取出来
npm install webpack-merge --save-dev
// webpack.prod.js
// 引入合并插件
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base');
const prodConfig = {
mode: 'production', // 在development开发环境中,默认sourceMaps devtool默认是false的
devtool: 'cheap-module-source-map',
}
module.exports = merge(baseConfig, prodConfig)
// webpack.dev.js
// 引入合并插件
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base');
const path = require('path')
const devConfig = {
mode: 'development', // 在development开发环境中,默认sourceMaps devtool默认是false的
devtool: 'cheap-module-eval-source-map',
devServer: { //
contentBase: path.join(__dirname, 'dist'),
open: false, // 自动打开浏览器
port: "8088", // 设置端口号为8088
hot: true, // 热模块
hotOnly: true, //html 热加载,html 失效的时候,如果是false ,会重新帮你刷新页面,一般也买你失效,不需要去刷新,所以这里值为true
proxy: {} // 设置代理
},
optimization: {
usedExports: true // tree shaking
}
}
module.exports = merge(baseConfig, devConfig);
// webapck.base.js
const path = require('path')
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 引入CleanWebpackPlugin插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry:'./src/index.js',
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html' //根据自己的指定的模板文件来生成特定的 html 文件。这里的模板类型可以是任意你喜欢的模板,可以是 html, jade, ejs, hbs, 等等,但是要注意的是,使用自定义的模板文件时,需要提前安装对应的 loader, 否则webpack不能正确解析
}),
new CleanWebpackPlugin(), //实例化,参数为目录
],
module: {
rules: [{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
name: '[name].[hash].[ext]',
limit: 2048 /*:2048 指的是2kb 204800 即200kb,如果小于200kb则会使用url-loader 打包成base64 到js文件中,
大于200kb的则不会打包到js文件中,而是按照设置的目录打包成图片*/
}
}
]
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader', 'postcss-loader']
},
{
test: /\.js$/,
exclude: /node_modules/, // 检测到js文件,排除node_modules下js文件,进行语法转化
loader: "babel-loader"
}
]
},
output:{
publicPath:'/',
filename:'[name].js',
path: path.resolve(process.cwd(), 'dist') // __dirname: 当前模块的目录名,而 process.cwd() 当前Node.js进程执行时的工作目录,就是根目录,放到上一层dist目录下
}
}
wepack.base.js中放置共有的配置,这里注意因为配置文件放在build里面,路径发生了变化,所以需要将输出地址修改,表示打包的dist文件放到项目根目录
path.resolve(process.cwd(), 'dist')
- 修改package.json,配置打包环境
"scripts": {
"dev": "webpack-dev-server --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
- 执行命令,你会发现项目打包和启动都ok
2.tree shaking(摇树优化,剔除无用的代码)
- 在根目录下新建一个untils.js
// untils.js
const a = () =>{
console.log('a')
}
const b = () =>{
console.log('b')
}
const c = () =>{
console.log('c')
}
export {
a,
b,
c
}
- 在index.js中引入a
import {a} from './untils.js'
a();
- 我们可以看到 在 utils.js中我们定义了三个方法, 在index.js中引入了方法a,也就是说方法b和方法c都是没有使用的。 我们看一下打包之后的结果
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。其实就是说把我们项目中没有使用到的代码在打包的时候丢弃掉,只保留我们引入了的JS代码和css代码
==注意:它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。不支持CommonJs的引入,因为这个是CommonJs动态加载模块==
开发环境配置tree shaking
- 在webpack.config.js中增加一个optimization 属性 设置usedExports:true
mode:'development',
optimization:{
usedExports:true
},
- 在在package.json 设置 "sideEffects",一般设置为false
{
"name": "review",
"version": "1.0.0",
"description": "",
"main": "index.js",
"sideEffects":["@babel/pollyfill"],
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server",
"server": "node server.js"
},
注意:如果项目中有使用babel/pollyfill 文件,这个文件没有导出任何的内容,three shaking
- 打包的时候会忽略点,需要做特殊的设置sideEffects":["@babel/pollyfill","*.css"]这类文件都没有导出文件,这样设置就会忽略这个,一般设置为false
- 打包结果如下,在开发环境下,方便代码调试,不会删除代码,但是会多出一个used属性
在production环境下
mode:'production',
- 在在package.json 设置 "sideEffects",一般设置为false
{
"name": "review",
"version": "1.0.0",
"description": "",
"main": "index.js",
"sideEffects":["@babel/pollyfill"],
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server",
"server": "node server.js"
},
- 去掉optimization
- 打包会发现只有a被调用,其他的代码都被删除
3.codesplit(代码分割)
Webpack 文件分离包括两个部分,一个是 Bundle 的分离,一个是 Code 代码的分离:
Bundle splitting: 实际上就是创建多个更小的文件,并行加载,以获得更好的缓存效果;主要的作用就是使浏览器并行下载,提高下载速度。并且运用浏览器缓存,只有代码被修改,文件名中的哈希值改变了才会去再次加载。
Code splitting: 只加载用户最需要的部分,其余的代码都遵从懒加载的策略;主要的作用就是加快页面加载速度,不加载不必要加载的东西。
比如我们在项目中使用lodash这个库,未进行代码分割配置
打包结果:
每次打包全部依赖都被打包到 main.xxx.js 中去,大小是 1.36mib,在项目中业务代码可能会很大,每次打包生成的文件,影响页面打开速度
Bundle Split 的主要任务是将多个引用的包和模块进行分离,避免全部依赖打包到一个文件下
- Webpack 4 中需要使用到 optimization.splitChunks 进行同步代码分割的配置:
// webpack.base.js
splitChunks:{ // 分片打包 ,其默认值是对异步代码打包
chunks:'all',
minSize: 30000, // 30kb
maxSize: 0, // 50kb
minChunks: 1, // chunks 在项目中引用的次数,满足条件则会进行分割,此时为2 ,但是只引用了一次,则不会进行分割
maxAsyncRequests: 5, // 同时加载的模块数最多是5个
maxInitialRequests: 3, // 整个网页首页或者入口文件引入的库做代码分割,超过3个也就不会进行分割
automaticNameDelimiter: '~', // 打包生成的js 文件~链接符 vendors-lodash.js
automaticNameMaxLength: 30,
name: true, // 生成的下面对应的fliename
cacheGroups: { // 缓存组:对应的分割生成所在组内,缓存组,将符合要求的文件打包生成一个文件
vendors: {
test: /[\\/]node_modules[\\/]/, // 匹配node_modules 里面的js,将其打包分割
priority: -10,
filename:'vendors.js'
},
default: { // 所有的文件都符合defaul 至于放到vendors 这个组内还是default 组内,取决于priority,-10 高于-20,则会打包到vebdors
priority: -20, // 引入两个文件,但是a 文件使用过b ,在打包a的时候,a用到的b ,会使用之前打包的b
reuseExistingChunk: true, 如果一个模块已经被打包过,那么再使用这个模块则会被忽略,直接复用之前的代码
filename:'common.js' // 当引用的文件不在node_modules 中,会打包到defalt 中设置的common.js 中
}
}
}
optimization.splitChunks 的意思是将所有来自 node_modules 中的依赖全部打包分离出来,这个时候我们再看打包的文件是个什么样子:
增加了 splitChunks 的配置,我们第三方模块都被打包到了 vendors~main.xxx.js 中去了,这个文件大小有 1.36mib,而入口文件 main.xxx.js 则只有9.24kib
- splitChunks的主要有以下配置
webpack 会对异步代码自动分割
异步代码import语法,类似路由的按需加载,无需做任何配置,会自动代码分割
- 安装@babel/plugin-syntax-dynamic-import
// 用以解析识别import()动态导入语法
npm install @babel/plugin-syntax-dynamic-import --save-dev
- 在index.js中写入以下代码:
function getComponent () {
// 里面的注释打包生成的name,生成对应得 vendors~lodash.js ,默认生成vendors~main.js
return import(/* webpackChunkName: 'loadsh' */'lodash').then(({default:_}) =>{
var element = document.createElement('div');
element.innerHTML = _.join(['vlolet','lee2'],'-');
return element
})
}
getComponent().then((res) => {
document.body.appendChild(res)
})
- 在bablerc中配置
{
presets: [
["@babel/preset-env", {
targets: {
chrome:"67"
},
useBuiltIns: "usage"
}],
"@babel/preset-react"
],
plugins: ["@babel/plugin-syntax-dynamic-import"]
}
- 打包结果:
css代码分割
- 在当前目录下。有一个css文件夹,引用了css,但是打包并没有生成独立的css文件,而是打包到js文件中啦
// index.css
body{
background: plum;
}
div{
font-size: 20px;
color: blueviolet;
margin: 0 aut0;
}
.avater{
width: 100px;
}
- css代码分割需要借助插件MiniCssExtractPlugin
- 因为只能在生成环境使用,所以需要将css相关打包规则移到各自对应的环境中,并进行压缩
// 分割代码并压缩css代码
npm install mini-css-extract-plugin optimize-css-assets-webpack-plugin --save-dev
// 安装js压缩代码
npm install terser-webpack-plugin --save-dev
// webpack.prod.js中配置插件
// 分割插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 压缩css代码
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// js压缩代码
const TerserJSPlugin = require('terser-webpack-plugin');
module: { // 配置对应的css打包
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader, // 使用插件的loader 替换style-loader
options: { // 配置对应的参数
publicPath: '../',
hmr: process.env.NODE_ENV === 'development',
},
},
'css-loader',
],
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader', 'postcss-loader']
},
]
},
optimization: {
runtimeChunk: { //解决打包两次 contHash 值不一样的情况
name: 'runtime'
},
minimize: true,
minimizer: [ new OptimizeCSSAssetsPlugin({})], // css 自动合并与压缩 TerserJSPlugin这个压缩js代码
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css', // dist 目录下index.html直接引用的会默认生成filename name.css ,间接的使用的文件会生成chunkFilename
chunkFilename: 'css/[name].css',
ignoreOrder: false,
}),
],
- 修改tree shaking package.json中
// 前面有提到过,css文件并没有输出模块,所以不进行tree shaking优化
"sideEffects": ["*.css"],
- 打包结果会生成一个压缩的main.css:
MiniCssExtractPlugin,这个插件不会热更新,不支持html,会降低开发效率,一般在线上环境打包使用这个,在配置css压缩时会覆盖掉webpack默认的优化设置,导致JS代码无法压缩,所以还需要把JS代码压缩插件导入进来 terser-webpack-plugin
4.preload和prefetch及懒加载
- webpackPrefetch: 等页面核心业务加载完成后,空闲时间去加载一些懒加载的代码
- webpackPreload: 通常用于本页面要用到的关键资源,包括关键js、字体、css文件。preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度。
通过import 这个语法,会提升加载速度,类似于vue 及react路由的概念,懒加载实际上并不是webpack的概念,而是es的语法,webpack只不过是能够识别这种语法进行代码分割而已
- 例如在index.js中写入以下代码
// preloading
document.addEventListener('click', () => {
const element = document.createElement('div');
element.innerText = '123'
document.body.appendChild(element);
})
- npm run build 打开dist目录下html文件
- 在浏览器控制台
- 看代码使用率 Ctrl+ shift+ p show Coverage 点击录制按钮,变红,刷新页面,查看main.js代码使用率
-
效果如下图:
main.js使用率15.2%
- 将这代点击才加载的代码放到异步组件,做出下面的修改
- 在src下,新建一个click.js
// click.js
function getComponent () {
const element = document.createElement('div');
element.innerText = 'dell'
document.body.appendChild(element);
}
export default getComponent
- 将index.js代码修改
//改成异步组件
document.addEventListener('click', () => {
import('./click.js').then(({default:func}) => {
func()
})
})
- 打包,查看代码使用率,改异步组件,使用率变成46.9%
Ctrl+ shift+ p show Coverage 看代码使用率
,代码使用率高,页面速度快,webpack希望你多写异步组件,多写异步代码,放到异步组件里面去,
但是异步加载或者懒加载可能影响用户体检,可以使用webpackPrefetch等提升体验
同步会有缓存
- 常用场景:页面弹窗,如果能在在网络空闲时间,加载登录页面,这样既不会在主业务逻辑加载的时候,影响页面速度,又能在点击的时候,快速打开
- 使用方法:
在import 语法中添加一下语法
import(/* webpackPrefetch: true */ 'LoginModal');
import(/* webpackPreload: true */ 'ChartingLibrary');
5.shamming
shimming简单翻译是垫片。在开发中,经常在一个文件中引入很多第三方库或者自己写的库,每个js文件用到的库都要引入,让人很繁琐,但又不能不引入。webpack内置插件ProvidePlugin能帮助我们解决上面遇到的问题。
举个列子:比如在下面文件使用lodash.js
npm install lodash --save-dev
- src目录下新建一个test.js
// test.js
const nameCount = () => {
let b = _.join(['123', 'abc', 'violet2'],'------------')
console.log(b)
}
export default nameCount
注意虽然index.js中导入了lodash,但是webpack 打包 是模块化打包,通过import 引入的只能在当前模块里面使用,这样可以降低模块的耦合性
// index.js
import nameCount from './test'
nameCount()
-
npm start
在webpack.base.js
const webpack = require('webpack'); // webpack内置插件,需引入webpack,
new webpack.ProvidePlugin({ // // 帮助第三方模块或者js文件结合自身需要,使用_ 的组件自动引用lodash模块
_: 'lodash',
}),
ProvidePlugin插件作用是自动加载模块,而不必到处 import 或 require。只要在ProvidePlugin插件中配置了变量。凡是在源码中用到_变量,在webpack打包时,就会文件最前面引入import _ from 'loadsh'就不要我们自己手动引入了。
6. webpack 与浏览器缓存(Caching)
项目开发完成,我们会将生成的dist 文件放到服务器环境,为了解决浏览器缓存的问题,我们可以在生成环境输出设置[contenthash]这个值是根据输出的时候,内容发生变化就会去更新hash值,不变化的js文件会缓存到浏览器中
// webapck.prod.js
output: { // 考虑去掉浏览器缓存更新的问题
// publicPath:'gttp://cdn.com.cn', //但是一般我们做好的项目都会上传到线上,域名下到文件,这个时候自动生成的index.html里面的js路径不是我想要到,我想要在路径前面加上域名
filename: 'js/[name].[contenthash].js', // 打包文件名,可以是bundle.js默认生成的main.js,如果打包多个文件,可以改成占位符,这样打包出来的名字就是entry配置的main,sub
chunkFilename: 'js/[name].[contenthash].js',
},
缓存失效问题
当js内容没变更的时候,但是由于模块与模块之间的对应关系发生变化,两次打包contenthash的不一样,此时可能老版本的用户引用的缓存文件会找报错,解决这个问题,去webpac配置 runtimeChunk
// webpack.prod.js
optimization: {
runtimeChunk: { // webpack 直接可以使用这个内置特性 //解决打包两次 contHash 值不一样的情况
name: 'runtime'
},
}
-
打包结果如下:
webpack 会把main.js 与vender.js 中,在版本不同的情况下,manifest内置的模块及js与jis直接的关系发生变化,这个runtime.js
就是会把manifest 处理关系的代码抽离出来,避免影响业务代码和模块代码
7.打包分析工具介绍
- 第一种在项目中生成stats.json
- 在package.json 中打包命令配置
--profile --json > stats.json
"scripts": {
"start": "webpack-dev-server --config ./build/webpack.dev.js",
"dev": "webpack --profile --json > stats.json --config ./build/webpack.dev.js",
"build": "webpack --config ./build/webpack.prod.js"
},
- 执行npm run dev 将生成的stats.json 在 http://webpack.github.io/analyse/ 打开这个文件
-
能够 看打包的关系以及过程的分析
- Bundle Analysis
//其他分析工具
https://webpack.js.org/guides/code-splitting/#bundle-analysis
- 例如:webpack-chart 通过打开生成的stats.json
- webpack-bundle-analyzer插件,这个比较常用,且查看的信息比较全