webpack开发环境基本配置

2021年6月2号更新
官方提供webpack配置描述信息:https://www.webpackjs.com/configuration/
我的项目地址:https://github.com/cirry/webpack-template
项目根据最新webpack版本,不断优化代码,复制即用,github上有没有注释的webpack配置文件,webpack.config.no-comment.js。

目录结构如下图

webpack.config.js

下图是我的简单配置,供日常开发使用:

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

// 设置nodejs的环境变量
// process.env.NODE_ENV = "development"
// process.env.NODE_ENV = "production"
const isProduction = process.env.NODE_ENV === 'production';

// css兼容性配置
const CommonCSSLoader = [
    {
        loader: "postcss-loader",
        options: {
            postcssOptions: {
                ident: 'postcss',
                //打包后有兼容性样式代码,只处理css文件,不处理less文件
                plugins: [
                    require('postcss-preset-env')
                ],
            }
        }
    },
]

module.exports = {
    mode: "development",
    // 下面这个是开启全部的js兼容性方式,开启的话需删除js中的按需加载
    // entry: ["@babel/polyfill", "./src/index.js"],
    // 第一个入口是js,第二个是html,这样再修改js和html都可以做到热更新
    entry: ["./src/index.js", "./src/index.html"],
    output: {
        filename: "js/[name].[contenthash:10].js",
        // __dirname, nodejs的变量,代表当前文件的目录绝对路径
        // path: resolve(__dirname, 'build'),
        publicPath: "/",
        chunkFilename: "js/[name].[contenthash:10]_chunk.js"
    },
    module: {
        rules: [
            {
                test: /.css$/, use: [
                    isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
                    'css-loader',
                    ...CommonCSSLoader,
                ]
            },
            {
                test: /.less$/, use: [
                    isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
                    'css-loader',
                    ...CommonCSSLoader,
                    'less-loader',
                ]
            },
            // 仅能处理css中的引入的图片
            {
                test: /.(jpg|png|gif|jpe?g)$/, loader: "url-loader", options: {
                    // 小于8kb的图片会被处理为base64
                    limit: 8 * 1024,
                    name: '[name].[hash:10].[ext]',
                    outputPath: 'image'
                }
            },
            // 处理html文件中的img图片(负责引入img,从而能被url-loader进行处理)
            {
                test: /.html$/, loader: "html-loader", options: {
                    // 问题: 因为url-loader默认使用es6模块化解析,而html-loader引入图片使用的是common.js[解析时会出问题]
                    esModule: false
                }
            },
            //npm install -D babel-loader @babel/core @babel/preset-env webpack
            // 只处理基本的js语法,全部js兼容性处理:@babel/polyfill
            {
                test: /.m?js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            ['@babel/preset-env',
                                {
                                    // 指定兼容性做到哪个版本的浏览器,使用默认,配置方式跟browserList差不多
                                    "targets": {
                                        "ie": 7,
                                        "edge": "17",
                                        "firefox": "60",
                                        "chrome": "67",
                                        "safari": "11.1",
                                    },
                                    // 按需加载
                                    useBuiltIns: "usage",
                                    // 指定corejs版本
                                    corejs: {
                                        version: 3
                                    }
                                }
                            ]
                        ],
                        cacheDirectory: true
                    }
                }
            },
            {
                test: /.(woff2?|eot|ttf|otf)(?.*)?$/,
                loader: 'url-loader',
                options: {
                    limit: 1024 * 8,
                    name: "[name].[hash:10].[ext]",
                    outputPath: "fonts"
                }
            },
            {
                test: /.(mp4|webm|ogg|mp3|wav|flac|aac)(?.*)?$/,
                loader: 'url-loader',
                options: {
                    limit: 1024 * 8 * 5,
                    name: "[name].[hash:10].[ext]",
                    outputPath: "media"
                }
            },
        ],
    },
    plugins: [
        // 在内存中创建index.html文件,并自动引入js和css文件,html模板为template路径的页面
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            // 压缩html代码
            minify: {
                collapseWhitespace: true,
                removeComments: true,
            }
        }),
        // 提取css代码为单独的文件,默认会被压缩在js文件中
        new MiniCssExtractPlugin({
            filename: 'css/[name].[contenthash:10].css',
        }),
        //压缩css代码
        new OptimizeCssAssetsWebpackPlugin()
    ],
    optimization: {
        // 配置生产环境的压缩方案,压缩js和css
        minimizer: [
            // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
            // `...`,
            new CssMinimizerPlugin(),
            new TerserPlugin()
        ],
        // 可以将node_modules中的代码单独打包成一个chunk输出
        splitChunks: {
            chunks: "all",
            //下面所有的属性都是默认值,不用写
            // minSize: 30 * 1024, // 小于30kb不分割
            // maxSize: 0, // 最大没有限制
            // minChunks: 1, // 要提取的chunks最少被引用1次
            // maxAsyncRequests: 5, // 按需加载时,并行加载文件的最大数量
            // maxInitialRequests: 3, //入口js文件,最大并行请求数量
            // automaticNameDelimiter: "~", // 名称连接符
            // name: true, // 可以使用命名规则
            // cacheGroups: { // 分割chunk的组
            //     // node_modules文件中的文件会被打包到vendors组的chunk中 --> vendors~xxx.ks,连接符号是波浪线是因为前面的automaticNameDelimiter属性
            //     // 满足上面的公共规则,如大小超过30kb,至少被引用一次
            //     vendors: {
            //         test: /[\/]node_modeuls[\/]/,
            //         // 优先级
            //         priority: -10
            //     },
            //     default:{
            //         // 要提取的chunk最少被引用两次
            //         minChunks: 2,
            //         priority: -20,
            //         // 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会被复用,而不是重新打包模块
            //         reuseExistingChunk: true
            //     }
            // }
        },
        // 将当前模块的记录其他模块的hash单独打包为一个文件,叫runtime文件
        // 解决:修改a文件导致b文件的contenthash变化
        runtimeChunk: {
            name: (entrypoint) => `runtime~${entrypoint.name}`,
        },
    },
    // 打包在内存中, 自动编译,自动打开浏览器,自动刷新
    devServer: {
        contentBase: resolve(__dirname, 'dist'), // 运行的目录,不是源代码,而是构建后的目录
        compress: true,
        host: 'localhost',
        port: 3000,
        open: true,
        hot: true,
        // 没有跨域问题,忽略proxy配置
        proxy: {
            // 一旦devServer(3000)服务器接收到/api/xxx的请求,就会把请求转发到另外一个服务器(5000)上
            '/api': {
                target: 'http://localhost:5000',
                // 发送请求时,请求路径重写: 将/api/xxx -->/xxx
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    },
    // 热更新需要属性
    target: 'web',
    // 开发生产坏境报错需要
    devtool: 'source-map',
    // 排除jquery不打包
    externals: {
        // 拒绝jquery被打包
        jquery: 'jQuery',
    }
}

package.json 需要装的包:

{
  "name": "webpack-template",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "dev": "cross-env NODE_ENV=development webpack serve",
    "build": "cross-env NODE_ENV=production webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.14.3",
    "@babel/preset-env": "^7.14.4",
    "babel-loader": "^8.2.2",
    "core-js": "^3.13.1",
    "cross-env": "^7.0.3",
    "css-loader": "^5.2.6",
    "css-minimizer-webpack-plugin": "^3.0.0",
    "file-loader": "^6.2.0",
    "html-loader": "^2.1.2",
    "html-webpack-plugin": "^5.3.1",
    "less": "^4.1.1",
    "less-loader": "^9.0.0",
    "mini-css-extract-plugin": "^1.6.0",
    "optimize-css-assets-webpack-plugin": "^6.0.0",
    "postcss-loader": "^5.3.0",
    "postcss-preset-env": "^6.7.0",
    "style-loader": "^2.0.0",
    "terser-webpack-plugin": "^5.1.3",
    "url-loader": "^4.1.1",
    "webpack": "^5.38.1",
    "webpack-cli": "^4.7.0",
    "webpack-dev-server": "^3.11.2"
  },
  "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "dependencies": {
    "@babel/polyfill": "^7.12.1",
    "jquery": "^3.6.0"
  }
}

详细说明配置含义

基础属性

  1. 在webpack5中,入口entry 默认值为:'./src',出口output默认值为:'./dist',可以不用配置。
  2. mode属性:可以设置为“development”或“production”,我已通过cross-env设置node环境。
  3. devtool属性:开发生产的时候代码报错,用来定位错误代码位置。
  4. externals属性:排除外来第三方包,不参与打包,减少打包代码量和加载速度,用第三方引入。

hash含义

为什么要引入hash?
首先webpack中有三种hash模式,分别为:hash/fullhash,chunkhash,contenthash,参考output中filename: "js/[name].[contenthash:10].js"的使用方式。

  • 从不同包中引入的相同名称的图片,可以通过打包hash还来区分他们。
  • 缓存问题。
    • 比如我们加载一个页面需要有a.js和a.css文件,当文件被缓存的时候,我们不加hash值,修改a.css的文件内容,重新打包部署,浏览器不会去请求服务器拿新页面而是使用缓存中的页面。因为浏览器会认为文件名称没有变化,内容没有变更,不需要去获取新的文件。
    • 这里我们给文件名称加上第一种hash值,js/[name].[hash:10].js, css/[name].[hash:10].css,这一种hash值是每次webpack打包都会生成的整体的hash值,这会让每个文件的hash值都是相同的。当我们修改css的文件内容然后重新打包的时候,会重新生成hash值,刷新浏览器,会去重新请求js和css文件,而我们只改了一个css文件,却也请求了两个文件,因为两个文件的名称都变了。
    • 第二种hash值,js/[name].[chunkhash:10].js, css/[name].[chunkhash:10].css,这里因为我们就一个chunk, 目录结构比较简单,所以会跟第一种情况。当项目复杂,有不同的chunk的时候,就会有差别。
    • 第三种hash值,js/[name].[contenthash:10].js, css/[name].[contenthash:10].css,这里使用的是根据文件内容生成hash值,当我们只修改css文件后重新打包,会发现,js文件还是之前的名称,而css文件名称已经发生了变化。刷新浏览器会去重新请求css文件,而js文件是走的缓存。

module用法

module中主要是对文件的处理操作,根据rules的匹配规则对不同的文件加载不同的loader处理方式。
例如对less文件,我们依次使用:['style-loader','css-loader','less-loader'],数组中的三个文件,在文件为.less文件的时候,会从右向左依次被处理。

  • 先被less-loader处理,将less转化为css
  • 再被css-loader处理,将css转化为commonjs
  • 再被style-loader处理,将样式通过<style>加载到html页面中

其中,css文件和js文件都有兼容性问题,js有例如promise,匿名函数,解构,es6新特性。css有例如3d样式,flex等等。
处理js文件的兼容性问题是使用了babel-loader,处理css文件兼容性问题是使用了postcss-loader。

插件作用

webpack.config.js中共用到了三个插件。

  1. HtmlWebpackPlugin 插件
    此插件,在开发过程中,将打包号的代码编译在内存中,并选择./src/index.html作为模版内容,并挂在上需要引入的css文件和js文件。
  2. MiniCssExtractPlugin 插件
    webpack默认会将css代码压缩在js文件中,通过此插件可以将css文件提取成单独的文件。
  3. OptimizeCssAssetsWebpackPlugin 插件
    第二个插件会将css文件提取出来,但并没有压缩css代码,通过此插件可压缩css代码。

optimization作用

  1. minimizer
    配置生产环境的代码压缩方法,CssMinimizerPlugin 压缩css代码,TerserPlugin 压缩js代码。
  2. splitChunks
    可以将node_modules中的代码单独打包成一个chunk输出,上图中被注释掉的代码都是webpack默认的参数配置。
  3. runtimeChunk
    将当前模块的记录其他模块的hash单独打包为一个文件,叫runtime文件。
    比如,我们在a.js中引入了b.js,当b.js的文件内容修改了之后,重新打包,会重新打包a.js文件和b.js文件。我们用runtimeChunk把a.js和b.js的hash值抽离出来单独管理,当b.js的hash改变了之后,a.js的内容并没有改变就不需要重新打包a.js。

externals 使用方式

externals: {
        jquery: 'jQuery',
        Konva: 'Konva'
    }

其中小写的"jquery"是你在项目中引用的包名,import $ from 'jquery'中的 'jquery'。
大写的"jQuery"是包暴露出来的对象名,比如jquery包中,最后暴露出来的名称是 export default jQuery

有疑问或者问题,请留言,本人常在回复比较及时。
作者:Cirry
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
原文地址:https://www.cnblogs.com/cirry/p/webpack-config-js.html