手摸手教你配置webpack

1,259 阅读11分钟

背景

相信很多童鞋在面试的时候经常会被问到"自己有没有手动配置过webpack", "webpack基础配置"等问题。通过本篇文章带你了解常用的webpack基础配置(此文章基于react技术栈)。

一、依赖包安装

1. webpack相关依赖包

  1. webpack webpack核心包 本文依赖版本4.x
  2. webpack-cli webpack命令行工具, 依赖于webpack核心包
  3. webpack-dev-server 开发环境代码live reloading(实时重新加载)与hot module replacement(热模块替换)
  4. webpack-merge 开发与生产环境合并webpack基础配置
npm install webpack webpack-cli webpack-dev-server webpack-merge -D

2. babel相关依赖包

  1. @babel/core 把js代码分析成ast,方便各个插件分析语法进行相应的处理
  2. @babel/preset-env 无需管理目标环境需要的语法转换或浏览器polyfill,就可以使用最新的js语法
  3. @babel/preset-react 提供对react的支持, 例如支持JSX语法格式
  4. @babel/plugin-proposal-class-properties
  5. core-js 支持es6+新特性, 例如Promise、Set、Iterator
  6. babel-loader 解析js
npm install @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties core-js@3 babel-loader -D 

从 Babel v7 开始,所有针对标准提案阶段的功能所编写的预设(stage preset)都已被弃用,官方已经移除了 @babel/preset-stage-x

3. 样式处理相关依赖包(此处拿less举例)

  1. autoprefixer 针对不同的浏览器在css属性上添加前缀
  2. css-loader
  3. postcss
  4. postcss-loader css解析成js可以操作的ast,并调用插件处理ast得到结果
  5. less
  6. less-loader
npm install autoprefixer css-loader postcss postcss-loader less less-loader -D
  1. 可以是用cssnext替代autoprefixer。
  2. cssnext 插件允许开发人员在当前的项目中使用 CSS 将来版本中可能会加入的新特性。
  3. cssnext 负责把这些新特性转译成当前浏览器中可以使用的语法。从实现角度来说,cssnext 是一系列与 CSS 将来版本相关的 PostCSS 插件的组合。比如,cssnext 中已经包含了对 Autoprefixer 的使用,因此使用了 cssnext 就不再需要使用 Autoprefixer。

4. 文件(包含图片、视频、音频、字体文件)处理相关依赖包

  1. file-loader
  2. url-loader
npm install file-loader url-loader -D
  1. file-loader 返回的是图片的url
  2. url-loader可以通过limit属性对图片分情况处理,当图片小于limit(单位:byte)大小时转base64,大于limit时调用file-loader对图片进行处理。

5. 常用plugins相关依赖包(重要)

  1. clean-webpack-plugin 清空打包文件
  2. copy-webpack-plugin 将某些静态资源复制到特定文件夹下
  3. html-webpack-plugin 创建一个html文件, 并把webpack打包后的静态文件自动插入到此文件中
  4. mini-css-extract-plugin 将css提取到单独的文件中
  5. optimize-css-assets-webpack-plugin 压缩css
  6. terser-webpack-plugin 压缩js
npm install clean-webpack-plugin copy-webpack-plugin html-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin terser-webpack-plugin -D
  1. html-webpack-plugin可配置多次, 常见于多入口打包
  2. mini-css-extract-plugin用来替换extract-text-webpack-plugin, 与 extract-text-webpack-plugin 相比优势在于: (1) 异步加载 (2) 没有重复的编译(性能)(3) 配置更简洁 (4) 特别针对 CSS 开发
  3. terser-webpack-plugin用来替换uglifyjs-webpack-plugin, 因为uglifyjs不支持es6语法

二、webpack配置

0. 前置路径配置文件

添加config.js配置文件

const path = require('path');
const resolve = (dir) => path.resolve(__dirname, dir);
const config = {
  // publicPath(客户端所访问的上线资源地址)指定的路径会被作为前缀添加到所有的url上, 例如html文件中的link标签,script标签、img标签
  pubicPath: '/',
  templateresolve('../public/index.html'),
  entry: resolve('../src/index.js'),
  // 打包文件放置位置
  path: resolve('../dist'),
};

module.exports = config;

1. 基础配置

添加webpack.config.js基础配置文件, 文件内容如下:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const config = require('../config');

const webpackConfig = {
  devtool: false, // 此选项控制是否生成,以及如何生成 source map
  resolve: {
    alias: {
      demo: path.join(__dirname, '../src/components'),
    },
    // 尝试按顺序解析文件后缀名, 如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀。
    extensions: ['*''.js''.jsx''.less''.css'],
  },
  // 入口配置
  entry: {
    bundle: config.entry,
    vendor: ['react''react-dom''react-router-dom'],
  },
  // 输出配置
  output: {
    filename: '[name]-[hash:8].js',
    // chunkFilename: '[name]-[chunkhash:8].js',
    path: config.path,
  },
  module: {
    // 解析器配置
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  targets: {
                    // 大于相关浏览器版本无需用到 preset-env
                    edge: '17',
                    firefox: '60',
                    chrome: '67',
                    safari: '11.1',
                  },
                  corejs: '3', // 声明corejs版本, 主要提供对es6+新语法、新特性的支持
                  // 根据代码逻辑中用到的 ES6+语法进行方法的导入,而不是全部导入
                  useBuiltIns: 'usage', // useBuiltIns就是是否开启自动支持 polyfill,它能自动给每个文件添加其需要的poly-fill。
                },
              ],
              '@babel/preset-react',
            ],
            plugins: ['@babel/proposal-class-properties'], // 解决Support for the experimental syntax 'classProperties' isn't currently enabled
          },
        },
        exclude: /node_modules/,
      },
      {
        test: /\.(css|less)$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // 只在开发模式中启用热更新
              hmr: process.env.NODE_ENV === 'development',
              // 如果模块热更新不起作用,重新加载全部样式
              reloadAll: true,
            },
          },
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                ident: 'postcss', // 说明options里面插件的使用是针对于谁的,我们这里是针对于postcss的
                plugins: [
                  // 这里的插件只是这对于postcss
                  require('autoprefixer')(), // 引入添加前缀的插件,第二个空括号是将该插件执行
                ],
              },
            },
          },
          'less-loader',
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 100, // 不超过100byte, 则转换成base64位
              name: 'assets/img/[name].[ext]', // 图片输出路径
            },
          },
        ],
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: 'assets/blob/[name].[ext]', // 音视频输出路径
            },
          },
        ],
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: 'assets/font/[name].[ext]', // 字体输出路径
            },
          },
        ],
      },
    ],
  },
  // 插件配置
  plugins: [
    new HtmlWebpackPlugin({
      template: config.template,
      inject: true, // 注入选项 有四个值 true,body(script标签位于body底部),head,false(不插入js文件)
      filename: 'index.html',
      minify: {
        // 压缩html
        removeComments: true, // 去除注释
        collapseWhitespace: true, // 去除空格
      },
    }),
    new MiniCssExtractPlugin({
      filename: process.env.NODE_ENV === 'development' ? 'assets/style.css' : 'assets/style.[hash:8].css', // 配置样式文件输出路径
    }),
  ],
};

module.exports = webpackConfig;

  1. 在不进行任何配置的情况下,@babel/preset-env 所包含的插件将支持所有最新的JS特性(ES2015,ES2016等,不包含 stage 阶段),将其转换成ES5代码。例如,代码中使用了还都处在在 stage 阶段新特新Optional Chaining(可选链操作符)或者Nullish coalescing Operator(空位合并操作符),那么只配置 @babel/preset-env,转换时会抛出错误,需要另外安装相应的插件。
  2. babel7.4以后官方以不推荐使用@babel/polyfill,用core-js@3代替
  3. MiniCssExtractPlugin插件配置filename时, 区分了开发与生产环境的样式文件名称, 原因是在开发环境中, 如果不固定样式文件名称, 会出现修改样式不能自动更新的问题
  4. url-loader支持两种图片引入方式:
    1.import img from './image.png'
    2.css样式引入, 例如backgorund: url('./image.png')

2. 基于基础配置添加dev环境配置文件

const webpack require('webpack');
const merge require('webpack-merge');
// const CopyWebpackPlugin = require('copy-webpack-plugin');
const baseConfig require('./webpack.config');
const config require('../config');

const devConfig merge(baseConfig, {
  mode'development',
  devtool'inline-source-map',
  devServer: {
    hottrue, // hotOnly 修改内容后command+s后,页面并不会刷新,而需要手动进行刷新
    inlinetrue,
    disableHostChecktrue,
    contentBase: config.path,
    compresstrue,
    host'localhost',
    port8080,
    overlaytrue,
    publicPath: config.pubicPath,
    proxy: {
      '/api': {
        target'http://xxx.meituan.com',
        changeOrigintrue,
      },
    },
  },
  output: {
    publicPath'/',
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
    // new CopyWebpackPlugin([
    //   {
    //     from: path.resolve(__dirname, '../static'),
    //     to: config.staticSubDirectory,
    //     ignore: ['.*'],
    //   },
    // ]),
  ],
});

module.exports = devConfig;

  1. hotOnly 修改内容后command+s后,页面并不会刷新,而需要手动进行刷新
  2. mode为development时可省略NamedModulesPlugin、NamedChunksPlugin、DefinePlugin配置
  3. 在开发环境中肯定少不了使用webpack-dev-server进行热更新, 通过如上配置大家可以发现热更新已经生效, 但是每次修改会重新加载整个页面, 并不会局部更新, 解决方案为在入口处添加如下代码(非常重要):
if (module.hot) { 
     module.hot.accept(App, () => { //App 代表需要热更新的dom
     render(App); 
   });
}
  1. wepback目前已经发布了5.x版本, 但是本人尝试了5.x版本的热更新不起作用, 还未找到解决方案

3. 基于基础配置添加prod环境配置文件

const merge require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserWebpackPlugin require('terser-webpack-plugin');
const OptimizeCssAssetsPlugin require('optimize-css-assets-webpack-plugin');
// const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const baseConfig require('./webpack.config');
const config require('../config');

module.exports = merge(baseConfig, {
  mode'production',
  output: {
    publicPath: config.pubicPath,
  },
  plugins: [
    new CleanWebpackPlugin(),
    // new BundleAnalyzerPlugin(), // 打包结束后会启动一个服务在浏览器查看打包的大小以及包内容
  ],
  optimization: {
    minimizer: [
      // js压缩
      new TerserWebpackPlugin({
        paralleltrue,
        exclude: /\/node_modules/,
        extractCommentsfalse, // 这个选项如果为true 会生成一个xxx.js.LICENSE.txt文件 存储特定格式的注释
        terserOptions: {
          warningsfalse,
          compress: {
            unusedtrue,
            drop_debuggertrue, // 删除debugger
            drop_consoletrue, // 删除console
          },
        },
      }),
      // css压缩
      new OptimizeCssAssetsPlugin({
        cssProcessorOptions: { safetrue, discardComments: { removeAlltrue } },
      }),
    ],
  },
});

  1. mode为production时可省略NoEmitOnErrorsPlugin、DefinePlugin、TerserPlugin、ModuleConcatenationPlugin配置

启动命令

"scripts": {
  "start": "NODE_ENV=development webpack-dev-server --inline --config webpack.dev.js",
  "build": "NODE_ENV=production webpack --config webpack.prod.js",
}

详细代码可参照https://github.com/XUGAOBO/webpack-demo