Webpack的性能优化

829 阅读6分钟

webpack相关文章

HMR

hot module replacement 热模块替换/模块热替换 作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块) 极大提升构建速度

module.exports = {
  entry: './src/js/index.html',
  ...
  devServer: {
    ...
    // 开启HMR功能
    // 当修改了webpack配置,新配置要想生效,必须重新webpack服务
    hot: true
    ...
  }
  ...
}
  • 样式文件: 可以使用HMR功能,因为style-loader内部实现了

  • js文件: 默认不能使用HMR功能

    注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。

    解决:

if (module.hot){
  // 一旦module.hot为true,说明开启了HMR功能。 -->让HMR功能代码生效
  module.hot.accept('./print.js', function(){
  // 方法会监听print.js文件的变化,一旦发生变化,其他模块不会重新打包构建。
  //会执行后面的回调函数
  print( );
})

  • html文件: 默认不能使用HMR功能,同时会导致问题: html 文件不能热更新了

    解决: 修改entry入口,将html文件引入

    module.exports = {
      entry: ['./src/js/index.js', './src/index.html'],
      ...
    }
    

source-map

一种提供源代码到构建后代码映射技术(如果构建后代码出错了,通过映射可以追踪源代码错误)

不同类型的source-map的区别

  • source-map
    • 外部
    • 能提示错误代码准确信息和源代码的错误位置
  • inline-source-map
    • 内联
    • 只生成一个内联source-map
    • 能提示错误代码准确信息和源代码的错误位置
  • hidden-source-map
    • 外部
    • 能提示错误代码错误原因,但是没有错误位置
    • 不能追踪源代码错误,只能提示到构建后代码的错误位置
  • eval-source-map
    • 内联
    • 每一个文件都生成对应的source-map, 都在eval
    • 能提示错误代码准确信息和源代码的错误位置
  • nosources-source-map
    • 外部
    • 错误代码准确信息,但是没有任何源代码信息
    • 防止源代码泄漏
  • cheap-source-map
    • 外部
    • 能提示错误代码准确信息和源代码的错误位置
    • 只能精确到行,而source-map能精确到列
  • cheap-module-source-map
    • 外部
    • 能提示错误代码准确信息和源代码的错误位置
    • module会将loader的source map加入

使用方法

module.exports = {
  ...
  //devtool: 'inline-source-map'
  //devtool: 'hidden-source-map'
  //devtool: 'eval-cheap-source-map'
  //...
  devtool: 'source-map'
  ...
}

内联和外部的区别

​ 1.外部生成了文件,内联没有

​ 2.内联构建速度更快

如何选择

开发环境需求: 速度快,调试更友好

​ 速度快慢(eval> inline> cheap>...)

​ 1. eval-cheap-source-map

​ 2.eval-source-map

​ 调试友好程度(是否更方便调试)

​ 1.souce-map

​ 2.cheap-module-souce-map

​ 3.cheap-souce-map

​ 综合速度与友好程度后,一般选择:

	* **eval-source-map (vue,react脚手架默认选择就是这种)**
	* eval-cheap-module-souce-map

生产环境:源代码要不要隐藏?调试要不要更友好

内联会让代码体积变大,所以在生产环境不用内联 nosources- source-map 全部隐藏 hidden-source-map 只隐藏源代码,会提示构建后代码错误信息

--> source-map / cheap- module - souce -map

oneOf

为什么需要oneOf

因为如果没有oneOf的话,每个文件都会去匹配一次,效率很低

使用

用oneOf包裹rules中的对象

module.exports = {
  ...
  module: {
    rules: [
      // 以下loader只会匹配一一个
      // 注意:不能有两个配置处理同一种类型文件
      // 比如有一个eslint处理js文件,babel也处理js文件,必须把eslint的loader提到oneOf的外面来
      oneOf: [
      	{
      		test: '',
      		...
      	},
  			...
    	]
    ]
  }
}

缓存

目的是为了让未被修改过的文件使用缓存,这样子运行效率更高

babel缓存

方法

  • cacheDirectory: true

目的

让第二次打包构建速度更快

文件资源缓存

方法

  • hash:每次wepack构建时会生成一个唯一的hash值。

    问题:因为js和css同时使用一个hash值。 如果重新打包,会导致所有缓存失效。

    如果我只改动一个文件,导致两个的文件名的改变,则都会使用新的文件,相当于没有使用缓存

  • chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样

    问题: js 和css的hash值还是一样的 因为css是在js中被引入的, 所以同属于一个chunk

  • contenthash:根据文件的内容生成hash值。不同文件hash值一定不一样

目的

让代码上线运行缓存更好使用

module.exports = {
  ...
  output: {
    //js文件名加上hash值后,文件更改时,文件名更改,就不会一直采用缓存的文件了
    //css文件同理
    filename: 'js/built.[hash:10].js', 
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
      	test: /\.js$/,
				exclude: /node_ _modules/ ,
        loader: ' babel-loader',
        options: {
					presets:[ ...
					], I
          // 开启babel缓存
          // 第二次构建时,会读取之前的缓存
          cacheDirectory: true
      	}
     ]
  }
  ...
}

tree shaking

树摇是为了去除我们在应用程序中没有使用的代码

前提

  1. 必须使用ES6模块化

  2. 开启production环境

作用

减少代码体积

问题

可能会把在js中引入的css文件干掉

解决办法:在package. json中配置

// 所有代码都没有副作用( 都可以进行tree shaking )
"sideEffects": false
//问题:可能会把css / @babel/polyfill (副作用) 文件干掉
//解决:
"sideEffects": ["*.css", "*.less" ]

code split 代码分割

分割成多个js文件,能实现并行加载,速度更快

打包多入口文件

module.exports = {
  // 单入口,单页面配置
	// entry: './src/js/index.js'
  entry:{
		// 多入口: 有几个入口,最终输出就有几个bundle
    // 多页面配置
		main: './src/js/index.js',
    test: './src/js/test.js'
  },
  output:{
    //[name]:取文件名
  	filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, "build')
	},
}

打包node_modules中的文件

module.exports = {
  ...
  entry:{
		// 多入口: 有几个入口,最终输出就有几个bundle
    // 多页面配置
		main: './src/js/index.js',
    test: './src/js/test.js'
  },
  ...
  /*
  	1.可以将node_modules中代码单独打包一个chunk最终输出
  	2.自动分析多入口chunk中,有没有公共的文件(依赖),如果有会打包成单独一个chunk
  */
  optimization:{
    splitChunks:{
    	chunks: 'all'
    }
   },
  ...
}

单入口单独打包某个js文件

在js中操作

/*
	通过js代码,让某个文件被单独打包成一个chunk
	import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName: 'test' */'./test' )
	.then(({ mul, count }) => {
    //文件加载成功~
    // eslint-disable-next- line
    console.1og(mu1(2, 5));
	})
  .catch(() => {
  // eslint-disable-next- lineconsole.1og('文件加载失败~');
	});

懒加载和预加载

设置点击按钮后,才去加载对应的js文件

懒加载的前提是需要进行代码分割,分割成一个单独的js文件,再对这个单独的文件进行懒加载

document.getElementById('btn').onclick = function() {
	// 懒加载:当文件需要使用时才加载
  // 预加载prefetch:会在使用之前,提前加载js文件
  // 正常加载可以认为是并行加载(同一时间加载多个文件)
	// 预加载prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
	import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(( { mul} ) => {
    console.log(mul(4, 5));
	});
};

PWA

pwa:渐进式网络开发应用程序(离线可访问)

workbox --> workbox -webpack-plugin

安装

npm i workbox-webpack-plugin -D

使用

webpack.config.js

module.exports = {
  ...
  plugins: [
    new WorkboxWebpackPlugin.GenerateSW({
      /*
				1.帮助serviceworker快速启动
				2.删除旧的serviceworker
				
				生成一个serviceworker 配置文件
			*/
			clientsClaim: true,
      skipWaiting: true
    })
  ]
  ...
}

入口js文件index.js

/*
	1. eslint不认识window、navigator 全局变量
    解决:需要修改package . json中eslintConfig配置
    "env": {
      "browser": true //支持浏览器端全局变量
    }
  2. sw代码必须运行在服务器上
    --> nodejs
    -->
    npm i serve -g
    serve -s build启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
//注册serviceworker
//处理兼容性问题
if ('serviceWorker' in navigator){
  window.addEventListener('load', () => {
  	navigator.serviceWorker
      .register('/service-worker.js')
      .then(()=>{
        console.log('sw注册成功了~');
      })
      .catch(() =>{
        console.log('sw注册失败了~');
      });
	})
}

多进程打包

安装

npm_ i thread- loader -D

使用

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          //开启多进程打包
         	//进程启动大概为600ms,进程通信也有开销
          //只有工作消耗时间比较长,才需要多进程打包
          //'thread- loader',
          {
            loader: 'thread-loader',
            options: {
							workers: 2 //进程2个
            }
          }, 
          {
          	loader: ' babel-loader',
            options:{
              presets :[
                [
              		'@babel/ preset-env',
                  {
              			useBuiltIns: ' usage',
                    corejs:{ version: 3 } ,
                    targets:{
                      chrome: ' 60',
                      firefox: '50'
                    }
                  }
                ]
              ]
            }
          ]
        }
      }
    ]
  }
	...
}

externals

module.exports = {
  ...
  externals: {
    //拒绝jQuery被打包进来
    jquery: 'jQuery'
  }
}

dll

使用dll技术,对某些库(第三方库: jquery、react. vue...)进行单独打包

webpack.dll.js(和webpack.config.js不一样,这里取什么名字都可以)

const{ resolve } = require('path');
const webpack = require('webpack');

module. exports = {
  entry: {
  	// 最终打包生成的[name] --> jquery
    // ['jquery'] -->要打包的库是jquery
    jquery: ['jquery']
  },
  output: {
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]', //打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins:[
    // 打包生成一个manifest.json --> 提供和jquery映射
    // 告诉webpack jquery不需要再打包
    new webpack.DllP1ugin({
      name: '[name]_[hash]', //映射库的暴露的内容名称
      path: resolve(_ dirname, 'dl1/manifest.json') //输出文件路径
    })
  ],
  mode: 'production'
};

当你运行webpack时,默认查找webpack. config.js配置文件

需求:需要运行webpack.dll.js文件

--> webpack --config webpack.dll.js

webpack.config.js

const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');


module. exports = {
  ...
  plugins: [
    //告诉webpack哪些库不参与打包,同时使用时的名称也得变
    //问题:不打包之后,不会引入jquery,所以需要用到下面的插件AddAssetHtmlWebpackPlugin自动引入该资源
    new webpack.DllReferencePlugin({
			manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    //将某个文件打包输出去,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin(
			filepath: resolve(__dirname, 'dll/jquery.js')
    })
  ],
  ...
}

性能优化总结

开发环境性能优化

  • 优化打包构建速度
    • HMR
  • 优化代码调试
    • source-map

生产环境性能优化

  • 优化打包构建速度
    • oneOf
    • babel缓存
    • 多进程打包
  • 优化代码运行性能
    • 缓存(hash-chunkhash-contenthash)
    • tree shaking
    • code split
      • 单入口项目
        • 直接打包只会打包成一个bundle,所以我们需要对bundle代码进行分割,分割成多个js文件,这样子多个文件能并行加载,比加载一个文件更快。
        • 可以使用配置optimization对node_modules进行打包,如果想对node_modules其中的某个文件打包,可以使用dll技术
    • 懒加载/预加载
      • 需要在代码分割的基础上,才能进行懒加载
    • pwa
    • externals
    • dll