tree-shaking

顾名思义就是将没用的内容摇晃掉,来看下面代码
calc.js

  1. export const add = (a,b) =>{
  2. return a+b + 'sum'
  3. }
  4. export const minus = (a,b) => {
  5. return a- b +'minus'
  6. }

main.js

  1. import {minus} from './calc
  2. console.log(minus(1,2)

tree-shaking 默认只支持es6语法的 静态导入
像下面这种是动态的

  1. if(){
  2. require(') //另:import {minus} from './calc这种写法是不可以的,会报错
  3. }

只在生产环境下有用
如果想看打包的哪个方法没有被用到,可以做如下配置

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')
 })

dll插件,我们一般会用在开发环境上

动态加载

比如我们在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。同时不要和业务逻辑放在一起,否则每次业务逻辑有改动就要重新打包,我们希望单独打包,产生缓存。
那么可以使用抽离 第三方模块

  1. 不要和业务逻辑放在一起
  2. 增加缓存 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
}