一个合格的Webpack4配置工程师素养:第三部分

前两篇

webpack配置分层

以前有提过webpack根据不一样的环境咱们会加载不一样的配置。咱们只须要提取出三部分。css

- base: 公共的部分
- dev: 开发环境部分
- prod: 生产环境部分
复制代码
npm i -D webpack-merge
复制代码

咱们这里如今简单分层:正式项目最好建立一个config/webpack目录管理。html

webpack目录

下面是源代码。vue

"scripts": {
    "dev": "cross-env NODE_ENV=development npx webpack --progress --config webpack.dev.config.js",
    "build": "cross-env NODE_ENV=production npx webpack --progress --config webpack.prod.config.js"
 },
复制代码
// webapck.base.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackplugin = require('clean-webpack-plugin')

module.exports = {
    entry: './src/index.js',
    module: {
        rules: [
            // 处理字体
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                loader: 'url-loader',
                options: {
                    // 文件大小小于limit参数,url-loader将会把文件转为DataUR
                    limit: 10000,
                    name: '[name]-[hash:5].[ext]',
                    output: 'fonts/',
                    // publicPath: '', 多用于CDN
                }
            },
            // 处理文件
            {
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                use: [
                    // 转base64
                    {
                        loader: 'url-loader',
                        options: {
                            // 具体配置见插件官网
                            limit: 10000,
                            name: '[name]-[hash:5].[ext]',
                            outputPath: 'img/', // outputPath所设置的路径,是相对于 webpack 的输出目录。
                            // publicPath 选项则被许多webpack的插件用于在生产模式下更新内嵌到css、html文件内的 url , 如CDN地址
                        },
                    },
                    {
                        loader: 'image-webpack-loader',
                        options: {
                          mozjpeg: {
                            progressive: true,
                            quality: 65
                          },
                          // optipng.enabled: false will disable optipng
                          optipng: {
                            enabled: false,
                          },
                          pngquant: {
                            quality: '65-90',
                            speed: 4
                          },
                          gifsicle: {
                            interlaced: false,
                          },
                          // the webp option will enable WEBP
                          webp: {
                            quality: 75
                          }
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        // 打包模板
        new HtmlWebpackPlugin({
            inject: true,
            hash: true,
            cache: true,
            chunksSortMode: 'none',
            title: 'Webapck4-demo', // 能够由外面传入
            filename: 'index.html', // 默认index.html
            template: path.resolve(__dirname, 'index.html'),
            minify: {
                collapseWhitespace: true,
                removeComments: true,
                removeRedundantAttributes: true,
                removeScriptTypeAttributes: true,
                removeStyleLinkTypeAttributes: true
            }
        }),
        // 清理dist目录
        new CleanWebpackplugin(['dist'])
    ]
}

复制代码
// webpack.dev.config.js

const path = require('path')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.config.js')


module.exports = merge(baseWebpackConfig, {
    mode: 'development',
    output: {
        filename: 'bound.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            // 处理css/scss/sass
            {
                test: /\.(sc|sa|c)ss$/,
                use: [
                    {
                        loader: 'style-loader',
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true
                        }
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            sourceMap: true,
                            plugins: (loader) => [
                                require('autoprefixer')({
                                    browsers: [
                                        'last 10 Chrome versions',
                                        'last 5 Firefox versions',
                                        'Safari >= 6',
                                        'ie > 8'
                                    ]
                                })
                            ]
                        }
                    },
                    {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: true
                        }
                    }
                ]
            }
        ]
    }
})

复制代码
// webapck.prod.config.js

const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssertsPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.config.js')
const devMode = process.env.NODE_ENV !== 'production'

module.exports = merge(baseWebpackConfig, {
    mode: 'production',
    output: {
        filename: 'bound.[hash:5].js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            // 处理css/scss/sass
            {
                test: /\.(sc|sa|c)ss$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            plugins: (loader) => [
                                require('autoprefixer')({
                                    browsers: [
                                        'last 10 Chrome versions',
                                        'last 5 Firefox versions',
                                        'Safari >= 6',
                                        'ie > 8'
                                    ]
                                })
                            ]
                        }
                    },
                    {
                        loader: 'sass-loader'
                    }
                ]
            }
        ]
    },
    plugins: [
        // 提取CSS
        new MiniCssExtractPlugin({
            filename: devMode ? '[name].css' : '[name].[hash:5].css', // 设置输出的文件名
            chunkFilename: devMode ? '[id].css': '[id].[hash:5].css'
        })
    ],
    optimization: {
        minimizer: [
            // 压缩JS
            new TerserPlugin({
                cache: true,
                parallel: true,
                sourceMap: true,
                // 等等详细配置见官网
            }),
            // 压缩CSS
            new OptimizeCSSAssertsPlugin({})
        ]
    }
})

复制代码

webpack配置js使用sourceMap

在webpack4使用inline-source-map选项就能够启动错误的堆栈跟踪, 只用于开发环境node

devtool: 'inline-source-map'
复制代码

监控文件变化自动编译

简单的方法就是启动watch模式: 如react

"dev": "cross-env NODE_ENV=development npx webpack --progress --config webpack.dev.config.js --watch"
复制代码

webpack开启热更新和代理配置

很明显上面watch模式效率不高并且很不方便, 编译完还须要刷新页面, webpack能够开启热更新模式,大大加速开大效率。jquery

npm i -D webpack-dev-server
复制代码

修改script脚本。webpack

"dev": "cross-env NODE_ENV=development npx webpack-dev-server --progress --config webpack.dev.config.js"
复制代码

修改配置文件git

const webpack = require('webpack')


plugins: [
    new webpack.NamedModulesPlugin(), // 更方便查看patch的依赖
    new webpack.HotModuleReplacementPlugin() // HMR
],
devServer: {
    clientLogLevel: 'warning', // 输出日志级别
    hot: true, // 启用热更新
    contentBase: path.resolve(__dirname, 'dist'), // 告诉服务器从哪里提供内容
    publicPath: '/', // 此路径下的打包文件可在浏览器下访问
    compress: true, // 启用gzip压缩
    // publicPath: './',
    disableHostCheck: true,
    host: '0.0.0.0',
    port: 9999,
    open: true, // 自动打开浏览器
    overlay: { // 出现错误或者警告时候是否覆盖页面线上错误信息
        warnings: true,
        errors: true
    },
    quiet: true,
    proxy: { // 设置代理
        '/dev': {
            target: 'http://dev.xxxx.com.cn',
            changeOrigin: true,
            pathRewrite: {
                '^/dev': ''
            }
            /**
             * 若是你的配置是
             * pathRewrite: {
                '^/dev': '/order/api'
                }
                即本地请求 /dev/getOrder   =>  其实是  http://dev.xxxx.com.cn/order/api/getOrder
            */
        },
        '/test': {
            target: 'http://test.xxxx.com.cn',
            changeOrigin: true,
            pathRewrite: {
                '^/test': ''
            }
        },
        '/prod': {
            target: 'http://prod.xxxx.com.cn',
            changeOrigin: true,
            pathRewrite: {
                '^/prod': ''
            }
        }
    },
    watchOptions: { // 监控文件相关配置
        poll: true,
        ignored: /node_modules/, // 忽略监控的文件夹, 正则
        aggregateTimeout: 300, // 默认值, 当你连续改动时候, webpack能够设置构建延迟时间(防抖)
    }
}
复制代码

webpack启用babel转码

npm i -D  babel-loader @babel/core @babel/preset-env @babel/runtime @babel/plugin-transform-runtime
复制代码

修改配置文件es6

// 编译js
{
    test: /\.js$/,
    exclude: /(node_modules|bower_components)/,
    use: {
        loader: 'babel-loader?cacheDirectory', // 经过cacheDirectory选项开启支持缓存
        options: {
          presets: ['@babel/preset-env']
        }
    }
},
复制代码

增长.babelrc配置文件github

// 仅供参考

{
    "presets": [
      [
        "@babel/preset-env"
      ]
    ],
    "plugins": [
      [
        "@babel/plugin-transform-runtime",
        {
          "corejs": false,
          "helpers": true,
          "regenerator": true,
          "useESModules": false,
          "absoluteRuntime": "@babel/runtime"
        }
      ]
    ]
  }

复制代码

webpack配置eslint校验

npm i -D eslint eslint-loader

// 校验规则
npm i -D babel-eslint standard
复制代码

修改webpack配置文件

// 编译js
  {
    test: /\.js$/,
    exclude: /(node_modules|bower_components)/,
    use: [
      {
        loader: 'babel-loader?cacheDirectory', // 经过cacheDirectory选项开启支持缓存
        options: {
          presets: ['@babel/preset-env']
        }
      },
      {
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      }
    ]
  },
复制代码

增长.eslintrc.js文件

/*
 * ESLint的JSON文件是容许JavaScript注释的,但在gist里显示效果很差,因此我把.json文件后缀改成了.js
 */

/*
 * ESLint 配置文件优先级:
 * .eslintrc.js(输出一个配置对象)
 * .eslintrc.yaml
 * .eslintrc.yml
 * .eslintrc.json(ESLint的JSON文件容许JavaScript风格的注释)
 * .eslintrc(能够是JSON也能够是YAML)
 *  package.json(在package.json里建立一个eslintConfig属性,在那里定义你的配置)
 */

/*
 * 你能够经过在项目根目录建立一个.eslintignore文件告诉ESLint去忽略特定的文件和目录
 * .eslintignore文件是一个纯文本文件,其中的每一行都是一个glob模式代表哪些路径应该忽略检测
 */

module.exports = {
  //ESLint默认使用Espree做为其解析器
  //同时babel-eslint也是用得最多的解析器
  //parser解析代码时的参数
  "parser": "babel-eslint",
  "parserOptions": {
    //指定要使用的ECMAScript版本,默认值5
    "ecmaVersion": 6
    //设置为script(默认)或module(若是你的代码是ECMAScript模块)
    // "sourceType": "script",
    //这是个对象,表示你想使用的额外的语言特性,全部选项默认都是false
    // "ecmafeatures": {
    //   //容许在全局做用域下使用return语句
    //   "globalReturn": false,
    //   //启用全局strict模式(严格模式)
    //   "impliedStrict": false,
    //   //启用JSX
    //   "jsx": false,
    //   //启用对实验性的objectRest/spreadProperties的支持
    //   "experimentalObjectRestSpread": false
    // }
  },
  //指定环境,每一个环境都有本身预约义的全局变量,能够同时指定多个环境,不矛盾
  "env": {
    //效果同配置项ecmaVersion同样
    "es6": true,
    "browser": true,
    "node": true,
    "commonjs": false,
    "mocha": true,
    "jquery": true,
    //若是你想使用来自某个插件的环境时,确保在plugins数组里指定插件名
    //格式为:插件名/环境名称(插件名不带前缀)
    // "react/node": true
  },

  //指定环境为咱们提供了预置的全局变量
  //对于那些咱们自定义的全局变量,能够用globals指定
  //设置每一个变量等于true容许变量被重写,或false不容许被重写
  "globals": {
    "globalVar1": true,
    "globalVar2": false
  },

  //ESLint支持使用第三方插件
  //在使用插件以前,你必须使用npm安装它
  //全局安装的ESLint只能使用全局安装的插件
  //本地安装的ESLint不只可使用本地安装的插件还可使用全局安装的插件
  //plugin与extend的区别:extend提供的是eslint现有规则的一系列预设
  //而plugin则提供了除预设以外的自定义规则,当你在eslint的规则里找不到合适的的时候
  //就能够借用插件来实现了
  "plugins": [
    // "eslint-plugin-airbnb",
    //插件名称能够省略eslint-plugin-前缀
    // "react"
  ],

  //具体规则配置
  //off或0--关闭规则
  //warn或1--开启规则,警告级别(不会致使程序退出)
  //error或2--开启规则,错误级别(当被触发的时候,程序会退出)
  "rules": {
    "eqeqeq": "warn",
    //你也可使用对应的数字定义规则严重程度
    "curly": 2,
    //若是某条规则有额外的选项,你可使用数组字面量指定它们
    //选项能够是字符串,也能够是对象
    "quotes": ["error", "double"],
    "one-var": ["error", {
      "var": "always",
      "let": "never",
      "const": "never"
    }],
    //配置插件提供的自定义规则的时候,格式为:不带前缀插件名/规则ID
    // "react/curly": "error"
  },

  //ESLint支持在配置文件添加共享设置
  //你能够添加settings对象到配置文件,它将提供给每个将被执行的规则
  //若是你想添加的自定义规则并且使它们能够访问到相同的信息,这将会颇有用,而且很容易配置
  "settings": {
    "sharedData": "Hello"
  },

  //Eslint检测配置文件步骤:
  //1.在要检测的文件同一目录里寻找.eslintrc.*和package.json
  //2.紧接着在父级目录里寻找,一直到文件系统的根目录
  //3.若是在前两步发现有root:true的配置,中止在父级目录中寻找.eslintrc
  //4.若是以上步骤都没有找到,则回退到用户主目录~/.eslintrc中自定义的默认配置
  "root": true,

  //extends属性值能够是一个字符串或字符串数组
  //数组中每一个配置项继承它前面的配置
  //可选的配置项以下
  //1.字符串eslint:recommended,该配置项启用一系列核心规则,这些规则报告一些常见问题,即在(规则页面)中打勾的规则
  //2.一个能够输出配置对象的可共享配置包,如eslint-config-standard
    //可共享配置包是一个导出配置对象的简单的npm包,包名称以eslint-config-开头,使用前要安装
    //extends属性值能够省略包名的前缀eslint-config-
  //3.一个输出配置规则的插件包,如eslint-plugin-react
    //一些插件也能够输出一个或多个命名的配置
    //extends属性值为,plugin:包名/配置名称
  //4.一个指向配置文件的相对路径或绝对路径
  //5.字符串eslint:all,启用当前安装的ESLint中全部的核心规则
    //该配置不推荐在产品中使用,由于它随着ESLint版本进行更改。使用的话,请本身承担风险
  "extends": [
    "standard"
  ]
}

复制代码

增长.eslintignore文件

/dist/
/node_modules/
/*.js

复制代码

webpack配置resolve模块解析

配置alias方便路径的检索效率。 配置文件默认扩展名,import时候自动匹配。

resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      '@': path.resolve(__dirname, 'src/')
    }
},
复制代码

webpack配置外部扩展externals

externals选项能够提供排除打包某些依赖到boundle的方法.例如咱们想经过CDN引入jQuery而不是把jQuery打包到boudle。

这里以jQuery为例:

修改配置文件

// 配置外部依赖不会打包到boudle
externals: {
    jquery: 'jQuery'
},
复制代码

在模板文件引入CDN

// index.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="google" value="notranslate">
    <meta http-equiv="Cache-Control" content="no-siteapp">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
    <meta name="format-detection" content="telephone=no">
    <script
    src="https://code.jquery.com/jquery-3.3.1.js"
    integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60="
    crossorigin="anonymous"></script>
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>

<body>
    <div id="app"></div>
    <div class="logo"></div>
    <div class="man"></div>
</body>

</html>

复制代码

在index.js使用jquery

import $ from 'jquery'

// 测试外部扩展配置
$(function () {
  $('.logo').click(function () {
    console.log('click')
  })
})
复制代码

webpack打包报表分析以及优化

npm i -D webpack-bundle-analyzer
复制代码

我单独配置了一个命令进行打包分析:

"build:report": "cross-env NODE_ENV=production npx webpack --progress --config webpack.analysis.config.js"
复制代码

固然你其实彻底能够经过传参数配置集成到prod那个配置文件如:

"build:report": "npm run build --report"
复制代码

而后在prod配置环境中若是参数判断:

// 伪代码
if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
复制代码

这里给出个人方案

const merge = require('webpack-merge')
const prodWebpackConfig = require('./webpack.prod.config.js')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = merge(prodWebpackConfig, {
  plugins: [
    new BundleAnalyzerPlugin() // 打包分析
  ]
})

复制代码

仓库地址

Rp地址

有用的话点个star, 有问题欢迎提issues。

相关文章
相关标签/搜索