Webpack项目优化——生产版(最详细无图片版)

完整版webpack开发环境配置,已上传至github仓库中,可以下载哦~

https://github.com/mrxu007/webpack-project.git

完整版webpack生产环境(上线环境)配置,已上传至github仓库中,可以下载哦~

(生产环境相对于开发环境做了优化,后期上线对照着修改即可,建议下载开发环境使用)
https://github.com/mrxu007/webpack-production-base.git

webpack性能优化
开发环境性能优化
生产环境性能优化 -------------

开发环境性能优化

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

生产环境性能优化(代码上线运行)

优化打包构建速度 优化代码运行的性能
oneOf 缓存(hash-chunkhash-contenthash)
babel缓存 tree shaking
多进程打包 code split
externals 懒加载/预加载
dll PWA技术

开发环境性能优化

优化打包构建速度
HMR(hot module replacement)

项目逐渐庞大,修改某个类型文件导致整个项目重新加载(非常消耗资源),引入热模块更新

webpack.config.js配置
devServer: {//本地运行服务器配置
    contentBase: resolve(__dirname, 'dist'),//解析文件地址
    compress: true,//打开gzip压缩
    port: 3000,//端口号
    // open: true,//自动打开浏览器
    //开启HMR: 热模块替换:一个模块发生变化,只会重新打包这个模块(而不是打包所有模块)
    hot: true
  }

样式文件:默认可以使用HMR功能

样式文件必须搭配style-loader,因为该loader内部实现了HMR功能

{//处理css
        test: /\.css$/,
        use:[
          'style-loader', 
          //抽离css功能请在生产模式再开启,不然会与HMR功能冲突
          // MiniCssExtractPlugin.loader,
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [
                require('postcss-preset-env')
                //帮助postcss-loader去找package.json文件中的browserlist兼容主流浏览器配置
              ]
            }
          }
        ]
      }

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

需要判断然后按需引入,处理非入口js文件

入口文件index.js配置
if (module.hot) {
  module.hot.accept('./print.js', () => {
    print();
  });
}

html文件:默认不能使用HMR功能

HMR功能的开启,会导致原本html文件热更新失效

解决:
将html文件并入主入口文件

webpack.config.js配置
 entry: ['./src/js/index.js', './src/index.html'],//文件入口
优化代码调试
source-map源码映射技术
[inline-|hidden-|eval-] 
[nosources-]
[cheap-[module-]] source-map
具体查看webpack官方文档
内联:构建速度更快,打包体积大
inline	
eval
外联:
source-map
hidden-
nosources-
cheap-
cheap-module-
推荐使用source-map

生产环境性能优化

优化打包构建速度
oneof

使一个loader只匹配一种文件类型,提升打包构建速度
eslint-loader babel-loader处理的都是js文件,需将eslint提到oneof的外面。

webpack.config.js配置
rules: [
        {//elint处理js}
 		{
	       oneOf: [ 
	         {//es6转esx5 },
	         {//处理文字图片 },
	         {//处理html中的img图片 },
	         {//处理import导入的图片 },
	         {//处理styl文件 },
	         {//处理less },
	         {//处理css }
	       ]
        }
    ]
babel缓存

HMR功能基于devserver的,生产环境下没有devserver

让第二次打包构建更快
将编译js代码进行缓存

{//es6转esx5
  test: /\.js$/,
  exclude: /node_modules/,//依赖库不需要进行转换
  include: resolve(__dirname, 'src'),
  use: {
    loader: 'babel-loader',
    options: {
      "presets": [
        '@babel/preset-env'
      ],
      //开启babel缓存
      //第二次构建时,会读取之前的缓存,提高速度
      cacheDirectory: true
    }
  }
},
多进程打包

配合babel-loader

npm i thread-loader -D
{//es6转esx5
            test: /\.js$/,
            exclude: /node_modules/,//依赖库不需要进行转换
            include: resolve(__dirname, 'src'),
            use: [
              /*
                开启多线程打包
                进程启动打包为600ms,进程通讯也要开销。
                只有构建时间比较长,才需要多进程打包
              */
               {
                 loader: 'thread-loader',
                 options: {
                   workers: 2//开启2个
                 }
              },
              {
                loader: 'babel-loader',
                options: {
                  "presets": [
                    [
                      '@babel/preset-env',
                      {
                        //按需加载
                        useBuiltIns: 'usage',
                        //指定core-js版本
                        corejs: {
                          version: 3
                        },
                        //指定兼容性做到那些版本浏览器
                        targets: {
                          chrome: '60',
                          firefox: '60',
                          ie: '9',
                          safari: '10',
                          edge: '17'
                        }
                      }
                    ] 
                  ],
                  //开启babel缓存
                  //第二次构建时,会读取之前的缓存,提高速度
                  cacheDirectory: true
                }
              }
            ]
          },
externals:

cdn引入的库不进行打包

 externals: {//externals用于不打包由外部连接引入的第三方库例如JQuery,vue等
  jquery: 'jquery'
 }, 	
dll

将node_modules里面的库单独进行打包
配合optimization

webpack.config.js
const webpack = require('webpack');
plugins: [
	new webpack.DllReferencePlugin({//告诉webpack那些库不参与打包,使用时查找映射文件里的库
	      manifest: resolve(__dirname, 'dll/manifest.json')
	    }),
]
 optimization: {
 //1.将/node_modules/的文件单独打包成一个chunk最终输出             
 //2.也可以分析多入口文件中引入相同的库,文件,单独抽离出来,避免了重复打包 
    splitChunks : {
      chunks: 'all'
    }
  }
webpack.dll.js
/**
 * 使用dll技术,对某些库(第三方库:jquery、react、vue)进行单独打包
 * 当运行webpack命令是,默认查找的是webpack.config.js
 * --> webpack --config webpack.dll.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: [
    //打包生成一个manifast.json ---> 提供的是库的映射文件
    new webpack.DllPlugin({
      name: '[name]_[hash]',//映射的库的名称
      path: resolve(__dirname, 'dll/manifest.json')
    })
  ],
  mode: 'production'
}
优化代码运行的性能
缓存(hash-chunkhash-contenthash)

文件资源缓存优化

后台强制缓存

问题:如果发生重大bug进行修复,重新构建打包,
强制缓存期间,新文件的修改不会重新请求

解决办法:
hash: 给css,js文件后面加上hash值,每次webpack构建时会新生成一个唯一的hash值

缺点:
不管你是否改变文件,重新构建就会生成新的hash值,导致缓存失效

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

缺点:
① 改变某一个类型文件,构建打包会导致hash变化,导致缓存失效
② 因为一般来说所有的文件都是通过入口文件引入,所以都同属于同一个chunk。

contenthash:根据文件的内容生成hash值,不同文件,生成的hash值就不同

treesharing(摇晃树)

webapck自带树摇 (配置side避免树摇将重要文件清除)
自动清理应用程序中没有使用的代码
前提:1.必须使用es6模块化 2.开启production
作用:减少打包体积
问题:
可能会把其他css / @babel/polyfill 等没有使用的,清除
解决:

package.json配置
"sideEffects": ['*.css', ' * .less',等等]

code_split(代码分割)
将打包的一个chunk分割成多个chunk,有利于并行加载,开启多线程。

第一种
多入口文件:有几个入口就会输出几个打包文件

第二种
optimization优化

optimization: {
//1.但入口文件:将/node_modules/的文件单独打包成一个chunk最终输出
//2.多入口文件:自动分析引入相同的库、文件,单独抽离出来,避免重复打包 
    splitChunks : {
      chunks: 'all'
    }
  }

②按需加载的需求

1>对js进行分割处理
分割入口引入中的js文件
使用import高级语法:import会返回一个promise对象

/**
 * 编写js代码,分割通过入口文件引入的文件单独打包成chunk
 * promise语法实现异步导入
 * 可以给个标识,容易区分分割后文件
 */
import(/* webpackChunkName:"test" */'./test')
.then(({ mul }) => {
  // console.log('文件打包成功');
  console.log(mul(10, 10));
}).catch(() => {
  console.log('文件打包失败');
});

但是import属于高级语法,eslint并不识别

报错:error Parsing error: Unexpected token import

解决:

npm i babel-eslint -D
.eslintrc配置文件
{
  "extends": "airbnb-base",
  "parser": "babel-eslint",//对import按需加载高级语法处理,要不然会报错
  "env": {
    "amd": true,
    "es6": true,
    "browser": true,
    "node": false   
   },
  "rules": {
    //未定义的变量不能使用
    "no-undef": 0,
    //一行结束后面不要有空格
    "no-trailing-spaces": 1,
    //强制驼峰命名法
    "camelcase": 2,
    //不能使用console
    "no-console": "off"
  }
}
懒加载(延迟加载):

不需要你加载,只有我需要的时候才去请求资源,文件也不会重复请求,自动读取缓存

思路:代码分割的思路,将import代码分割,放在异步得环境中,实现懒加载
缺点: 当我们需要用的资源体积较大时,用户等待时间比较长,适用于请求小的资源文件

入口文件index.js配置
$('.lazy_loading').click(() => {
  import(/* webpackChunkName: 'test' */ './test')
    .then(({ mul }) => {
      console.log(mul(4, 5));
    }).catch(() => {
      console.log('文件加载失败');
    });
});
预加载:

网页优先加载正常资源,当浏览器空闲时,偷偷加载预加载文件。
正常加载可以认为时并行加载啊
(同一个时间加载多个文件,容易造成堵塞)
缺点:兼容性不强

$('.lazy_loading').click(() => {
  import(/* webpackChunkName: 'test',webpackPrefetch: true */ './test')
    .then(({ mul }) => {
      console.log(mul(4, 5));
    }).catch(() => {
      console.log('文件加载失败');
    });
});

二者区别:
懒加载:当文件需要时才加载~
预加载prefetch: 会在使用之前,加载好资源文件

PWA技术(只能在服务器运行):

processive web application 渐进式网路访问程序(离线技术)

npm i workbox-webpack-plugin -D
webpack.config.js配置
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
 new WorkboxWebpackPlugin.GenerateSW({    //离线缓存技术
      // 帮助serviceworker快速启动
      // 删除旧的serviceworker
      // 生成serviceWorker文件
      clientsClaim: true,
      skipWaiting:true
    })
入口文件index.js配置
// 注册serviceWorker (离线可访问技术):淘宝等大厂都采用了PWA技术
// 处理兼容性问题
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
      .then(() => {
        console.log('注册成功');
      }).catch(() => {
        console.log('注册失败');
      });
  });
自己配置的本地服务器
npm i express -D
文件根目录配置:
server.js
/*
启动服务器

node server.js
*/
const express = require('express');
const app = express();
//将dist文件下的东西暴露出去
app.use(express.static('dist', {
  //当我们代码上线,我们的资源都要做缓存处理,
  //用户第二次访问的时候,直接走访缓存。不需要花太多时间,速度非常快
  //但是强制缓存有缺点,当我们代码在强缓存期间遇到紧急bug时,修复bug重新打包构建,
  //但是资源还在强制缓存期间,新代码就不会被请求
  //先去了解浏览器的缓存机制:强缓存,协商缓存
  //解决办法给资源名称做处理,给随机hash值
  maxage: 1000 * 3600//强制缓存1个小时
}));
app.listen(3000);

你可能感兴趣的:(webpack)