Webpack

Webpack

1、Webpack简介

1.1、webpack是什么

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

 

官网地址: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

使用npm查看webpack的版本:

npm view webpack versions

 

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

 

 

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

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

 

并生成一个配置文件(.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执行预获取,这回生成<link rel="prefetch" href="math.js">并加载到页面头部,指示着浏览器在空闲时间获取 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

 

 

 

 

posted @ 2022-07-31 22:13  nakano_may  阅读(151)  评论(0编辑  收藏  举报