tree-shaking
顾名思义就是将没用的内容摇晃掉,来看下面代码
calc.js
export const add = (a,b) =>{return a+b + 'sum'}export const minus = (a,b) => {return a- b +'minus'}
main.js
import {minus} from './calcconsole.log(minus(1,2)
tree-shaking 默认只支持es6语法的 静态导入
像下面这种是动态的
if(){require(') //另:import {minus} from './calc这种写法是不可以的,会报错}
只在生产环境下有用
如果想看打包的哪个方法没有被用到,可以做如下配置
optimization:{
usedExports:true //使用了哪个模块 (exports used:minus)
}
生产环境会默认 不打包add
举个例子:
test.js
function test(){
return 'hello
}
console.log(test())
export default test
引入
import {minus} from './calc
import test from './test
console.log(minus(1,2)
问:test要不要被删掉
import test from ‘./test 副作用的代码,可能开发时是无意义的,但是webpack会认为有用
在package.json配置:
“slideEffects”:false // 移除副作用 (引用了没有使用的代码,就删除掉)
但是:
如果引入的是css样式 import ‘./styles.css’ 这样也会被移除,因为默认只支持es6语法,可以改成require语法引入
require
还可以:
“slideEffects”:{
‘*/.css’
}
这样就可以使css样式不被移除了
webpack配置:
1.打包大小
2.打包速度
3.模块拆分
Scope-Hoisting
scope hoisting 是 webpack3 的新功能,直译过来就是「作用域提升」。熟悉 JavaScript 都应该知道「函数提升」和「变量提升」,JavaScript 会把函数和变量声明提升到当前作用域的顶部。「作用域提升」也类似于此,webpack 会把引入的 js 文件“提升到”它的引入者顶部。
每个模块都是个函数,会导致内存过大
scope hoisting 可以减少函数声明代码和内存开销
let a=1;
let b=2;
let c=3
let d = a+b+c
export default d;
---------------------------------------------------
其实本质上他是一个函数
function(){
return 6
}
引入
import d from './d'
console.log(d) //相当于直接打印6
DllPlugin && DllReferencePlugin
每次构建时第三方模块都需要重新构建,这个性能消耗比较大,我们可以先把第三方库打包成动态链接库,以后构建时只需要查找构建好的库就好了,这样可以大大节约构建时间
DllPlugin
动态链接库 每次打包都需要重新构建 react react-dom,可以react、react-dom单独打包到manifest文件中,引入的时候,如果有react、react-dom就去文件里找
理解:通常来说,我们的代码都可以至少简单区分成业务代码和第三方库。如果不做处理,每次构建时都需要把所有的代码重新构建一次,耗费大量的时间。然后大部分情况下,很多第三方库的代码并不会发生变更(除非是版本升级),这时就可以用到dll:
把复用性较高的第三方模块打包到动态链接库中,在不升级这些库的情况下,动态库不需要重新打包,每次构建只重新打包业务代码
问题:怎么打包第三方库
新建文件:webpack.dll.js
举例:
src/calc.js
export const add = (a,b) =>{
return a+b + 'sum'
}
export const minus = (a,b) => {
return a- b +'minus'
}
webpack.dll.js
const path = require('path')
module.exports={
mode:'production',
entry:'./src/test.js', // add minus
output:{
filename:'calc',
path:path.resolve(__dirname,'dll')
}
}
// 目前是为了将calc 打包成node可以使用的模块
package.json配置
"scripts": {
"dll":"webpack --config webpack.dll.js"
},
运行命令 npm run dll 就会生成 dll——>calc
我们发现他是一个自执行函数,需要使用一个参数接收他 library
const path = require('path')
module.exports={
mode:'production',
entry:'./src/test.js', // add minus
output:{
library:'calc', //打包后接受自执行函数的名字叫calc
// libraryTarget:'commonjs', //默认使用var, 也可以自定义 umd,this var commonjs commonjs2
filename:'calc',
path:path.resolve(__dirname,'dll')
}
}
比如react的话,
const path = require('path')
module.exports={
mode:'production',
entry:['react','react-dom'],
output:{
library:'react',
// libraryTarget:'commonjs', //默认使用var, 也可以自定义 umd,this var commonjs commonjs2
filename:'react.dll.js',
path:path.resolve(__dirname,'dll')
}
}
本地使用了import React语法,需要先去mainfest.json查找,找到后加载对应的库的名字,引入了哪个包,就去这个包对应的库里面查找,把真实的文件引入
const DLLPlugin = require('webpack').DllPlugin
plugins:[
new DLLPlugin({
name:'react',
path:path.resolve(__dirname,'dll/manifest.json')
})
]
npm install add-asset-html-webpack-plugin —save-dev 把需要的资源引入,他的作用是当我们想在跟页面打包后,插入我们特定script的引用,来达到全局变量的效果,会将文件先复制到dist目录下
webpack.base.js
const DLLReferencePlugin = require('webpack').DLLReferencePlugin
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
....
new DLLReferencePlugin({
manifest:path.resolve(__dirname,'dll/manifest.json')
}),
new AddAssetHtmlPlugin({
filepath:path.resolve(__dirname,'/dll/react.dll.js')
})
动态加载
比如我们在index.js引入 calc
import {add} from ‘./calc’
在打包的时候就会引入这个方法,但是如果用户根本没有点击行为,就有点浪费,可以采用动态引入的方式
动态引入类比路由的懒加载,import语法
会默认单独打包成一个文件,当点击的时候会使用jsonp动态(动态创建script标签)的加载这个文件
运行时。打开network中可以看到点击 就会加载这个文件,名字是0.bundle.js,这个名字是可以修改的。
output:{
filename:'bundle.js', //同步打包的名字
chunkFilename:'[name].min.js', //如果想自己定义名字,在引入的时候注释里加
path:path.resolve(__dirname,'../dist')
},
index.js
// import {add} from './calc'
let button = document.createElement('button')
// 在打包的时候就会引入方法,但是如果用户根本就没有点击行为,就有点浪费,所以动态导入
button.addEventListener('click',()=>{
import(/*webpackChunkName:'viedo'*/'./calc').then(data=>{//在注释里添加打包的别名
console.log(data.add(1,2))
})
console.log(add(1,2))
})
button.innerHTML = '点我'
document.body.appendChild(button)
多入口
比如: index.html 需要a.js文件,login.html需要b.js
修改entry
// entry有三种写法, 字符串 数组 对象
entry:{
"a":path.resolve(__dirname,"../src/a.js"),
"b":path.resolve(__dirname,"../src/b.js"), //这个时候打包就会报错:不能都是bundle.修改ouputfilename
},
output:{
filename:'[name].js',
.....
},
//多个页面入口配置生成多个页面
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html'), //打包模板
filename:'index.html',
chunks:['a']
}),
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html'), //打包模板
filename:'login.html',
chunksSortMode:'manual', //手动排序
chunks:['b','a'] //打包的顺序按照自己排序
}),
打包会发现生成2个html,并且login页面先后引入了b.js、a.js
vue-cli配置mpa,cli官网 pages
打包文件分析工具
安装 webpack-bundle-analyser 插件
使用插件
const {BundleAnalyserPlugin} = require('webpack-bundle-analyzer')
mode!=="development" && new BundleAnalyzerPlugin
SplitChunks
SplitChunks,他可以在编译是抽离第三方模块、公共模块
比如 入口 index的a.js和login里的b.js都引入jq,打包的时候都会打包jq,我们希望打包a的时候打包jq,打包b 的时候直接使用缓存里的jq。同时不要和业务逻辑放在一起,否则每次业务逻辑有改动就要重新打包,我们希望单独打包,产生缓存。
那么可以使用抽离 第三方模块
- 不要和业务逻辑放在一起
- 增加缓存 304
配置:
optimization: {
splitChunks: {
chunks: 'async', //支持异步的代码分割 import()
minSize: 30000, // 文件超过30k就会抽离
minRemainingSize: 0,
maxSize: 0,
minChunks: 1, //最少模块引用一次才会抽离
maxAsyncRequests: 5, //最多5个请求
maxInitialRequests: 3, //最多首屏加载3个请求
automaticNameDelimiter: '~', // XX~a~b
enforceSizeThreshold: 50000,
cacheGroups: { //缓存组 比如分离react
react:{
test: /[\\/]node_modules[\\/]react|react-dom/,
priority:1 //优先级
},
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true
},
default: { // defalut~a~b 可以改名字
minChunks: 2, //至少引用两次,会覆盖上面的
priority: -20,
reuseExistingChunk: true
}
}
}
},
我们在a.js和b.js都同时引入 loadsh:
import loadsh from ‘loadsh’ //同步的
这样打包之后发现 loadsh被重复打包
修改 chunks: ‘initial’, 这样打包发现loadsh和jq都只被打包了一次,因为jq import()也会进行代码分割
也可以 chunks:’all’
所以:initial 只操作同步的,all 所有的 async 异步的
另:dllPlugin不要和splitChunks同时使用
dllPlugin构建之前 代码抽离, splitChunks是编译之时
费时分析
可以计算每一步执行的运行速度
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const smw = new SpeedMeasureWebpackPlugin();
modules.exports = smw.wrap({
})
热更新
devServer:{ //开发服务的配置
hot:true, //热更新
port:3000,
open:true,
compress:true, //gzip 可以提升返回页面的速度
contentBase:path.resolve(__dirname,'../dist') //webpack启动服务会在dist目录下
},
.....
plugins:[
new webpack.NamedChunksPlugin(), //打印更新的模块路径
new webpack.HotModuleReplacementPlugin() //热更新插件
]
ignorePlugin
noParse
resolve
比如说页面引入 bootstrap,可以 使用 import ‘bootstrap’
resolve:{ //解析 第三方包
modules:[path.resolve('node_modules')], //指定目录
mainFiles:['style','main'],
mainFiles:[], //入口文件的名字 index.js
alias:{ //别名 vue vue.tuntime
bootstrap:'bootstrap/dist/css/bootscrap.css'
}
}
比我们希望不写后缀名,但是仍然能够解析
resolve:{ //解析 第三方包
modules:[path.resolve('node_modules')], //指定目录
extensions:['.js','.css','.json'] //先去找.js 的找不到 去找.css的....
}
include/exclude
happypack
模块happy可以实现多线程来打包 进程
分配多线程也会消耗时间,所以一般是大型项目采用这个方法
let Happypack = require('happypack')
{
test:/\.js$/, //匹配js
exclude:/node_modules/,
include:path.resolve('src'),
use:'Happypack/loader?id=js' //使用id= js,找到对应的loader去打包
},
.....
new Happypack({
id:'js',
use:[{
loader:'babel-loader',
options:{
presets:[
'@babel/preset-env',
'@babel/preset-react'
]
}
}]
}),
sourceMap
我们打包后,代码会被压缩,如果控制台报错,点进去只能定位到压缩之后的代码,我们希望能够看到源码,快速查看问题。这就是sourceMap的出现
SourceMap是一种映射关系。当项目运行后,如果出现错误,我们可以利用sourceMap反向定位到源码
1)
module.exports = {
// 源码映射 会单独生成一个sourcemap文件,出错了 会标识 当前报错的列和行
devtool: 'source-map', // 增加映射文件 可以帮我们调试源码
}
这样就会直接定位到 错误了
其他的配置:
2)
module.exports = {
devtool:'eval-source-map' //不会产生单独的文件,但是可以显示行和列
}
3)
不会产生列 但是一个单独的映射文件
module.exports = {
devtool:'cheap-module-source-map' //产生后你可以保留起来
}
4) 不会产生文件 集成在打包后的文件中,不会产生列
module.exports = {
devtool:'cheap-module-eval-source-map
}
