Webpack

简介: 本质上,*webpack* 是一个现代 JavaScript 应用程序的*静态模块打包器(module bundler)*。当 webpack 处理应用程序时,它会递归地构建一个*依赖关系图(dependency graph)*,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 *bundle*。

Webpack

1、Webpack简介

1.1、webpack是什么

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

image-20220729174455413.png

官网地址: https://webpack.js.org/
中文网址: https://www.webpackjs.com/
Github: https://github.com/webpack/webpack

1.2、安装

首先使用 npm init 初始化项目,然后安装 webpack 以及 webpack-cli 。

// 全局安装    -global简写:-g
npm i webpack webpack-cli -g

// 本地安装(推荐) --save-dev简写:-D
npm i webpack webpack-cli --D

卸载命令

# 卸载全局的webpack
npm uninstall webpack webpack-cli --global

查看webpack打包的详细日志

webpack --stats detailed

1.3、配置文件

在文件根目录下新建 webpack.config.js 配置文件

// webpack.config.js

module.exports = {
  entry: './assets/js/main.js',
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [

    ]
  },
  plugins: [

  ],
  mode: 'development'
}

1.4、打包命令

使用本地环境进行打包输出

# 本地打包
npx webpack
# 如果是全局的打包
webpack

image-20220730152550115.png

2、webpack五个核心概念

01、入口(entry)

入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

可以通过在 webpack 配置中配置 entry 属性,来指定一个入口起点(或多个入口起点)。默认值为 ./src

接下来我们看一个 entry 配置的最简单例子:

webpack.config.js

module.exports = {
  entry: './path/to/my/entry/file.js'
};

02、输出(output)

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。你可以通过在配置中指定一个 output 字段,来配置这些处理过程:

webpack.config.js

module.exports = {
  ...
  output: {
    // 输出文件名称
    filename: 'app.js',
    // 输出文件路径
    path: path.resolve(__dirname, 'dist'),
    // 删除不需要的旧文件
    clean: true
  }
}

03、loader

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

在更高层面,在 webpack 的配置中 loader 有两个目标:

  1. test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
  2. use 属性,表示进行转换时,应该使用哪个 loader。

webpack.config.js

const path = require('path');

const config = {
  output: {
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};

module.exports = config;

以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:testuse。这告诉 webpack 编译器(compiler) 如下信息:

“嘿,webpack 编译器,当你碰到「在 require()/ import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 使用 raw-loader 转换一下。”

04、插件(Plugins)

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件

const config = {
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

module.exports = config;

05、模式(mode)

通过选择 developmentproduction 之中的一个,来设置 mode 参数,你可以启用相应模式下的 webpack 内置的优化

module.exports = {
  mode: 'production'
};

或者从 CLI 参数中传递:

webpack --mode=production

webpack相对应模式的配置!

选项 描述 特点
development 会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPluginNamedModulesPlugin 能让代码本地调试运行的环境
production 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPluginUglifyJsPlugin. 能让代码优化上线运行的环境
none

3、devServer

在开发环境中,用于自动编译并自动刷新页面,方便开发过程中的调试。注:该功能只会在内存中编译打包,不会有任何文件输出,如需更新到生产环境中,还需重新打包代码。

下载

npm i webpack-dev-server -D

配置

在 webpack.config.js 文件中进行配置

const path = require('path')

module.exports = {
  ...
  devServer: {
    // 环境目录
    static: path.resolve(__dirname,'./dist'),
    // 设置 gzip 压缩,提高传输效率
    compress: true,
    // 设置服务器主机
    host: '0.0.0.0',
    // 设置端口号
    port: 3000,
    // 添加响应头,也可以写成函数
    headers:{
      'X-Access-Token': '125hythujgdtr'
    },
    // 这个配置会让你在重新启动服务后,自动更新代码!
    devMiddleware: {
      writeToDisk: true,  // 写入到硬盘里!
    },
    // 设置路由
    historyApiFallback: true,
    // 自动打开页面
    open: true,
    // 更改后自动更新
    watchFiles: {
      paths: [
        './*'
      ],
      options: {
        usePolling: false
      }
    },
    // 启用热加载功能
    liveReload: true,
    // 启用热模块功能
    hot: true
}

开启代理

我们打包的 js bundle里面时会含有一些对特定接口的网络请求(ajax/fetch),要注意,此时客户端地址是在 http://localhost:3000/下,假设我们接口来自http://localhost:5000/,那么毫无疑问,此时控制台里会报错并提示你跨域。如何解决这个问题?在开发环境中,我们可以使用devServer自带的proxy功能:

module.exports = {
  ...
  devServer: [
    proxy: {
        '/api': {
            target: 'http://localhost:5000',
            // 如果不希望传递api,重写路径
            pathRewrite:{'^/api':''},
            // 如果想让我们本地的http服务变成https服务,设置为true,但是这个是不安全的,需要证书
            // https: true,
        https: {
          cacert: './server.pem',
          pfx: './server.pfx',
          key: './server.key',
          cert: './servercrt',
          passphare: 'webpack-dev-server',
          requestCert: true,
        }
        }
    }
  ],
};
  • http2

配置http2,与https不同的是http2自带https证书,也可以通过https配置自己的证书!

module.exports = {
  ...
  devServer: [
    http2: true
  ],
};

historyApiFallback

如果我的应用是个SPA(单页面应用),当路由到 /index 时(可以直接在地址栏输入),会发现此时页面刷新后,控制台会报错!

GET http://localhost:3000/home 404 (Not Found)

此时打开network,刷新并查看,就会发现问题所在– – 浏览器把这个路由当成了静态的资源地址去请求,然后我们并没有打包出/home这样的资源,所以访问无疑是404的,如何解决它?这种时候,我们可以通过配置来提供页面代替任何404的静态资源响应:

module.exports = {
  // ...
  devServer: [
    historyApiFallback: true
  ],
};

此时重启服务器刷新后发现请求变成了index.html。当然,在很多业务场景下,我们需要根据不同的访问路径定制替代的页面,这种情况下,我们可以通过rewrites这个配置项。类似这样:

module.exports = {
  // ...
  devServer: [
    historyApiFallback: {
        rewrites: [
            { from: /^\/$/, to: '/view/lading.html'},
            { from: /^\/subpage/, to: '/views/subpage.html'},
            {...},
        ]
    }
  ],
};

启动

npx webpack-dev-server

4、资源模块 Asset Modules

官方说明:https://webpack.docschina.org/guides/asset-modules

该方法需将资源在 JS 中通过 import 进行导入或css中进行导入

// js 文件导入
import 命名 from '资源路径'

// css 文件引用
.box {
  background-image: url('资源路径');
}

资源模块类型

  • asset/resource:发送一个单独的文件并导出 URL
  • asset/inline:导出一个资源的 Data URI ( 64位图 )
  • asset/source:导出资源的源代码
  • asset:在导出一个资源的 Data URI 和发送一个单独的文件之间自动进行选择

resource

module.exports = {
  ...
  module: {
    output: [
      // 生成资源名称 contenthash自动生成文件名, ext自己定义的拓展类型
      assetModuleFilename: 'images/[contenthash].[ext]'
    ],
    rules: [
      {
        // 监听资源文件
        test: /\.png$/,
        // 设置资源类型
        type: 'asset/resource',
        generator: {
          // 生成资源名称
          filename: 'assets/images/[name][ext]'
        }
      }
    ]
  }
}
  • 资源名称可以使用 contenthash 将资源名称生成为 hash 值命名

inline

module.exports = {
  ...
  module: {
        output: [
      // 生成资源名称 contenthash自动生成文件名, ext自己定义的拓展类型
      assetModuleFilename: 'images/[contenthash].[ext]'
    ],
    rules: [
      {
        // 监听资源文件
        test: /\.svg$/,
        // 设置资源类型
        type: 'asset/inline'
      }
    ]
  }
}

source

module.exports = {
  ...
  module: {
        output: [
      // 生成资源名称 contenthash自动生成文件名, ext自己定义的拓展类型
      assetModuleFilename: 'images/[contenthash].[ext]'
    ],
    rules: [
      {
        // 监听资源文件
        test: /\.txt$/,
        // 设置资源类型
        type: 'asset/source'
      }
    ]
  }
}

asset

module.exports = {
  ...
  module: {
        output: [
      // 生成资源名称 contenthash自动生成文件名, ext自己定义的拓展类型
      assetModuleFilename: 'images/[contenthash].[ext]'
    ],
    rules: [
      {
        // 监听资源文件
        test: /\.jpg$/,
        // 设置资源类型
        type: 'asset',
        // 小于设置的大小则转为 64 位图,否则转 URL
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024 // 4kb
          }
        },
        generator: {
          // 生成资源名称
          filename: 'assets/images/[contenthash].[ext]'
        }
      }
    ]
  }
}

5、资源处理

5.1、HTML 资源

打包 HTML

1、下载 html-webpack-plugin 插件

npm i html-webpack-plugin - D

2、在 webpack.config.js 文件中引入插件并调用

// 引用插件
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  ...
    plugins: [
        new HtmlWebpackPlugin({
            // 指定 HTML 模版文件
            template: './index.html',
            filename: 'app.html',
            // 指定 Script 标签位置
            inject: 'body',
        }),
    ],
}
  • Webpack 会在输出目录中新创建一个 HTML 文件,在原始的 HTML 文件中无需引入 JS 文件,通过 Webpack 编译后的 HTML 文件会自动引入。

官方说明:https://webpack.docschina.org/plugins/html-webpack-plugin/

配置选项:https://github.com/jantimon/html-webpack-plugin#options

5.2、样式资源

打包 CSS 资源

下载样式处理解析器 css-loader 与 style-loader

npm i css-loader style-loader -D

在配置文件中添加解析器

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 在 head 中创建 style 标签
          'style-loader',
          // 将 css 文件整合到 js 文件中
          'css-loader'
        ]
      }
    ]
  }
}

在 JS 文件中导入 CSS 文件

import '../css/main.css'

打包 SCSS / LESS 资源

下载样式处理解析器

npm i sass-loader sass -D

在配置文件中添加解析器

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(css|sass)$/,
        use: [
          // 在 head 中创建 style 标签
          'style-loader',
          // 将 css 文件整合到 js 文件中
          'css-loader',
          // 编译 sass 文件为 css 文件
          'sass-loader'
        ]
      }
    ]
  }
}

这里实例为sass,如果打包less就将对应配置改为less!

在 JS 文件中导入 SCSS 文件

import '../css/main.scss'

抽离 CSS 代码为独立文件

下载插件 mini-css-extract-plugin

npm i mini-css-extract-plugin -D

引用插件

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(css|sass)$/,
        use: [
          // 抽离 css 为独立文件
          MiniCssExtractPlugin.loader,
          // 将 css 文件整合到 js 文件中
          'css-loader',
          // 编译 sass 文件为 css 文件
          'sass-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      // 对输出结果重命名
      filename: 'assets/css/[name].css'
    })
  ]
}
  • 如果是生成模式,将自动压缩css文件,无需额外配置。

官方文档:https://webpack.docschina.org/plugins/mini-css-extract-plugin

CSS 代码压缩(生产模式)

安装插件 css-minimizer-webpack-plugin

npm i css-minimizer-webpack-plugin -D

在配置文件中进行配置

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")

module.exports = {
  ...
  optimization: {
    minimizer: [
      // 使用插件优化 css 代码
      new CssMinimizerPlugin()
    ],
  },
  // 模式
  mode: 'production'
}
  • 压缩 CSS 代码,仅在生产模式下有效

官方文档:https://webpack.docschina.org/plugins/css-minimizer-webpack-plugin/

CSS 兼容性处理

下载 postcss-loader, postcss, postcss-preset-env 模块

npm i postcss-loader postcss postcss-nested postcss-preset-env -D
# 安装插件,自动管理浏览器前缀解析CSS文件并且添加浏览器前缀到CSS内容里
npm i autoprefixer -D

在根目录下创建 postcss.config.js 文件并进行配置

module.exports = {
  ...
  plugins: [
    require('autoprefixer'),    // 解析css文件添加到浏览器中
    require('postcss-nested'), // 可以实现嵌套css
    [
      'postcss-preset-env',
      {
        // 其他选项
      },
    ],
  ],
};

引用模块

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(css|sass)$/,
        use: [
          // 抽离 css 为独立文件
          MiniCssExtractPlugin.loader,
          // 将 css 文件整合到 js 文件中
          {
            loader: 'css-loader',
            options: {
              modules: true, // 如果采用这种方式引入,就需要import模块化进行处理!
            }
          },
          // css 兼容处理
          'postcss-loader',
          // 编译 sass 文件为 css 文件
          'sass-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      // 对输出结果重命名
      filename: 'assets/css/[name].css'
    })
  ]
}
import style from './color.css'

const div = document.createElement('div')
div.textContent = 'hello postcss'
div.classList.add(style.xxx)  // 这里xxx就是你定义的样式!
document.body.appendChild(div)

postcss-preset-env 帮助 postcss 找到 package.json 中 browserslist 里的配置,通过配置加载指定的 css 兼容性

// 在 package.json 中添加浏览器列表
{
  ...
  "browserslist": {
    "development": [
      "> 1%",  // 这个插件要在全球浏览器使用率大于1%
      "last 1 chrome version",
      "last 1 firfoxe version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  }
}

CSS模块模式

*.global.css 普通模式

*.css css module模式

这里统一用global关键字进行识别,用正则表达式匹配文件:

// css module
module.exports = {
    module: {
        rules: [
            {
                test: new RegExp(`^(?!.*\\.global).*\\.css`),
                use: [
                    {
                        loader: 'style-loader',
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            modules: true,
                            localIdentName: '[hash:base64:6]',
                        },
                    },
                    {
                        loader: 'postcss-loader',
                    },
                ],
                exclude: [path.resolve(__dirname, '...', 'node_modules')],
            },
        ],
    },
};
// 普通模式
// css module
module.exports = {
    module: {
        rules: [
            {
                test: new RegExp(`^(.*\\.global).*\\.css`),
                use: [
                    {
                        loader: 'style-loader',
                    },
                    {
                        loader: 'css-loader',
                    },
                    {
                        loader: 'postcss-loader',
                    },
                ],
                exclude: [path.resolve(__dirname, '..', 'node_modules')],
            },
        ],
    },
};

图片资源 *

下载图片处理解析器

npm i url-loader file-loader html-loader -D

.…..

字体资源

通过 CSS 引入字体资源

@font-face {
  font-family: 'PujiSansExpandedHeavy';
  src: url('../fonts/PujiSans-ExpandedHeavy.eot'); /* IE9 Compat Modes */
  src: url('../fonts/PujiSans-ExpandedHeavy.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
    url('../fonts/PujiSans-ExpandedHeavy.woff2') format('woff2'), /* Modern Browsers */
    url('../fonts/PujiSans-ExpandedHeavy.woff') format('woff'), /* Modern Browsers */
    url('../fonts/PujiSans-ExpandedHeavy.ttf') format('truetype'); /* Safari, Android, iOS */
  font-style: normal;
  font-weight: normal;
  text-rendering: optimizeLegibility;
}

在 webpack.config.js 文件中进行配置

module.exports = {
  ...
  module: {
    rules: [
      {
        // 监听资源文件
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        // 设置资源类型
        type: 'asset/resource',
        generator: {
          // 生成资源名称
          filename: 'assets/fonts/[name][ext]'
        },
      }
    ]
  }
}

数据资源

如需导入 CSV, TSV, XML 等数据格式文件,需使用相关的数据 loader 进行加载

下载csv-loader、xml-loader

npm i xml-loader csv-loader -D

在 webpack.config.js 文件中进行配置

module.exports = {
  ...
  module: {
    rules: [
     {
        test: /\.(csv|tsv)/,
           use: 'csv-loader',
        },
     {
           test: /\.xml$/,
         use: 'xml-loader',
     },
    ]
  }
}

结论:

  • data.xml会转换成一个js对象
  • data.csv会转换成一个数组!

自定义 JSON 资源

安装依赖

npm install toml yaml json5 -D

在webpack.config.js中进行配置

const toml = require('toml');
const yaml = require('yaml');
const json5 = require('json5');

module.exports = {
        ...
    module: {
        rules: [
            {
                test: /\.toml$/,
                type: 'json',
                parser: {
                    parser: toml.parser,
                },
            },
            {
                test: /\.yaml$/,
                type: 'json',
                parser: {
                    parser: yaml.parser,
                },
            },
            {
                test: /\.json5$/,
                type: 'json',
                parser: {
                    parser: json5.parser,
                },
            },
        ],
    },
};

JS 资源

ESLint

使用 eslint 扫描我们所写的代码是否符合规范,严格意义上来说,eslint 配置跟 webpack 无关,但在工程化开发环境中,他往往是不可或缺的。

安装

yarn add eslint -D

创建配置文件,根据提示选择需要的类型。配置完成后,将在 node_modules 文件夹中生成一个 .eslintrc(或者.eslintrc.json, .js等) 文件,将文件复制到根目录下。

npx eslint --init

我们可以看到控制台里的展示:

image-20220731144828236.png

并生成一个配置文件(.eslint.json),这样我们就完成了eslint的基本配置规则。eslint配置文件里的配置选项含义如下:

{
// 环境定义了预定义的全局变量。
"env": {
        //环境定义了预定义的全局变量。更多在官网查看
        "browser":true,
        "node":true,
        "commonjs":true,
        "amd":true,
        "es6":true,
        "mocha":true
 },
// JavaScript 语言选项
"parserOptions": {
// ECMAScript 版本
"ecmaVersion":6,
"sourceType":"script",//module
        // 想使用的额外的语言特性:
        "ecmaFeatures": {
            // 允许在全局作用域下使用 return 语句
            "globalReturn":true,
            // impliedStric
            "impliedStrict":true,
            // 启用 JSX
            "jsx":true
         }
 },
/**
 * "off" 或 0 - 关闭规则
 * "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出),
 * "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
 */
"rules": {
    

// 可能的错误 //


// 禁止条件表达式中出现赋值操作符
"no-cond-assign":2,
// 禁用 console
"no-console":0,
// 禁止在条件中使用常量表达式
// if (false) {
// doSomethingUnfinished();
// } //cuowu
"no-constant-condition":2,
// 禁止在正则表达式中使用控制字符 :new RegExp("\x1f")
"no-control-regex":2,
// 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,
// always-multiline:多行模式必须带逗号,单行模式不能带逗号
"comma-dangle": [1,"always-multiline"],
// 禁用 debugger
"no-debugger":2,
// 禁止 function 定义中出现重名参数
"no-dupe-args":2,
// 禁止对象字面量中出现重复的 key
"no-dupe-keys":2,
// 禁止重复的 case 标签
"no-duplicate-case":2,
// 禁止空语句块
"no-empty":2,
// 禁止在正则表达式中使用空字符集 (/^abc[]/)
"no-empty-character-class":2,
// 禁止对 catch 子句的参数重新赋值
"no-ex-assign":2,
// 禁止不必要的布尔转换
"no-extra-boolean-cast":2,
// 禁止不必要的括号 //(a * b) + c;//报错
"no-extra-parens":0,
// 禁止不必要的分号
"no-extra-semi":2,
// 禁止对 function 声明重新赋值
"no-func-assign":2,
// 禁止在嵌套的块中出现 function 或 var 声明
"no-inner-declarations": [2,"functions"],
// 禁止 RegExp 构造函数中无效的正则表达式字符串
"no-invalid-regexp":2,
// 禁止在字符串和注释之外不规则的空白
"no-irregular-whitespace":2,
// 禁止在 in 表达式中出现否定的左操作数
"no-negated-in-lhs":2,
// 禁止把全局对象 (Math 和 JSON) 作为函数调用 错误:var math = Math();
"no-obj-calls":2,
// 禁止直接使用 Object.prototypes 的内置属性
"no-prototype-builtins":0,
// 禁止正则表达式字面量中出现多个空格
"no-regex-spaces":2,
// 禁用稀疏数组
"no-sparse-arrays":2,
// 禁止出现令人困惑的多行表达式
"no-unexpected-multiline":2,
// 禁止在return、throw、continue 和 break语句之后出现不可达代码
/*
 function foo() {
 return true;
 console.log("done");
 }//错误
 */
"no-unreachable":2,
// 要求使用 isNaN() 检查 NaN
"use-isnan":2,
// 强制使用有效的 JSDoc 注释
"valid-jsdoc":1,
// 强制 typeof 表达式与有效的字符串进行比较
// typeof foo === "undefimed" 错误
"valid-typeof":2,
    
//
// 最佳实践 //
//
    
// 定义对象的set存取器属性时,强制定义get
"accessor-pairs":2,
// 强制数组方法的回调函数中有 return 语句
"array-callback-return":0,
// 强制把变量的使用限制在其定义的作用域范围内
"block-scoped-var":0,
// 限制圈复杂度,也就是类似if else能连续接多少个
"complexity": [2,9],
// 要求 return 语句要么总是指定返回的值,要么不指定
"consistent-return":0,
// 强制所有控制语句使用一致的括号风格
"curly": [2,"all"],
// switch 语句强制 default 分支,也可添加 // no default 注释取消此次警告
"default-case":2,
// 强制object.key 中 . 的位置,参数:
// property,'.'号应与属性在同一行
// object, '.' 号应与对象名在同一行
"dot-location": [2,"property"],
// 强制使用.号取属性
// 参数: allowKeywords:true 使用保留字做属性名时,只能使用.方式取属性
// false 使用保留字做属性名时, 只能使用[]方式取属性 e.g [2, {"allowKeywords": false}]
// allowPattern: 当属性名匹配提供的正则表达式时,允许使用[]方式取值,否则只能用.号取值 e.g [2, {"allowPattern": "^[a-z]+(_[a-z]+)+$"}]
"dot-notation": [2, {"allowKeywords":false}],
// 使用 === 替代 == allow-null允许null和undefined==
"eqeqeq": [2,"allow-null"],
// 要求 for-in 循环中有一个 if 语句
"guard-for-in":2,
// 禁用 alert、confirm 和 prompt
"no-alert":0,
// 禁用 arguments.caller 或 arguments.callee
"no-caller":2,
// 不允许在 case 子句中使用词法声明
"no-case-declarations":2,
// 禁止除法操作符显式的出现在正则表达式开始的位置
"no-div-regex":2,
// 禁止 if 语句中有 return 之后有 else
"no-else-return":0,
// 禁止出现空函数.如果一个函数包含了一条注释,它将不会被认为有问题。
"no-empty-function":2,
// 禁止使用空解构模式no-empty-pattern
"no-empty-pattern":2,
// 禁止在没有类型检查操作符的情况下与 null 进行比较
"no-eq-null":1,
// 禁用 eval()
"no-eval":2,
// 禁止扩展原生类型
"no-extend-native":2,
// 禁止不必要的 .bind() 调用
"no-extra-bind":2,
// 禁用不必要的标签
"no-extra-label:":0,
// 禁止 case 语句落空
"no-fallthrough":2,
// 禁止数字字面量中使用前导和末尾小数点
"no-floating-decimal":2,
// 禁止使用短符号进行类型转换(!!fOO)
"no-implicit-coercion":0,
// 禁止在全局范围内使用 var 和命名的 function 声明
"no-implicit-globals":1,
// 禁止使用类似 eval() 的方法
"no-implied-eval":2,
// 禁止 this 关键字出现在类和类对象之外
"no-invalid-this":0,
// 禁用 __iterator__ 属性
"no-iterator":2,
// 禁用标签语句
"no-labels":2,
// 禁用不必要的嵌套块
"no-lone-blocks":2,
// 禁止在循环中出现 function 声明和表达式
"no-loop-func":1,
// 禁用魔术数字(3.14什么的用常量代替)
"no-magic-numbers":[1,{"ignore": [0,-1,1] }],
// 禁止使用多个空格
"no-multi-spaces":2,
// 禁止使用多行字符串,在 JavaScript 中,可以在新行之前使用斜线创建多行字符串
"no-multi-str":2,
// 禁止对原生对象赋值
"no-native-reassign":2,
// 禁止在非赋值或条件语句中使用 new 操作符
"no-new":2,
// 禁止对 Function 对象使用 new 操作符
"no-new-func":0,
// 禁止对 String,Number 和 Boolean 使用 new 操作符
"no-new-wrappers":2,
// 禁用八进制字面量
"no-octal":2,
// 禁止在字符串中使用八进制转义序列
"no-octal-escape":2,
// 不允许对 function 的参数进行重新赋值
"no-param-reassign":0,
// 禁用 __proto__ 属性
"no-proto":2,
// 禁止使用 var 多次声明同一变量
"no-redeclare":2,
// 禁用指定的通过 require 加载的模块
"no-return-assign":0,
// 禁止使用 javascript: url
"no-script-url":0,
// 禁止自我赋值
"no-self-assign":2,
// 禁止自身比较
"no-self-compare":2,
// 禁用逗号操作符
"no-sequences":2,
// 禁止抛出非异常字面量
"no-throw-literal":2,
// 禁用一成不变的循环条件
"no-unmodified-loop-condition":2,
// 禁止出现未使用过的表达式
"no-unused-expressions":0,
// 禁用未使用过的标签
"no-unused-labels":2,
// 禁止不必要的 .call() 和 .apply()
"no-useless-call":2,
// 禁止不必要的字符串字面量或模板字面量的连接
"no-useless-concat":2,
// 禁用不必要的转义字符
"no-useless-escape":0,
// 禁用 void 操作符
"no-void":0,
// 禁止在注释中使用特定的警告术语
"no-warning-comments":0,
// 禁用 with 语句
"no-with":2,
// 强制在parseInt()使用基数参数
"radix":2,
// 要求所有的 var 声明出现在它们所在的作用域顶部
"vars-on-top":0,
// 要求 IIFE 使用括号括起来
"wrap-iife": [2,"any"],
// 要求或禁止 “Yoda” 条件
"yoda": [2,"never"],
// 要求或禁止使用严格模式指令
"strict":0,
    
//
// 变量声明 //
//

// 要求或禁止 var 声明中的初始化(初值)
"init-declarations":0,
// 不允许 catch 子句的参数与外层作用域中的变量同名
"no-catch-shadow":0,
// 禁止删除变量
"no-delete-var":2,
// 不允许标签与变量同名
"no-label-var":2,
// 禁用特定的全局变量
"no-restricted-globals":0,
// 禁止 var 声明 与外层作用域的变量同名
"no-shadow":0,
// 禁止覆盖受限制的标识符
"no-shadow-restricted-names":2,
// 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
"no-undef":2,
// 禁止将变量初始化为 undefined
"no-undef-init":2,
// 禁止将 undefined 作为标识符
"no-undefined":0,
// 禁止出现未使用过的变量
"no-unused-vars": [2, {"vars":"all","args":"none"}],
// 不允许在变量定义之前使用它们
"no-use-before-define":0,

//
// Node.js and CommonJS //
//

// require return statements after callbacks
"callback-return":0,
// 要求 require() 出现在顶层模块作用域中
"global-require":1,
// 要求回调函数中有容错处理
"handle-callback-err": [2,"^(err|error)$"],
// 禁止混合常规 var 声明和 require 调用
"no-mixed-requires":0,
// 禁止调用 require 时使用 new 操作符
"no-new-require":2,
// 禁止对 __dirname 和 __filename进行字符串连接
"no-path-concat":0,
// 禁用 process.env
"no-process-env":0,
// 禁用 process.exit()
"no-process-exit":0,
// 禁用同步方法
"no-sync":0,

//
// 风格指南 //
//

// 指定数组的元素之间要以空格隔开(, 后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格
"array-bracket-spacing": [2,"never"],
// 禁止或强制在单行代码块中使用空格(禁用)
"block-spacing":[1,"never"],
//强制使用一致的缩进 第二个参数为 "tab" 时,会使用tab,
// if while function 后面的{必须与if在同一行,java风格。
"brace-style": [2,"1tbs", {"allowSingleLine":true}],
// 双峰驼命名格式
"camelcase":2,
// 控制逗号前后的空格
"comma-spacing": [2, {"before":false,"after":true}],
// 控制逗号在行尾出现还是在行首出现 (默认行尾)
// http://eslint.org/docs/rules/comma-style
"comma-style": [2,"last"],
//"SwitchCase" (默认:0) 强制 switch 语句中的 case 子句的缩进水平
// 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
"computed-property-spacing": [2,"never"],
// 用于指统一在回调函数中指向this的变量名,箭头函数中的this已经可以指向外层调用者,应该没卵用了
// e.g [0,"that"] 指定只能 var that = this. that不能指向其他任何值,this也不能赋值给that以外的其他值
"consistent-this": [1,"that"],
// 强制使用命名的 function 表达式
"func-names":0,
// 文件末尾强制换行
"eol-last":2,
"indent": [2,4, {"SwitchCase":1}],
// 强制在对象字面量的属性中键和值之间使用一致的间距
"key-spacing": [2, {"beforeColon":false,"afterColon":true}],
// 强制使用一致的换行风格
"linebreak-style": [1,"unix"],
// 要求在注释周围有空行 ( 要求在块级注释之前有一空行)
"lines-around-comment": [1,{"beforeBlockComment":true}],
// 强制一致地使用函数声明或函数表达式,方法定义风格,参数:
// declaration: 强制使用方法声明的方式,function f(){} e.g [2, "declaration"]
// expression:强制使用方法表达式的方式,var f = function() {} e.g [2, "expression"]
// allowArrowFunctions: declaration风格中允许箭头函数。 e.g [2, "declaration", { "allowArrowFunctions": true }]
"func-style":0,
// 强制回调函数最大嵌套深度 5层
"max-nested-callbacks": [1,5],
// 禁止使用指定的标识符
"id-blacklist":0,
// 强制标识符的最新和最大长度
"id-length":0,
// 要求标识符匹配一个指定的正则表达式
"id-match":0,
// 强制在 JSX 属性中一致地使用双引号或单引号
"jsx-quotes":0,
// 强制在关键字前后使用一致的空格 (前后腰需要)
"keyword-spacing":2,
// 强制一行的最大长度
"max-len":[1,200],
// 强制最大行数
"max-lines":0,
// 强制 function 定义中最多允许的参数数量
"max-params":[1,7],
// 强制 function 块最多允许的的语句数量
"max-statements":[1,200],
// 强制每一行中所允许的最大语句数量
"max-statements-per-line":0,
// 要求构造函数首字母大写 (要求调用 new 操作符时有首字母大小的函数,允许调用首字母大写的函数时没有 new 操作符。)
"new-cap": [2, {"newIsCap":true,"capIsNew":false}],
// 要求调用无参构造函数时有圆括号
"new-parens":2,
// 要求或禁止 var 声明语句后有一行空行
"newline-after-var":0,
// 禁止使用 Array 构造函数
"no-array-constructor":2,
// 禁用按位运算符
"no-bitwise":0,
// 要求 return 语句之前有一空行
"newline-before-return":0,
// 要求方法链中每个调用都有一个换行符
"newline-per-chained-call":1,
// 禁用 continue 语句
"no-continue":0,
// 禁止在代码行后使用内联注释
"no-inline-comments":0,
// 禁止 if 作为唯一的语句出现在 else 语句中
"no-lonely-if":0,
// 禁止混合使用不同的操作符
"no-mixed-operators":0,
// 不允许空格和 tab 混合缩进
"no-mixed-spaces-and-tabs":2,
// 不允许多个空行
"no-multiple-empty-lines": [2, {"max":2}],
// 不允许否定的表达式
"no-negated-condition":0,
// 不允许使用嵌套的三元表达式
"no-nested-ternary":0,
// 禁止使用 Object 的构造函数
"no-new-object":2,
// 禁止使用一元操作符 ++ 和 --
"no-plusplus":0,
// 禁止使用特定的语法
"no-restricted-syntax":0,
// 禁止 function 标识符和括号之间出现空格
"no-spaced-func":2,
// 不允许使用三元操作符
"no-ternary":0,
// 禁用行尾空格
"no-trailing-spaces":2,
// 禁止标识符中有悬空下划线_bar
"no-underscore-dangle":0,
// 禁止可以在有更简单的可替代的表达式时使用三元操作符
"no-unneeded-ternary":2,
// 禁止属性前有空白
"no-whitespace-before-property":0,
// 强制花括号内换行符的一致性
"object-curly-newline":0,
// 强制在花括号中使用一致的空格
"object-curly-spacing":0,
// 强制将对象的属性放在不同的行上
"object-property-newline":0,
// 强制函数中的变量要么一起声明要么分开声明
"one-var": [2, {"initialized":"never"}],
// 要求或禁止在 var 声明周围换行
"one-var-declaration-per-line":0,
// 要求或禁止在可能的情况下要求使用简化的赋值操作符
"operator-assignment":0,
// 强制操作符使用一致的换行符
"operator-linebreak": [2,"after", {"overrides": {"?":"before",":":"before"} }],
// 要求或禁止块内填充
"padded-blocks":0,
// 要求对象字面量属性名称用引号括起来
"quote-props":0,
// 强制使用一致的反勾号、双引号或单引号
"quotes": [2,"single","avoid-escape"],
// 要求使用 JSDoc 注释
"require-jsdoc":1,
// 要求或禁止使用分号而不是 ASI(这个才是控制行尾部分号的,)
"semi": [2,"always"],
// 强制分号之前和之后使用一致的空格
"semi-spacing":0,
// 要求同一个声明块中的变量按顺序排列
"sort-vars":0,
// 强制在块之前使用一致的空格
"space-before-blocks": [2,"always"],
// 强制在 function的左括号之前使用一致的空格
"space-before-function-paren": [2,"always"],
// 强制在圆括号内使用一致的空格
"space-in-parens": [2,"never"],
// 要求操作符周围有空格
"space-infix-ops":2,
// 强制在一元操作符前后使用一致的空格
"space-unary-ops": [2, {"words":true,"nonwords":false}],
// 强制在注释中 // 或 /* 使用一致的空格
"spaced-comment": [2,"always", {"markers": ["global","globals","eslint","eslint-disable","*package","!"] }],
// 要求或禁止 Unicode BOM
"unicode-bom":0,
// 要求正则表达式被括号括起来
"wrap-regex":0,

//
// ES6.相关 //
//

// 要求箭头函数体使用大括号
"arrow-body-style":2,
// 要求箭头函数的参数使用圆括号
"arrow-parens":2,
"arrow-spacing":[2,{"before":true,"after":true}],
// 强制在子类构造函数中用super()调用父类构造函数,TypeScrip的编译器也会提示
"constructor-super":0,
// 强制 generator 函数中 * 号周围使用一致的空格
"generator-star-spacing": [2, {"before":true,"after":true}],
// 禁止修改类声明的变量
"no-class-assign":2,
// 不允许箭头功能,在那里他们可以混淆的比较
"no-confusing-arrow":0,
// 禁止修改 const 声明的变量
"no-const-assign":2,
// 禁止类成员中出现重复的名称
"no-dupe-class-members":2,
// 不允许复制模块的进口
"no-duplicate-imports":0,
// 禁止 Symbol 的构造函数
"no-new-symbol":2,
// 允许指定模块加载时的进口
"no-restricted-imports":0,
// 禁止在构造函数中,在调用 super() 之前使用 this 或 super
"no-this-before-super":2,
// 禁止不必要的计算性能键对象的文字
"no-useless-computed-key":0,
// 要求使用 let 或 const 而不是 var
"no-var":0,
// 要求或禁止对象字面量中方法和属性使用简写语法
"object-shorthand":0,
// 要求使用箭头函数作为回调
"prefer-arrow-callback":0,
// 要求使用 const 声明那些声明后不再被修改的变量
"prefer-const":0,
// 要求在合适的地方使用 Reflect 方法
"prefer-reflect":0,
// 要求使用扩展运算符而非 .apply()
    "prefer-spread":0,
// 要求使用模板字面量而非字符串连接
"prefer-template":0,
// Suggest using the rest parameters instead of arguments
"prefer-rest-params":0,
// 要求generator 函数内有 yield
"require-yield":0,
// enforce spacing between rest and spread operators and their expressions
"rest-spread-spacing":0,
// 强制模块内的 import 排序
"sort-imports":0,
// 要求或禁止模板字符串中的嵌入表达式周围空格的使用
"template-curly-spacing":1,
// 强制在 yield* 表达式中 * 周围使用空格
"yield-star-spacing":2
 }
}

在 VSCode 中安装扩展 Eslint ,重启软件后将自动生效。

结合webpack使用

我们期望eslint能够实时提示错误而不必等待执行命令。这个功能可以通过自己的IDE(代码编辑器)安装对应的eslint插件来实现。然而,不是每个IDE都有插件,如果不想安装插件,又想实时提示报错,那么我们就需要webpack 的打包编译功能来实现。

安装

# eslint-loader已经被弃用,可以安装eslint-webpack-plugin代替
yarn add webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader eslint-loader @babel/core -D

这个采用了eslint-loader,配置如下:

// ...
    {
                test:/\.(js|jsx)$/,
                exclude: /node_modules/,
                use: ['babel-loader','eslint-loader']
    }
// ...

因为我们使用了devServer,因此需要在DevServer下添加一个对应的配置参数:

module.exports = {
    // ...
    devServer: {
    client: {
      overlay: false,  // 设置这个参数就只会在控制台报错,网页不会显示!
    },
        liveReload: false, // 默认为true,即开启热更新功能!
    }
}

现在我们就可以实时看到代码里的不规范报错了。

git-hooks 与 husky

husky

为了保证团队里的开发人员提交的代码符合规范,我们可以在开发者论坛上传代码的时候进行校验。我们常使用 husky 和 lint-staged 来进行代码提交时的eslint 校验:

// package.json
// 先安装:yarn add husky lint-staged -D
"husky": {
    "hooks": {
            "pre-commit": "lint-staged"
    }
}

为了保证团队里的开发人员提交的代码符合规范,我们可以在开发者上传代码时进行校验。我们常用 husky来协助进行代码提交时的eslint校验。在使用husky之前,我们先来研究一下 git-hooks

我们回到项目的根目录下。运行ls -a命令----“ a" 可以显示隐藏目录(目录名的第一-位是)。我们可以看到,存在一个".git" 名称的文件夹。

事实上,在我们项目中根目录下运行git命令时,git会根据它来工作。

接来下我们进入到这个文件夹,进-步查看它内部的内容。

cd .git
ls -a

我们发现它内部还挺有料!不慌,我们这节课仅仅只讲到其中的一个内容---- hooks,可以看到,当前目录下存在一个hooks文件夹,顾名思义,这个文件夹提供了git命令相关的钩子。

继续往里看。

cd hooks
ls -a

ok,那我们可以看到有很多git命令相关的文件名。比如"pre- commit.sample pre push.sample".

回到正题一-我们期望在gi提交(commit)前, 对我们的代码进行检测,如果不能通过检测,就无法提交我们的代码。

自然而然的,这个动作的时机应该是? ---- "pre -commit,也就是commit之前。

# cat 命令擦汗一个文件的内容
cat pre-commit.sample

ok,他返回了这样的内容,是一串shell注释,大概意思就是,这是个示例钩子。

husky官网地址:https://typicode.github.io/husky/#/

Github地址:https://github.com/typicode/husky

JS 兼容处理

将 ES6 代码转换为低版本 ES 代码

安装模块

  • babel-loader: 在 webpack 里应用 babel 解析 ES6 的桥梁
  • @babel/core: babel 核心模块
  • @babel/preset-env: babel 预设,一组 babel 插件的集合
npm i babel-loader @babel/core @babel/preset-env -D

在 webpack.config.js 中配置

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        // 排除 node_modules 中安装的库
        exclude: /(node_modules|bower_components)/,
        use: {
          // 加载 loader
          loader: 'babel-loader',
          options: {
            // 配置预设
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}
regeneratorRuntime

regeneratorRuntime 是 webpack 打包生成的全局辅助函数,由 babel 生成,用于兼容 async/await 的语法。

安装

npm i @babel/runtime @babel/plugin-transform-runtime -D

在 webpack.config.js 中配置:

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        // 排除 node_modules 中安装的库
        exclude: /(node_modules|bower_components)/,
        use: {
          // 加载 loader
          loader: 'babel-loader',
          options: {
            // 配置预设
            presets: ['@babel/preset-env']
            plugins: [
              [
                '@babel/plugin-transform-runtime'
              ]
            ]
          }
        }
      }
    ]
  }
}

JS 压缩

安装插件 terser-webpack-plugin

npm i terser-webpack-plugin -D

配置

const TerserWebpackPlugin = require("terser-webpack-plugin")

module.exports = {
  ...
  optimization: {
    minimizer: [
      // 使用插件压缩 js 代码 (生产模式)
      new TerserWebpackPlugin()
    ]
  }
}

6、优化

八个通用构建优化

01、更新到最新版本

02、将loader应用于最少数量的必要模块

const path = require('path')

module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.js$/,
                include: path.resolve(__dirname,'src'), // 只解析src提高速度!
                loader: 'babel-loader'
            }
        ]
    }
}

03、引导(bootstrap)每个额外的 loader/plugin 都有其启动的时间。尽量减少使用工具。

以下步骤可以提高解析速度:

  • 减少 resolve.modules,resolve.extensions,resolve.mainFiles,resolve.descriptionFiles中条目数量,因为他们会增加文件系统调用的次数。
  • 如果你不使用 symlinks(例如npm link 或者 yarn link ),你可以设置resolve.symlinks:false。
  • 如果你使用自定义 resolve plugin规则,并且没有指定 content上下文,可以设置 resolve.cacheWithContent:false。

04、小即是快(smaller = faster)

减少编译结果整体大小,以提高构建性能。尽量保持 chunk 提价小。

  • 使用数量更少/体积更小的e library。
  • 在多页面应用程序中使用 SplitChunksPlugin。
  • 在多页面应用程序中使用 SplitChunksPlugin,并开启 async 模式。
  • 移除未引用代码。
  • 只编译你当前正在开发的哪些代码。

05、持久化缓存

在webpack配置中使用cache选项。使用 package.json中的“postinstall”清除缓存目录。

将cache类型设置为内存或者文件系统。memory 选项很简单,他告诉webpack在内存中存储缓存,不允许额外的配置:

module.exports = {
    // ...
    cache: {
    type: 'memory'
    }
}

06、自定义 plugin/loader,对他们进行概要分析,以免在此处引入性能问题。

07、progress plugin 将ProgressPlugin 从 webpack中删除,可以缩短构建时间。请注意,ProgressPlugin 可能不会为快速构建提供太多价值,因此,注意使用!

08、对于不同环境的配置不同设置优化!

公共路径 publicPath

publicPath 配置公共路径,所有文件的引用将自动添加公共路径的绝对地址。

module.exports = {
  ...
  output: {
    ...
    publicPath: 'https://localhost:3000/'
  }
}

环境变量 Environment variable

环境变量可以消除 webpack.config.js 在开发环境和生产环境之间的差异

module.exports = ( env ) => {
  return {
    ...
    mode: env.production ? 'production' : 'development'
  }
}

打包命令时如果使用生产模式,则在命令后增加:

npx webpack --env production

配置文件优化

分别对 development 和 production 两种模式优化。完整配置文件可查看本页下方 “完整配置”。

1、首先新建 webpack-config 文件夹,在文件夹中添加三个文件,分别为通用的配置文件、开发模式的配置文件以及生产模式的配置文件。

2、使用 webpack-merge 将文件进行合并。安装 webpack-merge

npm i webpack-merge -D

3、添加一个合并文件 webpack.config.js

const { merge } = require('webpack-merge')

const commonConfig = require('./webpack.config.common')
const developmentConfig = require('./webpack.config.dev')
const productionConfig = require('./webpack.config.prod')

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

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

    default:
      return new Error('No matching configuration was found.')
  }
}

4、修改 package.json 文件

// 将自定义的命令分别指向相应的文件以及添加 env 环境变量的参数
{
  "scripts": {
    "start": "webpack serve -c ./webpack-config/webpack.config.js --env development",
    "build": "webpack -c ./webpack-config/webpack.config.js --env production"
  },
}

5、使用命令运行

npm run start
npm run build

文件大小问题

打开webpack.config.js中定义,去除文件太大提示:

module.exports = {
  ...
  performance: {
    ...
    // 关闭文件太大提示
    hints: false
  }
}

HMR ( 开发环境 )

Hot module replacement 热模块替换,可使一个模块发生变化,只重新打包这一个模块,而非全部重新打包,可以更快速的构建代码打包速度。

module.exports = {
  ...
  devServer: {
    ...
    // 开启 HMR 功能
    hot: true
  }
}

热加载

热加载(文件更新时,自动刷新我们的服务和页面)新版的webpack-dev-server默认开启了热加载的功能。它对应的参数是devServer.liveReload,默认为true。注意,如果想要关闭它,要将liveReload设置为false的同时,也要关闭hot

module.exports = {
  ...
  devServer: {
    ...
    // 开启 HMR 功能
    liveReload: false, // 默认为true,即开启热更新功能。
  }
}

Source Map

一种提供源代码到构建后代码映射的技术,如果构建后代码出错了,通过映射关系可以追踪源代码的错误。在 webpack.config.js 文件中配置

module.exports = {
  ...
  devtool: 'source-map'
}

常用的几种 source-map 类型

  • source-map:生成外部文件,错误代码的准确信息和源代码的错误位置
  • inline-source-map:内联,错误代码的准确信息和源代码的错误位置。在代码底部生成,构建速度比外部文件更快
  • hidden-source-map:生成外部文件,错误代码的原因,没有错误位置,无法追踪源代码错误。
  • eval-source-map:内联,错误代码的准确信息和源代码的错误位置。每一个文件都生成对应的 source-map
  • nosources-source-map:生成外部文件,
  • cheap-source-map:生成外部文件,错误代码的准确信息和源代码的错误位置。只精确到行
  • cheap-module-source-map:同 cheap-source-map,会将 loader 的 source map 加入
开发环境建议
  • eval-source-map
  • eval-cheap-module-source-map
生产环境建议
  • source-map
  • nosources-source-map
  • hidden-source-map

要注意的是,生产环境我们一般不会开启source-map功能,主要有以后两种原因:

  1. 通过bundle和sourcemap文件,可以反编译出源码- - - 也就是说,线上产物有sourcemap文件的话,就意味着有暴露源码的风险。
  2. 我们可以观察到,sourcemap文件的体积相对比较巨大,这跟我们生产环境的追求不同(生产环境追求更小更轻量的bundle)。
移到思考题:有时候我们期望能第一时间通知线上的错误信息,来追踪到源码的位置,从而快速解决掉bug以及损失,但不希望sourcemap文件暴露在生产环境中,有什么比较好的方案吗?

Oneof ( 生产模式 )

每个loader只会匹配一个,不能有两个配置处理一个类型的文件

module.exports = {
  module: {
    rules: [
      {
        oneOf:[
          {
            // 处理 css 资源
            test: /\.css$/,
            use: [...CommonCssLoader]
          },
          {
            // 处理 scss 资源
            test: /\.sass$/,
            use: [...CommonCssLoader, 'sass-loader']
          },
          {
            // 处理图片资源
            test: /\.(jpg|jpeg|png|gif)$/,
            loader: 'url-loader',
            options: {
              limit: 8 * 1024,
              name: '[hash:12].[ext]',
              esModule: false,
              outputPath: 'images'
            }
          },
          {
            // 处理 html 中的图片资源
            test: /\.html$/,
            loader: 'html-loader'
          }
        ]
      }
    ]
  }
}

Tree shaking ( 生产模式 )

去除应用程序中没有使用的代码,可更大程度的优化代码。必须使用 ES6 模块化,并开启 production 模式。

import { module } from './filename.js'

如果不需要某些文件被 webpack 清除,可以在 package.json 中配置 sideEffects 属性

{
  "sideEffects": ["*.css" ,"*.scss", "*.global.js"...]
}

Code split ( 生产模式 )

代码分离是 webpack 中最引人瞩目的特性之一,可将代码分离到不同的文件中,然后将这些文件按需加载或并行加载,同时还可以获取代码加载的优先级。

方法1: 入口起点( 不推荐 )

使用 entry 配置手动分离代码,如果多个入口共享的文件,会分别在每个包里重复打包。

module.exports = {
  entry: {
    main: './src/index.js',
    other: './src/another-module.js',
  },
  output: {
    filename: 'scripts/[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },
}

方法2: 防止重复

使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离代码

module.exports = {
  entry: {
    main: {
      import: './assets/js/main.js',
      dependOn: 'shared'
    },
    other: {
      import: './assets/js/add.js',
      dependOn: 'shared'
    },
    shared: 'jQuery'
  },
  output: {
    filename: 'js/[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
}
sliptChunks 插件

另外,还可使用 sliptChunks 插件来实现

module.exports = {
  ...
  entry: {
    main: './assets/js/main.js',
    other: './assets/js/add.js'
  },
  output: {
    filename: 'js/[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  optimization: {
    sliptChunks: {
      chunks: 'all'
    }
  }
}

可以通过 import 方法对文件名进行自定义

import(/* webpackChunkName: '自定义文件名' */'文件路径')

方法3: 动态导入

通过模块的内联函数调用分离代码:需要自己创建一个js文件

function getComponent() {
    return import('lodash').then(({ default: _ }) => {
        const element = document.createElement('div');

        element.innerHTML = _.join(['hello', 'word'], ' ');

        return element;
    });
}

getComponent().then(element => {
    document.body.appendChild(element);
});

懒加载

指的是 JS 文件的懒加载,当事件触发或条件满足后才进行加载。是很好的优化网页或应用的方法。这种方法实际上是先把代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用一些新的代码块。这样加快了应用的初始加载速度,减轻总体体积,因为某些代码块可能永远不会被加载。

document.querySelector('button').addEventListener('click', () => {
  import(/* webpackChunkName: 'filename' */'./filename').then(({ module }) => {
    ...
  })
})

预加载

webpack4.6.0增加了对预获取和预加载的支持。

在生命import时,使用下面这些内置命令,可以让webpack输出 “resource hint(资源提示)”,来告诉浏览器:

  • prefetch(预获取):将来某些导航下可能需要的资源
  • perload(预加载):当前导航下可能需要资源

等其他资源加载完毕后再进行加载,当事件触发或条件满足后,才会执行。兼容性较差,只能在pc端高版本浏览器中使用,手机端浏览器兼容较差。

添加第二句魔法注释:webpackPrefetch:true 、或者是webpackPreload:true

告诉webpack执行预获取,这回生成并加载到页面头部,指示着浏览器在空闲时间获取 math.js文件。

document.querySelector('button').addEventListener('click', () => {
  import(/* webpackChunkName: 'filename', webpackPrefetch: true */'./filename').then(({ module }) => {
    ...
  })
})

缓存 ( 生产模式 )

使用 hash 值为文件命名。

module.exports = {
  ...
  output: {
    filename: 'js/[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },
}

缓存第三方库

将第三方库(library)提取到单独的 vendor chunk 文件中。利用 client 的长效缓存机制,命中缓存来消除请求,并减少向 server 获取资源,同时还能保证 client 代码和 server 代码版本一致。我们在optimization.splitChunks 添加如下 cacheGroups 参数构建:

module.exports = {
  ...
  splitChunks: {
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        chunks: 'all'
      }
    }
  }
}

PWA

非离线环境下运行

下载

npm i http-server -D

配置package.json

{
        "script": {
                "start": "http-server dist"
        }
}

Workbox

渐进式网络应用开发程序,可实现网页离线访问,兼容性较差

这样我们就算服务挂了,也可以访问,不会影响用户体验!

下载插件

npm i workbox-webpack-plugin -D

webpack.config.js配置文件:

// 引入插件
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')

// 配置
module.exports = {
  ...
  plugins: [
    new WorkboxWebpackPlugin.GenerateSW({
      // 帮组 serviceworkder 快速启动
      clientsClaim: true,
      // 删除旧的 serviceworker
      skipWaiting: true
    })
  ]
}

// js 文件中注册
if( 'serviceWorker' in navigator ) {
  window.addEventListener('load', () => {
    navigator.serviceworker.register('/service-worker.js')
        .then(registration => {
          console.log('serviceWorker 注册成功:',registration)
    })
        .catch( registrationError => {
             console.log('serviceWorker 注册失败:',registrationError) 
    })
  })
}

也可以手动取消服务,输入网址:

chrome://serviceworker-internals 单击:Unregister进行取消!

  • 会和eslint 产生冲突,需要修改 package.json 中 eslintConfig 配置
  • 必须运行在服务器上

多进程打包

通常给 babel 使用,只有工作消耗时间较长时才建议使用。

npm i babel-loader @babel/core @babel/preset-env -D

worker 池(worker pool)

thread-loader 可以将非常消耗资源的loader分流给一个 worker pool。通用环境提升性能。用来代替happy pack

安装

npm i thread-loader -D

配置文件

const path = require('path');

module.exports = {
    mode: 'development',

    entry: './src/index.js',

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-ent'],
                        },
                    },
                    {
                        loader: 'thread-loader',
                        options: {
                            workers: 2,
                        },
                    },
                ],
            },
        ],
    },
};

不要使用太多的 worker,因为nodejs的runtime 和loader都有启动开销。最小化 worker 和main process(主进程)之间的模块传输。进程间通讯(IPC、inter process communication)是非常耗资源的。

Externals

为了减小打包后的文件体积,从而把一些第三方库用 cdn 的形式引入进来,如 jQuery。Externals 就是用来防止将某些文件打包到最终生成的文件包中。

定义外部第三方包

module.exports = {
  ...
  // 定义标签类型
  externalsType: 'script',
  // 定义第三方包
  externals: {
    // 这里的文件名jquery就是key必须跟你引入的文件名一致!
    // jquery: 'jQuery', // 如果使用这个,但是需要在html页面导入cdn
    jquery: [
      'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js',
      'jQuery',  // "$"也可以!
    ]
  }
}

在 JS 文件中使用 import 方式引入,这里 from 后的名称需和定义时的名称进行对应。

import $ from 'jQuery'

模块解析(resolve)

const path = require('path');

module.exports = {
    mode: 'development',
    entry: './app.js',

    resolve: {
        alias: {
            '@': path.resolve(__dirname, './src'),
        },
        extensions: ['.json', '.js','.vue'], // 此配置可以让你在相同文件名选择优先加载哪个后缀开始的!
    },
};

导入方法:

const math = require('@/math.js')

Shimming(预置依赖)

让我们开始第一个shimming全局变量的用例。还记得我们之前用过的lodash吗?出于演示目的,例如把这个应用程序中的模块依赖,改为- 个全局变量依赖。要实现这些,我们需要使用ProvidePlugin插件。

使用ProvidePlugin后,能够在webpack编译的每个模块中,通过访问一个变量来获取一个package.如果webpack看到模块中用到这个变量,它将在最终bundle中引入给定的package.让我们先移除lodash 的import语句,改为通过插件提供它:

  • src/index.js
console.log(_.join(['hello','word'],' '))
  • webpack.config.js
const http = require('http')
const webpack = require('webpack')

module.exports = {
    mode: 'development',
    entry: './src/index.ts',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname,'./dist')
    }

    plugins: [new webpack.ProvidePlugin(
        _: 'lodash'    // 这样在业务代码就不需要引入了
    )],
};
细粒度 Shimming

一些遗留模块依赖的 this 指向的是 window 对象。在接下来的用例中,调整我们的index.js:

this.alert('hello,webpack');

当模块运行在CommonJS 上下文中,这将会成为一个问题,也就是说此时的 this 指向的是module.exports。在这种情况下,你可以通过使用 imports-loader 覆盖 this 指向:

安装

npm i imports-loader -D

配置文件

const http = require('http')
const webpack = require('webpack')

module.exports = {
    mode: 'development',
    entry: './src/index.ts',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname,'./dist')
    }

    plugins: [new webpack.ProvidePlugin(
        _: 'lodash'
    )],

    module: {
        rules: [
            {
                test: require.resolve('./src/index.js'),
                use: 'imports-loader?wrapper=window'
            }
        ]
    }
};

Export

让我们假设,某个library创建出一个全局变量,它期望 consumer(使用者)使用这个变量。为此,我们可以在项目配置中,添加一个小模块来演示说明:

安装

npm i exports-loader -D

定义要导出的js文件

  • src/globals.js
const file = 'example.txt';

const helpers = {};
test: function () {
    console.log("test something");
}

parse: function() {
    console.log('parse something');
}

配置文件

  • webpack.config.js
const http = require('http')
const webpack = require('webpack')

module.exports = {
    mode: 'development',
    entry: './src/index.ts',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname,'./dist')
    }

    plugins: [new webpack.ProvidePlugin(
        _: 'lodash'
    )],

    module: {
        rules: [
            {
                test: require.resolve('./src/index.js'),
                use: 'imports-loader?wrapper=window'
            },
            {
                test: require.resolve('./src/global.js'),
                use: 'exports-loader?type=commonjs&exports=file,multiple|helpers.parse|parse'
            }
        ]
    }
};

Polyfills

目前为止,我们讨论的所有内容都是处理那些遗留的package,让我们进入到第二个话题: polyfill。

有很多方法来加载polyfill。例如,想要引入 @babel/polyfill 我们只需如下操作:

npm install --save @babel/polyfill

然后,使用import 将其引入到我们的主bundle文件

import '@babel/polyfill'
console.log(Array.from([1,2,3], x => x + x))

注意,这种方式优先考虑正确性,而不考虑bundle体积大小。为了安全和可靠,polyfill/shim 必须运行于所有其他代码之前,而且需要同步加载,或者说,需要在所有polyfill/shim 加载之后,再去加载所有应用程序代码。社区中存在许多误解,即现代浏览器“不需要polyfill,或者polyfill/shim 仅用于添加缺失功能-实际上, 它们通常用于修复损坏实现(repair broken implementation),即使是在最现代的浏览器中,也会出现这种情况。因此,最佳实践仍然是,不加选择地和同步地加载所有polyfill/shim,尽管这会导致额外的bundle体积成本。

进一步 优化Polyfills

不建议使用 import @babel/polyfill 。因为这样做的缺点是会全局引入整个polyfill包,比如 Array.from 会全局引入,不但包的体积大,而且还会污染全局环境。

babel-preset-env package 通过 browserslist 来转义那些你浏览器不支持的特性。这个preset使用 useBuiltIns 选项,默认值是 false , 这种方式可以将全局 babel-polyfill 导入,改进为更细粒度的import格式:

安装@babel/preset env及相关的包

npm i babel-loader @babel/core @babel/preset-env -D

webpack.config.js配置

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer');

module.exports = {
    mode: 'production',

    entry: './src/index.js',

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            [
                                '@babel/preset-env',
                                {
                                    targets: ['last 1 versions', '> 1%'],
                                    useBuiltIns: 'usage',
                                    corejs: 3,  // 这里的3就是corejs的版本根据自己的定义
                                },
                            ],
                        ],
                    },
                },
            },
        ],
    },
};

但是这样配置的话,我们就还需要安装一个依赖:

npm i core-js -D

然后在进行配置,如上!以及不需要手动引入 @babel/polyfill

Dll

动态连接库,dll会对某些库(第三方)进行单独打包。

使用DllPlugin为更改不频繁的代码生成单独的编译结果。这可以提升应用程序的编译速度,尽管它增加了构建过程的复杂度。

1、下载好第三方库后,使用 import 语法在 JS 文件中引入文件

import { gsap } from 'gsap';

2、在根目录中创建 webpack.dll.config.js

const path = require('path');
const Webpack = require('webpack');

module.exports = {
  entry: {
    // 需要单独打包的库
    gsap: ['gsap'],
  },
  output: {
    // 输出文件名称
    filename: '[name].js',
    // 输出文件路径
    path: path.resolve(__dirname, '../dll'),
    // 导出库名称
    library: '[name]_[hash]',
  },
  plugins: [
    // 引入插件
    new Webpack.DllPlugin({
      // 对应导出的库名称
      name: '[name]_[hash]',
      // 生成 manifest 文件
      path: path.resolve(__dirname, '../dll/manifest.json'),
    }),
  ],
  mode: 'production',
}

3、在 package.json 中编辑

"scripts": {
  "dll": "webpack --config ./webpack.dll.config.js"
}

4、执行指令

npm run dll

5、然后配置 webpack.config.js 文件

const path = require('path')
const Webpack = require('webpack')

module.exports = {
  ...
  plugins: [
    // 告诉 webpack 哪些库布参与打包,以及使用的名称
    new Webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, 'dll/manifest.json')
    })
  ]
}

6、如需在页面中自动引用,需安装一个插件 add-asset-html-webpack-plugin,再在 webpack.config.js 文件中进行配置

下载

npm i add-asset-html-webpack-plugin -D

配置文件

const path = require('path')
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')

module.exports = {
  ...
  plugins: [
    // 在html中自动引入
    new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, 'dll/gsap.js'),
      publicPath: './'
    })
  ]
}

扩展

肯定遇到过如果我们在页面中进行导入,但是虽然没有使用这个方法,但是暴露了出来,可以进行如下设置:

webpack.config.js配置

module.exports = {
  optimization: {
      usedExports: true, // 这样就不会暴露我们没有使用的方法!
  }
}

模块联邦

参考博客:https://zhuanlan.zhihu.com/p/485148715

参考博客:https://blog.csdn.net/qq_41887214/article/details/122084965

依赖图 Dependency graph

每当一个文件依赖另一个文件时,webpack 会直接将文件视为存在依赖关系。这使 webpack 可以获取非代码资源,如 images 或 web 字体等。并会把他们作为依赖提供给应用程序。当 webpack 开始工作时,它会根据我们写好的配置,从入口 (Entry) 开始,webpack 会递归的构建一个依赖关系图,这个依赖图包含着应用程序中所需的每个模块,然后将所有模块打包为输出文件。

bundle 分析工具

  • webpack-chart:webpack stats 可交互饼图;
  • webpack-visualizer:可视化并分析你的bundle,检查哪些模块占用空间,哪些可能使重复使用的;
  • webpack-bundle-analyzer:一个 plugin 和 CLI 工具,它将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式;
  • webpack bundle optimize helper:分析你的bundle并提供可操作的改进措施,减少 bundle 的大小;
  • bundle-stats:生成一个 bundle 报告 ( bundle大小、资源、模块 ),并比较不同构建之间的结果。

我们来使用 webpack-bundle-analyzer 实现。

# 首先安装这个插件作为依赖
# npm安装
npm install --save-dev webpack-bundle-analyzer
# yarn安装
yarn add -D webpack-bundle-analyzer

然后我们配置它:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer');

module.exports = {
        // ...
    plugins: [new BundleAnalyzerPlugin()],
};

TypeScript

安装

npm i typescript ts-loader -D
# or
yarn add typescript ts-loader -D

配置文件如下:

const http = require('http')
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    entry: './src/index.ts',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname,'./dist')
    }

    devtool: 'inline-source-map',

    module: {
        rules: [
            test: /\.ts$/,
            use: 'ts-loader',
            exclude: /node_modules/ 
        ],
    },
    resolve: {
        extensions: ['.ts','.js']
    },

    plugins: [new HtmlWebpackPlugin()],
};

前提:已经创建了tsconfig.js文件!

如果想要整合一些其他第三方模块,请参考以下网站进行寻找:

7、多页面应用

entry 配置

module.exports = {
  ...
  entry: {
    main: {
      // 将多个文件打包合成一个文件
      import: ['app1.js', 'app2.js'],
      dependOn: 'jquery',
      filename: '[name].js'
    },
    main2: {
      import: 'app3.js',
      dependOn: 'jquery',
      filename: 'page/[name].js'
    },
    // 第三方库依赖
    jquery: 'jQuery',
    finename: '[name].js'
  }
}

页面配置

// 引用插件
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      title: '多页面应用', // 这个配置如果使用的话,需要在html页面配置取值!
      template: './index.html',
      inject: 'body',
      chunks: [
        'main',
        'jquery',
      ],
      filename: 'index.html'
    }),
    new HtmlWebpackPlugin({
      template: './page.html',
      inject: 'body',
      chunks: [
        'main2',
        'jquery',
      ],
      filename: 'page/page.html'
    })
  ]
}

html页面模板取值

<title> <%= htmlWebpackPlugin.options.title %> </title>

8、创建 Library

module.exports = {
  ...
  entry: './mylib.js',
  output: {
    path: path.resolve(__dirname. 'dist'),
    filename: 'mylib.js',
    // 防止文件被 tree shaking
    library: {
      // name: 'mylib',  // 设置了type就不能设置name
        type: 'module'  // 就是以下的几种,如果配置这个就需要添加下面的这个配置!
    },
    experiments: {
      outputModule: true,
    }
  }
}

然而它只能通过 script 标签引用而发挥作用,它不能运行在 CommonJS、AMD、Nodejs等环境中。

作为一个库作者,我们希望它能够兼容不同的环境,也就是说,用户应该可以通过以下方式使用打包后的库:

  • CommonJS module require:
const webpackNumbers = require('webpack-numbers');
// ...
webpackNumbers.wordToNum('Two');
  • AMD module require:
require(['webpackNumbers'],function (webpackNumbers) {
    // ...
    webpackNumbers.wordToNum('Two')
});
  • script tag:
<body>
    <script src="http://example.org/webpack-numbers.js"></script>
    <script>
        // ... 
        // Global variable
        webpackNumbers.wordToNum('Five');
        // Property in the window object
        window.webpackNumbers.wordToNum('Five')
        // ...
    </script>
</body>

我们更新 output.library 配置项,将其 type 设置为 umd :

const path = require('path');

module.exports = {
    mode: 'production',

    entry: './src/index.js',
    // experiments: {
    //     outputModule: true,
    // },
    output: {
        library: {
            name: 'mylib',
            type: 'umd',
        },
        globalObject: 'globalThis', // 如果使用其他模块就要设置这个,例commonjs..
    },
    externals: {
        lodash: {
            commonjs: 'lodash',
            amd: 'lodash',
            root: '_'
        }
    }
};

9、完整配置 *

开发环境与生产环境优化建议

一、增量编译

使用webpack的watch mode(监听模式)。而不使用其他工具来watch文件和调用webpack。内置的watch mode会记录时间戳并将此信息传递给compilation以使缓存失效。

在某些配置环境中,watch mode会回退到poll mode(轮询模式)。监听许多文件会导致CPU大量负载。在这些情况下,可以使用watchOptions. poll来增加轮询的间隔时间。

二、在内存中编译

下面几个工具通过在内存中(而不是写入磁盘)编译和serve资源来提高性能:

  • webpack-dev-server
  • webpack- hot-middleware
  • webpack -dev- middleware

三、stats.toJson 加速

webpack 4默认使用stats.toJson()输出大量数据。除非在增量步骤中做必要的统计,否则请避免获取stats对象的部分内容。

webpack-dev-server在v3.1.3以后的版本,包含-个重 要的性能修复,即最小化每个增量构建步骤中,从stats 对象获取的数据量。

四、Devtool

需要注意的是不同的devtool 设置,会导致性能差异。

  • "eval"具有最好的性能,但并不能帮助你转译代码。
  • 如果你能接受稍差一些的 map质量,可以使用cheap-source-map变体配置来提高性能
  • 使用eval-source -map变体配置进行增量编译。

在大多数情况下,最佳选择是eval-cheap-module-source-map。

五、避免在生产环境才用到的工具

某些utility, plugin和loader都只用于生六坏境。例如,在开发环境下使用TerserPlugin来minify(圧縮)和mangle(混淆破坏)代码是没有意义的。通常在开发环境下,立垓排除以下这些工具:

  • TerserPlugin
  • [fullhash]/[chunkhash]/[contenthash]
  • AggressiveSplittingPlugin
  • AggressiveMergingPlugin
  • ModuleConcatenationPlugin

六、最小化 entry chunk

Webpack只会在文件系统中输出已经更新的chunk。某些配置选项( HMR,

output.chunkFilename的[name]/chunkhash, [fullhash]) 来说,除了对已经更新的chunk无效之外,对于entry chunk也不会生效。

确保在生成entry chunk时,尽量减少其体积以提高性能。下面的配置为运行时代码创建了一个额外的chunk,所以它的生成代价较低:

module.exports = {
    // ...
    optimization: {
        runtimeChunk: true,
    }
}

七、避免额外的优化步骤

webpack 通过执行额外的算法任务,来优化输出结果的体积和加载性能。这些游湖适合于小型代码库,但是在大型代码库中却非常耗费性能:

module.exports = {
    // ...
    optimization: {
        removeAvailableModules: false,
        removeEmptyChunks: false,
        splitChunks: false
    }
}

八、输出结果不携带路径信息

Webpack会在输出的bundle中生成路径信息。然而,在打包数千个模块的项目中,这会导致造成垃圾回收性能压力。在

options.output.pathinfo设置中关闭:

module.exports = {
    // ...
    output: {
        pathinfo: false,
    }
}

九、Node.js版本

注意更新到最新!

十、TypeScript loader

你可以为loader传入transpileOnly选项,以缩短使用ts-loader时的构建时间。使用此选项,会关闭类型检查。如果要再次开启类型检查,请使用ForkTsCheckerWebpackPlugin。使用此插件会将检查过程移至单独的进程,可以加快TypeScript的类型检查和ESLint插入的速度。

module.exports = {
  // ... 
  test: /\.tsx?$/,
  use: [
    {
      loader: 'ts-loader',
      options: {
        transpileOnly: true
      }
    }
  ]
}

生产环境提升构建性能

不启用 SourceMap

source map相当消耗资源,开发环境下不要设置source map

开发环境

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

const CommonCssLoader = [
    MiniCssExtractPlugin.loader,
    {
        loader: 'css-loader',
        options: { importLoaders: 1 }
    },
    {
        loader: 'postcss-loader',
        options: {
            postcssOptions: {
                plugins: [
                    [
                        'postcss-preset-env'
                    ]
                ]
            }
        }
    }
]

process.env.NODE_ENV = 'development'

module.exports = {
    entry: './assets/js/main.js',
    output: {
        filename: 'js/app.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            {
                oneOf:[
                    {
                        // 处理 css 资源
                        test: /\.css$/i,
                        use: [...CommonCssLoader]
                    },
                    {
                        // 处理 scss 资源
                        test: /\.s[ac]ss$/i,
                        use: [...CommonCssLoader, 'sass-loader']
                    },
                    {
                        // 处理图片资源
                        test: /\.(jpg|jpeg|png|gif)$/i,
                        loader: 'url-loader',
                        options: {
                            limit: 8 * 1024,
                            name: '[hash:12].[ext]',
                            esModule: false,
                            outputPath: 'images'
                        }
                    },
                    {
                        // 处理 html 中的图片资源
                        test: /\.html$/i,
                        loader: 'html-loader'
                    }
                ]
            }
        ]
    },
    plugins: [
        // 处理 html 资源
        new HtmlWebpackPlugin({
            template: './index.html',
            minify: {
                collapseWhitespace: true,
                removeComments: true
            }
        }),
        new MiniCssExtractPlugin({
            // 对输出结果重命名
            filename: 'css/app.css'
        })
    ],
    mode: 'development',
    devServer: {
        static: {
            directory: path.join(__dirname, 'build'),
        },
        compress: true,
        port: 3000,
        liveReload: true,
        watchFiles: {
            paths: [
                './assets/*/*',
                './*.html'
            ],
            options: {
                usePolling: false
            }
        },
        open: true,
        hot: true
    },
    devtool: 'source-map'
}

生产环境

// 生产环境
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

const CommonCssLoader = [
    MiniCssExtractPlugin.loader,
    {
        loader: 'css-loader',
        options: { importLoaders: 1 }
    },
    {
        loader: 'postcss-loader',
        options: {
            postcssOptions: {
                plugins: [
                    [
                        'postcss-preset-env'
                    ]
                ]
            }
        }
    }
]

// process.env.NODE_ENV = 'development'

module.exports = {
    entry: './assets/js/main.js',
    output: {
        filename: 'js/app.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            {
                // 处理 css 资源
                test: /\.css$/i,
                use: [...CommonCssLoader]
            },
            {
                // 处理 scss 资源
                test: /\.s[ac]ss$/i,
                use: [...CommonCssLoader, 'sass-loader']
            },
            {
                // 处理图片资源
                test: /\.(jpg|jpeg|png|gif)$/i,
                loader: 'url-loader',
                options: {
                    limit: 8 * 1024,
                    name: '[hash:12].[ext]',
                    esModule: false,
                    outputPath: 'images'
                }
            },
            {
                // 处理 html 中的图片资源
                test: /\.html$/i,
                loader: 'html-loader'
            }
        ]
    },
    plugins: [
        // 处理 html 资源
        new HtmlWebpackPlugin({
            template: './index.html',
            minify: {
                collapseWhitespace: true,
                removeComments: true
            }
        }),
        new MiniCssExtractPlugin({
            // 对输出结果重命名
            filename: 'css/app.css'
        })
    ],
    mode: 'production',
}

10、详细配置 *

11、插件 *

官方列举了可在 Webpack 5 中所有可使用的插件。

https://webpack.docschina.org/plugins/

12、使用技巧 *

// 忽略警告(包含注释)
// eslint-disable-next-line

// 输出 ES6 版本代码
output.ecmaVersion: 2015

// 删除全局 webpack
npm uninstall webpack webpack-cli --global

13、参考链接

官方文档(中文)
https://webpack.docschina.org/concepts/

https://stackoverflow.com/questions/49348365/webpack-4-size-exceeds-the-recommended-limit-244-kib

1、运行指令:

webpack4版本处理:

开发环境:webpack ./sr c/index.js -o ./build/built.js --mode=development

生产环境:webpack ./src/index.js -o ./build/built.js --mode=production

webpack5版本处理:(如果你是webpack5,推荐你使用这个命令!)

开发环境:webpack --entry ./src/index.js --output-filename bundle.js --mode=development

生产环境:webpack --entry ./src/index.js --output-filename bundle.js --mode=production

相关文章
|
1月前
|
缓存 JavaScript
webpack之SplitChunksPlugin
webpack之SplitChunksPlugin
30 0
|
1月前
|
前端开发 JavaScript
webpack使用
webpack使用
|
1月前
|
前端开发 JavaScript 测试技术
对Webpack的理解
对Webpack的理解
28 0
|
10月前
|
JSON 缓存 前端开发
webpack相关详细讲解。
webpack相关详细讲解。
|
10月前
|
缓存 前端开发 JavaScript
浅谈webpack
浅谈webpack
90 0
|
JavaScript 前端开发
从了解到使用webpack
Webpack本质上是一种前端资源编译,打包工具
74 0
|
前端开发 JavaScript
关于 Webpack 的介绍,什么是 Webpack?
Webpack 是一款现代化的前端构建工具,它可以将你的项目中的多个 JavaScript 模块打包成一个或多个 bundle 文件,同时还可以处理各种类型的静态资源,比如 CSS、图片等等。 Webpack 最初是由 Tobias Koppers 开发的,目前已经成为了前端开发中使用最广泛的构建工具之一。
124 0
|
缓存 算法
webpack的chunkFilename详细说明
webpack的chunkFilename详细说明
209 0
|
JSON JavaScript 前端开发
你可以看懂的webpack5知识(上)
你可以看懂的webpack5知识
|
JavaScript 前端开发
你可以看懂的webpack5知识(下)
你可以看懂的webpack5知识