devServer

自动编译

  • 目前我们的代码,为了运行需要有两个操作:
    • 操作一:npm run build,编译相关的代码;
    • 操作二:通过live server或者直接通过浏览器,打开index.html代码,查看效果;
    • 频繁执行这个过程会影响我们的开发效率,其实可以做到,当文件发生变化时,可以自动的完成 编译和展示;
  • 为了完成自动编译,webpack提供了几种可选的方式:

    • webpack watch mode;
    • webpack-dev-server(常用);
    • webpack-dev-middleware;

      Webpack watch

  • webpack给我们提供了watch模式

    • 在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,代码将被重新编译;
    • 不需要手动运行 npm run build指令了
  • 如何开启watch呢?两种方式:

    • 方式一:在导出的配置中,添加 watch: true;

      1. modele.exports = {
      2. watch: true,
      3. }
    • 方式二:在启动webpack的命令中,添加 —watch的标识;

      1. "scripts": {
      2. "build": "webpack --watch"
      3. },

      webpack-dev-server

  • 添加watch的方式虽然可以监听到文件的变化,但它本身没有自动刷新浏览器的功能:

    • 类似VSCode中使用live-server这样的插件可以完成自动刷新的功能;
    • 在不使用live-server的情况下,可以具备live reloading(实时重新加载)的功能,我们可以使用webpack-dev-server

npm install webpack-dev-server -D

  1. "scripts": {
  2. "build": "webpack",
  3. "serve": "webpack serve"
  4. },

contentBase,只能在webpack4中使用,在webpack5的版本中,用static属性来替换,默认是 ‘public’ 文件夹),将其设置为 false 以禁用:
static设置以后如果我们引入了文件中src中没有的内容,就回到默认的public文件夹中查找。

  1. devServer: {
  2. // contentBase: './test', // webpack4版本
  3. // static: false, //禁用
  4. static: ['public'],
  5. },
  • webpack-dev-server 在编译之后不会写入到任何输出文件,而是将 bundle 文件保留在内存中:

    • webpack-dev-server使用了一个库叫memfs(memory-fs webpack自己写的)

      开启HMR

      修改webpack的配置:

      ```javascript module.exports = { target: ‘web’,

    devServer: … hot: true, }, } ``` image.png

  • 但是当修改了某一个模块的代码时,依然是刷新的整个页面:

    • 需要手动去指定哪些模块发生更新时,进行HMR;
    • 这个可以配置在入口文件中; ```javascript import ‘./js/element’; console.log(‘我是测试..’);

// webpack默认情况下不知道要对哪些模块做热替换,所以模块更新的时候都会刷新浏览器。 if (module.hot) { module.hot.accept(‘./js/element.js’, () => { console.log(‘热替换’); }); }

  1. <a name="nJKFH"></a>
  2. ## 框架的HMR
  3. 开发Vue、React项目时,修改了组件,希望进行热更新,社区已经针对这些有很成熟的解决方案了;
  4. - vue开发中,vue-loader支持vue组件的HMR,提供开箱即用的体验;
  5. - 而react开发中,有React Hot Loader,实时调整react组件(目前React官方已经弃用了,改成使用reactrefresh);
  6. <a name="cNvWx"></a>
  7. ## HMR的原理
  8. <a name="EUOSv"></a>
  9. ### HMR的原理是什么?如何做到只更新一个模块中的内容?
  10. <a name="coWhJ"></a>
  11. #### 模块热替换(hot module replacement)
  12. - webpack-dev-server会创建两个服务:
  13. - 提供静态资源的服务(express)和 Socket服务(net.Socket);
  14. - express server是负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析);
  15. - HMR Socket Server,是一个socket的长链接:
  16. - 长链接最大的好处是建立链接后双方可以通信(服务器可以直接发送文件到客户端);
  17. - 当服务器监听到对应模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk);
  18. - 通过长连接,可以直接将这两个文件主动发送给客户端(浏览器);
  19. - 浏览器拿到两个新的文件后,通过HMR runtime机制加载这两个文件,并且针对修改的模块进行更新;
  20. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/439030/1646899717649-4939afd1-1add-44fd-bb6c-ed622b4e9996.png#clientId=u454c9f0d-93a4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=492&id=u3a96f1ed&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1568&originWidth=2552&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2623743&status=done&style=none&taskId=udfdc161c-262d-44e0-8cc4-8876985fa62&title=&width=800)
  21. <a name="PVFk0"></a>
  22. ## hotOnly、host配置
  23. - host设置主机地址:
  24. - 默认值是localhost;
  25. - 如果希望其他地方也可以访问,可以设置为 `0.0.0.0`;
  26. - localhost 和 0.0.0.0 的区别:
  27. - localhost:本质上是一个域名,通常情况下会被解析成127.0.0.1;
  28. - 127.0.0.1:回环地址(Loop Back Address),表示主机自己发出去的包,直接被自己接收;
  29. - 正常的数据库包经过 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层 ;
  30. - 而回环地址,是在网络层直接就被获取到了,不会经过数据链路层和物理层的;
  31. - 比如监听 127.0.0.1时,在同一个网段下的主机中,通过ip地址是不能访问的;
  32. - 0.0.0.0:监听IPV4上所有的地址,再根据端口找到不同的应用程序;
  33. - 比如我们监听 0.0.0.0时,在同一个网段下的主机中,通过ip地址是可以访问的;
  34. <a name="OsSuy"></a>
  35. ## port、open、compress
  36. - port设置监听的端口,默认情况下是8080
  37. - open是否打开浏览器:
  38. - 默认值是false,设置为true会打开浏览器;
  39. - 也可以设置为类似于 Google Chrome等值;
  40. - compress是否为静态文件开启gzip compression:
  41. - 默认值是false,可以设置为true;
  42. ```javascript
  43. module.export = {
  44. devServer: {
  45. // contentBase: './test',
  46. static: false,
  47. static: ['public'],
  48. hot: true,
  49. compress: false,
  50. host: '0.0.0.0',
  51. port: '5200',
  52. open: true,
  53. },
  54. }

image.png

proxy

  • proxy用于设置代理来解决跨域访问的问题:
    • 比如api请求是 http://localhost:8888,但是本地启动服务器的域名是 http://localhost:8000,这个时候发送网络请求就会出现跨域的问题;
    • 可以将请求先发送到一个代理服务器,代理服务器和API服务器没有跨域的问题,就可以解决我们的跨域问题了;
  • 我们可以进行如下的设置:
    • target:表示的是代理到的目标地址,
    • pathRewrite:默认情况下, /api也会被写入到URL中,可以使用pathRewrite删除;
    • secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false;
    • changeOrigin:它表示是否更新代理后请求的headers中host地址;
      • changeOrigin官方说的非常模糊,查看源码发现其实是修改代理请求中的headers中的host属性:
      • 因为真实的请求是需要通过 http://localhost:8888来请求的;
      • 但是因为使用了代码,默认情况下它的值是 http://localhost:8000
      • 如果我们需要修改,那么可以将changeOrigin设置为true即可;

image.png

  1. devServer: {
  2. ...
  3. proxy: {
  4. '/api': 'http://localhost: 8888',
  5. },
  6. },
  1. devServer: {
  2. ...
  3. proxy: {
  4. '/api': {
  5. target: 'http://localhost: 8888',
  6. pathRewrite: {
  7. '^/api': '',
  8. },
  9. secure: false,
  10. changeOrigin: true,
  11. },
  12. },
  13. },

historyApiFallback

  • historyApiFallback是开发中一个常见的属性,它主要作用是解决SPA页面在路由跳转之后,进行页面刷新时,返回404的错误。
    • boolean值:默认是false
    • 如果设置为true,则在刷新返回时404错误时,会自动返回 index.html 的内容;
  • object类型的值,可以配置rewrites属性(了解):
    • 可以配置from来匹配路径,决定要跳转到哪一个页面;
  • 事实上devServer中实现historyApiFallback功能是通过connect-history-api-fallback库的:
    • 可以查看connect-history-api-fallback 文档

resolve模块解析

resolve用于设置模块如何被解析

  • 在开发中会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库;
  • resolve可以帮助webpack从每个 require/import 语句中,找到需要引入的模块代码;
  • webpack 使用 enhanced-resolve 来解析文件路径;

    webpack能解析三种文件路径

  • 绝对路径:

    • 由于已经获得文件的绝对路径,因此不需要再做进一步解析。
  • 相对路径:
    • 在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录;
    • 在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径;
  • 模块路径

    • resolve.modules中指定的所有目录检索模块;
      • 默认值是 [‘node_modules’],所以默认会从node_modules中查找文件;
    • 我们可以通过设置别名的方式来替换初识模块路径,详见alias的配置;

      确定文件还是文件夹

  • 如果是一个文件:

    • 如果文件具有扩展名,则直接打包文件;
    • 否则,将使用 resolve.extensions选项作为文件扩展名解析;
  • 如果是一个文件夹:

    • 会在文件夹中根据 resolve.mainFiles配置选项中指定的文件顺序查找;
    • resolve.mainFiles的默认值是 [‘index’];
    • 再根据 resolve.extensions来解析扩展名;

      extensions和alias配置

  • extensions是解析到文件时自动添加扩展名:

    • 默认值是 [‘.wasm’, ‘.mjs’, ‘.js’,’.json’];
    • 所以如果代码中想要添加加载 .vue 或者 jsx 或者 ts 等文件时,必须自己写上扩展名;
  • 另一个非常好用的功能是配置别名alias

    • 特别是当项目的目录结构比较深的时候,或者一个文件的路径可能需要 ../../../这种路径片段;
    • 可以给某些常见的路径起一个别名;
      1. resolve: {
      2. extensions: ['.js', '.json', '.mjs', '.vue', '.ts', '.jsx', '.tsx'],
      3. alias: {
      4. '@': path.resolve(__dirname, './src'),
      5. js: path.resolve(__dirname, './src/js'),
      6. css: path.resolve(__dirname, './src/css'),
      7. },
      8. },

      如何区分开发环境

  • 目前webpack配置信息都是放到一个配置文件中的:webpack.config.js

    • 当配置越来越多时,这个文件会变得越来越不容易维护;
    • 并且某些配置是在开发环境需要使用的,某些配置是在生成环境需要使用的,当然某些配置是在开发和生成环境都会使用的;
    • 所以,我们最好对配置进行划分,方便我们维护和管理;
  • 那么,在启动时如何可以区分不同的配置呢?
    • 方案一:编写两个不同的配置文件,开发和生成时,分别加载不同的配置文件即可;
    • 方式二:使用相同的一个入口配置文件,通过设置参数来区分它们;
      1. "scripts": {
      2. "serve": "webpack serve --config ./config/webpack.dev.config.js",
      3. "build": "webpack --config ./config/webpack.prod.config.js"
      4. },

入口文件解析

  • 入口文件的配置文件所在的位置如果变成了 config 目录,依然要写成 ./src/index.js;
    • 这是因为入口文件是和另一个属性是有关的 context;
  • context的作用是用于解析入口(entry point)和加载器(loader):
    • 官方说法:默认是当前路径(但是经过我测试,默认应该是webpack的启动目录)
    • 另外推荐在配置中传入一个值;

这里我们创建三个文件:
pwebpack.comm.conf.js
pwebpack.dev.conf.js
pwebpack.prod.conf.js

我们通过这个插件用来合并不通的配置文件
npm install webpack-merge -D

  1. const path = require('path');
  2. const HtmlWebpackPlugin = require('html-webpack-plugin');
  3. const { DefinePlugin } = require('webpack');
  4. const { VueLoaderPlugin } = require('vue-loader/dist/index');
  5. module.exports = {
  6. target: 'web',
  7. entry: './src/main.js',
  8. output: {
  9. path: path.resolve(__dirname, '../build'),
  10. filename: 'js/bundler.js',
  11. },
  12. resolve: {
  13. extensions: ['.js', '.json', '.mjs', '.vue', '.ts', '.jsx', '.tsx'],
  14. alias: {
  15. '@': path.resolve(__dirname, '../src'),
  16. js: path.resolve(__dirname, '../src/js'),
  17. css: path.resolve(__dirname, '../src/css'),
  18. },
  19. },
  20. module: {
  21. rules: [
  22. {
  23. test: /\.css$/,
  24. use: ['style-loader', 'css-loader', 'postcss-loader'],
  25. },
  26. {
  27. test: /\.less$/i,
  28. use: ['style-loader', 'css-loader', 'less-loader'],
  29. },
  30. {
  31. test: /\.(jpe?g|png|svg|gif)$/,
  32. type: 'asset',
  33. generator: {
  34. filename: 'img/[name]-[hash:6][ext]',
  35. },
  36. parser: {
  37. dataUrlCondition: {
  38. maxSize: 100 * 1024,
  39. },
  40. },
  41. },
  42. {
  43. test: /\.(eot|ttf|woff2?)$/,
  44. type: 'asset/resource',
  45. generator: {
  46. filename: 'fonts/[name]-[hash:6][ext]',
  47. },
  48. },
  49. {
  50. test: /\.js$/,
  51. loader: 'babel-loader',
  52. },
  53. {
  54. test: /\.vue$/,
  55. loader: 'vue-loader',
  56. },
  57. ],
  58. },
  59. plugins: [
  60. new HtmlWebpackPlugin({
  61. title: 'Hello cos..',
  62. template: './public/index.html',
  63. }),
  64. new DefinePlugin({
  65. BASE_URL: '"./"',
  66. __VUE_OPTIONS_API__: true,
  67. __VUE_PROD_DEVTOOLS__: false,
  68. }),
  69. new VueLoaderPlugin(),
  70. ],
  71. };
  1. const { merge } = require('webpack-merge');
  2. const commonConfig = require('./webpack.common.config');
  3. module.exports = merge(commonConfig, {
  4. mode: 'development',
  5. devtool: 'source-map',
  6. devServer: {
  7. static: ['public'],
  8. hot: true,
  9. compress: false,
  10. host: '0.0.0.0',
  11. port: '5200',
  12. open: true,
  13. proxy: {
  14. '/api': {
  15. target: 'https://o2o.dailyyoga.com.cn',
  16. pathRewrite: {
  17. '^/api': '',
  18. },
  19. secure: false,
  20. changeOrigin: true,
  21. },
  22. },
  23. },
  24. });
  1. const { merge } = require('webpack-merge');
  2. const commonConfig = require('./webpack.common.config');
  3. const { CleanWebpackPlugin } = require('clean-webpack-plugin');
  4. const CopyWebpackPlugin = require('copy-webpack-plugin');
  5. module.exports = merge(commonConfig, {
  6. mode: 'production',
  7. plugins: [
  8. new CleanWebpackPlugin(),
  9. new CopyWebpackPlugin({
  10. patterns: [
  11. {
  12. from: 'public',
  13. to: './',
  14. globOptions: {
  15. ignore: ['**/index.html'],
  16. },
  17. },
  18. ],
  19. }),
  20. ],
  21. });