Budování hřiště s Nuxt a Markdown

V poslední době jsem si hrál s Nuxtem při vytváření prototypů nápadů pro sebe a pro klienty. Opravdu se mi líbilo mít možnost bootstrap aplikace z příkazového řádku s hrstkou opravdu užitečných základních nástrojů. Nuxt má několik vychytávek, které mohu využít hned na začátku:

  • Je to rámec pro vytváření aplikací Vue, který abstrahuje složitosti klient/server. To znamená, že jej můžeme použít jako výchozí bod pro novou aplikaci nebo pro připojení ke stávajícím rozhraním API.
  • Příkazový řádek, který generuje shellovou aplikaci ze startovací šablony, kde je integrováno vykreslování na straně serveru pro SEO a rychlé načítání.
  • Zavedení rámce na straně serveru (je-li to nutné, ale není to vždy), rámce uživatelského rozhraní, testovací rámce, linting a prettifying, knihovna (Axios) pro vytváření požadavků HTTP.

Pro tento projekt jsem chtěl základní zkušenosti s blogem Markdown s Vue a Nuxt, abych mohl mít hřiště pro oba.

Zde je návod, jak to dopadlo a jak si také můžete udělat svůj vlastní. Projdeme si tyto kroky:

  • Vytvořte aplikaci Shell
  • Načíst soubory Markdown
  • Zobrazit příspěvek na blogu
  • Zobrazit seznam příspěvků
  • Generovat dynamické trasy pro statický web

A skončete s tím.

Nebo pokud jste netrpěliví, stáhněte si to z úložiště GitHub zde.

Vytvořte aplikaci Shell

Vytvořte shellovou aplikaci z výchozí spouštěcí šablony Nuxt spuštěním následujícího z příkazového řádku:

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

Takto vypadá výstup:

➜  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 "[email protected]" 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.

Po vytvoření aplikace se podívejte, jak vypadá výchozí startovací šablona Nuxt tím, že ji spustíte pomocí:

yarn dev

Poté přejděte na http://localhost:3000.

Načíst soubory Markdown

Dále použijeme frontmatter-markdown-loader balíček pro stažení souborů markdown z adresáře s názvem content a získat přístup k markdown frontmatter (metadata o souboru markdown, v tomto případě metadata příspěvku, jako je název, štítky, obrázek hrdiny) pro každý příspěvek.

Přidejte balíček:

yarn add frontmatter-markdown-loader

Vytvořte adresář obsahu:

mkdir -P content/blog

Chcete-li vytvořit první příspěvek, vložte tento soubor do content/blog .

Poté vytvořte přidružený adresář pro obrazové prostředky:

mkdir -P assets/images/blog

A přidejte tento obrázek do assets/images/blog .

Nyní, když máme nějaký obsah, můžeme rozšířit konfiguraci webpack přidáním frontmatter-markdown-loader ke kroku sestavení v nuxt.config.js :

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

Zobrazit příspěvek na blogu

Nemusíme vytvářet statické stránky pro každý příspěvek, který máme, takže místo toho použijeme dynamické směrování k načtení souboru markdown. Zvažte následující cesty URL:

/blog/2019-09-22-veggies

/blog/:blog_post_title

nebo

/users/jenna-pederson

/users/:username

V obou těchto příkladech :blog_post_title a :username představují dynamickou část trasy neboli slimáka.

Vytvořte adresář blogu:

mkdir pages/blog

Vytvoříme blog adresář a přidejte _slug.vue soubor. Toto _slug.vue soubor bude šablonou Vue pro náš blogový příspěvek. V pages/blog/_slug.vue , přidejte následující základní šablonu:

    <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>

V asyncData importovali jsme soubor markdown na základě hodnoty slug, kterou získáme z params . Slug je opět definován adresou URL. Například slimák pro naši adresu URL http://localhost:3000/blog/2019-09-22-veggies je 2019-09-22-veggies , takže to importuje 2019-09-22-veggies.md soubor a přiřaďte objekt příspěvku k datům komponenty.

Používáme v-html za účelem vykreslení raw HTML z našeho markdown. To způsobí eslintovo varování:

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

Více o zranitelnostech XSS si můžete přečíst zde a zde. Ujistěte se, že víte, odkud váš obsah pochází – pokud jej píšete, vězte, že dokonce i knihovny uživatelského rozhraní třetích stran mohou způsobit zranitelnosti zabezpečení. Tohoto varování se můžeme zbavit tím, že jej budeme ignorovat pomocí eslint-disable-next-line řádek přímo nahoře.

Nyní můžeme nasměrovat náš prohlížeč na http://localhost:3000/blog/2019-09-22-veggies a zobrazit příspěvek!

Zobrazit seznam příspěvků

Dalším krokem je možnost zobrazit seznam blogových příspěvků z naší domovské stránky a možnost přejít na každý jednotlivý příspěvek.

Abychom mohli v našem seznamu příspěvků na blogu zobrazit více než jeden příspěvek, přidejte tento příspěvek do content/blog a je to obrázek na assets/images/blog .

V pages/index.vue , budeme používat asyncData společnosti Nuxt metoda znovu načíst všechny blogové příspěvky, abychom je mohli zobrazit na stránce. V budoucnu bychom je mohli stránkovat nebo načítat pouze doporučené příspěvky, které by se zobrazovaly na domovské stránce webu. Poté přidáme v-for smyčka v šabloně pro zobrazení příspěvků.

    <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>

Zde načítáme všechny soubory markdown v content/blog adresář a všechny podadresáře (jak je označeno true ). Potom mapujeme každý klíč (název souboru) na jeho kontext a vše, co chceme. V tomto případě také mapujeme _path na cestu URL k příspěvku, abychom mohli později vytvořit odkazy. Kontext končí tím, co načte frontmatter-markdown-loader:atributy (přední část souboru markdown) a html (markdown zkompilovaný do HTML).

Nyní, když nasměrujeme náš prohlížeč zpět na http://localhost:3000/, měli bychom vidět toto:

Generování dynamických tras pro statický web

Stále nám zbývá jeden krok, a to nastavení dynamických tras pro práci s yarn generate , krok, který generuje statický web pro produkci. V nuxt.config.js , budeme generovat trasy na základě souborů značek, které máme v content adresář.

Nejprve přidejte const glob = require('glob') v horní části souboru a poté definujte markdownPaths tam také:

const markdownPaths = ['blog']

Toto bude pole... cest k našim souborům markdown. V našem případě máme pouze jeden, ale můžete jej rozšířit na ['blog', 'portfolio', 'photos', 'recipes'] nebo cokoli potřebujete.

Potom na konec tohoto souboru přidáme tuto funkci:

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

Tuto funkci budeme volat v generate.routes blok. Toto lze přidat na stejné úrovni jako modules nebo build :

generate: {
  routes: dynamicMarkdownRoutes()
},

Abychom to otestovali, vrátíme se zpět do příkazového řádku a spustíme yarn generate , který by měl vytvořit tento výstup:

➜  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.

Tím se vygeneruje vaše stránka v dist adresář. Chcete-li to vyzkoušet (a pravděpodobně byste měli!) před nasazením naživo, můžete také spustit yarn build a poté yarn start ke spuštění HTTP serveru statického webu v tomto adresáři.

Doufejme, že vám to pomůže začít s budováním blogu pomocí souborů Nuxt a markdown! Zde si můžete stáhnout tuto verzi kódu. Budu pokračovat v aktualizaci tohoto repo, jak ho buduji více. Možná se příště pustíme do těch varování o tom, že „nelze řetězit POJO pomocí symbolických klíčů“ nebo formátování zobrazení data pomocí Moment.js nebo dokonce připojení k bezhlavému CMS.

Jste připraveni začít s tím jako svým startérem na Netlify právě teď? Můžete to udělat taky!