返回介绍

从 NuxtJS 迁移

发布于 2024-06-05 21:19:56 字数 28883 浏览 0 评论 0 收藏 0

这里是一些帮助你开始的关键概念和迁移策略。使用我们的其余文档和我们的 Discord 社区 来保持更新!

本指南是针对 Nuxt 2 的,而不是新的 Nuxt 3。虽然两者有一些相似的概念,但 Nuxt 3 是该框架的新版本,迁移的某些部分可能需要不同的策略。

Nuxt 和 Astro 的关键相似之处

Nuxt 和 Astro 有一些相似之处,这些相似性能够帮助你完成迁移:

Nuxt 和 Astro 的关键区别

当你使用 Astro 来重构 Nuxt 项目时,你会注意到一些重要的差异:

  • Nuxt 是一个基于 Vue 的 SPA (单页应用程序)。而 Astro 站点是使用 .astro 组件构建的多页应用,但也支持 React、Preact、Vue.js、Svelte、SolidJS、AlpineJS、Lit 和原生 HTML 模板。

  • 页面路由:Nuxt 使用 vue-router 进行 SPA 路由,并且使用 vue-meta 来管理 <head>。在 Astro 中,你将创建单独的 HTML 页面路由,并直接控制页面的 <head>,或者在布局组件中控制。

  • 内容驱动:Astro 旨在展示你的内容,并允许你仅在需要时选择加入交互性。Nuxt 应用更多是针对客户端的高交互性构建的。Astro 内置有能够处理内容的功能,例如页面生成,但是一些使用 .astro 组件难以复制、更具挑战性的功能,可能需要高级 Astro 技术来处理,例如仪表盘。

转换你的 NuxtJS 项目

每个项目在迁移时的步骤不尽相同,但在从 Nuxt 转换到 Astro 时,你会执行一些常见的操作。

新建一个 Astro 项目

使用你的包管理器的 create astro 来启动 Astro 的 CLI 向导,或者从 Astro 主题展示 中选择一个社区主题。

你可以将 --template 参数传给 create astro 命令,使用我们的官方入门模板之一启动新的 Astro 项目 (例如 docs, blog, portfolio)。或者,你可以从 GitHub 上的任何现有的 Astro 库开始一个新项目

  • npm
  • pnpm
  • Yarn
# 启动 Astro CLI 向导
npm create astro@latest


# 使用官方示例创建新项目
npm create astro@latest -- --template <example-name>
# 启动 Astro CLI 向导
pnpm create astro@latest


# 使用官方示例创建新项目
pnpm create astro@latest --template <example-name>
# 启动 Astro CLI 向导
yarn create astro@latest


# 使用官方示例创建新项目
yarn create astro@latest --template <example-name>

接下来,将现有的 Nuxt 项目文件复制到 Astro 项目中的 src 文件夹之外的单独文件夹中。

安装集成(可选)

安装一些 Astro 的可选集成 在将 Nuxt 项目转换为 Astro 时很有用:

  • @astrojs/vue:你可以在新的 Astro 网站中重用一些现有的 Vue UI 组件,或者继续使用 Vue 组件编写。

  • @astrojs/mdx:从你的 Nuxt 项目将现有的 MDX 文件带入,或者在新的 Astro 网站中使用 MDX。

将你的源码放入 src

  1. 将 Nuxt 的 static/ 文件夹中的内容 移动public/ 中。

    Astro 使用 public/ 存放静态资源,类似 Nuxt 的 static/ 文件夹。

  2. 复制 或 移动 Nuxt 的其他文件和文件夹(例如 pageslayouts 等)到 Astro 的 src/ 文件夹中。

    和 Nuxt 一样,Astro 的 src/pages/ 是一个特殊的文件夹,用于基于文件的路由。所有其他文件夹都是可选的,你可以以任何方式组织 src/ 文件夹的内容。Astro 项目中的其他常见文件夹包括 src/layouts/src/componentssrc/stylessrc/scripts

将 Vue SFC 页面转换为 .astro 文件

这里有一些将 Nuxt 的 .vue 组件转换为 .astro 组件的建议:

  1. 使用现有 NuxtJS 组件的 <template> 作为你的 HTML 模板的基础。

  2. 将任意的 Nuxt 或 Vue 语法更改为 Astro 或转换为 HTML Web 标准。包括<NuxtLink>:class{{variable}}v-if等。

  3. <script> 中的 JavaScript 代码移到一个 “代码块” (---) 中。将组件的数据属性转换为服务器端 JavaScript - 参见 Nuxt 数据获取到 Astro 中

  4. 使用 Astro.props 来访问之前传递给 Vue 组件的其他 props。

  5. 决定是否需要将导入的组件也转换为 Astro。安装了官方的集成后,你可以 在 Astro 文件中使用现有的 Vue 组件。但是,你可能想把它们转换成 Astro,尤其是如果它们不需要任何交互的情况!

参见 从 Nuxt 应用逐步转换的示例

比较:Vue 和 Astro

比较以下 Nuxt 组件和对应的 Astro 组件:

  • Vue
  • Astro
Page.vue
<template>
  <div>
    <p v-if="message === 'Not found'">
      The repository you're looking up doesn't exist
    </p>
    <div v-else>
      <Header/>
      <p class="banner">Astro has {{stars}}‍</p>
      <Footer />
    </div>
  </div>
</template>


<script>
import Vue from 'vue'


export default Vue.extend({
  name: 'IndexPage',
  async asyncData() {
    const res = await fetch('https://api.github.com/repos/withastro/astro')
    const json = await res.json();
    return {
      message: json.message,
      stars: json.stargazers_count || 0,
    };
  }
});
</script>


<style scoped>
.banner {
  background-color: #f4f4f4;
  padding: 1em 1.5em;
  text-align: center;
  margin-bottom: 1em;
}
<style>
Page.astro
---
import Header from "./header";
import Footer from './footer';
import "./layout.css";


const res = await fetch('https://api.github.com/repos/withastro/astro')
const json = await res.json()
const message = json.message;
const stars = json.stargazers_count || 0;
---


{message === "Not Found" ?
      <p>The repository you're looking up doesn't exist</p> :
      <>
            <Header />
            <p class="banner">Astro has {stars}‍</p>
            <Footer />
        </>
}


<style>
  .banner {
    background-color: #f4f4f4;
    padding: 1em 1.5em;
    text-align: center;
    margin-bottom: 1em;
  }
<style>

迁移布局文件

你可能会发现首先将你的 Nuxt 布局和模板转换为 Astro 布局组件 会很有帮助。

Astro 中的每个页面都要求显式存在 <html><head><body> 标签。你的 Nuxt layout.vue 和模板不会包括这些。

请注意标准的 HTML 模板,以及直接访问 <head>

src/layouts/Layout.astro
<html lang="zh-cn">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <title>Astro</title>
  </head>
  <body>
    <!-- 用你现有的布局模板包装 slot 元素 -->
    <slot />
  </body>
</html>

你可能还希望重用来自 你的 Nuxt 页面的 head 属性 以包含额外的站点元数据。请注意,Astro 既不使用 vue-meta 也不使用组件的 head 属性,而是直接创建 <head> 。你可以在 <head> 内部导入和使用组件,以将页面内容分离和组织起来。

迁移页面和文章

在 NuxtJS 中,你的 页面 位于 /pages。在 Astro 中,页面的所有内容必须位于 src/pagessrc/content 中。

Vue 页面

你现有的 Nuxt Vue (.vue) 页面需要 从 Vue 文件转换为 .astro 页面。你不能在 Astro 中直接使用现有的 Vue 页面文件。

这些 .astro 页面 必须位于 src/pages/ 中,它们的文件路径会自动为它们生成页面路由。

动态文件路径命名

在 Nuxt 中,你的动态页面使用下划线来表示动态页面属性,然后该属性将用于页面生成:

  • 文件夹pages/
    • 文件夹pokemon/
      • _name.vue
    • index.vue
  • nuxt.config.js

要转换到 Astro,请将该动态路径属性(例如 _name.vue)用一对方括号包裹起来(例如 [name].astro):

  • 文件夹src/
    • 文件夹pages/
      • 文件夹pokemon/
        • [name].astro
      • index.astro
  • astro.config.mjs

Markdown 和 MDX 页面

Astro 内置支持 Markdown 和可选的 MDX 集成。你可以重用任何现有的 Markdown 以及 MDX 页面,但它们可能需要对前言进行一些调整,例如添加 Astro 的特殊 layout 前言属性

你不再需要为每个 Markdown 生成的路由手动创建页面,或使用像 @nuxt/content这样的外部包。这些文件可以放在 src/pages/中,以利用自动生成的基于文件的路由。

作为 内容集合 的一部分,Markdown 和 MDX 文件将位于 src/content/ 文件夹内,并且将会 动态生成生成这些页面

迁移测试

由于 Astro 输出的是原始的 HTML ,因此,你可以使用构建步骤的输出来编写端到端测试。如果你能够匹配 Nuxt 站点的标记,那么以前编写的任何端到端测试都是开箱即用的。在 Astro 中,你可以导入并使用测试库,例如 Jest 和 Vue Testing Library,来测试你的 Vue 组件。

请参阅 Astro 的测试指南 来了解更多信息。

参考:将 NuxtJS 语法转换到 Astro

Nuxt 本地变量转换到 Astro

要在 Astro 组件的 HTML 中使用本地变量,请将两组大括号更改为一组大括号:

src/components/Component.astro
---
const message = "Hello!"---<p>{{message}}</p>
<p>{message}</p>

Nuxt 属性传递转换到 Astro

要在 Astro 组件中绑定属性或组件属性,请将此语法更改为以下方式:

src/components/Component.astro
---
---<p v-bind:aria-label="message">...</p><!-- 或者是这样 --><p :aria-label="message">...</p><!-- 也支持组件的 props 属性 --><Header title="Page"/>

<p aria-label={message}>...</p><!-- 也支持组件的 props 属性 --><Header title={"Page"}/>

Nuxt 的链接转换到 Astro

将任何 Nuxt 的 <NuxtLink to=""> 组件转换为 HTML 的 <a href=""> 标签

<NuxtLink to="/blog">Blog</Link><a href="/blog">Blog</a>

Astro 不使用任何特殊的链接组件,但你可以构建自定义链接组件。然后,可以像导入和使用任何其他组件一样导入并使用此 <Link>

src/components/Link.astro
---
const { to } = Astro.props
---
<a href={to}><slot /></a>

Nuxt 导入转换到 Astro

如有必要,请更新任何 文件导入 确保引用的是精确的相对文件路径。这可以通过 导入别名 或通过完整编写相对路径来完成。

请注意,必须使用完整的文件扩展名导入.astro 和其他几种文件类型。

src/pages/authors/Fred.astro
---
import Card from `../../components/Card.astro`;
---
<Card />

Nuxt 动态页面生成转换到 Astro

在 Nuxt 中,要生成动态页面,你必须满足以下条件之一:

在 Astro 中,你同样有两个选择:

将 Nuxt 中的 generate 函数转换为 Astro 中的 getStaticPaths 函数。

为了生成多个页面,将你的 nuxt.config.js 中用于创建路由的函数直接替换为在动态路由页面内使用 getStaticPaths()

nuxt.config.js
{
  // ...
    generate: {
        async routes() {
          // 需要使用 axios ,除非你使用的是 Node.js 18 版本。
          const res = await axios.get("https://pokeapi.co/api/v2/pokemon?limit=151")
          const pokemons = res.data.results;
          return pokemons.map(pokemon => {
            return '/pokemon/' + pokemon.name
          })
        }
      }
}
src/pages/pokemon/[name].astro
---
export const getStaticPaths = async () => {
  const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
  const resJson = await res.json();
  const pokemons = resJson.results;
  return pokemons.map(({ name }) => ({
      params: { name },
    }))
}
// ...
---
<!-- 这里是你的模板代码 -->

Nuxt 数据获取转换到 Astro

Nuxt 有两种获取服务器端数据的方法:

在 Astro 中,在页面的代码块中获取数据。

迁移以下内容:

pages/index.vue
{
  // ...
  async asyncData() {
    const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
    const resJson = await res.json();
    const pokemons = resJson.results;
    return {
      pokemons,
    }
  },
}

转换为一个没有包装函数的代码块:

src/pages/index.astro
---
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
const resJson = await res.json();
const pokemons = resJson.results;
---


<!-- 这里是你的模板代码 -->

Nuxt 样式转换到 Astro

Nuxt 利用 Vue 的组件样式来生成页面样式。

pages/index.vue
<template>
  <!-- 这里是你的模板代码 -->
</template>


<script>
  // 你的服务的逻辑代码
</script>


<style scoped>
    .class {
        color: red;
    }
</style>

类似地,在 Astro 中,你可以在页面的模板中添加一个 <style> 元素来为组件提供作用域样式。

src/pages/index.vue
---
// 你的服务的逻辑代码
---


<style>
    .class {
        color: red;
    }
</style>

全局样式

Astro 的 <style> 标签是默认具有 scoped 属性的。想要将 <style> 标签转换为全局样式,需要使用 is:global 来标记:

src/pages/index.vue
<style is:global>
  p {
    color: red;
  }
</style>

预处理器支持

将它们作为开发依赖项安装,Astro 支持最流行的 CSS 预处理器 。例如,要使用SCSS:

npm install -D sass

之后,你就可以在Vue组件中使用 .scss.sass 样式,无需进行修改。

src/layouts/Layout.astro
<p>Hello, world</p>
<style lang="scss">
p {
   color: black;


   &:hover {
       color: red;
   }
}
</style>

有关 Astro 中的样式设置的更多信息,请参阅 样式指南

Nuxt 图片插件迁移到 Astro

根据情况在你的 Vue 组件中将任何 Nuxt 的<nuxt-img/> 或者 <nuxt-picture/> 组件 转换为 Astro 自己的图像集成组件 或者转化成 标准的 HTML <img> 标签 或者 <picture> 标签。

Astro 的 <Image /> 组件只能在 .astro.mdx 文件中工作。查看该组件可用的完整组件属性列表,并注意它与 Nuxt 的属性有一些不同。

src/pages/index.astro
---
import { Image } from 'astro:assets';
import rocket from '../assets/rocket.png';
---
<Image src={rocket} alt="太空中的火箭飞船。" />
<img src={rocket.src} alt="太空中的火箭飞船。">

在你的 Astro 应用中的 Vue (.vue) 组件中,使用标准的 JSX 图像语法 (<img />)。Astro 不会对这些图像进行优化,但你可以安装和使用 NPM 包以获得更大的灵活性。

你可以在图像指南中了解更多关于 在 Astro 中使用图像 的信息。

指导示例:请查看以下步骤:

这里有一个将 Nuxt 的 Pokémon 数据获取转换为 Astro 的示例。

pages/index.vue 使用 the REST PokéAPI 获取并显示前 151 个 Pokémon 的列表。

src/pages/index.astro 中,你可以使用 fetch() 取代 asyncData() 来重新创建这个功能。

  1. 识别 Vue SFC(单文件组件)中的 <template><style> 部分。

    pages/index.vue
    <template>  <ul class="plain-list pokeList">    <li v-for="pokemon of pokemons" class="pokemonListItem" :key="pokemon.name">      <NuxtLink class="pokemonContainer" :to="`/pokemon/${pokemon.name}`">        <p class="pokemonId">No. {{pokemon.id}}</p>        <img          class="pokemonImage"          :src="`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`"          :alt="`${pokemon.name} picture`"/>        <h2 class="pokemonName">{{pokemon.name}}</h2>      </NuxtLink>    </li>  </ul>
    </template>
    
    
    <script>
    import Vue from 'vue'
    export default Vue.extend({
      name: 'IndexPage',
      layout: 'default',
      async asyncData() {
        const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151")
        const resJson = await res.json();
        const pokemons = resJson.results.map(pokemon => {
            const name = pokemon.name;
            // https://pokeapi.co/api/v2/pokemon/1/
            const url = pokemon.url;
            const id = url.split("/")[url.split("/").length - 2];
            return {
                name,
                url,
                id
            }
        });
        return {
          pokemons,
        }
      },
      head() {
        return {
          title: "Pokedex: Generation 1"
        }
      }
    });
    </script>
    
    <style scoped>.pokeList {  display: grid;  grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) );  gap: 1rem;}
    /* ... */</style>
  2. 创建 src/pages/index.astro

    使用 Nuxt SFC(单文件组件)的 <template><style> 标签。将任何 Nuxt 或 Vue 语法转换为 Astro。

    请注意:

    • <template> 已经被移除

    • <style> 元素的 scoped 属性被移除

    • v-for 变为 .map

    • :attr="val" 变为 attr={val}

    • <NuxtLink> 变为 <a>

    • 在 Astro 模板中不需要使用 <> </> 片段

    src/pages/index.astro
    ---
    ---
    <ul class="plain-list pokeList">
      {pokemons.map((pokemon) => (
        <li class="pokemonListItem" key={pokemon.name}>
          <a class="pokemonContainer" href={`/pokemon/${pokemon.name}`}>
            <p class="pokemonId">No. {pokemon.id}</p>
            <img class="pokemonImage" src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`} alt={`${pokemon.name} picture`}/>
            <h2 class="pokemonName">{pokemon.name}</h2>
          </a>
        </li>
      ))}
    </ul>
    
    
    <style>
    .pokeList {
      display: grid;
      grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) );
      gap: 1rem;
    }
    
    
    /* ... */
    </style>
  3. 添加任何必要的导入、属性和 JavaScript 代码。

    请注意:

    • asyncData 函数不再需要。你能够直接在代码块中获取 API 数据。
    • 引入 <Layout> 组件,并将其用作页面模板的包装。
      • 我们将 Nuxt 的 head() 方法传递给 <Layout> 组件,该组件被传递给 <title> 元素作为属性。
    src/pages/index.astro
    ---import Layout from '../layouts/layout.astro';
    
    const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");const resJson = await res.json();const pokemons = resJson.results.map(pokemon => {    const name = pokemon.name;    // https://pokeapi.co/api/v2/pokemon/1/    const url = pokemon.url;    const id = url.split("/")[url.split("/").length - 2];    return {        name,        url,        id    }});
    ---
    
    <Layout title="Pokedex: Generation 1">
      <ul class="plain-list pokeList">
        {pokemons.map((pokemon) => (
          <li class="pokemonListItem" key={pokemon.name}>
            <a class="pokemonContainer" href={`/pokemon/${pokemon.name}`}>
              <p class="pokemonId">No. {pokemon.id}</p>
              <img class="pokemonImage" src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png`} alt={`${pokemon.name} picture`}/>
              <h2 class="pokemonName">{pokemon.name}</h2>
            </a>
          </li>
        ))}
      </ul></Layout>
    
    
    <style>
    .pokeList {
      display: grid;
      grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) );
      gap: 1rem;
    }
    
    
    /* ... */
    </style>

社区资源

更多迁移指南

Recipes

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文