webpack优化配置

HMR热模块替换

HMR:hot moddule replacement 热模块替换
作用:一个模块发生变化,只会重新打包这个模块,极大的提升了构建速度
样式文件:可以使用HMR,style-loader内部实现
js文件:默认没有HMR功能,需要修改js代码,添加支持该功能的代码
HMR功能对js的处理,只能处理非入口文件的js文件
html文件: 默认没有HMR功能,同时会导致js文件不能热更新了(不需要HMR)
解决(热更新):修改entry,将HTML文件引入 entry: ['./src/index.js', './src/index.html']

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: ['./src/index.js', './src/index.html'],
  output: {
    filename: 'build.js',
    path: resolve(__dirname, 'build'),
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          linit: 8 * 1024,
          name: '[hash:10].[ext]',
          exModule: false, // 关闭es6模块化
        },
        // outputPath:'img' 输出到build/img文件夹
      },
      {
        // 处理html中的img
        test: /.html$/,
        loader: 'html-loader',
      },
      {
        exclude: /.(html|css|js|jpg|png|gif|less)$/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true, // 优化
    port: 3000,
    open: true,
    hot: true //开启HMR功能
    // 当修改了webpack配置,新配置想要生效必须重启webpack服务
  },
};

js文件的修改:

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

懒加载

懒加载lazy-loading:当文件需要使用时才加载
预加载prefetch:会在使用前提前加载js文件(兼容性差,慎用)
正常加载可以认为是并行加载(同一时间加载多个文件),预加载则是等其他资源加载完毕,浏览器空闲了,再加载资源

console.log('index.js is loaded')

document.getElementById('btn').onclick = () => {
//webpackChunkName: 'test'设置模块名字
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test')
  .then(({mul}) => {
    console.log(mul(4, 5));
    
  })
}

source-map

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

[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map (顺序不能弄错)

source-map:外部,错误代码的准确信息和源代码的错误位置
inline-source-map:内联source-map,只生成一个source-map,错误代码的准确信息和源代码的错误位置
hidden-source-map:外部,错误代码错误原因,但是没有错误位置,只能提示到构建后代码的错误位置
eval-source-map:内联,在每个文件后追加source-map,都在eval中,错误代码的准确信息和源代码的错误位置
nosources-source-map:外部,错误代码是准确信息,但是没有任何源代码信息
cheap-source-map:外部,错误代码的准确信息和源代码的错误位置,只能精确到行
cheap-module-source-map:外部,错误代码的准确信息和源代码的错误位置,module会将loader的source map加入
内联和外部的区别:1.外部生成了文件,内联没有 2.内联构建速度更快

module.exports = {
  entry: ['./src/index.js', './src/index.html'],
  output: {
    filename: 'build.js',
    path: resolve(__dirname, 'build'),
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          linit: 8 * 1024,
          name: '[hash:10].[ext]',
          exModule: false, // 关闭es6模块化
        },
        // outputPath:'img' 输出到build/img文件夹
      },
      {
        // 处理html中的img
        test: /.html$/,
        loader: 'html-loader',
      },
      {
        exclude: /.(html|css|js|jpg|png|gif|less)$/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true, // 优化
    port: 3000,
    open: true,
    hot: true //开启HMR功能
    // 当修改了webpack配置,新配置想要生效必须重启webpack服务
  },
  devtool: 'source-map',
};

开发环境:速度快,调试更友好
速度快(eval>inline>cheap>...)
参考:

eval-cheap-source-map
eval-source-map
调试更友好:
source-map
cheap-moudle-source-map
cheap-source-map

-->eval-source-map / eval-cheap-module-source-map
生产环境:源代码要不要隐藏,调试更友好
内联会让代码体积变大,所以生产环境不用内联
隐藏:
nosource-source-map 全部隐藏
hidden-source-map 只隐藏源代码,会提示构建后代码错误

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

oneof

一般情况下,一个文件只能被一个loader处理,当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:先执行eslint再执行babel

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/build.js',
    path: resolve(__dirname, 'build'),
  },
  module: {
    rules: [
      {
        // 在package.josn中eslintConfig --> airbnb
        test: /.js$/,
        exclude: /node_module/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true,
        },
      },
      {
        // 一下loader只会匹配一个,提升构建速度
        // 注意:不能有两项配置处理同一类型的文件
        oneOf: [
          {
            test: /.css$/,
            use: [
              ...commonCssLoader,
            ],
          }, {
            test: /.less$/,
            use: [
              ...commonCssLoader,
              'less-loader',
            ],
          }, {
          // 在package.josn中eslintConfig --> airbnb
            test: /.js$/,
            exclude: /node_module/,
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: {
                      version: 3,
                    },
                    targets: {
                      chrome: '60',
                      firefox: '50',
                    },
                  },
                ],
              ],
            },
          }, {
            test: /.(jpg|png|gif)$/,
            loader: 'url-loader',
            options: {
              limit: 8 * 1024,
              name: '[hash:10].[ext]',
              outputPath: 'imgs',
              esModule: false,
            },
          }, {
            test: /.html$/,
            loader: 'html-loader',
          }, {
            exclude: /.(js|css|less|html|jpg|png|gif)$/,
            loader: 'file-loader',
            options: {
              outputPath: 'media',
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/build.css',
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
  ],
  mode: 'production',
};

缓存

babel缓存:CacheDirectory:true --让第二次打包构建速度更快
文件资源缓存:让代码上线运行缓存更好使用
hash:每次webpack构建时会生成一个唯一的hash值
问题:因为js和css同时使用一个hash值
如果重新打包,会导致所有缓存失败(可能只改动了一个文件)
chunkhash:根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值就一样
问题:js和css的hash值还是一样
因为css是在js中被引入,所以属于同一chunk(所有根据入口文件生成的东西都会成为一个chunk)
contenthash:根据文件的内容生成hash值,不同的文件hash一定不一样

const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义node环境变量,决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader', {
    // 还需在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => {
        require('postcss-preset-env')();
      },
    },
  },
];
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'js/build.[contenthash:10].js',
    path: resolve(__dirname, 'build'),
  },
  module: {
    rules: [
      {
        // 在package.josn中eslintConfig --> airbnb
        test: /.js$/,
        exclude: /node_module/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true,
        },
      },
      {
        oneOf: [
          {
            test: /.css$/,
            use: [
              ...commonCssLoader,
            ],
          }, {
            test: /.less$/,
            use: [
              ...commonCssLoader,
              'less-loader',
            ],
          }, {
          // 在package.josn中eslintConfig --> airbnb
            test: /.js$/,
            exclude: /node_module/,
            use: {
              loader: 'babel-loader',
              options: {                
                presets: [
                  [
                    '@babel/preset-env',
                    {
                      useBuiltIns: 'usage',
                      corejs: {
                        version: 3,
                      },
                      targets: {
                        chrome: '60',
                        firefox: '50',
                      },
                    }
                  ]
                ],
                // 开启babel缓存
                // 第二次构建时,会读取之前的缓存
                cacheDirectory: true,
              },
            },
          }, {
            test: /.(jpg|png|gif)$/,
            loader: 'url-loader',
            options: {
              limit: 8 * 1024,
              name: '[hash:10].[ext]',
              outputPath: 'imgs',
              esModule: false,
            },
          }, {
            test: /.html$/,
            loader: 'html-loader',
          }, {
            exclude: /.(js|css|less|html|jpg|png|gif)$/,
            loader: 'file-loader',
            options: {
              outputPath: 'media',
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/build.[contenthash:10].css',
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
  ],
  mode: 'production',
  devtool: 'source-map',
};

服务器代码:
安装express:

cnpm install express--save
cnpm install body-parser cookie-parser multer --save

启动服务器指令:

npm i nodemon -g
nodemon server.js
//-------or-------
node server.js

访问服务器地址:http://localhost:3000

const express = require('express');
const app = express();

app.use(express.static('build', { maxAge: 1000 * 3600 }));//maxAge:有效期
app.listen(3000);

tree shaking

去除无用代码:减少代码体积
前提:

  • 必须使用ES6

  • 开启production环境

满足以上两个条件就会开启tree shaking

在package.json中配置:
"sideEffects": false 所有代码都没有副作用(都可以进行tree shaking),默认值时true,若不指定其他值,就默认所有文件都有副作用,无法进行tree shaking。
设置为false的问题:可能会把css、@babel、polyfill(副作用)文件去掉
"sideEffects": [".css", ".less"] 不会把css文件处理

code-split

方法一:通过多入口拆分代码

const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // 多入口:有几个入口,最终输出就有几个bundle
  entry: {
    main: './src/index.js',
    test: './src/test.js' 
  },
  output: {
    // [name]:取文件名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
  ],
  mode: 'production'
};

方法二:

  1. 可将node_modules中代码单独打包一个chunk输出
  2. 自动分析多入口chunk中,有没有公共的文件,若有会打包成单独一个chunk
module.exports = {
  entry: {
    main: './src/index.js',
    test: './src/test.js'
  },
  output: {
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
  ],
  /* 
  1.可以将node_modules中代码单独打包一个chunk输出
  2.自动分析多入口chunk中,有没有公共的文件,若有会打包成单独一个chunk
  */
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  mode: 'production'
};

方法三:
通过js代码,让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包

import (/* webpackChunkName: 'test' */'./test')
  .then(({mul, count}) => {
    // 文件加载成功
    console.log(mul(2, 5));
  })
  .catch(() => {
    console.log('文件加载失败');
    
  })

PWA

渐进式网络开发应用程序(离线可访问)
workbox --> workbox-webpack-plugin

const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/build.[contenthash:10].css',
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
    new WorkboxWebpackPlugin.GenerateSW({
      /* 
      1.帮助serviceworker快速启动
      2.删除旧的serviceworker

      生成一个serviceworker配置文件
      */
      clientsClaim: true,
      skipWaiting: true
    }),
  ],

js文件

/*
1.eslint 不认识window,navigator全局变量
 解决:需要修改package.json中的eslintConfig配置
 "env": {
   "browser": true
 }
2.sw必须运行在服务器上
  --> node.js
  --> 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 registed');
      })
      .catch(() => {
        console.log('sw registe failed');
      });
  });
}

多进程打包

开启多进程打包:thread-loader
进程开启大概需要600ms,进程通信也有开销,只有工作消耗时间长,才需要多进程打包(慎用)

const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

process.env.NODE_ENV = 'production';

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'js/build.[contenthash:10].js',
    path: resolve(__dirname, 'build'),
  },
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_module/,
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true,
        },
      },
      {
        oneOf: [
         {
          // 在package.josn中eslintConfig --> airbnb
            test: /.js$/,
            exclude: /node_module/,
            use: [
              {
                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',
                        },
                      },
                    ],
                  ],
                  // 开启babel缓存
                  // 第二次构建时,会读取之前的缓存
                  cacheDirectory: true,
                },
              },
            ],
          }
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/build.[contenthash:10].css',
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
    
  ],
  mode: 'production',
  devtool: 'source-map',
};

外部扩展externals

让webpack不对一些不怎么需要更新的第三方 库打包,减少打包时间。可通过CMD,AMD,global等方式引入第三方库。

const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry:'./src/index.js',
  output:{
    filename:'js/build.js',
    path:resolve(__dirname, 'build')
  },
  module:{
    rules:[

    ]
  },
  plugins:[
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }
      )
  ],
  mode: 'production',
  externals: {
    // 忽略库名--npm包
    // 拒绝jQuery被打包,通过cdn引入,提高加载速度
    jquery: 'jQuery'
  }
}
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

dll

使用dll技术,对一些第三方库(如:jquery,react等)进行单独打包(不重复打包)。
当运行webpack时,默认查找webpack.config.js文件,使用dll技术需要运行webpack.dll.js文件,运行:

webpack --config webpack.dll.js
//webpack.dll.js
const webpack = require('webpack')
const {resolve} = require('path')
module.exports = {
  entry: {
    // 最终打包生成[name] --> jquery
    // ['jquery'] --> 要打包的库是jquery
    jquery: ['jquery']
  },
  output: {
    filename: '[name].js',
    path:resolve(__dirname, 'dll'),
    library: '[name]_[hash]' //打包库里向外暴露出去的内容叫什么名字
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]_[hash]', //映射库的暴露内容名称
      path: resolve(__dirname, 'dll/manifest.json') //输出文件路径
    })
  ]
}
//webpack.config.js
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack') //使用dll的插件
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')

module.exports = {
  entry:'./src/index.js',
  output:{
    filename:'build.js',
    path:resolve(__dirname, 'build')
  },
  module:{
    rules:[

    ]
  },
  plugins:[
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }
      ),
      // 告诉webpack哪些库不参与打包,同时使用时名称也得变
      new webpack.DllReferencePlugin({
        manifest: resolve(__dirname, 'dll/manifest.json')
      }),
      // 将某个文件打包输出,并在 html中自动引入该资源(不用手动引入)
      new AddAssetHtmlWebpackPlugin({
        filepath: resolve(__dirname, 'dll/jquery.js')
      })
  ],
  mode: 'production'
}

与externals的区别:
externals:彻底不需要打包,可用cdn引入
dll:需要打包,只用打包一次

原文地址:https://www.cnblogs.com/xuewting/p/13330164.html