发布于 2026-01-06 3 阅读
0

使用 Nuxt 和 Markdown 构建游乐场

使用 Nuxt 和 Markdown 构建游乐场

最近我一直在尝试使用 Nuxt 来为自己和客户开发原型。我非常喜欢它能够通过命令行使用一些非常实用的基础工具快速启动应用程序的功能。Nuxt 提供了一些我可以立即利用的强大功能:

  • 它是一个用于创建 Vue 应用的框架,抽象化了客户端/服务器的复杂性。这意味着我们可以将其作为开发新应用的起点,或者接入现有的 API。
  • 一个命令行工具,可以根据初始模板生成 shell 应用程序,其中内置了服务器端渲染功能,以实现 SEO 优化和快速加载。
  • 引入服务器端框架(如有必要,但并非总是如此)、UI 框架、测试框架、代码检查和美化工具、用于发出 HTTP 请求的库(Axios)。

在这个项目中,我想要一个基于 Vue 和 Nuxt 的基本 Markdown 博客体验,这样我就可以同时拥有这两个框架的试验场。

以下是具体步骤,您也可以自己动手操作:

  • 创建一个 Shell 应用程序
  • 加载 Markdown 文件
  • 显示博客文章
  • 显示帖子列表
  • 为静态网站生成动态路由

最后就变成了这样

或者,如果您比较心急,可以从这里的GitHub 仓库获取

创建一个 Shell 应用程序

使用默认的 Nuxt 启动模板创建 shell 应用程序,请在命令行运行以下命令:

yarn create nuxt-app starter-for-nuxt-markdown-blog

以下是输出结果:

➜  examples yarn create nuxt-app starter-for-nuxt-markdown-blog
yarn create v1.17.3
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Installed "create-nuxt-app@2.10.1" with binaries:
      - create-nuxt-app
[#################################################################################################################################################################################################] 373/373
create-nuxt-app v2.10.1
✨  Generating Nuxt.js project in starter-for-nuxt-markdown-blog
? Project name starter-for-nuxt-markdown-blog
? Project description Starter for a Nuxt Markdown Blog
? Author name Jenna Pederson
? Choose the package manager Yarn
? Choose UI framework Bulma
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose linting tools ESLint
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools jsconfig.json (Recommended for VS Code)
yarn run v1.17.3
$ eslint --ext .js,.vue --ignore-path .gitignore . --fix
✨  Done in 3.35s.

🎉  Successfully created project starter-for-nuxt-markdown-blog

  To get started:

    cd starter-for-nuxt-markdown-blog
    yarn dev

  To build & start for production:

    cd starter-for-nuxt-markdown-blog
    yarn build
    yarn start

✨  Done in 191.25s.
Enter fullscreen mode Exit fullscreen mode

创建应用后,运行以下命令启动 Nuxt 启动器模板,查看其默认外观:

yarn dev

然后访问http://localhost:3000

加载 Markdown 文件

接下来,我们将使用该frontmatter-markdown-loader软件包从名为“markdown”的目录中拉取 markdown 文件content,并访问每个帖子的 markdown frontmatter(关于 markdown 文件的元数据,在本例中是帖子元数据,如标题、标签、主图)。

添加软件包:

yarn add frontmatter-markdown-loader

创建内容目录:

mkdir -P content/blog

要创建第一篇帖子,请将此文件拖放到content/blog……

然后创建用于存放图像资源的关联目录:

mkdir -P assets/images/blog

并将此图像添加到assets/images/blog

frontmatter-markdown-loader现在我们已经有一些内容了,可以通过在构建步骤中添加以下内容来扩展 webpack 配置nuxt.config.js

build: {
    ...
    extend(config, ctx) {
      config.module.rules.push(
        {
            test: /\.md$/,
            include: path.resolve(__dirname, "content"),
            loader: "frontmatter-markdown-loader",
        }
      );
    }
}
Enter fullscreen mode Exit fullscreen mode

显示博客文章

我们不需要为每篇文章都创建静态页面,因此我们将使用动态路由来加载 Markdown 文件。请参考以下 URL 路径:

/blog/2019-09-22-veggies

/blog/:blog_post_title

或者

/users/jenna-pederson

/users/:username

在这两个例子中,:blog_post_title:username代表了路线的动态部分,或者说是slug。

创建博客目录:

mkdir pages/blog

我们将创建blog目录并添加一个_slug.vue文件。该_slug.vue文件将作为我们博客文章的 Vue 模板。在该文件中pages/blog/_slug.vue,添加以下基本模板:

    <template>
      <div class="container">
        <h1 class="title">
          {{ post.attributes.title }}
        </h1>
        <h2 class="subtitle">
          {{ post.attributes.date }}
        </h2>
        <div class="columns">
          <div class="column is-half is-offset-one-quarter">
            <figure class="image">
              <img :src="imgSrc">
            </figure>
          </div>
        </div>
        <!-- eslint-disable-next-line -->
        <div class="content" v-html="post.html" />
      </div>
    </template>
    <script>
    export default {
      computed: {
        imgSrc () {
          return require(`~/assets/images/blog/${this.post.attributes.hero}`)
        }
      },
      async asyncData ({ params }) {
        try {
          const post = await import(`~/content/blog/${params.slug}.md`)
          return {
            post
          }
        } catch (error) {
          return false
        }
      },
      head () {
        return {
          title: this.post.attributes.title
        }
      }  
    }
    </script>
Enter fullscreen mode Exit fullscreen mode

asyncData我们根据从 URL 获取的 slug 值导入了 Markdown 文件。slug 由 URL 定义。例如params,我们的 URL http://localhost:3000/blog/2019-09-22-veggies的 slug是2019-09-22-veggies`<blog_name>`,因此这将导入该2019-09-22-veggies.md文件并将文章对象分配给组件的数据。

我们使用该v-html指令是为了从 Markdown 渲染原始 HTML。这会导致 eslint 发出警告:

9:26 warning 'v-html' directive can lead to XSS attack vue/no-v-html

您可以点击此处此处阅读更多关于 XSS 漏洞的信息。务必了解您的内容来源——如果您是内容的编写者,请注意,即使是第三方 UI 库也可能引入安全漏洞。我们可以通过忽略eslint-disable-next-line上面这行代码来消除此警告。

现在我们可以打开浏览器,访问http://localhost:3000/blog/2019-09-22-veggies查看这篇文章!

显示帖子列表

下一步是能够在首页显示博客文章列表,并能够导航到每篇文章。

为了在博客文章列表中显示多篇文章,请将此文章content/blog及其图片添加到列表中assets/images/blog

接下来pages/index.vue,我们将asyncData再次使用 Nuxt 的方法来加载所有博客文章,以便将它们显示在页面上。将来,我们可以对这些文章进行分页,或者只加载精选文章显示在网站首页。然后,我们将v-for在模板中添加一个循环来显示这些文章。

    <template>
      <div class="container">
        <h1 class="title">
          Blog Posts
        </h1>
        <section class="posts">
          <div v-for="post in posts" :key="post.attributes.title" class="columns">
            <div class="column is-one-quarter">
              <figure class="image">
                <img :src="imgSrc(post)" :alt="post.attributes.title">
              </figure>
            </div>
            <div class="column is-three-quarters">
              <p class="title is-4">
                <nuxt-link :to="post._path">
                  {{ post.attributes.title }}
                </nuxt-link>
              </p>
              <p class="subtitle is-6">
                {{ post.attributes.tags }}
              </p>
              <div class="content">
                <p>{{ post.attributes.excerpt }}</p>
                <p>{{ post.attributes.date }}</p>
                <nuxt-link :to="post._path">
                  Read
                </nuxt-link>
              </div>
            </div>
          </div>
        </section>
      </div>
    </template>

    <script>
    export default {
      async asyncData () {
        const context = await require.context('~/content/blog', true, /\.md$/)
        const posts = await context.keys().map(key => ({
          ...context(key),
          _path: `/blog/${key.replace('.md', '').replace('./', '')}`
        }))
        return { posts: posts.reverse() }
      },
      methods: {
        imgSrc (post) {
          return require(`~/assets/images/blog/${post.attributes.hero}`)
        }
      }
    }

    </script>
Enter fullscreen mode Exit fullscreen mode

这里我们加载content/blog目录及其所有子目录中的所有 Markdown 文件(如 所示true)。然后,我们将每个键(文件名)映射到其上下文以及其他我们需要的内容。在本例中,我们还将其映射_path到文章的 URL 路径,以便稍后构建链接。上下文最终会包含 frontmatter-markdown-loader 加载的内容:属性(Markdown 文件的 frontmatter)和 html(编译成 HTML 的 Markdown)。

现在,当我们把浏览器指向http://localhost:3000/ 时,应该会看到以下内容:

博客首页

为静态网站生成动态路由

我们还有最后一步,那就是设置动态路由yarn generate,以便与生成用于生产环境的静态站点的步骤配合使用。在这一步中nuxt.config.js,我们将根据content目录中的 Markdown 文件生成路由。

首先,const glob = require('glob')在文件顶部添加以下内容,然后markdownPaths在那里进行定义:

const markdownPaths = ['blog']

这将是一个数组,其中包含指向我们 Markdown 文件的路径。在本例中,我们只有一个文件,但您可以根据['blog', 'portfolio', 'photos', 'recipes']需要扩展到多个文件。

然后,我们将在该文件的末尾添加以下函数:

function dynamicMarkdownRoutes() {
  return [].concat(
    ...markdownPaths.map(mdPath => {
      return glob.sync(`${mdPath}/*.md`, { cwd: 'content' })
        .map(filepath => `${mdPath}/${path.basename(filepath, '.md')}`);
    })
  );
} 
Enter fullscreen mode Exit fullscreen mode

我们将在该代码块中调用该函数。它可以添加到与`or`generate.routes相同的级别modulesbuild

generate: {
  routes: dynamicMarkdownRoutes()
},
Enter fullscreen mode Exit fullscreen mode

为了验证这一点,我们回到命令行并运行命令yarn generate,应该会产生以下输出:

➜  starter-for-nuxt-markdown-blog git:(master) ✗ yarn generate
yarn run v1.17.3
$ nuxt generate
ℹ Production build                                                                                                                                                                                16:54:52
✔ Builder initialized                                                                                                                                                                             16:54:52
✔ Nuxt files generated                                                                                                                                                                            16:54:52

✔ Client
  Compiled successfully in 6.85s

✔ Server
  Compiled successfully in 2.18s


Hash: edf5326aac7133378e50
Version: webpack 4.40.2
Time: 6853ms
Built at: 2019-09-25 16:55:01
                         Asset       Size   Chunks                                Chunk Names
../server/client.manifest.json   7.26 KiB           [emitted]
       125f300a35d8d87618b7.js   2.08 KiB        2  [emitted] [immutable]         pages/blog/_slug
       2eef474de7f0fce0b490.js   2.29 KiB        7  [emitted] [immutable]
       47f38e821f6391ec3abe.js   2.38 KiB        4  [emitted] [immutable]         runtime
       50c6bbcdbcd3e3f623ea.js   34.9 KiB        0  [emitted] [immutable]         app
       72339ed6891dc9a5bab0.js    192 KiB        5  [emitted] [immutable]         vendors.app
                      LICENSES  389 bytes           [emitted]
       d6bf890be21b759c97e5.js   3.38 KiB        6  [emitted] [immutable]
       dc728afc9091988c21a1.js   8.63 KiB  3, 6, 7  [emitted] [immutable]         pages/index
       fc1ca6aa66dbc344a014.js    152 KiB        1  [emitted] [immutable]         commons.app
               img/8c66f4e.jpg   5.78 MiB           [emitted]              [big]
               img/ca9c582.jpg   1.03 MiB           [emitted]              [big]
 + 2 hidden assets
Entrypoint app = 47f38e821f6391ec3abe.js fc1ca6aa66dbc344a014.js 72339ed6891dc9a5bab0.js 50c6bbcdbcd3e3f623ea.js

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
  img/8c66f4e.jpg (5.78 MiB)
  img/ca9c582.jpg (1.03 MiB)

Hash: 898a2ef2951dc7e6c3b6
Version: webpack 4.40.2
Time: 2180ms
Built at: 2019-09-25 16:55:03
                  Asset       Size   Chunks                         Chunk Names
461c3c4ac5f760555a13.js   1.67 KiB        1  [emitted] [immutable]  pages/blog/_slug
8ca9a115422e5af94cd9.js   2.32 KiB        4  [emitted] [immutable]
abf1051240f49f9b6062.js   3.41 KiB        3  [emitted] [immutable]
ec1f17082565c8004784.js   7.71 KiB  2, 3, 4  [emitted] [immutable]  pages/index
              server.js    214 KiB        0  [emitted]              app
   server.manifest.json  603 bytes           [emitted]
 + 5 hidden assets
Entrypoint app = server.js server.js.map
ℹ Generating pages                                                                                                                                                                                16:55:03

 WARN  Cannot stringify POJOs with symbolic keys Symbol(Symbol.toStringTag)                                                                                                                       16:55:03


 WARN  Cannot stringify POJOs with symbolic keys Symbol(Symbol.toStringTag) (repeated 1 times)                                                                                                    16:55:03

✔ Generated /                                                                                                                                                                                     16:55:04
✔ Generated blog/2019-09-25-cupcake                                                                                                                                                               16:55:04
✔ Generated blog/2019-09-22-veggies                                                                                                                                                               16:55:04
✨  Done in 16.11s.
Enter fullscreen mode Exit fullscreen mode

这将在dist目录中生成您的网站。如果您想在正式部署之前进行测试(强烈建议您这样做!),您还可以运行命令yarn build,然后yarn start在该目录中启动静态网站的 HTTP 服务器。

希望这能帮助你开始使用 Nuxt 和 Markdown 文件构建博客!你可以从这里获取这个版本的代码。我会随着项目的不断完善持续更新这个仓库。接下来,我们或许可以深入探讨“无法将带有符号键的 POJO 字符串化”的警告,或者使用 Moment.js 格式化日期显示,甚至将其连接到无头 CMS。

准备好立即以此作为您在 Netlify 上的入门方案了吗?您也可以这样做!

部署到 Netlify

文章来源:https://dev.to/jennapederson/building-a-playground-with-nuxt-and-markdown-4c5e