Webpack 5 超详细解读(三)

21.babel-loader 使用

使用babel-loaderjs文件进行处理,在lg.Webpack.js配置文件中配置js文件规则。

使用单独的插件进行转换

使用预设进行转换

使用babel.config.js配置文件进行babel配置

const path = require('path')
const CopyWebpackPlugin = require('copy-Webpack-plugin')
const { DefinePlugin } = require('Webpack')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
const HtmlWebpackPlugin = require('html-Webpack-plugin')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'js/main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              esModule: false
            }
          },
          'postcss-loader'
        ]
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
      {
        test: /\.(png|svg|gif|jpe?g)$/,
        type: 'asset',
        generator: {
          filename: "img/[name].[hash:4][ext]"
        },
        parser: {
          dataUrlCondition: {
            maxSize: 30 * 1024
          }
        }
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: 'asset/resource',
        generator: {
          filename: 'font/[name].[hash:3][ext]'
        }
      },
      {
        test: /\.js$/,
        use: ['babel-loader']
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'copyWebpackPlugin',
      template: './public/index.html'
    }),
    new DefinePlugin({
      BASE_URL: '"./"'
    }),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'public',
          globOptions: {
            ignore: ['**/index.html']
          }
        }
      ]
    })
  ]
}

对于是否转换ES6+语法,此处还需要看.browserslistrc文件的配置,如果在.browserslistrc只配置了如:chrome 91,由于chrome 91版本已经支持const以及箭头函数,所以此时babel并不会对箭头函数块级作用域进行转换。

如果有.browserslistrc文件配置,还有presets targets配置,babel会优先以targets为主。

presets targets配置

22.polyfill 配置

Webpack4时不需要进行单独的polyfill处理,因为Webpack4默认已经加入的polyfill,但是正因为默认加入了polyfill,导致打包后的产出内容特别大。到了Webpack5之后,基于优化打包速度的考虑,默认情况下,polyfill就被移除掉了。如果需要用到,就需要自己进行安装配置。

什么是polyfill

Polyfill 是一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。

为什么使用polyfill

首先在index.js中写入ES6新增的promise语法,然后执行打包,查看打包结果。

可以发现,打包过后的main.js中保留了Promise。但是有个问题,如果直接把main.js放入浏览器中运行,例如.browserslistrc中包含了IE7、IE8、IE9等低版本浏览器,那些不支持Promise的浏览器就会报错。所以想要的是希望打包时,Webpack可以帮助我们定义一个Promise函数,用于支持低版本的浏览器。所以这时候,就需要一个Polyfill的存在,处理babel-preset-env不能处理的更新的语法(generator、symbol、promise等),以适配低版本浏览器。

更早的时候会使用@babel/polyfill,根据安装提示,查看官方文档。目前已经不建议直接安装@babel/polyfill,建议使用core-js/stable以及regenerator-runtime/runtime

所以卸载@babel/polyfill,重新安装core-js/stableregenerator-runtime/runtime

yarn add core-js regenerator-runtime

接下来配置babel-loader,之前我们的babel-loader放到了单独的配置文件babel.config.js中。

index.js导入core-js/stableregenerator-runtime/runtime

import "core-js/stable";
import "regenerator-runtime/runtime"

const title = '前端'
const foo = () => {
  console.log(title)
}

const p1 = new Promise((resolve, reject) => {
  console.log(111)
})
console.log(p1)

foo()

babel-config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        // false: 不对当前的JS处理做 polyfill 的填充
        // usage: 依据用户源代码当中所使用到的新语法进行填充
        // entry: 依据我们当前筛选出来的浏览器.browserslistrc决定填充什么
        useBuiltIns: 'usage',
        corejs: 3
      }
    ]
  ]
}

23.Webpack-dev-server 初始

在前端开发过程中,我们希望在一个项目的里程碑的时候对一些功能进行测试或者调试,在手动修改js代码后,希望Webpack重新打包,并自动刷新浏览器操作。之前可以使用–watch的模式进行监听。–watch有两种使用方法,第一种是package.json的scripts中添加命令行参数:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "Webpack --config lg.Webpack.js --watch",
  },

第二种是在lg.Webpack.js中添加watch字段,并赋值为true(默认为false)

watch和live server的不足:

  • 一个文件改动,所有源代码都会重新编译,耗时长
  • 每次编译成功后都需要进行文件读写(dist目录)
  • live server是node生态下的
  • 模块化开发过程中,一个页面的由多个组件组成,我们希望一个组件改动后,只针对该部分组件进行刷新,而不是整个页面都刷新

Webpack Dev Server是Webpack官方推出的一个开发工具,根据名字,就应该知道它提供了一个开发服务器,并且,它将自动编译和自动刷新浏览器等一系列对开发非常友好的功能全部集成在了一起。这个工具可以直接解决我们之前的问题。

打开命令行,以开发依赖安装

yarn add Webpack-dev-server --dev

它提供了一个Webpack-dev-server的cli程序,那我们同样可以直接通过yarn去运行这个cli,或者,可以把它定义到npm script中。运行这个命令 yarn Webpack-dev-server,它内部会自动去使用Webpack去打包应用,并且会启动一个HTTP server去运行打包结果。在运行过后,它还会去监听我们的代码变化,一旦语言文件发生变化,它就会自动立即重新打包,这一点,与watch模式是一样的。不过这里也需要注意Webpack-dev-serverr为了提高工作效率,它并没有将打包结果写入到磁盘当中,它是将打包结果,暂时存放在内存当中,而内部的HTTP server从内存当中把这些文件读出来,然后发送给浏览器。这样一来的话它就会减少很多不必要的磁盘读写操作,从而大大提高我们的构建效率。

这里,我们还可以为这个命令传入一个**–open**的参数,它可以用于去自动唤起浏览器,去打开我们的运行地址,打开浏览器过后(如果说你有两块屏幕的话),你就可以把浏览器放到另外一块屏幕当中,然后,我们去体验这种一边编码,一边即时预览的开发环境了。

yarn Webpack-dev-server --open

在package.json中配置server命令:

运行yarn serve,这时动态的更改index.js,发现浏览器会自动刷新并打印。

当当前端口8080被占用时,我们需要手动指定端口进行启动服务,命令如下:"serve": "Webpack serve --config lg.Webpack.js --port 3000"

24.Webpack-dev-middleware 使用

Webpack-dev-middleware 是一个容器(wrapper),它可以把 Webpack 处理后的文件传递给一个服务器(server)。 Webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。

首先需要明确,在实际开发阶段很少使用Webpack-dev-middleware,但是我们需要理解这样做的目的是什么,可以对打包过程做一个自由度非常高的定制。具体实现思路是:

  1. 在本地利用express开启一个服务
  2. 将Webpack打包的结果交给这个服务
  3. 浏览器进行访问

具体实施步骤:

安装expressWebpack-dev-middleware

yarn add express Webpack-dev-middleware

利用express自己实现一个server

Server.js

const express = require('express')
const WebpackDevMiddleware = require('Webpack-dev-middleware')
const Webpack = require('Webpack')

const app = express()

// 获取Webpack打包的配置文件
const config = require('./Webpack.config')
const compiler = Webpack(config)

app.use(WebpackDevMiddleware(compiler))

// 开启端口上的服务
app.listen(3000, () => {
  console.log('Server 运行在3000端口上')
})

使用node ./Server.js启动之后,使用浏览器打开既可以看到我们打包过后的内容。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cdURBgTV-1669024537924)(http://5coder.cn/img/1667985169_f226c9deae68b804821a85e89a82005c.png)]

25.HMR 功能使用

HMR全称是Hot Module Replacement,叫做模块热替换或者叫做模块热更新。计算机行业经常听到一个叫做热拔插的名词,那指的就是可以在一个正在运行的机器上随时去插拔设备,而机器的运行状态是不会受插设备的影响,而且插上的设备可以立即开始工作,例如电脑上的USB端口就是可以热拔插的。

模块热替换当中的这个,跟刚刚提到的热拔插实际上是一个道理,它们都是在运行过程中的即时变化,那Webpack中的模块热替换指的就是可以在应用程序运行的过程中实时的去替换掉应用中的某个模块,而应用的运行状态不会因此而改变

例如在应用程序的运行过程中,修改了某个模块,通过自动刷新就会导致整个应用整体的刷新,页面中的状态信息都会丢失掉,而如果这个地方使用的是热替换的话,就可以实现只将刚刚修改的这个模块实时的去替换到应用当中,不必去完全刷新应用。

在src/index.js中打印文字,并且在Webpack.config.js中进行配置相应字段:target: 'web'devServer: {hot: true}

Webpack.config.js

const path = require('path')
const CopyWebpackPlugin = require('copy-Webpack-plugin')
const { DefinePlugin } = require('Webpack')
const { CleanWebpackPlugin } = require('clean-Webpack-plugin')
const HtmlWebpackPlugin = require('html-Webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    filename: 'js/main.js',
    path: path.resolve(__dirname, 'dist')
  },
  target: 'web',
  devServer: {
    hot: true
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              esModule: false
            }
          },
          'postcss-loader'
        ]
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      },
      {
        test: /\.(png|svg|gif|jpe?g)$/,
        type: 'asset',
        generator: {
          filename: "img/[name].[hash:4][ext]"
        },
        parser: {
          dataUrlCondition: {
            maxSize: 30 * 1024
          }
        }
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: 'asset/resource',
        generator: {
          filename: 'font/[name].[hash:3][ext]'
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader']
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'copyWebpackPlugin',
      template: './public/index.html'
    }),
    new DefinePlugin({
      BASE_URL: '"./"'
    }),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'public',
          globOptions: {
            ignore: ['**/index.html']
          }
        }
      ]
    })
  ]
}

这是启动yarn serve命令,发现我们手动修改index.js中的打印内容后,页面也会随之变化。

但是,我们发现,当在文本框内输入内容后,再次修改index.js打印内容,页面刷新后输入框中的内容也随之消失,整个界面被全部刷新,这并不符合我们的局部模块热更新的需求。

新建与index.js同目录的title.js文件,其中打印字符串,并在index.js做如下配置。修改过后,重新启动yarn serve,然后依次修改title.js中的打印内容。我们发现,html文件的打印内容随着变化,并且console中的打印历史纪录也不会被清空。随后在页面输入框内输入文字,再次修改title.js中的打印内容,Webpack热更新过后,发现页面的输入框文字并没有消失,而且打印内容也随之变化。这样就实现了HMR热更新效果。

26.React 组件支持热更新

第一步:Webpack支持jsx打包;第二步:Webpack支持react的热更新。

第一步创建App.jsx React组件,在其中书写title文字内容。其次在index.js中进行导入,并挂在到提前准备好的index.html模板中的id="app"中。其次在Webpack.config.js中引入针对jsxloader。此时,启动yarn serve,发现每当修改title.js中的打印内容时,console会随之变化。但是当修改App.jsx中的title文字时,发现console中已经热更新的打印内容被清除了。这是因为目前还没有实现针对React组件模块的热更新效果。

第二步,需要实现针对React组件的热更新效果。在Webpack.config.js中引入@pmmmwh/react-refresh-Webpack-plugin,并且在相应的plugins中进行创建new ReactRefreshWebpackPlugin()。随后在babel.config.js中进行单独的配置react-refresh/babel,使其提供模块热更新的能力。

随后,再次修改title.js中内容后,我们发现对应的console中为打印结果已经被修改。再次修改React组件App.jsx中的title文字内容后,发现console中的打印内容被保留了下来,至此我们就实现了React组件支持热更新功能。

module.exports = {
  presets: [
    ['@babel/preset-env'],
    ['@babel/preset-react'],
  ],
  plugins: [
    ['react-refresh/babel']
  ]
}

27.Vue 组件支持热更新

提前准备App.vue

<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello world!',
    }
  },
}
</script>

<style>
.example {
  color: orange;
}
</style>

当前Webpack并不能识别.vue结尾的文件,所以需要在Webpack.confiog.js中针对.vue结尾的文件进行处理.在Vue2中使用vue-loader14版本的时候只需要做如下配置。

{
  test: /\.vue$/,
    use: ['vue-loader']
}

但是在VUe2中使用vue-loader15版本时,需要单独引入vue-loader/lib/plugin

根据Webpack官方文档的提示,vue-loader是其默认提供的,不需要单独安装。

vue-loader的16版本是专门提供给Vue3的,不能在Vue2中使用。其次Vue2中,如果使用vue-loader14版本的,可以做到开箱即用。但是如果升级到15版本后,需要单独的引入vue-loader/lib/plugin,并且进行new操作才可以正常实现HMR功能。

下一步就是在index.js中引入App.vue文件,将其挂在到依赖图当中。

import './title'
import Vue from 'vue'
import App from './App.vue'

if (module.hot) {
  module.hot.accept(['./title.js'], () => {
    console.log('title.js模块更新')
  })
}

new Vue({
  render: h => h(App)
}).$mount('#root')

随后,依据上一个内容的测试,分别修改title.js与App.vue中的内容,发现已经实现了Vue的HMR模块热更新的功能。

28.output 中的 path

  • path:所有输出文件的目标路径;打包后文件在硬盘中的存储位置。
  • publicPath:输出解析文件的目录,指定资源文件引用的目录 ,打包后浏览器访问服务时的 url 路径中通用的一部分。

区别:

path是Webpack所有文件的输出的路径,必须是绝对路径,比如:output输出的js,url-loader解析的图片,HtmlWebpackPlugin生成的html文件,都会存放在以path为基础的目录下。publicPath 并不会对生成文件的路径造成影响,主要是对你的页面里面引入的资源的路径做对应的补全,常见的就是css文件里面引入的图片。

output:“path”项和“publicPath”项

output项告诉Webpack怎样存储输出结果以及存储到哪里。output的两个配置项“path”和“publicPath”可能会造成困惑。“path”仅仅告诉Webpack结果存储在哪里,然而“publicPath”项则被许多Webpack的插件用于在生产模式下更新内嵌到css、html文件里的url值。

例如,在localhost(译者注:即本地开发模式)里的css文件中边你可能用“./test.png”这样的url来加载图片,但是在生产模式下“test.png”文件可能会定位到CDN上并且你的Node.js服务器可能是运行在HeroKu上边的。这就意味着在生产环境你必须手动更新所有文件里的url为CDN的路径。

然而你也可以使用Webpack的“publicPath”选项和一些插件来在生产模式下编译输出文件时自动更新这些url。

// 开发环境:Server和图片都是在localhost(域名)下
.image { 
  background-image: url('./test.png');
 }
// 生产环境:Server部署下HeroKu但是图片在CDN上
.image { 
  background-image: url('https://someCDN/test.png');
 }

29.devserver 中的 path

这个配置其实是蛮有讲头的,他的本意也是路径前缀,但是跟其他publicPath有所区别:

首先,devServer配置起作用需要两个条件:

安装了Webpack-dev-server
使用开发环境起服务,脚本一般为Webpack serve
有了以上的前缀,现在具体来讲讲这个配置的具体应用:

比如publicPath设置成了/yuhua/,那么意思就是访问服务需要带上公共资源路径/yuhua/,也就是说,不写publicPath,那么访问在http://localhost:8080,如果publicPath/yuhua/,那么访问就在http://localhost:8080/yuhua/,包括访问打包产物,也是在http://localhost:8080/yuhua/下访问。

const path = require('path');
module.exports = {
    devServer: {
        publicPath: '/yuhua/', // 服务会起在/yuhua/下,比如不写这个publicPath的时候,访问是http://localhost:8080/,那么写了之后就是http://localhost:8080/yuhua就能访问到首页,以及dist打包出来的资源都以后者为路径前缀
        contentBase: path.resolve(__dirname, 'public'), // 额外的静态文件目录内容,所谓的额外的,就是指不会被打包进去的,但是我可以将contentBase作为静态服务器资源目录,其实就相当于景台服务器,浏览器可以直接访问对应目录下的文件
      },
}

如果在output中也设置了publicPath,这其实是不冲突的,比如下面的代码,那么你所有的打包产物的访问地址会有两步处理:

打包出的结果,引用资源都会加上output下的publicPath。起开发服务后,会在服务地址后加上devServer下的publicPath。所以综上所述

  • 起的开发服务地址是:http://localhost:8080/[devServer下的publicPath]
  • 起的开发服务下打包资源地址是:http://localhost:8080/[devServer下的publicPath]/[output下的publicPath]
const path = require('path');
module.exports = {
    output: {
        path: ptah.resolve(__dirname, 'dist')
        filename: 'main.js',
        publicPath: 'abc/' // 这里配置,那么html中引用的图片<img src="./a,png"/>就会打包成<img src="abc/a.png">
    },
    devServer: {
        publicPath: '/yuhua/', // 服务会起在/yuhua/下,比如不写这个publicPath的时候,访问是http://localhost:8080/,那么写了之后就是http://localhost:8080/yuhua就能访问到首页,以及dist打包出来的资源都以后者为路径前缀
        contentBase: path.resolve(__dirname, 'public'), // 额外的静态文件目录内容,所谓的额外的,就是指不会被打包进去的,但是我可以将contentBase作为静态服务器资源目录,其实就相当于景台服务器,浏览器可以直接访问对应目录下的文件
      },
}

devServer还有一个属性叫做contentBase,这个属性用于额外的静态文件目录内容,所谓的额外的,就是指不会被打包进去的,但是我可以将contentBase作为静态服务器资源目录,其实就相当于景台服务器,浏览器可以直接访问对应目录下的文件。另外,如果找不到dist下(内存中)的静态文件,就会去这个目录下找。

举例说明:

有一个public文件夹下的text.txt,内容为123。同时,contentBase属性的值就是path.resolve(__dirname, ‘public’),那么这时候访问text.txt文件的地址为:http://localhost:8080/test.txt。意思就是我将contentBase对应的目录,所谓我静态资源服务目录,可以直接访问。

那么contentBasepublicPath有关系吗?其实没有什么关系,不论是outputpublicPath还是devServerpublicPath,都没关系

const path = require('path');
module.exports = {
    output: {
        path: ptah.resolve(__dirname, 'dist')
        filename: 'main.js',
        publicPath: 'abc/'
    },
    devServer: {
        publicPath: '/yuhua/',
        contentBase: path.resolve(__dirname, 'public'),
      },
}

30.devServer 常用配置

使用Webpack-dev-server是,有其他可用配置,可使得开发阶段拥有跟好的体验和性能。

  • hotOnly:true

    当我们的某个组件发生语法性错误时,Webpack会自动帮我们抛出错误,但是当我们修改完错误后,Webpack-dev-server会自动刷新整个页面,这就导致某些已经拥有数据的组件会重新刷新初始化。我们希望在修改完错误后,只针对修改错误的组件进行刷新,这时就可以开启hotOnly只针对当前错误组件进行刷新,保留其他组件的状态。

  • port:4000

    Webpack-dev-server默认的端口号是8080,但是如果我们的8080端口号被其他服务占用时,可以开启port配置,并设置自己想要Webpack-dev-serve提供服务的端口号。

  • open:true

    Webpack-dev-server打包完成后,默认情况下不会帮我们打开浏览器,需要用户手动打开浏览器访问localhost:8080。这时可以设置opentrue,当Webpack-dev-server打包完成后,自动打开浏览器。但是,当我们修改其中的文件后,Webpack-dev-server会帮我们再次打开浏览器,这就导致有多个浏览器进程存在,影响性能。所以一般情况下,我们保持关闭false状态。

  • compress:true

    Webpack-dev-server默认打包的资源不会进行压缩,可以开启compress选项,对文件进行gzip压缩,提高页面访问性能。

  • historyApiFallback:true

    一般情况下,当我们使用VueReact时,提供路由跳转功能,当前端路由跳转后,浏览器路径为前端控制。但是当我们手动对当前页面刷新(相当于重新向服务器索要about页面),会出现404状态。historyApiFallback开启后,会将404重定向到index.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

5coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值