# webpack 常用配置

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HappyPack = require('happypack');
const BundleAnalyzerPlugin =
  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { VueLoaderPlugin } = require('vue-loader/dist/index');
const { DefinePlugin } = require('webpack');
module.exports = {
  // 表示当前是什么环境
  mode: 'development',
  // 用于设置入口文件路径的相对值,也就是说入口文件的路径不是相对于自身的,二是相对于 context 的
  // 默认值是 webpack 的启动路径
  context: path.resolve(__dirname, './'),
  // 打包的时候生成一个 source-map 文件方便调试
  devtool: 'source-map',
  // 入口文件
  entry: './src/main.js',
  // 出口
  output: {
    // 打包后的文件名
    filename: 'bundle.js',
    // 打包后的文件的存储路径
    path: path.resolve(__dirname, '/dist'),
    // 该配置和 CleanWbpeckPlugin 效果一样,配置了就不需要再配置插件了
    // clear: true
  },
  module: {
    rules: [
      {
        test: /\.m?js$/,
        // 因为 loder 在处理文件的时候是单线程的,所以交给 happypack 来开启多线程处理,详细请看插件部分的 happypack
        loader: 'happypack/loader?id=happybabel',
        // 只处理 src 文件夹下面的 js 文件
        include: [path.resolve('src')],
        // 排除 exclude 文件
        exclude: /node_modules/,
      },
      {
        test: /\.vue$/,
        use: [{ loader: 'vue-loader' }],
      },
      {
        test: /\.css$/,
        use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
      },
      {
        test: /\.less$/,
        use: [
          // 将 css 样式插入到 DOM 中
          { loader: 'style-loader' },
          // 解析 css
          { loader: 'css-loader' },
          // 解析 less,将 less 转换为 css
          { loader: 'less-loader' },
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        //webpack5 以前的方法
        // use: [
        //   {
        //     loader: 'file-loader',
        //     options: {
        //       name: 'img/[name].[hash:8].[ext]',
        //     },
        //   },
        // ],
        //webpack5 的方法
        type: 'asset/resource',
        genetator: {
          filename: 'img/[name].[hash:8].[ext]',
        },
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        //webpack5 以前的方法
        // use: [
        //   {
        //     // 对小的图片直接转换为 base64 形式的 url,和页面一起请求,提高图片加载的速度
        //     loader: 'url-loader',
        //     options: {
        //       limit: 100 * 1024,
        //       name: '[name].[hash:8].[ext]',
        //       outputPath: 'img',
        //     },
        //   },
        // ],
        //webpack5 的方法
        type: 'asset',
        genetator: {
          filename: 'img/[name].[hash:8].[ext]',
        },
        parser: {
          dataUrlConditon: {
            maxSize: 100 * 1024,
          },
        },
      },
      {
        test: /\.(woff2?|eot|ttf)$/,
        // 利用 webpack5 的资源模块来处理字体图标
        type: 'asset/resource',
        genetator: {
          filename: 'font/[name].[hash:8].[ext]',
        },
      },
    ],
  },
  plugins: [
    // 用来处理.vue 文件
    new VueLoaderPlugin(),
    // 每次重新打包的时候都需要手动删除 dist 文件夹,配置这个插件,就可以自动删除 dist
    new CleanWebpackPlugin(),
    //webpack 打包的时候默认没有 index.html 文件,但是我们部署到静态服务器的时候是需要这个文件的,用这个插件就可以生成一个 index.html
    new HtmlWebpackPlugin({
      title: '这里设置index.html的标题',
      // 用来配置生成 index.html 文件的模板
      // template: './public/index.html'
    }),
    // 用来定义环境变量
    new DefinePlugin({
      //   BASE_URL: './',
    }),
    // 可以将 public 文件夹里面的一些内容复制到打包的 dist 文件夹里面
    new CopyWebpackPlugin({
      // 进行相关配置
      patterns: {
        // 从哪里开始复制
        from: 'public',
        // 复制到哪里,默认为打包的目录下
        to: 'dist',
        // 一些额外的选项
        globOptions: {
          // 忽略哪些文件
          ignore: ['**/.DS_Store', '**/index.html'],
        },
      },
    }),
    // 开启多线程,增快 webpack 的速度
    new HappyPack({
      id: 'happybabel',
      loaders: [
        {
          // 使用 babel 将 es6 的代码转换成 es5 的代码,并将 cacheDirectory 设置为 true,将 babel 编译过的文件缓存起来
          loader: 'babel-loader?cacheDirectory=true',
          options: {
            //   plugins: [
            //     // 转化 const
            //     '@babel/plugins-transform-block-scoping',
            //     // 转化箭头函数
            //     '@babel/plugins-transform-arrow-functions',
            //   ],
            // 一个一个的加载插件太麻烦了,可以使用 babel 的预设来自动根据预设加载插件
            // 如果要使用 ts,也可以用 @babel/preset-typescript 预设
            presets: ['@babel/preset-env'],
          },
        },
      ],
      // 开启 4 个线程
      threads: 4,
    }),
    // 可视化 webpack 输出文件的体积
    new BundleAnalyzerPlugin(),
    // 将每个 js 文件包含的 css 单独打包成一个文件,支持按需映入
    new MiniCssExtractPlugin(),
  ],
  resolve: {
    // 当模块导入的时候要是没有后缀名,会自动加上下面配置的后缀名
    extensions: ['.js', '.json', '.vue', '.jsx', '.tsx', '.ts'],
    // 配置路径别名,注意如果有 ts 的话,需要在 tsconfig.ts 中也配置路径别名
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  devServer: {
    // 开启热更新,默认就是开启的,但是需要手动指定对哪些模块进行热更新
    hot: true,
    // 项目运行的端口号
    port: 8888,
    // 项目运行的主机
    host: '127.0.0.1',
    // 打包的时候是否自动打开浏览器
    open: true,
    // 是否对文件进行压缩
    compress: true,
    // 配置代理,解决跨域的问题
    proxy: {
      '/api': 'http://localhost:3000',
      pathRewrite: { '^/api': '' },
    },
  },
  optimization: {
    // 开启 scope Hoisting 分析模块之间的依赖关系,经可能的将模块打包到一个函数里面
    concatenateModules: true,
  },
};

# 不同环境怎么切换不同的 webpack

  • 通常会在 config 文件夹里面写 webpack.prod.config.js , webpack.dev.config.js , webpack.common.js 这样的三份文件
  • 生产环境 and 开发环境对应的配置文件中,利用 webpack-merge 这个包提供的 merge 函数,来对通用的配置文件进行合并,下面是一个列子
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.comman.config');
module.exports = merge(commonConfig, {
  mode: 'development',
  devServer: {
    // 开启热更新,默认就是开启的,但是需要手动指定对哪些模块进行热更新
    hot: true,
    // 项目运行的端口号
    port: 8888,
    // 项目运行的主机
    host: '127.0.0.1',
    // 打包的时候是否自动打开浏览器
    open: true,
    // 是否对文件进行压缩
    compress: true,
  },
});
  • 再通过配置 package.json 里面的 script 属性,来执行不同的 webpack 配置文件
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config ./config/webpack.prod.config.js",
    "serve": "webpack serve --config ./config/webpack.dev.config.js --progress"
  },

# 怎么利用 webpack 来优化前端性能

  • 压缩代码体积
    利用 webpack 的 UglifyJsPluginParallelUglifyPlugin 来压缩 js 代码(在 webpack 里面 modeproduction 的时候会自动开启),通过 mini-css-extract 来 压缩 css,将每个 js 文件包含的 css 单独打包成一个 css 文件,支持按需引入。
  • 利用 CDN 加速
    将引用的一些资源的路径修改成对应的 CDN 上面的路径
  • 利用 tree Shaking
    将永远不会执行的代码片段删除
  • 利用 Scope Hoisting 将可能的将打包出来的模块合并到一个函数里面

# 怎么提高 webpack 的打包速度

  • 优化 Loader
    • 利用 include , exclude 来优化 loader 的搜索范围,比如只对 src 文件下的 js 代码进行编译,忽略 node_modules 文件。
    • 利用缓存,将 loader 处理过的文件缓存起来。
  • 利用 HappyPack
    受限于 node,webpack 在打包过程中也是单线程的,但是利用 HappyPack 就可以将 loader 的同步执行变成多线程的。
  • 利用 DllPlugin
    Dllolugin 可以提前将指定的类库打包然后引入,这样可以减少打包的次数,只有类库版本更新的时候才需要打包。
  • 代码压缩
    压缩的一些操作同上

# 怎么减小 webpack 打包的体积

  • tree shaking
  • Scope Hoisting
  • 代码压缩

# webpack 构建流程

  • 初始化参数
    从配置文件和 shell 语句中读取参数,合并参数
  • 开始编译
    利用上一步得到的参数初始化 compiler 对象,并加载所有需要的插件,调用 compiler 对象的 run 方法,开始编译
  • 确定入口
    entry 中找到所有的入口文件
  • 编译模块
    利用 loader 开始编译文件,在从文件中找到相关的依赖模块,然后递归此步骤,直到所有的文件都被编译
  • 完成模块编译
    此时所有的文件都已经被编译,并且也已经得到了文件之间的依赖关系
  • 整理输出的资源
    将所有的编译完成的文件,按照他们之间的依赖关系,组成多个 chunk, 再把 chunk 合并为一个单独的文件,作为输出的文件内容
  • 输出文件
    按照配置文件里面确定的路径和文件名写入文件内容

# webpack HMR 热更新原理

  • 监视阶段
    • webpack 的 watch 模式会监视文件的变化,一旦文件有变化,就会对该文件重新编译打包,然后将重新编译打包后的代码存在一个 js 对象里
    • webpack-dev-server 的中间件 webpack-dev-middleware 会对代码进行监控,一旦有代码发生改变,就会通知 webpack 将代码打包到内存中
    • devServer.watchContentBase 设置为 true 的时候,server 就会监视这些配置文件夹里面的静态文件的变化,一旦有变化就会让浏览器刷新也就是 live reload
  • 传递阶段
    • webpack-dev-serverwebpack-dev-server/client 客户端之间会维护一个 websockt 长连接,将 webpack 的信息传递给客户端,其中最主要的是传递新模块的 hash 值。
    • 因为客户端 webpack-dev-server/client 不能请求更新的代码,所以它会将信息传递给 webpack/hot/dev-server .
    • webpack/hot/dev-server 会将新模块的 hash 值与 devSever 配置里面允许进行热更新模块的模块的 hash 值进行比对,如果一致,则说明需要热更新,就会把 hash 值进一步传递给 HotModuleReplacementRuntime ,不过不一致则会直接刷新浏览器。
    • HotModuleReplacementRuntime 会通过 jsonpMainTemplateRuntime 来发送 Ajax 请求,来返回一个 json,这个 json 文件包括了所有要进行热更新的模块的 hash 值,然后会再发送一个 jsonp 请求,获取到最新的模块代码。
  • 比对阶段
    • HotModulePlugin 会对新旧模块进行对比,来决定是否进行热更新,在决定热更新后,就会更新模块,以及模块的依赖

HMR

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

ziMu 微信支付

微信支付

ziMu 支付宝

支付宝