前言

本篇文章将介绍一些webpack的进阶用法,演示内容继承自上一篇文章的内容,所以没看过上一篇文章的倡议先学习上一篇内容再浏览此篇内容,会更有利于此篇的学习~

文件指纹

文件指纹指的是打包输入的文件名后缀,个别用来做版本治理、缓存等

webpack的指纹策略有三种:hashchunkhashcontenthash,它们之间最次要的区别就是每种hash影响的范畴不同。

占位符

webpack提供占位符用于将特定信息附加在打包输入的文件上
名称含意
[ext]资源后缀名
[id]文件标识符
[name]文件名称
[path]文件的相对路径
[folder]文件所在的文件夹
[hash]模块标识符的 hash,默认是 md5 生成
[chunkhash]chunk 内容的 hash,默认是 md5 生成
[contenthash]文件内容的 hash,默认是 md5 生成
[query]文件的 query,例如,文件名 ? 前面的字符串
[emoji]一个随机的指代文件内容的 emoji

咱们能够应用特定的语法,对 hashchunkhashcontenthash 进行切片:[chunkhash:4],像 8c4cbfdb91ff93f3f3c5 这样的哈希会最初会变为 8c4c

hash

与整个我的项目的构建无关,只有我的项目内文件有批改,整个我的项目构建的hash值就会扭转

咱们应用多入口打包来体验一下:

// webpack.config.jsmodule.exports = {  entry: {         main: './src/main.js',        index: './src/index.js'    },    output: {        filename: '[name].[hash:6].js',        path: __dirname + '/dist',        clean: true    },  // ...}

此时咱们应用了占位符来设置文件指纹[name].[hash:6].js代表的是文件名+6位hash

此时咱们执行npm run build,看打包进去的内容如下:

此时两个js文件的hash都是207495

咱们批改一下index.js的内容,再打包一次

咱们会发现此时两个js文件的hash都变成了9f0e2d

chunkhash

chunkhash 是和 webpack 打包的模块相干,每一个 entry 作为一个模块,会产生不同的 Chunkhash 值,文件扭转时只会影响以后chunk组的hash值

咱们再来看看chunkhash

// webpack.config.jsmodule.exports = {  entry: {         main: './src/main.js',        index: './src/index.js'    },    output: {        filename: '[name].[chunkhash:6].js',        path: __dirname + '/dist',        clean: true    },  // ...}

还是延用下面的例子,这次咱们只批改main.js文件内容

批改前两个文件的hash值如下:

批改后:

此时只有main.js的打包产物的hash产生了变动

contenthash

contenthash 是和依据文件内容相干,单个文件发生变化,只会引起此文件的hash值

这里咱们应用miniCssExtractPlugin将CSS内容提取成文件,并为它设置contenthash

// webpack.config.jsconst miniCssExtractPlugin = require('mini-css-extract-plugin')module.exports = {  entry: {         main: './src/main.js',        index: './src/index.js'    },    output: {        filename: '[name].[contenthash:6].js',        path: __dirname + '/dist',        clean: true    },  mudole: {    rules: [      {        test: /\.css$/,        use: [miniCssExtractPlugin.loader, 'css-loader']      },      // ...    ]  },  plugins: [    // ...    new miniCssExtractPlugin({            filename: 'css/[name].[contenthash:6].css'        }),  ]  // ...}

而后打包看一下此时的hash:

咱们批改index.css内容再打包一次

此时只有index.css的打包产物hash值产生了变动。

依据不同的文件类型个别抉择不同的文件指纹策略,通常状况下:

  • JS文件采纳[chunkhash]文件指纹策略
  • CSS文件采纳[contenthash]文件指纹策略
  • 图片资源采纳[hash]文件指纹策略

代码压缩

压缩JS

目前最成熟的JavaScript代码压缩工具是UglifyJS,它可能剖析JavaScript语法树,了解代码含意,从而能做到诸如去掉有效代码、去掉日志输入代码、缩短变量名等优化。但很遗憾的是UglifyJS不再保护,并且它不反对 ES6 + 。

当初举荐应用的是Terser,它在 UglifyJS 根底上减少了 ES6 语法反对,并重构代码解析、压缩算法,使得执行效率与压缩率都有较大晋升,并且Webpack5.0 后默认应用 Terser 作为 JavaScript 代码压缩器

简略实用:

// webpack.config.jsmodule.exports = {  //...  optimization: {    minimize: true  }}

须要留神的是在生产模式中构建时,Terser压缩是默认开启的

当然它也容许你通过提供一个或多个定制过的TerserPlugin实例,笼罩默认的压缩工具,实现更精密的压缩性能

// webpack.config.jsconst TerserPlugin = require("terser-webpack-plugin");module.exports = {  //...  optimization: {    minimize: true,    minimizer: [      new TerserPlugin({        parallel: true,        terserOptions: {          // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions        },      })    ]  }}

在Webpack4中 默认应用 uglifyjs-webpack-plugin压缩代码,也能够通过 minimizer 数组替换为 Terser 插件

压缩CSS

CSS代码同样也能够应用webpack来进行压缩,比拟常见的CSS压缩工具有:cssnanocss-minimizer-webpack-plugin

对于 webpack5 或更高版本,官网举荐应用 CssMinimizerWebpackPlugin,该插件是应用 cssnano 优化和压缩 CSS,反对缓存和并发模式下运行。

装置:

npm i css-minimizer-webpack-plugin

配置:

// webpack.config.jsconst CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 用压缩cssconst MiniCssExtractPlugin = require("mini-css-extract-plugin");  // 用来提取css成独自的文件module.exports = {  //...  module: {    rules: [      {        test: /.css$/,        // 留神,MiniCssExtractPlugin.loader 与 style-loader不能同时应用        use: [MiniCssExtractPlugin.loader, "css-loader"],      },    ],  },  optimization: {    minimize: true,    minimizer: [      // Webpack5 之后,约定应用 '...' 字面量保留默认 minimizer 配置      "...",      new CssMinimizerPlugin(),    ],  },  plugins: [new MiniCssExtractPlugin()],};

⚠️这里须要留神的是须要应用 mini-css-extract-plugin 将 CSS 代码抽取为独自的 CSS 产物文件,这样能力命中 css-minimizer-webpack-plugin 默认的 test 逻辑。

压缩HTML

咱们之前应用的html-webpack-plugin,它除了能够生成html模版,也能够用来对html进行压缩。

htmlWebpackPlugin常见参数

  • template:模板的门路,默认会去寻找 src/index.ejs 是否存在。
  • filename:输入文件的名称,默认为 index.html
  • inject:是否将资源注入到模版中,默认为 true
  • minify:压缩参数。在生产模式下(production),默认为 true;否则,默认为false
// webpack.config.jsmodule.exports = {  // ...  plugins: [    // ...    new HtmlWebpackPlugin({            template: './public/index.html',            filename: 'index.html',            minify: true        }),  ]}

生成的 HTML 将应用 html-minifier-terser 和以下选项进行压缩,所以它实际上的压缩性能其实是html-minifier-terser来实现的,更多配置能够查看这个工具文档

{  collapseWhitespace: true,  keepClosingSlash: true,  removeComments: true,  removeRedundantAttributes: true,  removeScriptTypeAttributes: true,  removeStyleLinkTypeAttributes: true,  useShortDoctype: true}

禁止生成LICENSE文件

通过下面这些配置后,我发现了一个奇怪的问题,那就是每个bundle产物都多了一个同名的LICENSE.txt文件,关上一看外面都是一些正文内容。

为什么会生成这些文件,带着纳闷我去翻了下官网文档,Webpack5 默认压缩代码工具为terser-webpack-plugin,那就先从它动手吧。

在它的配置中找到了extractComments参数,默认值为true,示意将正文剥离到独自的文件中。

如果咱们不想要,间接关掉该配置就行了

module.exports = {  // ...  optimization: {        minimize: true,        minimizer: [            new cssMinimizerPlugin(),            new terserPlugin({                extractComments: false,  // 敞开正文剥离性能            }),            '...'        ]    },}

CSS加强(autoprefixer)

前端最头疼的问题莫过于解决兼容性,因为前端的运行环境并不固定,能够在各种浏览器以及各种webview中运行,并且每个浏览器厂商对CSS的写法也各不相同,这就势必会导致呈现一些问题。

比方为了兼容各种浏览器内核,圆角属性应该这样写:

.container {  -moz-border-radius: 16px;  -webkit-border-radius: 16px;  -o-border-radius: 16px;  border-radius: 16px;}

试想一下如果在开发中须要你这样写,那是不是太不合理了?

咱们个别都会通过webpack配置插件来帮咱们解决这个问题,解决CSS咱们首先会想到postcss,没错webpack也有应用postcss解决CSS的loader --- postcss-loader,而后咱们还须要应用postcss的插件autoprefixer来帮咱们主动增加浏览器前缀。

装置:

npm i postcss postcss-loader autoprefixer

批改配置:

// webpack.config.jsmodule.exports = {  // ...  module: {    rules: [      //...      {        test: /\.css$/,        use: [miniCssExtractPlugin.loader,               'css-loader',               {                loader: 'postcss-loader',                options: {                  postcssOptions: {                    plugins: ['autoprefixer']                  }                }              }]      },    ]  }  //...}

⚠️这里须要留神的是,如果你想自定义转换的规定,最好是将 autoprefixer 的 browsers选项替换为 browserslist 配置。在 package.json 或。Browserslistrc 文件。应用 browsers选项可能导致谬误,并且browserslist 配置能够用于 babel、 autoprefixer、 postcss-norize 等工具。

比方package.json中配置browserslist:

// package.json{  //...  "browserslist": [    "last 10 Chrome versions",    "last 5 Firefox versions",    "Safari >= 6",     "ie> 8"  ] }

此时咱们打包的CSS的产物就会主动增加浏览器前缀

动态资源拷贝

如果咱们须要在html中援用一些不须要打包解决的资源,比方上面这种状况

index.html中引入了一些日志的工具函数,这时候咱们间接跑起来会发现这个文件间接404了,这是怎么回事?

首先咱们写的门路必定是没问题的,问题在于咱们打包后这个utils文件必定是不在这个地位了,所以会报404

所以这里咱们须要应用copy-webpack-plugin将文件拷贝至dist目录下

// webpack.config.jsconst copyWebpackPlugin = require('copy-webpack-plugin')module.exports = {  // ...  plugins: [    new copyWebpackPlugin({      patterns: [        {from: 'module', to: __dirname + '/dist/module/'}      ]    }),  ]}

此时再打包,咱们会发现dist目录下曾经有了module/utils.js,并且页面也不会再报404了

sourcemap

SourceMap 就是一个信息文件,外面贮存着代码的地位信息。这种文件次要用于开发调试,当初代码都会通过压缩混同,这样报错提醒会很难定位代码。通过 SourceMap 能疾速定位到源代码,并进行调试。

比方咱们没有开启sourcemap,而后开发过程中报错了,它的报错信息是这样的:

定位过来是打包后的内容,这样的话对咱们排查报错十分不不便。

当咱们开启sourcemap,再来看看这个同样的报错是怎么的:

// webpack.config.jsmodule.exports = {  // ...  devtool: 'eval-cheap-module-source-map',}

此时的报错指向就十分清晰了~

关键字

devtool的值有20多种,并且都是由以下七种关键字的一个或多个组成

  • eval 关键字

devtool 值蕴含 eval 时,生成的模块代码会被包裹进一段 eval 函数中,且模块的 Sourcemap 信息通过 //# sourceURL 间接挂载在模块代码内

  • source-map 关键字

devtool 蕴含 source-map 时,Webpack 才会生成 Sourcemap 内容

  • cheap 关键字

devtool 蕴含 cheap 时,生成的 Sourcemap 内容会摈弃维度的信息,这就意味着浏览器只能映射到代码行维度

  • module 关键字

module 关键字只在 cheap 场景下失效,例如 cheap-module-source-mapeval-cheap-module-source-map。当 devtool 蕴含 cheap 时,Webpack 依据 module 关键字判断按 loader 联调处理结果作为 source,还是按解决之前的代码作为 source

  • nosources 关键字

devtool 蕴含 nosources 时,生成的 Sourcemap 内容中不蕴含源码内容 —— 即 sourcesContent 字段

  • inline 关键字

devtool 蕴含 inline 时,Webpack 会将 Sourcemap 内容编码为 Base64 DataURL,间接追加到产物文件中

  • hidden 关键字

通常,产物中必须携带 //# sourceMappingURL= 指令,浏览器能力正确找到 Sourcemap 文件,当 devtool 蕴含 hidden 时,编译产物中不蕴含 //# sourceMappingURL= 指令

devtool的值以及各自的性能能够在webpack文档上查看

如何抉择

  • 对于开发环境,适宜应用:

    • eval:速度极快,但只能看到原始文件构造,看不到打包前的代码内容;
    • cheap-eval-source-map:速度比拟快,能够看到打包前的代码内容,但看不到 loader 解决之前的源码;
    • cheap-module-eval-source-map:速度比拟快,能够看到 loader 解决之前的源码,不过定位不到列级别;
    • eval-source-map:首次编译较慢,但定位精度最高;
  • 对于生产环境,则适宜应用:

    • source-map:信息最残缺,但安全性最低,内部用户可轻易获取到压缩、混同之前的源码,谨慎应用;
    • hidden-source-map:信息较完整,安全性较低,内部用户获取到 .map 文件地址时仍然能够拿到源码,谨慎应用;
    • nosources-source-map:源码信息缺失,但安全性较高,须要配合 Sentry 等工具实现残缺的 Sourcemap 映射。

解决跨域

在开发过程中,咱们势必会遇到跨域问题,对于本地开发咱们个别能够通过配置代理来解决

咱们先来简略写一个接口:

const express = require('express')const app = express()app.get('/api/getInfo', (req, res) => {    res.json({        code: 200,        data: {            name: 'nanjiu',            age: 18        }    })})app.listen(3000, () => {    console.log('服务已启动~')})

而后把服务跑起来,再到vue我的项目中去调用

const getInfo = async () => {    try {        const res = await axios.get('http://localhost:3000/api/getInfo')        console.log(res)    } catch(err) {        console.log(err)    }}

这时候你会发现接口调用跨域了

配置代理

接着咱们再来通过webpack配置代理解决跨域问题,因为咱们本地应用了webpack-dev-server,所以咱们能够间接通过它来配置

// webpack.config.jsmodule.exports = {  // ...  devServer: {    hot: true,    open: true,    proxy: {      '/api': 'http://localhost:3000'    }  }}

这个时候咱们的接口申请就失常了

因为篇幅问题,这篇文章就介绍到这里了,前面会接着更新webpack更多高级用法。

如果这篇文章有帮忙到你,❤️关注+点赞❤️激励一下作者,文章公众号首发,关注 前端南玖 第一工夫获取最新文章~