pngwn / MDsveX

A markdown preprocessor for Svelte.
https://mdsvex.pngwn.io
MIT License
2.48k stars 103 forks source link

How would I go about making an `.svx` blog? #122

Open rchrdnsh opened 4 years ago

rchrdnsh commented 4 years ago

Hi MDsveX! XD

I recently cloned the mdsvex starter and it's quite nice :-) However, I noticed that the blog portion of the MDSveX starter is still a single file of js objects, rather than a folder of separate .svx files for each post, so I was wondering how one might go about altering the starter to include an MDSveX powered .svx blog :-)

Would it be similar to some of the sapper/markdown tutorials floating around or would it require something completely different in regards to parsing the .svx files and spitting them out as valid svelte/html?

Would love to see this added/implemented/instructed in the near future XD

pngwn commented 4 years ago

I would like to create some guides, templates, tutorials on this in the future to go through a few ways of achieving it. I doubt I'll get this done anytime soon but I would like to get a half decent blog starter figured out in the short term as a reference. I'll see if I can dedicate some time to this soon.

rchrdnsh commented 4 years ago

A reference would be awesome, thank you :-) Maybe add it to the MDsveX starter! XD

shiryel commented 4 years ago

I was using MDsveX precisely to make my blog, where each post is a .svx file.

Maybe it can helps you :smiley_cat:: https://github.com/shiryel/shiryel_blog_sapper

rchrdnsh commented 4 years ago

Hi @shiryel :-) It's a bit hard for me to follow your code...where do you grab the svx files and parse them into svelte files in your codebase?

Hi @pngwn...I have a semi working version of basic markdown support using the following code:

first, in index.json.js:

import fs from 'fs'
import path from 'path'
import marked from 'marked'
import grayMatter from 'gray-matter'

function getAllPosts(filesPath) {
  const data = fs.readdirSync(filesPath).map((fileName) => {
    const post = fs.readFileSync(path.resolve(filesPath, fileName), "utf-8");

    // Parse Front matter from string
    const { data, content } = grayMatter(post);

    // Turns markdown into html
    const renderer = new marked.Renderer();
    const html = marked(content, { renderer });

    // Builds data
    return {
      ...data,
      html
    };
  });
  return data;
}

export function get(req, res) {
  const posts = getAllPosts("src/posts");
  res.writeHead(200, {
    "Content-Type": "application/json",
  });
  res.end(JSON.stringify(posts));
}

...and in [slug].json.js:

import fs from "fs";
import path from "path";
import marked from "marked";
import grayMatter from "gray-matter";

export function get(req, res) {
  // the `slug` parameter is available because
  // this file is called [slug].json.js
  const { slug } = req.params;

  res.writeHead(200, {
    "Content-Type": "application/json",
  });

  // Reading correct file
  const post = fs.readFileSync(
    path.resolve("src/posts", `${slug}.md`),
    "utf-8"
  );

  // Parse front matter
  const { data, content } = grayMatter(post);

  // Render html from string
  const renderer = new marked.Renderer();
  const html = marked(content, { renderer });

  res.end(
    JSON.stringify({
      ...data,
      html
    })
  );
}

...which are both used in the index and slug svelte files respectively:

first, in index.svelte:

<script context="module">
  export async function preload({ params, query }) {
    const res = await this.fetch(`blog.json`);
    const posts = await res.json();
    return { posts };
    console.log(posts);
  }
</script>

<script>
  export let posts;
</script>

<style>
  ul {
    margin: 0 0 1em 0;
    line-height: 1.5;
  }
</style>

<svelte:head>
  <title>Blog</title>
</svelte:head>

<div>

  <h1>Home Page of Posts and stuff :-)</h1>

  <ul>
    {#each posts as post}
      <li>
        <a rel="prefetch" href="blog/{post.slug}">{post.title}</a>
      </li>
    {/each}
  </ul>

</div>

and in [slug].svelte:

<script context="module">
  export async function preload({ params, query }) {
    const res = await this.fetch(`blog/${params.slug}.json`);
    const data = await res.json();

    if (res.status === 200) {
      return { post: data };
    } else {
      this.error(res.status, data.message);
    }
  }
</script>

<script>
  export let post;
</script>

<styles> ...styles go here... </styles>

<svelte:head>
  <title>{post.title}</title>
</svelte:head>

<div class="content">
  <h1 class='title'>{post.title}</h1>
  {@html post.html}
</div>

...I got all this from Level up Tuts, btw, not by myself :-(

Anyway, my question is would the process of using .svx files instead .md files be similar to this approach, or would it require a completely different approach? Off the top of my head I would imagine that gray-matter and marked probably aren't needed, but one would still need to traverse the file system, no? Does JSON need to be generated for .svx files? Or does MDsveX handle that already? Would it be simpler? More complex?

Apologies, for all the questions, I would just love to figure this all out XD

rdela commented 4 years ago

@rchrdnsh:

Anyway, my question is would the process of using .svx files instead .md files be similar to this approach, or would it require a completely different approach? Off the top of my head I would imagine that gray-matter and marked probably aren't needed, but one would still need to traverse the file system, no? Does JSON need to be generated for .svx files? Or does MDsveX handle that already? Would it be simpler? More complex?

If you can't wait for pngwn, some of your questions are answered already in this thread, starting with Larry's comment from May 31.

shiryel commented 4 years ago

@rchrdnsh I recommend you using a sapper template (you can find it in the docs on integrations), they will setup all the basics for what you want to do, like using the --ext '.svelte .svx' on package.json. And they will work like a .svelte on sapper, you just need to follow the sapper guides on how to make your blog :smile_cat:

PS: Sorry for the late response, a game jam wiped my free time xD

mcmxcdev commented 4 years ago

@rchrdnsh I also struggled a long time with getting .svx up and running, but I managed by taking a look at @shiryel implementation (thanks a lot!!) and now it works fine. Frontmatter gets used accordingly, images in the blog posts are lazy loaded, code snippets highlighted with prism.

You can take a look here for reference: https://github.com/mhatvan/markushatvan.com

rchrdnsh commented 4 years ago

hmmmm...so i've been trying to go through everybody's code and I noticed that content is living inside of the routes folder, which won't work for me, unfortunately.

Firstly, I want to eventually use some sort of cms in the future, like netlify CMS, and it expects content to live in a certain place in the file structure, outside of the routes folder. So now I am trying to figure out how to use mdsvex in this capacity, where the content lives somewhere OUTSIDE of the routes folder and also each post is not named using the name of the article, but rather its parent folder is the name of the article. All the posts themselves are labeled index.md and exist in a folder with the post name, like this:

src/content/articles/post-one/index.md

...the reason for this is that each post has associated assets, like images and audio and video clips, sometimes many of them per article, and I don't want to have to chase these down over the entire known universe. I was able to achieve this all in gatsby using their gatsby-node file, so I'm trying to figure out how to essentially recreate that particular workflow in svelte/sapper/sveltekit

any thoughts as to how this might be achieved would be very welcome :-) Extremely new to node, so learning as I go here...

alecStewart1 commented 3 years ago

While I'm glad I found this, I'm curious about something.

In the example you give, @mhatvan, there's the use of marked here, but from what I understand, mdsvex's job is to create valid Svelte code, and then that's handled by the svelte compiler. In the case of Sapper, you could run npm export and you'd get static HTML, Javascript, and CSS (if I have that correctly).

So isn't the use of marked redundant?

All we need to do is make use of mdsvex's compile function, yes? Then make sure the created corresponding [slug].svelte files are passed along to the svelte compiler? I imagine with rollup this isn't quite hard with the svelte-preprocessor plugin, but I'm not entirely sure.

Again, I don't exactly know for sure, I'm just asking.

mcmxcdev commented 3 years ago

A lot of time has passed since I set this up, so I don't have a good answer to your question. I assume that it is necessary to run the code through marked to be able to identify code snippets which can be highlighted with prismjs.

Madd0g commented 3 years ago

hey, I've been chasing a problem for a few days now.

I'm trying to separate my markdowns from the files in /routes and load them from a separate folder on the disk. Everyhing I tried failed, I wasn't able to find any approach that allows to dynamically load a markdown (with fs.readFile) that contains a svelte component.

I tried to use mdsvex programatically (mdsvex.compile()), but I don't know how to handle it next.

I tried:

compiling with svelte

(based on an example a nice person in the svelte discord put together)

import { compile as mdsvexCompile } from "mdsvex";

const preprocessed = await mdsvexCompile(testSource, mdsvexConfig);

const compiled = svelte.compile(preprocessed.code);

but this just gives back JS which I couldn't turn back into HTML, I even tried to use eval(compiled.js.code); (which felt dirty but after many tries worked, but failed to actually load the referenced svelte component as JS code).

creating a symlink in /routes

I feel this almost worked - it fails with:

Cannot find module 'svelte/internal' from '<my-project>/src/routes/p'

which makes me think it's looking for svelte in the symlinked folder and not in the folder where sveltekit is installed.

Any advice? I don't want to put markdowns in the same folder where I put code and .svelte files.

alecStewart1 commented 3 years ago

@Madd0g

What is in compiled.js.code? Just a bunch of jumbled JS code?

Madd0g commented 3 years ago

@alecStewart1 - yes

I forgot to mention that these are the parameters I'm compiling with (it's in the linked gist)

const compiled = svelteCompile(
        preprocessed.code,
        {
            generate: 'ssr',
            format: "cjs",
            hydratable: false,
        }
    );

so basically I'm getting an SSR version of the compiled component with require statements (because of the format: "cjs")

sharu725 commented 3 years ago

Hi MDsveX! XD

I recently cloned the mdsvex starter and it's quite nice :-) However, I noticed that the blog portion of the MDSveX starter is still a single file of js objects, rather than a folder of separate .svx files for each post, so I was wondering how one might go about altering the starter to include an MDSveX powered .svx blog :-)

Would it be similar to some of the sapper/markdown tutorials floating around or would it require something completely different in regards to parsing the .svx files and spitting them out as valid svelte/html?

Would love to see this added/implemented/instructed in the near future XD

That's exactly what I have done here - https://github.com/sharu725/hagura-sveltekit. It is live here - https://hagura-sveltekit.netlify.app/

The main problem was getting all the posts in an index file(homepage) Vite has a nice utility called import.meta.glob() which can be used like this

const posts = import.meta.glob("./blog/*.{md,svx}")

This will fetch all the frontmatter in .md and .svx files.

Here is how I have done it - https://github.com/sharu725/hagura-sveltekit/blob/main/src/routes/index.svelte

spences10 commented 3 years ago

This is great @sharu725 I'm doing something similar with globEager I'll use your example for getting the path because at the moment I'm using a frontmatter slug.

This may be considered off topic so I'll open another issue for it but I would really like to be able to get the post headings so I can use them in a ToC, have you had explored doing that?

sharu725 commented 2 years ago

This is great @sharu725 I'm doing something similar with globEager I'll use your example for getting the path because at the moment I'm using a frontmatter slug.

This may be considered off topic so I'll open another issue for it but I would really like to be able to get the post headings so I can use them in a ToC, have you had explored doing that?

I'm trying that. Apparently I'm facing an issue in Sveltekit. Let me know if you've found some solution to get headers.

<script context="module">
  const modules = import.meta.globEager("./*.md");
  export const body = Object.entries(modules).map(([filepath, module]) => {
    const { metadata } = module;
    const { html } = module.default.render();
    return {
      html,
      ...metadata,
    };
  });
  export const load = async () => {
    const posts = await Promise.all(body);

    return {
      props: {
        posts,
      },
    };
  };
</script>

In the above code

const { html } = module.default.render()

runs fine on server. But not on the browser.

It throws a 500 error

module.default.render is not a function
httpassoca commented 2 years ago

I'm with same issue here, module.default.render is not a function

janosh commented 2 years ago

As for a guide/template/source of inspiration, there's also https://github.com/mvasigh/sveltekit-mdsvex-blog by @mvasigh which looks very well done.

spences10 commented 2 years ago

This is great @sharu725 I'm doing something similar with globEager I'll use your example for getting the path because at the moment I'm using a frontmatter slug. This may be considered off topic so I'll open another issue for it but I would really like to be able to get the post headings so I can use them in a ToC, have you had explored doing that?

I'm trying that. Apparently I'm facing an issue in Sveltekit. Let me know if you've found some solution to get headers.

<script context="module">
  const modules = import.meta.globEager("./*.md");
  export const body = Object.entries(modules).map(([filepath, module]) => {
    const { metadata } = module;
    const { html } = module.default.render();
    return {
      html,
      ...metadata,
    };
  });
  export const load = async () => {
    const posts = await Promise.all(body);

    return {
      props: {
        posts,
      },
    };
  };
</script>

In the above code

const { html } = module.default.render()

runs fine on server. But not on the browser.

It throws a 500 error

module.default.render is not a function

So, just for reference and anyone else coming across this discussion I've been informed by @babichjacob that using globEager in a .svelte file should be avoided as it loads every single file from globEager into memory of the svelte component.

So glob, yes with the specified file path but globEager no.

I'm still yet to fin an effective way to do this with the filing structure I use for my .md files.