webpack5 配置

337 阅读4分钟
// 导出webpack 配置
// vue inspect --mode=development > webpack.config.dev.js
// vue inspect --mode=production > webpack.config.prod.js


/**
 * 通用环境提升构建速度
 *
 * 1. webpack nodejs 更新到最新版本
 * 2. 将loader 应用于最少数量的必要模块
 * 3. 尽量少的使用 loader / plugin, 每个额外的都有其启动时间
 * 4. 移除不必要的模块
 * 5. 多进程、多实例 TerserPlugin.parallel
 * 6. 减少 resolve.modules, resolve.extensions, resolve.mainFiles,
 *    resolve.descriptionFiles 中条目数量,提高解析速度
 * 7. babel-loader、TerserPlugin.cache、hard-source-webpack-plugin 开启缓存
 * 8. 图片压缩
 * 9. 动态 Polyfill 服务,不同设备浏览器返回不同的 Polyfill
 *    https://polyfill.io/v3/polyfill.min.js
 * */

/**
 * 代码分离 3-3 动态导入 import('./math.js').then()
 *
 * 懒加载   事件监听后 import
 *
 * 预获取   Prefetch 将来可能用到的资源 ,会在页面头部生成
 *  <link rel="prefetch" href="math.js" >,指示浏览器在空闲时间加载math.js
 *
 * 预加载   Preload 当前可能会用到的资源 和懒加载效果类似
 **/

// 魔法注释
// import(/* webpackChunkName: math, webpackPreload: true, webpackPrefetch: true */ './math.js').then()

// npm install html-webpack-plugin -D
// 生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle
const HtmlWebpackPlugin = require('html-webpack-plugin');

const webpack = require('webpack')

// npm install mini-css-extract-plugin -D
// 将 CSS 提取到单独的文件中, webpack5可用
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// npm install css-minimizer-webpack-plugin -D
// 使用 cssnano 优化和压缩 CSS
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// npm install terser-webpack-plugin -D
// 压缩 JavaScript
const TerserPlugin = require("terser-webpack-plugin");

// hard-source-webpack-plugin 图片压缩

// npm install --save-dev speed-measure-webpack-plugin
// 打包速度分析
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

// npm install webpack-bundle-analyzer -D
// 打包结果分析工具。
// 		vue.config.js 中,不用添加插件,在命令行后添加 --report 可用
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

// npm install workbox-webpack-plugin -D
// 渐进式网络应用程序(progressive web application - PWA) serviceWorker 服务停了 离线时 浏览器页面还能继续运行
const WorkboxPlugin = require('workbox-webpack-plugin');

// npm install @babel/polyfill  -D



/**
 *  npm install webpack-merge -D 合并配置文件
 *
 *  const { merge } = require('webpack-merge')
    const commonConfig = require('./webpack.config.common')
    const prodConfig = require('./webpack.config.prod')
    const devConfig = require('./webpack.config.dev')

    module.exports = (env) => {
      switch (true) {
        case env.development:
          return merge(commonConfig, devConfig)

        case env.production:
          return merge(commonConfig, prodConfig)

        default:
          return new Error()
      }
    }
 *
 * */

const path = require('path');
module.exports = smp.wrap({
  entry: 'index.js',
  // entry: {
  //   main: {
  //     import: ['./src/app.js', './src/app2.js'],
  //     dependOn: 'lodash', // 重复公共包
  //     filename: 'channel1/[name].js'
  //   },
  //   main2: {
  //     import: './src/app3.js',
  //     dependOn: 'lodash', // 重复公共包
  //     filename: 'channel2/[name].js'
  //   },
  //   lodash: { // 代码分离 3-1 重复公共包在此
  //     import: 'lodash',
  //     filename: 'common/[name].js'
  //   }
  // },

  output: {
    // webpack 配置文件同层的 dist 文件夹
    path: path.resolve(__dirname, './dist'),
    filename: 'scripts/[name].[contenthash].js', // 生成的js 文件, name 是entry下的键名
    clean: true, // 打包之前自动清理dist下的文件
    assetModuleFilename: 'images/[contenthash][ext]',
    publicPath: 'http://localhost:8080/'
  },

  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src') // 模块加载时可用@替代src
    },

    extensions: ['.js', '.json', '.vue'] // 模块加载时 不写文件扩展名 同名 加载顺序
  },

  mode: 'development', // 'none' | 'development' | 'production'

  // 开发环境调试代码可看到出错的具体位置
  devtool: 'cheap-module-source-map',

  // npm install webpack-dev-server -D
  // 简写: dev-server
  devServer: {
    static: path.resolve(__dirname, './dist'), // 监听目录
    compress: true, // 开启压缩代码 gzip
    port: 8080,
    host: '0.0.0.0',
    // https: true, // 模拟https环境测试
    http2: true,
    historyApiFallback: true,
    hot: true, // 模块热替换
    liveReload: true, // 模块热加载
  },

  plugins: [
    new HtmlWebpackPlugin({
      title: '页面标题',
      template: './index.html', // 依据这个文件生成dist文件价下的index.html
      filename: 'app.html', // 生成的html 文件
      inject: 'body', // script 标签插入到body中
      chunks: ['main', 'main2', 'lodash'] // 默认entry下的全部
    }),
    new webpack.ProvidePlugin({
      _: 'lodash' // 全局预置依赖 业务代码里不用引入lodash,也可暴露在全局
    }),
    new MiniCssExtractPlugin({
      filename: 'styles/[contenthash].css' // 提取单独的css文件
    }),
    new BundleAnalyzerPlugin(),
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true, // 快速启用 ServiceWorkers
      skipWaiting: true // 跳过等待 不允许遗留任何“旧的” ServiceWorkers
    })
    /** 代码里注册才能用 serviceWorker
    *  if ('serviceWorker' in navigator) {
         window.addEventListener('load', () => {
           navigator.serviceWorker.register('/service-worker.js').then(registration => {
             console.log('注册 Service Worker ok: ', registration);
           }).catch(registrationError => {
             console.log('注册 Service Worker failed: ', registrationError);
           });
         });
       }
    */
  ],

  module: {
    rules: [

      /*
       *   资源类型有4种:
       *       resource资源
       *       inline资源
       *       source资源
       *       通用资源
       */
      {
        test: /\.png$/,
        type: 'asset/resource', // resource资源 会导出单独图片文件
        generator: {
          filename: 'images/[contenthash][ext]' // 优先级比 assetModuleFilename 高
        }
      },
      {
        test: /\.svg$/,
        type: 'asset/inline' // inline资源 会导出base64
      },
      {
        test: /\.txt$/,
        type: 'asset/source' // source资源 会导出源代码
      },
      {
        test: /\.jpg$/,
        type: 'asset', // 通用资源 自动选择, parser 未设置时,小于8k用 'asset/inline',大于8k用 'asset/resource'
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024 * 1024 // 4M
          }
        }
      },

      // npm install style-loader css-loader less-loader -D
      // npm install postcss-loader postcss autoprefixer -D
      {
        test: /\.(css|less)$/,
        // /use: [MiniCssExtractPlugin.loader /* css抽出单独文件, 从后向前执行loader */, 'css-loader', 'less-loader']
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true // 开启css模块 避免类名重复
            }
          },
          'less-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  [
                    'postcss-preset-env', // postcss-preset-env 包含 autoprefixer,因此如果你已经使用了 preset 就无需单独添加它了
                    // 'autoprefixer',
                    {
                      // 其他选项
                    },
                  ],
                ],
              },
            },
          }
        ] // 样式在htmlstyle标签中, 从后向前执行loader
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        type: 'asset/resource'
      },

      // npm install csv-loader xml-loader -D
      // 加载 csv xml 文件数据
      {
        test: /\.(csv|tsv)$/,
        use: ['csv-loader']
      },
      {
        test: /\.xml$/,
        use: ['xml-loader']
      },

      // npm install toml yaml json5 -D
      // 加载 toml yaml json5 文件数据
      {
        test: /\.toml$/,
        type: 'json',
        paeser: {
          parse: toml.parse
        }
      },
      {
        test: /\.yaml$/,
        type: 'json',
        paeser: {
          parse: yaml.parse
        }
      },
      {
        test: /\.json5$/,
        type: 'json',
        paeser: {
          parse: json5.parse
        }
      },

      // npm install babel-loader @babel/core @babel/preset-env @babel/runtime @babel/plugin-transform-runtime core-js@3 eslint-loader -D
      // babel-loader,        es6 转为 es5
      // @babel/core,         babel核心
      // @babel/preset-env,   babel插件合集
      // @babel/runtime,      可兼容 async await 写法
      // @babel/plugin-transform-runtime,
      {
        test: /.\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: [
                '@babel/preset-env',
                {
                  targets: [
                    'last 1 version',
                    '> 1%'
                  ],
                  useBuiltIns: 'usage',
                  corejs: 3
                }
              ],
              plugins: [
                [
                  '@babel/plugin-transform-runtime'
                ]
              ]
            }
          },
          'eslint-loader'
        ],
      },

      // npm i imports-loader -D 细粒度预置依赖
      {
        test: require.resolve('./src/index.js'),
        use: 'imports-loader?wrapper=window'
      },

      // npm i exports-loader -D 全局导出
      {
        test: require.resolve('./src/global.js'),
        use: 'exports-loader?type=commonjs&exports=file'
      }
    ]
  },

  externalsType: 'script', // 第三方库 使用的标签
  externals: { // 从输出的 bundle 中排除下列依赖
    jquery: [  // 第三方库
      'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js',
      '$'
    ]
  },

  // 优化配置   mode 为 production 可用
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserPlugin({
        parallel: 4 // 使用多进程并发运行以提高构建速度
      })
    ],
    splitChunks: {
      // chunks: 'all' // 代码分离 3-2 自动抽离公共代码
      cacheGroups: { // 缓存组 存放 第三方文件 一般在node_modules
        vendor: {
          test: /[\\/]node_modules[\\/]/, // 当 webpack 处理文件路径时,它们始终包含 Unix 系统中的 / 和 Windows 系统中的 \。这就是为什么在 {cacheGroup}.test 字段中使用 [\\/] 来表示路径分隔符的原因。
          name: "vendors",
          chunks: 'all',
          minSize: 20480, // 拆分包的大小(以 bytes 为单位), 至少为minSize,如果包的大小不超过minSize,这个包不会拆分
          maxSize: 40960 // 将大于maxSize的包,拆分为不小于minSize的包
        }
      }
    },
    usedExports: true // tree-shaking
  },

  performance: {
    hints: false // 关闭性能提示
  },
  
  cache: {
    type: 'filesystem'
  }
});