webpack基础
webpack打包
打包工具解决的是前端整体的模块化并不单指JavaScript模块化
webpack优点(核心)
- 模块化打包器(将代码进行整合打包)
- 模块加载器(编译转换)
- 代码拆分(可以按照需求拆分打包,实现增量加载或者渐进式加载)
- 资源模块(模块的方式载入任意形式文件)
webpack快速上手
-
安装yarn add webpack webpack-cli --dev
-
运行打包yarn webpack
-
webpack配置文件(基础)
// webpack.comfig.js是运行在node环境下的文件,需要安装common.js规范 const path=require('path') module.exports={ entry:'./src/main.js',//指定webpack入口文件打包的文件路径,如果是相对路径是不能省略`./` output:{ filename:"bundle.js",//输出文件的名称 path:path.join(__dirname,'output'),//指定文件输出的目录,但是必须是绝对路径,node中的path },//指定输出文件的位置 }
-
webpack工作模式
yarn webpack --mode development//开发模式-添加调试中的辅助内容
yarn webpack --mode none//运行最原始的打包,不会进行任何处理
在webpack.config.js中的添加一个mode属性// webpack.comfig.js是运行在node环境下的文件,需要安装common.js规范 const path=require('path') module.exports={ mode:'development',//webapck的工作模式 entry:'./src/main.js',//指定webpack入口文件打包的文件路径,如果是相对路径是不能省略`./` output:{ filename:"bundle.js",//输出文件的名称 path:path.join(__dirname,'output'),//指定文件输出的目录,但是必须是绝对路径,node中的path },//指定输出文件的位置 }
-
webpack打包结果运行原理(折叠代码ctrl+k和ctrl+0)yarn webpack --mode none调式
webpack打包后是一个立即执行的函数 -
webpack资源模块加载loader
webpack默认只会处理JavaScript的代码,而处理其他模块的需要通过loader进行加载(如:css文件html文件图片等) loader是整个webpack实现整个前端模块化的核心,借助不同的loader就可以加载如何类型的资源module:{ rules:[ { test:/.css$/,//正则表达式匹配文件路径 use:[//多个loader配置 'style-loader', 'css-loader' ],//'css-loader'当配置,//匹配文件需要的loader,配置多个loader时,执行顺序是从后向前执行 } ],//其他资源模块加载规则配置 },//为了打包加载除js文件外的其他文件
-
webpack导入资源模块(JavaScript驱动整个前端应用业务)
在js的代码中引入css文件(loader也能正常工作)//在导入文件中(webpack.config.js中的entry的指定的文件路径)main.js引入css样式文件 import './main.css'
导入模块的好处就是可以根据代码的需求动态的导入资源文件
-
webpack文件资源加载器(主要处理图片字体等的文件加载器)file-loader
//通过import的方式导入图片 import icon from './icon' //在webpack.config.js的module中配置loader module:{ rules:[ { test:/.css$/,//正则表达式匹配文件路径 use:[//多个loader配置 'style-loader', 'css-loader' ],//'css-loader'当配置,//匹配文件需要的loader,配置多个loader时,执行顺序是从后向前执行 }, { test:/.png$/, use:'file-loader' }, ],//其他资源模块加载规则配置 }
由于webpack的打包结果会默认的放在网址的根目录下,可以通过配置告诉webpack打包文件后的文件所在路径(publicPath)
//在webpack.config.js中配置 output:{ publicPath:'dist/',//告诉webpack打包文件后的文件所在路径,空字符串publicPath:'',表示网站的根目录 filename:"bundle.js",//输出文件的名称 path:path.join(__dirname,'output'),//指定文件输出的目录,但是必须是绝对路径,node中的path },//指定输出文件的位置
-
webpack DataURLs(文件表示)与url-loader
DataURLs特殊的url的协议可以直接表示一个文件,是通过url表示文件内容方式 图片和文字无法表示的二进制的文件需要将文件的内容同base64进行编码 通过url-loader可以将文件转换成DataURLs特殊的url(适用于项目中体积较小资源),在使用url-loader的前提是要安装file-loader//在webpack.config.js文件中配置 module:{ rules:[ { test:/.png$/, use:'url-loader',//可以将图片文字转换成DataURL形式 // use:'file-loader' } ],//其他资源模块加载规则配置 },//为了打包加载除js文件外的其他文件
webpack最佳的文件(字体或者图片)资源加载: 小文件使用DataURLs,减少请求参数; 大文件单独提取存放,提高加载速度;
{ test:/.png$/, use:{ loader:'url-loader', options:{ limit:10*1024,//限制文件大小,10kb,这里单位是字节,将会把10kb以下的文件转换DataURL },//添加配置选项 },//最有解决(字体或者图片)资源加载 }
-
webpack 常见加载器分类
- 编译装换类
- css-loader
- 文件操作类
- file-loader
- 代码检查类
- eslint-loader
- 编译装换类
-
Webpack处理ES2015
webpack因为模块打包的需要,才处理import和export,但不能转换代码中es6的其他的语法特性,需要额外的插件处理(babel-loader) 安装:yarn add babel-loader @babel/core @babel/preset-env --dev// 在webpack.config.js的module中的rules设置 { test:/.js$/, // use:'babel-loader',//babel-loader只会提供一个js代码转换平台,不会实际的转换,需要使用其他的插件 use:{ loader:'bable-loader', options:{ presets:['@babel/preset-env'],//包含es中最新特性 } } },
-
webpack模块加载方式
- 遵循ES Modules标准的import声明
- 遵循CommonJS标准的require函数(注意如果通过require导入的是ES Modules的默认变量即default,需要在require后加上default属性)
- 遵循AMD标准的define函数和require函数
- Loader加载非JavaScript也会触发资源加载(如:样式代码中的@import指令和url函数、HTML代码中图片标签的src属性)
// css文件中使用@import引入其他样式文件 @import url(reset.css) // css文件中的样式中url background-image:url('./background.png') //HTML文件中使用 // 在导入模块中指定的main.js引入的html模块的文件是一个字符串的形式导出的需要参数表示 import footerHtml from './footer.html' // 配置html加载的loader——yarn add html-loader --dev // 在webpack.config.js的module中的rules设置 { test:/.html$/, use:{ loader:'html-loader',//html-loader默认只会处理src属性 options:{ attrs:[ 'img:src',//默认 'a:href' ],//为了处理除src以外的html的其他属性也能触发打包 } } }, <footer> <img src='better.png' alt='better' /> <a href='better.png'></a> </footer>
-
webpack 核心工作原理(loader机制是webpack的核心)
核心工作过程:webpack通过配置文件找到其中的一个文件作为打包入口,通过入口文件中的代码,根据代码中出现的import、require语句等解析文件所依赖的资源模块,分别解析每个资源模块对应的依赖,最后形成了整个项目中依赖关系的依赖树,webpack就会递归依赖树找到每个节点对应的资源文件,最后通过配置文件中rules属性去找到模块所对应的加载器加载模块,最后将加载的结果放到bundle.js中 -
webpack loader 工作原理(loader负责资源文件从输入到输出的转换,对于同一个资源可以依次使用多个loader)
webpack加载资源的过程,有点类似工作管道,在工作的过程中依次使用多个loader,管道工作后的最终结果必须是一个JavaScript的代码处理方法:(返回值不是一个JavaScript的代码)
- 使用的loader直接返回一个标准的JavaScript代码
- 找一个合适的加载器,接着处理上次返回的结果(即不是JavaScript的代码)
// markdown-loader.js文件 // 每个webpack的loader都要导出一个函数,这个函数就loader对加载资源的处理过程,输入就是文件加载的内容,输出就是加工过后的结果 const marker = require('marked') module.exports = source => { const html = marked(source) //返回值为html的字符串 // 第一种方法使用的loader直接返回一个标准的JavaScript代码 // 由于html中存在换行符等特殊字符,通过JSON.stringify()方法转义 //return `module.exports=${JSON.stringify(html)}` //webpack允许在返回的代码中使用ES module的方式导出export default // 第二种方法找一个合适的加载器,接着处理上次返回的结果(即不是JavaScript的代码) // 将返回html字符串交给下一个loader处理——使用html-loader return html } // webpack.config.js文件 const path = require('path') module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), publicPath: 'dist/' }, module: { rules: [{ test: /.md$/, use: [ //多个loader的执行顺序是从后面开始 'html-loader', './markdown-loader' ] //use属性不止可以用模块的名称,也可以模块的文件路径 }] } }
-
webpack 插件机制(增强webpack自动化能力)
loader专注实现资源模块加载,plugin解决除资源加载以外其他自动化工作- 清除dist目录
- 拷贝不需要打包的静态文件至输出目录
- 压缩打包输出代码 webpack+plugin实现大多前端工程化工作
-
webpack常用插件
- clean-webpack-plugin(自动清理输出目录的插件)
// 导入插件 const {CleanWebpackPlugin}=require('clean-webpack-plugin') // 在webpack中的plugin的属性中添加插件 plugin:[ new CleanWebpackPlugin()//清理dist文件 ],//配置webpack插件
- html-webpack-plugin(自动生成使用bundle.js的HTML,通过webpack自动生成HTML文件)
// html-webpack-plugin默认导出就是一个插件的类型,不需要解构内部成员 const HtmlWebpackPlugin=require('html-webpack-plugin') plugin:[ // 添加实例对象 // 用于生成index.html文件 new HtmlWebpackPlugin({ // 生成html的配置选项 title:'Webpack Plugin Sample',//用于生成dist目录中的html标题 meta:{ viewport:'width=device-width', },//可以以对象的方式设置页面中语言性质的标签 template:'./src/index.html',//可以在src目录下创建一个模板html,可以将相应的配置放到模板的html文件中 }),//在dist目录中生成html文件,如果在输出文件中即output中添加了属性publicPath,会导致生成的html中引入的bundle.js的文件路径不对,则需要请求publicPath中设置的路径 // 生成多个html文件,可以创建多个HtmlWebpackPlugin来对应生成 new HtmlWebpackPlugin({ filename:'about.html',//指定生成文件的名称 }), new HtmlWebpackPlugin({ filename:'XXXX.html',//指定生成文件的名称 }), ],//配置webpack插件
- copy-webpack-plugin(静态资源拷贝到dist目录中)
const CopyWebpackPlugin=require('copy-webpack-plugin') plugin:[ new CopyWebpackPlugin([ // 用于指定文件拷贝的路径,可以是通配符、文件的相对路径、目录 'public',//拷贝整个目录中的文件 // 'public/**',拷贝文件 ]) ],//配置webpack插件
-
webpack 插件机制的工作原理(相比于loader,plugin拥有更宽的能力范围)
loader工作范围只是在文件资源加载模块时工作,插件会在触及到webpack工作每个环节中 插件机制的实现:webpack的插件机制其实就软件开发过程中经常见到的钩子机制(plugin通过钩子机制实现) 钩子机制有点类似于web中的事件,在webpack的工作过程中会有很多环节,webpack会给每个环节都会埋下一个钩子,在开发插件的时候,向每个节点上挂载任务进行扩展// webpack要求插件必须是一个函数或者是一个包含apply方法的对象
插件是通过webpack的生命周期的钩子中挂载函数实现拓展
如何增强webpack开发体验
webapck实现自动编译
watch工作模式(yarn webpack --watch),项目下的源文件会被监视,如果被监视的文件发生变化,就会自动的重新运行打包任务
实现自动刷新浏览器(在编译过后自动刷新浏览器)
BrowserSync实现自动刷新功能(可以全局安装)
```javascript
// 在命令行中输入browser-sync dist --files '**/*'
browser-sync dist --files '**/*'//启动http的服务同时监听了dist目录下的文件变化
```
webpack dev server(推荐)
- 提供用于开发的http sever服务器
- 集成【自动编译】和【自动刷新浏览器】等功能
- webpack-dev-server为了提高工作效率,没有将打包后的结果写入到磁盘中(无dist的目录),将打包的结果暂时存放在内存中,而内部的http-serve也是从内存中将文件读出出来发送给浏览器,这样可以减少磁盘读写操作
// 安装webpack-dev-server yarn add webpack-dev-sever --dev // 命令运行 yarn webpack-dev-server yarnwebpack-dev-server --open//可以自动打开浏览器
- webpack dev server 静态资源访问
webpack dev server默认会将构建结果输出的文件,全部作为开发服务器的资源文件,只要通过webpack输出的文件,都可以被直接访问,如果一些静态资源也需要作为开发服务器的资源,需要额外的告诉webpack dev server// 需要在webpack.config.js添加配置 devServer:{ contentBase:'./public',//""或者[]单个或者多个//指定额外的静态资源路径 proxy:{//每个属性就是一个代理规则配置 // 属性的名称就是需要被代理请求路径前缀-请求以哪个地址开始,就走代理请求 '/api':{//当请求以/开头,我们的代理目标就是api.github.com // http://localhost:8080/api/users->https://api.github.com/api/users target:'https://api.github.com', // 如果代理中没有/api,可以通过重写的方式代替同时需要设置changeOrigin:true pathRewrite:{//会以正则的方式替换路径 '^/api':'' },//可以实现代理路径的重写 // 不能使用localhost:8080作为请求Github的主机名 changeOrigin:true, } },//用于配置代理服务 },//webpack-dev-server指定相关的配置选项 //new CopyWebpackPlugin(['public'])用于项目打包上线前使用,开发阶段最好不要使用这个插件
- webpack dev server 代理API服务(开发阶段接口跨域问题)
跨域问题处理方式:- 跨域资源共享(CORS),请求的api支持CORS
- 开发服务器中配置代理服务,将接口服务代理到本地的开发服务地址 webpack dev server 支持配置代理
source map(处理编译后运行的代码和源码之间不一样所导致的调式问题)
主要用于映射源代码和转换过后的代码之间的关系,转换后的代码通过转换过程中生成的source map文件逆向解析到源代码
在转换后的代码中source map文件的引入: //#sourceMappingURL=文件sourcemap名称(如://#sourceMappingURL=jquery-3.4.1.min.map)
webpack配置source map
- webpack.config.js中的简单配置
devtool:'source-map',//用于配置开发过程中的辅助工具,与source map配置相关功能
- webpack支持12种不同的方式实现source map,每种方式的效率和效果各不相同(一般效果好的,一般生成的速度慢,生成速度快的效果差)
- eval模式下的source map
//eval是js中的函数,可以运行字符串代码,默认情况下eval会运行在临时虚拟机环境中
eval(`console.log(123)`) //123
// 可以通过sourceURL来声明下面代码所属的文件路径,改变eval代码运行环境的名称(//# sourceURL=文件路径`)
eval(`console.log(123) //# sourceURL=./foo/bar.js`)
在webpack.config.js中配置eval
- eval模式下会将每个模块所转换后代码,都会放在eval函数中执行,并且eval函数执行到最后通过sourceURL说明对应的源码文件路径
- eval模式下调式代码只能找到错误所对应的文件,不能找到错误对应具体位置
- eval模式下不会生成source map文件,所以其构建速度最快但效果很简单,只能定位源代码的名称,不能提供具体的行列信息
// eval模式下会将每个模块所转换后代码,都会放在eval函数中执行,并且eval函数执行到最后通过sourceURL说明对应的源码文件路径;eval模式下调式代码只能找到错误所对应的文件,不能找到错误对应具体位置,eval模式下不会生成source map文件,所以其构建速度最快但效果很简单,只能定位源代码的名称,不能提供具体的行列信息
devtool: 'eval',
- webpack中source map的对比(webpack对比不同devtool模式)
- eval模式——将模块代码放到eval函数中运行,通过sourceURL标注模块文件的路径,没有生成source map文件,只能定位到那个文件出现错误
- eval-source-map模式——将模块代码放到eval函数中运行,并且生成了source map文件,但是它可以定位那个文件出现错误同时也能定位出现在文件的哪一行、哪一列
- cheap-eval-source-map模式——定位那个文件出现错误同时也能定位出现在文件的哪一行,但是不能定位到哪一列,并且cheap-eval-source-map定位错误显示的结果是js转换后的结果定位
- cheap-module-eval-source-map模式——定位那个文件出现错误同时也能定位出现在文件的哪一行,但是不能定位到哪一列,并且cheap-module-eval-source-map定位错误显示的结果源代码位置
- cheap-source-map模式——定位出现在文件的哪一行,但是不能定位到哪一列,定位错误的地方为代码转换后
- inline-source-map模式——使用dataURL的模式将source map嵌入到代码中(将source map文件放到源代码后,会导致代码体积变大)
- hidden-source-map模式——看不到source map效果(无法定位到错误),但会生成source map文件,需要通过注释引入://#sourceMappingURL=source map文件路径,使用范围:是在制作第三方插件时,需要生成source map,但是不想在代码中进行引用,如果出现问题再继续引入
- nosources-source-map模式——只能看到错误出现的位置(行列信息),但是看不到源码,主要用于保护源代码不会暴露
- webpack选择合适的source map
- 开发模式:cheap-module-eval-source-map(vue代码经过loader转换过后差异较大便于调试、虽然首次打包速度慢,但是重新打包相对较快)
- 生产模式:none(source map会暴露源代码),其次是nosources-source-map(可以找到报错的位置,但是不会暴露源码)
webpack自动刷新问题
webpack-dev-server导致改动源码后会页面整体刷新,会导致页面部分的操作状态丢失,需要重复操作
解决办法:
1. 代码中写死需要操作内容,再进行代码调试
2. 通过额外代码实现刷新前保存,刷新后读取
3. HMR——模块热替换(页面不刷新的前提下,模块也可以及时更新)
webpack中的热替换就是应用运行过程中实时替换某个模块,而应用运行状态不受影响,热替换只是将修改的模块实时的替换到应用中(HMR是webpack中最强大的功能之一,极大程度的提高开发者的工作效率)
// 开启HMR,HMR已集成在webpack-dev-server中
// 1. 通过运行webpack-dev-server --hot命令开启HMR
// 2. 通过配置文件开启
// 下面配置条件只能满足样式文件修改模块更新,但是js代码的修改还是会造成全局刷新操作
const webpack=require('webpack')
devServer:{
hot:true,
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
// webpack中HMR并不可以开箱即用,webpack中HMR需要手动处理模块热替换逻辑,需要手动处理js模块更新后热替换
// 使用HMR APIs来手动的进行模块的热替换
// main.js打包的入口文件,在main.js才开始加载其他模块,如果模块更新后,就需要重新加载相应的模块,所以就要处理模块的热替换
import createHeading from './heading.js'
import './main.css'
import icon from './icon.png'
const heading = createHeading()
document.body.append(heading)
const img = new Image()
img.src = icon
document.body.appendChild(img)
// main.js打包的入口文件,在main.js才开始加载其他模块,如果模块更新后,就需要重新加载相应的模块,所以就要处理模块的热替换
// js模块热替换
let lastheading = heading
module.hot.accept('./heading.js', () => {
const value = lastheading.innerHTML
document.body.remove(lastheading)
const newheading = createHeading()
newheading.body.append(newheading)
lastheading = newheading
}) //accept主要注册某个模块更新后的处理函数,第一个参数是依赖模块的路径,第二个参数依赖路径更新后的处理函数
// 图片模块热替换
module.hot.accept('./icon.png', () => {
img.src = icon
})
HMR注意事项:
- 处理HMR的代码报错会导致自动刷新,导致无法找到HMR中错误代码的位置,处理的方法是将webpack.config.js中的devServer中的参数换成hotOnly(devServer:{// hot:true,hotOnly:true,})
- 没有启动HMR的情况下,HMR API报错,处理方法是对module.hot对象进行判断(if(module.hot){.......})
- HMR在未开启的情况下,针对HMR写的热替换代码,会导致多了一些与业务无关的代码,在页面编译打包后的代码中不会存在相应的代码
webpack生产环境的优化
针对HMR和source map带来的代码量增加,导致生产环境的代码的运行效率可能降低,webpack4推出了mode(模式)用法,提供不同模式下的预设配置,为不同的工作环境创建不同的配置
不同环境下的配置
配置文件根据环境不同导出不同的配置(配置文件中添加判断,适用中小型项目)
//在webpack.config.js中进行配置
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = (env, argv) => {
// env——通过cli传递的环境名参数
// argv——指运行cli过程中传递的所有参数
// 定义开发模式的config
const config = {
mode: 'development',
entry: './src/main.js',
output: {
filename: 'js/bundle.js'
},
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public'
},
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
outputPath: 'img',
name: '[name].[ext]'
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Tutorial',
template: './src/index.html'
}),
new webpack.HotModuleReplacementPlugin()
]
}
if (env === 'production') {
config.mode = 'production'
config.devtool = false
config.plugins = [
...config.plugins,
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
}
return config
} //webpack配置文件支持导出一个函数,在函数中返回需要的配置对象
// yarn webpack 运行——只会开发模式打包
// yarn webpack --env production 运行生产模式打包
一个环境对应一个配置文件(多个配置文件——适用大型项目)
- 创建三个不同的配置文件,分别是公共配置文件、开发环境配置文件、生产环境配置文件
- 安装webpack merge插件用于合并公共配置文件与开发环境配置文件或者合并公共配置文件与生产环境配置文件
- 运行相应的开发环境
//生产环境下的配置文件
const merge = require('webpack-merge') //合并配置
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
})
// 开发环境下的配置文件
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
// 公共配置文件
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
// 在运行生产环境打包时需要运行yarn webpack --config webpack.prod.js或者在package.json中scripts属性中配置:"scripts": {"build": "webpack --config webpack.prod.js"}再用npm run build运行
webpack definePlugin(为代码注入全局成员)
在production模式下,默认启用,并向代码中注入process.env.NODE_ENV常量,很多第三方的模块通过这个常量来判断当前的运行环境,从而实行一些方法或者操作
// 在webapck.config.js的plugins插件属性中进行使用
const webpack = require('webpack')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js'
},
plugins: [
// DefinePlugin是一个内置插件,需要先导入webpack模块
// webpack.DefinePlugin会接收一个对象,对象中的每个键值都会注入到代码中,接着就可以在全局的情况下进行使用
//主要注入可能发现变化的值(例如环境不同情况下的baseUrl)
new webpack.DefinePlugin({
// 值要求的是一个代码片段
API_BASE_URL: JSON.stringify('https://api.example.com')
})
]
}
// 在src下的main.js中使用
console.log(API_BASE_URL)
Tree-shaking(主要是【摇掉】代码中未引用部分)生产环境主动开启,移除未引用代码
Tree Shaking不是指某个配置选项,是一组功能搭配使用后的优化效果,在生产模式下自动使用
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: 'bundle.js'
},
// 集中配置webpack内部优化功能
optimization: {
// 模块只导出被使用的成员,webpack的Tree-shaking
usedExports: true, //表示输出结果中只导出外部使用的成员
// webpack合并模块
concatenateModules: true, // 尽可能合并每一个模块到一个函数中,可以提升运行效率,又减少了代码体积,但是在未开启的情况下,webpack会将一个一个的模块代码划分为一个一个函数
// minimize: true,// 压缩输出结果
}
}
Tree-Shaking&Babel(使用babel-loader可能会导致Tree—Shaking失效)
Tree-Shaking前提是ES Modules,由webpack打包的代码必须使用ES Modules,但是为了处理代码中ES Moudles的新特性,会使用babel-loader进行处理,babel在转换代码时会将ES Modules转换成CommonJS,由于babel中的present-env插件,最后导致Tree-Shaking工作无效(注意在最新的babel-loader中关闭了转换ES Modules的插件)
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
// 如果 Babel 加载模块时已经转换了 ESM,则会导致 Tree Shaking 失效
// ['@babel/preset-env', { modules: 'commonjs' }],//强制将ES Modeles转换成CommonJS
// ['@babel/preset-env', { modules: false }]
// 也可以使用默认配置,也就是 auto,这样 babel-loader 会自动关闭 ESM 转换
['@babel/preset-env', { modules: 'auto' }]
]
}
}
}
]
},
optimization: {
// 模块只导出被使用的成员
usedExports: true,
// 尽可能合并每一个模块到一个函数中
// concatenateModules: true,
// 压缩输出结果
// minimize: true
}
}
webpack sideEffects(副作用——模块执行时除了导出成员之外所作的事情)
webpack4中的新特性,通过配置的方式,标识代码是否有副作用,从而为tree-shaking提供更大的压缩空间,sideEffects一般用于npm包标记是否有副作用
- 主要用于将没有使用的组件代码进行剔除
// 将不同组件模块放在不同的文件中,再将不同组件模块的文件导入到公共的一个文件中(index.js),再进行集体导出使用,但是在webpack进行打包时,index.js中没有被页面使用的组件模块也会被进行打包
// 处理的方法
// 第一步在webpack.config.js中配置
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
optimization: {//在production的情况下会自动开启
sideEffects: true,//开启功能
}
}
// 第二步在package.json中配置
"sideEffects": false,//标识项目中所有的代码没有副作用
sideEffects注意
使用sideEffects要确保代码真的没有副作用,否则会使webpack在打包时误删掉有副作用的代码,所有的css样式在导入到其他模块时都是副作用代码
// extend.js中拓展Number原型链上的一个方法,但是没有进行导出
// 为 Number 的原型添加一个扩展方法
Number.prototype.pad = function (size) {
// 将数字转为字符串 => '8'
let result = this + ''
// 在数字前补指定个数的 0 => '008'
while (result.length < size) {
result = '0' + result
}
return result
}
// 在index.js中导入使用
// 副作用模块
import './extend'
console.log((8).pad(3))//此时extend.js中的代码为副作用代码
// 如果此时在page.json中还是标识代码中没有副作用代码,在webpack打包时,就不会对extend.js中的代码进行打包压缩
// 解决的办法就实在page.json中进行配置sideEffects,1.关掉副作用;2.标识哪些文件是有副作用的
// page.json
{
"name": "31-side-effects",
"version": "0.1.0",
"main": "index.js",
"author": "zce <w@zce.me> (https://zce.me)",
"license": "MIT",
"scripts": {
"build": "webpack"
},
"devDependencies": {
"css-loader": "^3.2.0",
"style-loader": "^1.0.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.9"
},
"sideEffects": [
"./src/extend.js",
"*.css"
]
}
code Splitting(代码分包/代码分割)
webpack打包会将所有代码最终都会被打包到一起,如果应用复杂、模块多,则会导致打包结果特别大(bundle.js体积过大),但是在应用开始时,并不是每个模块在启动时都需要进行加载的,此时需要将打包的结果按照相应的规则,分离到多个bundle.js中然后根据应用的运行需要按需加载模块,提高应用的响应速度以及运行效率
webpack提供分包方式有两种:
多入口打包(输出多个打包结果)
多入口打包使用于多页面应用程序,常用的划分规则是:一个页面对应一个打包入口,对于不同页面的公共部分单独提取
配置多入口的打包方式及输出的html中指定bundle.js(打包文件)
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
// 多个入口,需要将entry定义成对象,如果定义成数组则是多个文件打包到一起,对于整个应用还是一个入口
entry: {
index: './src/index.js',
album: './src/album.js'
},
// 通过[name]占位符,动态的输出文件名,[name]就会被动态替换成入口的名称
output: {
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
// chunks作用主要是用于指定需要引入的打包文件,防止生成的html中引入所有的打包结果
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/index.html',
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/album.html',
filename: 'album.html',
chunks: ['album']
})
]
}
提取公共模块(不同入口中肯定会有公共模块)webpack会自动分包和动态加载
// 在webpack.config.js中optimization属性中设置splitChunks
optimization: {
splitChunks: {
// 自动提取所有公共模块到单独 bundle
chunks: 'all'
}
},
// 完整配置
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
entry: {
index: './src/index.js',
album: './src/album.js'
},
output: {
filename: '[name].bundle.js'
},
optimization: {
splitChunks: {
// 自动提取所有公共模块到单独 bundle
chunks: 'all'
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/index.html',
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/album.html',
filename: 'album.html',
chunks: ['album']
})
]
}
动态导入(ES Modules动态导入功能)按需加载(需要用到某个模块时,再加载这个模块)
动态导入的模块会被自动分包
// 动态导入按照ES Modules标准中的动态导入
// 静态导入
// import posts from './posts/posts'
// import album from './album/album'
const render = () => {
const hash = window.location.hash || '#posts'
const mainElement = document.querySelector('.main')
mainElement.innerHTML = ''
if (hash === '#posts') {
// mainElement.appendChild(posts())
// 动态导入返回的是一个promise,需要用then进行操作,由于导出是一个默认导出,所以需要使用结构解析
import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {
mainElement.appendChild(posts())
})
} else if (hash === '#album') {
// mainElement.appendChild(album())
import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) => {
mainElement.appendChild(album())
})
}
}
render()
window.addEventListener('hashchange', render)
魔法注释
由于默认通过动态导入产生的bundle文件名称只是个序号,如果需要给生成的bundle.js命名,则需要使用webpack特有的魔法注释实现
在import动态导入中添加行内注释/webpackChunkName:'名称'/,如果动态导入的ChunkName相同,则会被打包到一起,可以借助于这个特点,根据实际情况,灵活组织动态的加载的模块所输出的文件
import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {
mainElement.appendChild(posts())
})
MiniCssExtractPlugin(提取css到单个文件)yarn add mini-css-extract-plugin --dev
MiniCssExtractPlugin将css代码从打包结果中提取出来的插件,实现css模块的按需加载
css文件超过150kb才需要考虑是否放在单文件中
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
mode: 'none',
entry: {
main: './src/index.js'
},
output: {
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 'style-loader', // 将样式通过 style 标签注入
MiniCssExtractPlugin.loader,//通过link的方式引入
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Dynamic import',
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin()
]
}
OptimizeCssAssetsWebpackPlugin(压缩输出的css文件插件)
使用MiniCssExtractPlugin将样式文件进行打包到单文件,当是生产模式下,不会对生成的单个css样式文件进行压缩,则需要用optimize-css-assets-webpack-plugin插件压缩
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
mode: 'none',
entry: {
main: './src/index.js'
},
output: {
filename: '[name].bundle.js'
},
optimization: {
// 压缩类的插件最好配置到minimizer中,便于统一控制
// 如果开启了,webpack默认的js的压缩插件就不能进行使用,需要手动进行添加
minimizer: [
new TerserWebpackPlugin(),//压缩js文件
new OptimizeCssAssetsWebpackPlugin(),//只会在minimizer这个特性被开启的情况下才会工作,压缩css文件
]
},
module: {
rules: [{
test: /\.css$/,
use: [
// 'style-loader', // 将样式通过 style 标签注入
MiniCssExtractPlugin.loader, //通过link的方式引入
'css-loader'
]
}]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Dynamic import',
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin()
// new OptimizeCssAssetsWebpackPlugin(),//在任何情况下都会正常工作
]
}