记一次 Nuxt 中 的 loadingTemplate 的学习

312 阅读6分钟

Nuxt 自定义 dev 模式下的 错误模板(loadingTemplate)

每天一个小知识点

在Nuxt 的 dev 里面有一个配置 是loadingTemplate,我原先以为是 开发模式下的 加载 HTML 模板,结果 好像没有我想的那么简单呢!!!

先来看看 Nuxt 官方对其的介绍吧。

loadingTemplate

Template to show a loading screen (显示一个加载场景的模板)接受一个 Function,需要返回 加载的HTML render 字符串

我一开始以为也是这样,直到我配置 了 之后 ,我发现好像没什么 卵用,好像 在开发的时候 不会渲染出来

出于好奇心,我就顺着 Nuxt 的 cli 工具 找 Nuxt 的这个 配置到底是干嘛的。

在 nuxi 的 dev.ts 下 就可以看到其是如何使用这个 loadingTemplate的呢

  async _renderError(res: ServerResponse, _error?: Error) {
    res.statusCode = 503
    res.setHeader('Content-Type', 'text/html')
    const loadingTemplate
      = this.options.loadingTemplate
      || this._currentNuxt?.options.devServer.loadingTemplate
      || (
        await importModule('@nuxt/ui-templates', this.options.cwd).then(
          r => r.loading,
        )
      ).catch(() => {})
      || ((params: { loading: string }) => `<h2>${params.loading}</h2>`)
    res.end(
      loadingTemplate({
        loading: _error?.toString() || this._loadingMessage || 'Loading...',
      }),
    )
  }

在这个地方可以清楚的看到这个地方调用了 devServer 的 loadingTemplate ,options 就是我们传递的 Nuxt 配置项,光看这个函数名称就很明显了,renderError 渲染错误?,我大概是明白了,原来 loadingTemplate 是为了 在 Nuxt 中渲染 错误 存在的呀!!,只是这个名称,真有点无语了。。

好了问题来了?怎么 触发错误了,一开始我看 里面有一个res ,那百分百是什么请求从而 引发的错误 呀,那会不会是我们在 Nuxt 中请求 的时候 出错了,这个模板就 生效了呢。

这里我尝试了两种,一种就是 在Nuxt 中 代码引发的错误,还有一种就是由Nuxt 的 拦截器 引发的 响应错误。

这是我的Nuxt 配置,其中配置了 loadingTemplate

export default defineNuxtConfig({
  // 指定您的应用程序的兼容性日期。 这将影响 CSS 兼容性和 polyfills。
  compatibilityDate: "2024-04-03",
  // Nuxt 开发者 工具的配置项
  devtools: { enabled: true },

  build: {
    // babel 转义
    transpile: ["vuetify"],
  },
  modules: [
    (_options, nuxt) => {
      nuxt.hooks.hook("vite:extendConfig", (config) => {
        // @ts-expect-error
        config.plugins.push(vuetify({ autoImport: true }));
        // fsfdsdfss
      });
    },
    //...
  ],
  devServer: {
    loadingTemplate(data) {
      console.log(data, 'loading template');
      return `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    dev server error(服务错误) 22222
</body>
</html>`;
    },
  },
  vite: {
    vue: {
      template: {
        transformAssetUrls,
      },
    },
  },
});
    },
  },
});

代码引发错误

这个比较简单,我们主要随便在 代码 里面乱写就完了。

对了 我还发现比较离谱的一点,就是 在 vue的 template 当中 你 用了 没有定义的变量,应用程序不会 出错了,但是TS 会 报警

2024-11-02-00-53-12-image.png 但是页面是正常可以的

2024-11-02-00-53-53-image.png

这里看到有一个警告,其实我感觉这种的话,直接报错会不会好一些呢

这样包报错的!

<template>
  <div>
    {{ data }}
  </div>
</template>
<script setup lang="ts">
const data = await useFetch("/api/hello")
fdsfdsfs
</script>

这是 Nuxt 默认的 500 样式

2024-11-02-00-55-14-image.png

当然我们的 目的是触发 503 从而看我们设置的 loadingTeamplate 生效了吗

这种 系统默认的程序错误 一般都是 500,我们怎么自定义我们的错误码呢,让Nuxt可以识别到。

这里就需要 用到 Nuxt 中提供的 创建错误的方法 createError()

       createError 是nuxt 提供的 内置的工具函数,可以 在 服务端和 客户端 中抛出

在服务器端,它将触发一个全屏错误页面,您可以使用 clearError 清除该页面。

在客户端,它将引发一个非致命错误供您处理。如果需要触发全屏错误页面,则可以通过设置 fatal: true 来实现此目的。    

这里我们就可以 使用 createError 来抛出 503错误,看看会不会 使用我们的 模板。

<template>
  <div>
  </div>
</template>
<script setup lang="ts">
 throw createError({ statusCode: 503})
</script>

可以看到 并没有 生效我们的配置。

2024-11-02-01-07-41-image.png

这里说一下 Nxut 这个 默认的错误 样式真的 不错,还有 404 也还挺好看的。

响应 引发 503

这里使用 middleware 对响应进行拦截,然后 返回状态码为 503

export default defineResponseMiddleware((event) => {
    // 设置条件来控制是否返回 503
    const isUnderMaintenance = true;

    if (isUnderMaintenance) {
      // 设置响应状态码为 503
      event.node.res.statusCode = 503;
      // 设置响应内容类型
      event.node.res.setHeader('Content-Type', 'text/html');
      // 设置字符集为中文
      event.node.res.setHeader('charset', 'utf-8');
      // 返回 503 错误内容 
      event.node.res.end(`<div class="loading">
        <div class="loading__bar">loading.....</div>
      </div>`);
    }
  });

发起请求,引发错误

<template>
  <div>
    {{ data }}
  </div>
</template>

其实结果已经显而易见了,渲染的 东西肯定是我们返回的 错误内容呀,虽然这里肯定可以实现 loadingTemplate 的 作用,但是 我们 dev 的loadingTemplate 还是没有生效呀,真是离了个大普呀。\

2024-11-02-01-18-38-image.png

不断的尝试,不断的试错

在 这两种方式都不信的时候 我真的就是欲哭无泪了,特别想放弃,反正用不到,但是 感觉又花了一些时间在这上面,不搞好就 感觉之前的时间不都全浪费了吗,算了,实在不行就看 Nxut 的源码吧,看一下 renderError 是在什么时候触发的。

好在我突然想到了,会不会 loadingTemplate 是跟dev 服务器相关的,也就是 说他捕获的是 服务在启动时的错误,而不是 说 服务在运行中所引发的错误,算了 都试了这么多次,也不缺这一次。

一开始的时候,我直接在 Nuxt.config.ts 中·直接 报错,结果可想而知,那么就是 启动都起不起来。所以应该是,Nuxt 请求 dev 服务器的时候,或者是 dev 服务肯定是运行起来了,产生的错误,这里我也是运行比较好,在 modules 里面 去触发错误,结果您猜怎么着,结果 成功了,真是 跌跌撞撞呀 。

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  // 指定您的应用程序的兼容性日期。 这将影响 CSS 兼容性和 polyfills。
  compatibilityDate: "2024-04-03",
  // Nuxt 开发者 工具的配置项
  devtools: { enabled: true },

  build: {
    // babel 转义
    transpile: ["vuetify"],
  },
  modules: [
    (_options, nuxt) => {
      nuxt.hooks.hook("vite:extendConfig", (config) => {
        // @ts-expect-error
        config.plugins.push(vuetify({ autoImport: true }));
        // 这个地方 会 触发 程序错误。
        fsfdsdfss
      });
    },
    //...
  ],
  devServer: {
    loadingTemplate(data) {
      console.log(data, 'loading template');
      return `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    dev server error(服务错误) 22222
</body>
</html>`;
    },
  },
  vite: {
    vue: {
      template: {
        transformAssetUrls,
      },
    },
  },
});

当前 改了之后需要重启 一下 Nuxt ,因为 热更新 只是 会 去 重新加载 模块,并不会 重新启动 dev 服务(也有可能是 nuxt 的缓存,把之前的响应结果缓存起来了。)

2024-11-02-01-34-25-image.png

我靠,成功了,终于呀,虽然我也不知道有什么 用,但是 就感觉一个东西 从不知道 到知道(就算最后是错的),这种就很舒服。

然后这个 地方我们还可以 把 loadingTemplate 单独 抽离 出去,成为一个HTML文件,因为其本质就是 HTML 字符串嘛,那么 HTML 文件 读出来,返回去那不是一样的 渲染吗?

使用 fs 读取 HTML 文件内容

import fs from "fs";

const devServerError = fs.readFileSync("./public/dev-server-error.html");
// 转为string
const devString = devServerError.toString();

在 loadingTemplate 的时候 返回

  loadingTemplate(data) {
      console.log(data, 'loading template');
      return devString;
    },

最后 我 感觉 这个功能 有点鸡肋,可能都没什么 实质的用处,也有可能是我 了解的还不够,只是误打误撞,撞出来了,但是 经过这么一套下来,我感觉 我对于Nuxt 和 SSR 又理解了不少。