{"id":263893,"date":"2017-12-22T08:59:16","date_gmt":"2017-12-22T15:59:16","guid":{"rendered":"http:\/\/css-tricks.com\/?p=263893"},"modified":"2018-09-19T13:27:17","modified_gmt":"2018-09-19T20:27:17","slug":"the-rise-of-the-butt-less-website","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/the-rise-of-the-butt-less-website\/","title":{"rendered":"The Rise of the Butt-less Website"},"content":{"rendered":"
It seems like all the cool kids have divided themselves into two cliques: the Headless CMS<\/strong> crowd on one side and the Static Site Generator<\/strong> crowd on the other. While I admit those are pretty cool team names, I found myself unable to pick a side. To paraphrase Groucho Marx, \u201cI don’t care to belong to any club that will have me as a member.\u201d<\/p>\n <\/p>\n For my own simple blog (which is embarrassingly empty at the moment), a static site generator could be a great fit. Systems like Hugo<\/a> and Jekyll<\/a> have both been highly recommended by developers I love and trust and look great at first glance, but I hit stumbling blocks when I wanted to change my theme or set up more complex JavaScript and interactions across pages. There are ways to solve both these issues, but that\u2019s not the kind of weekend I want to have.<\/p>\n Besides that, I love to experiment, make new things, and I\u2019ve got a major crush on Vue<\/a> at the moment. Having a Headless CMS setup with a front-end that is decoupled from the back-end could be a great combination for me, but after 7+ years of PHP slinging with WordPress, the whole setup feels like overkill for my basic needs.<\/p>\n What I really<\/em> want is a static site generator that will let me write a blog as a component of a larger single-page app so I have room to try new things and still have full control over styling, without the need for a database or any sort of back-end. This is a long way of telling you that I\u2019ve found my own club to join, with a decidedly un-cool name.<\/p>\n Get ready for it… <\/p>\n Because there\u2019s no back-end, get it? 😶<\/p>\n It takes a few steps to go butt-less:<\/p>\n That last point has to be a part of every proposal, right?<\/p>\n I know it looks like a lot of steps but this is not quite as tough as it seems. Let’s break down the steps together.<\/p>\n Let’s get Vue up and running. We’re going to need Webpack<\/a> to do that.<\/p>\n I get it, Webpack is pretty intimidating even when you know what\u2019s going on. It\u2019s probably best to let someone else do the really hard work, so we\u2019ll use the Vue Progressive Web App Boilerplate<\/a> as our foundation and make a few tweaks.<\/p>\n We could use the default setup from the repo, but even while I was writing this article, there were changes being made there. In the interest of not having this all break on us, we will use a repo I created<\/a> for demonstration purposes. The repo has a branch for each step we’ll be covering in this post to help follow along.<\/p>\n View on GitHub<\/a><\/p>\n Cloning the repo and check out the One of my favorite parts of modern development is that it takes a mere thirty seconds to get a progressive web app up and running!<\/p>\n Next, let\u2019s complicate things.<\/p>\n Out of the box, single page apps only have a single entry point. In other words, it lives lives at a single URL. This makes sense in some cases, but we want our app to feel like a normal website. <\/p>\n We\u2019ll need to make use of the history mode in the Vue Router file<\/a> in order to do that. First, we\u2019ll turn that on by adding Our starter app has two routes. In addition to All this works pretty well in development mode ( That command will generate your Vue site into the Sorry Windows folks, I don\u2019t know the equivalent!<\/p>\n Now visit Refresh the page. Oops! This reveals our first problem with single page apps: there is only one HTML file being generated at build time, so there\u2019s no way for the browser to know that Of course, there’s an app for that. Or, at least a plugin. The basic usage is noted in the Vue Progressive Web App Boilerplate documentation<\/a>. Here’s how it says we can spin up the plugin:<\/p>\n Let’s add our routes to the Webpack production configuration file<\/a>:<\/p>\n That\u2019s it. Now, when you run a new build, each route in that array will be rendered as a new entry point to the app. Congratulations, we\u2019ve basically just enabled static site generation!<\/p>\n If you\u2019re skipping ahead, we\u2019re now up to the This step is pretty straightforward. We\u2019ll create two new components, and link them together.<\/p>\n Let’s register the the blog component. We’ll create a new file called All we\u2019re really doing here is creating a placeholder array ( The article component is a similar process. We’ll create a new file called We\u2019ll use the props passed along by the router to know what article ID we\u2019re working with. For now, we\u2019ll just use that as the post title, and hardcode the body.<\/p>\n We can’t move ahead until we add our new views to the router. This will ensure that our URLs are valid and allows our navigation to function properly. Here is the entirety of the router file<\/a>:<\/p>\n Notice that we’ve appended Finally, we can add a link to our homepage that points to the blog. This is what we drop into the Hello.vue file<\/a> to get that going: <\/p>\n We’ve done a lot of work so far but none of it will stick until we pre-render our routes. Pre-rendering is a fancy way of saying that we tell the app what routes exist and to dump the right markup into the right route. We added a Webpack plugin for this earlier, so here’s what we can add to our Webpack production configuration file<\/a>:<\/p>\n I have to admit, this process can be cumbersome and annoying. I mean, who wants to touch multiple files to create a URL?! Thankfully, we can automate this, which we’ll cover further down.<\/p>\n We\u2019re now up to the We\u2019ll be using Markdown<\/a> to write our posts, with some FrontMatter<\/a> to create meta data functionality.<\/p>\n Fire up a new file in the One annoying thing at the moment is that we need to hardcode our routes for the pre-rendering plugin. Luckily, it isn\u2019t complicated to make this dynamic with a bit of Node magic. First, we\u2019ll create a helper in our utility file<\/a> to find the files:<\/p>\n This can really just be copied and pasted, but what we\u2019ve done here is create a utility method called All we have to do to make our blog post routes dynamic is merge this new array into our Since we’ve already imported We\u2019re now up to the In order to actually turn our Markdown files into parse-able content, we\u2019ll need some Webpack loaders in place. Again, someone else has done all the work for us, so we only have to install and add them to our config.<\/p>\n Update September 2018: Seif Sayed wrote in to say that the package ‘markdown-it-front-matter-loader’ is deprecated. The rest of this article still uses that, but that you probably should use markdown-with-front-matter-loader<\/a> instead, and has the bonus that you don’t need a json-loader to go with it.<\/p>\n We will only be calling the To use these fancy new loaders, we\u2019re going to add a block to our Webpack base config<\/a>:<\/p>\n Now, any time Webpack encounters a require statement with a This is exactly what we need and it\u2019s pretty easy to extend with other metadata if you need to. But so far, this doesn\u2019t do anything! We need to We could just write:<\/p>\n …but then we\u2019d have to do that for every article we create, and that won\u2019t be any fun as our blog grows. We need a way to dynamically require all our Markdown files.<\/p>\n Luckily, Webpack does this! It wasn\u2019t easy to find documentation for this but here it is<\/a>. There is a method called What\u2019s happening here? We\u2019re creating a posts object that we\u2019ll first populate with articles, then use later within the component. Since we\u2019re pre-rendering all our content, this object will be instantly available.<\/p>\n The In our case, we only want Markdown files in the posts directory, so:<\/p>\n This will give us a kind of strange new function\/object that we need to parse in order to use. That’s where Finally, in the computed There are two things you\u2019ll probably want to do right away if you use this method. First is to sort by date and second is to filter by article status (i.e. draft and published). Since we already have an array, this can be done in one line, added just before One last thing to do now, and that\u2019s instruct our Since we know that our component will be pre-rendered, we can disable the ESLint<\/a> rules that disallow dynamic and global requires, and require the path to the post that matches the Go ahead and test this out:<\/p>\n Visit I want to emphasize just how cool this is.<\/strong> We\u2019ve turned a folder of Markdown files into an array of objects that we can use as we wish, anywhere on our website. The sky is the limit!<\/p>\n If you want to just see how it all works, you can check out the final branch:<\/p>\n My favorite part about this technique is that everything is extensible and replaceable.<\/p>\n Did someone create a better Markdown processor? Great, swap out the loader! Need control over your site\u2019s SEO? There\u2019s a plugin<\/a> for that. Need to add a commenting system? Add that plugin<\/a>, too.<\/p>\n I like to keep an eye on these two repositories for ideas and inspiration:<\/p>\n You thought this step was a joke?<\/p>\n The very last thing we\u2019ll want to do now is profit from the simplicity we\u2019ve created and nab some free hosting. Since your site is now being generated on your git repository, all you really need is to do is push your changes to Github, Bitbucket, Gitlab or whatever code repository you use. I chose Gitlab because private repos are free and I didn\u2019t want to have my drafts public, even in repo-form.<\/p>\n After that’s that set up, you need to find a host. What you really want is a host that offers continuous integration and deployment so that merging to your master branch triggers the I used Gitlab\u2019s own CI tools for the first few months after I set this up. I found the setup to be easy but troubleshooting issues to be difficult. I recently switched to Netlify<\/a>, which has an outstanding free plan and some great CLI tools built right in.<\/p>\n In both cases, you\u2019re able to point your own domain at their servers and even setup an SSL certificate<\/a> for HTTPS support—that last point being important if you ever want to experiment with things like the With all this set up, you\u2019re now a member of the Butt-less Website club. Congratulations and welcome, friends! Hopefully you find this to be a simple alternative to complex content management systems for your own personal website and that it allows you to experiment with ease. Please let me know in the comments if you get stuck along the way…or if you succeed beyond your wildest dreams. 😉<\/p>\n","protected":false},"excerpt":{"rendered":" It seems like all the cool kids have divided themselves into two cliques: the Headless CMS crowd on one side and the Static Site Generator crowd on the other. While I admit those are pretty cool team names, I found myself unable to pick a side. To paraphrase Groucho Marx, \u201cI don’t care to belong […]<\/p>\n","protected":false},"author":250985,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4,19],"tags":[425,920],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"","jetpack-related-posts":[],"featured_media_src_url":null,"_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/263893"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/250985"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=263893"}],"version-history":[{"count":13,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/263893\/revisions"}],"predecessor-version":[{"id":276767,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/263893\/revisions\/276767"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=263893"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=263893"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=263893"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}The Butt-less Website<\/h3>\n
\n
Setup a single page app with Vue<\/h3>\n
step-1<\/code> branch:<\/p>\n
$ git clone https:\/\/github.com\/evanfuture\/vue-yes-blog.git step-1\r\n$ cd vue-yes-blog\r\n$ npm install\r\n$ npm run dev<\/code><\/pre>\n
Generate each route at build time<\/h3>\n
mode: 'history'<\/code> to the Router object\u2019s properties like so:<\/p>\n
\/\/ src\/router\/index.js\r\nVue.use(Router);\r\n\r\nexport default new Router({\r\n mode: 'history',\r\n routes: [\r\n\/\/ ...<\/code><\/pre>\n
Hello<\/code>, we have a second view component called
Banana<\/code> that lives at the route
\/banana<\/code>. Without history mode, the URL for that page would be
http:\/\/localhost:1982\/#\/banana<\/code>. History mode cleans that up to
http:\/\/localhost:1982\/banana<\/code>. Much more elegant!<\/p>\n
npm run dev<\/code>), but let\u2019s take a peek at what it would look like in production. Here’s how we compile everything:<\/p>\n
$ npm run build<\/code><\/pre>\n
.\/dist<\/code> folder. To see it live, there\u2019s a handy command for starting up a super simple HTTP server on your Mac:<\/p>\n
$ cd dist\r\n$ python -m SimpleHTTPServer<\/code><\/pre>\n
localhost:8000<\/code> in your browser. You\u2019ll see your site as it will appear in a production environment. Click on the Banana link, and all is well.<\/p>\n
\/banana<\/code> should target the main app page and load the route without fancy Apache-style redirects!<\/p>\n
$ npm install -D prerender-spa-plugin<\/code><\/pre>\n
\/\/ .\/build\/webpack.prod.conf.js\r\n\/\/ ...\r\nconst SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin')\r\nconst PrerenderSpaPlugin = require('prerender-spa-plugin')\r\nconst loadMinified = require('.\/load-minified')\r\n\/\/ ...\r\nconst webpackConfig = merge(baseWebpackConfig, {\r\n \/\/ ...\r\n plugins: [\r\n \/\/ ...\r\n new SWPrecacheWebpackPlugin({\r\n \/\/ ...\r\n minify: true,\r\n stripPrefix: 'dist\/'\r\n }),\r\n \/\/ prerender app\r\n new PrerenderSpaPlugin(\r\n \/\/ Path to compiled app\r\n path.join(__dirname, '..\/dist'),\r\n \/\/ List of endpoints you wish to prerender\r\n [ '\/', '\/banana' ]\r\n )\r\n ]\r\n})<\/code><\/pre>\n
Create blog and article components<\/h3>\n
step-2<\/code> branch of my demo repo. Go ahead and check it out:<\/p>\n
$ git checkout step-2<\/code><\/pre>\n
Blog Component<\/h4>\n
YesBlog.vue<\/code> in the
\/src\/components<\/code> directory and drop in the markup for the view:<\/p>\n
\/\/ .\/src\/components\/YesBlog.vue\r\n<template>\r\n <div class=\"blog\">\r\n <h1>Blog<\/h1>\r\n <router-link to=\"\/\">Home<\/router-link>\r\n <hr\/>\r\n <article v-for=\"article in articles\" :key=\"article.slug\" class=\"article\">\r\n <router-link class=\"article__link\" :to=\"`\/blog\/${ article.slug }`\">\r\n <h2 class=\"article__title\">{{ article.title }}<\/h2>\r\n <p class=\"article__description\">{{article.description}}<\/p>\r\n <\/router-link>\r\n <\/article>\r\n <\/div>\r\n<\/template>\r\n\r\n<script>\r\nexport default {\r\n name: 'blog',\r\n computed: {\r\n articles() {\r\n return [\r\n {\r\n slug: 'first-article',\r\n title: 'Article One',\r\n description: 'This is article one\\'s description',\r\n },\r\n {\r\n slug: 'second-article',\r\n title: 'Article Two',\r\n description: 'This is article two\\'s description',\r\n },\r\n ];\r\n },\r\n },\r\n};\r\n<\/script><\/code><\/pre>\n
articles<\/code>) that will be filled with article objects. This array creates our article list and uses the
slug<\/code> parameter as the post ID. The
title<\/code> and
description<\/code> parameters fill out the post details. For now, it\u2019s all hard-coded while we get the rest of our code in place.<\/p>\n
Article Component<\/h4>\n
YesArticle.vue<\/code> and establish the markup for the view:<\/p>\n
\/\/ .\/src\/components\/YesArticle.vue\r\n<template>\r\n <div class=\"article\">\r\n <h1 class=\"blog__title\">{{article.title}}<\/h1>\r\n <router-link to=\"\/blog\">Back<\/router-link>\r\n <hr\/>\r\n <div class=\"article__body\" v-html=\"article.body\"><\/div>\r\n <\/div>\r\n<\/template>\r\n\r\n<script>\r\n export default {\r\n name: 'YesArticle',\r\n props: {\r\n id: {\r\n type: String,\r\n required: true,\r\n },\r\n },\r\n data() {\r\n return {\r\n article: {\r\n title: this.id,\r\n body: '<h2>Testing<\/h2><p>Ok, let\\'s do more now!<\/p>',\r\n },\r\n };\r\n },\r\n };\r\n<\/script><\/code><\/pre>\n
Routing<\/h4>\n
\/\/ .\/src\/router\/index.js\r\nimport Router from 'vue-router';\r\nimport Hello from '@\/components\/Hello';\r\nimport Banana from '@\/components\/Banana';\r\nimport YesBlog from '@\/components\/YesBlog';\r\nimport YesArticle from '@\/components\/YesArticle';\r\n\r\nVue.use(Router);\r\n\r\nexport default new Router({\r\n mode: 'history',\r\n routes: [\r\n {\r\n path: '\/',\r\n name: 'Hello',\r\n component: Hello,\r\n },\r\n {\r\n path: '\/banana',\r\n name: 'Banana',\r\n component: Banana,\r\n },\r\n {\r\n path: '\/blog',\r\n name: 'YesBlog',\r\n component: YesBlog,\r\n },\r\n {\r\n path: '\/blog\/:id',\r\n name: 'YesArticle',\r\n props: true,\r\n component: YesArticle,\r\n },\r\n ],\r\n});<\/code><\/pre>\n
\/:id<\/code> to the
YesArtcle<\/code> component’s path and set its props to
true<\/code>. These are crucial because they establish the dynamic routing we set up in the component’s props array in the component file.<\/p>\n
<router-link to=\"\/blog\">Blog<\/router-link><\/code><\/pre>\n
Pre-rendering<\/h4>\n
\/\/ .\/build\/webpack.prod.conf.js\r\n\/\/ ...\r\n \/\/ List of endpoints you wish to prerender\r\n [ '\/', '\/banana', '\/blog', '\/blog\/first-article', '\/blog\/second-article' ]\r\n\/\/ ...<\/code><\/pre>\n
Integrate Webpack to parse Markdown content<\/h3>\n
step-3<\/code> branch. Check it out if you’re following along in the code:<\/p>\n
$ git checkout step-3<\/code><\/pre>\n
The Posts<\/h4>\n
posts<\/code> directory to create our very first post:<\/p>\n
\/\/ .\/src\/posts\/first-article.md\r\n---\r\ntitle: Article One from MD\r\ndescription: In which the hero starts fresh\r\ncreated: 2017-10-01T08:01:50+02\r\nupdated:\r\nstatus: publish\r\n---\r\nHere is the text of the article. It's pretty great, isn't it?\r\n\r\n\r\n\r\n\/\/ .\/src\/posts\/second-article.md\r\n---\r\ntitle: Article Two from MD\r\ndescription: This is another article\r\ncreated: 2017-10-01T08:01:50+02\r\nupdated:\r\nstatus: publish\r\n---\r\n## Let's start with an H2\r\nAnd then some text\r\nAnd then some code:\r\n```html\r\n<div class=\"container\">\r\n <div class=\"main\">\r\n <div class=\"article insert-wp-tags-here\">\r\n <h1>Title<\/h1>\r\n <div class=\"article-content\">\r\n <p class=\"intro\">Intro Text<\/p>\r\n <p><\/p>\r\n <\/div>\r\n <div class=\"article-meta\"><\/div>\r\n <\/div>\r\n <\/div>\r\n<\/div>\r\n```<\/code><\/pre>\n
Dynamic Routing<\/h4>\n
\/\/ .\/build\/utils.js\r\n\/\/ ...\r\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\r\nconst fs = require('fs')\r\n\r\nexports.filesToRoutes = function (directory, extension, routePrefix = '') {\r\n function findFilesInDir(startPath, filter){\r\n let results = []\r\n if (!fs.existsSync(startPath)) {\r\n console.log(\"no dir \", startPath)\r\n return\r\n }\r\n const files = fs.readdirSync(startPath)\r\n for (let i = 0; i < files.length; i++) {\r\n const filename = path.join(startPath, files[i])\r\n const stat = fs.lstatSync(filename)\r\n if (stat.isDirectory()) {\r\n results = results.concat(findFilesInDir(filename, filter)) \/\/recurse\r\n } else if (filename.indexOf(filter) >= 0) {\r\n results.push(filename)\r\n }\r\n }\r\n return results\r\n }\r\n\r\n return findFilesInDir(path.join(__dirname, directory), extension)\r\n .map((filename) => {\r\n return filename\r\n .replace(path.join(__dirname, directory), routePrefix)\r\n .replace(extension, '')\r\n })\r\n}\r\n\r\nexports.assetsPath = function (_path) {\r\n\/\/ ...<\/code><\/pre>\n
filesToRoutes()<\/code> which will take in a
directory<\/code>,
extension<\/code>, and an optional
routePrefix<\/code>, and return an array of routes based on a recursive file search within that directory.<\/p>\n
PrerenderSpaPlugin<\/code> routes. The power of ES6 makes this really simple:<\/p>\n
\/\/ .\/build\/webpack.prod.conf.js\r\n\/\/ ...\r\nnew PrerenderSpaPlugin(\r\n \/\/ Path to compiled app\r\n path.join(__dirname, '..\/dist'),\r\n \/\/ List of endpoints you wish to prerender\r\n [\r\n '\/',\r\n '\/banana',\r\n '\/blog',\r\n ...utils.filesToRoutes('..\/src\/posts', '.md', '\/blog')\r\n ]\r\n)<\/code><\/pre>\n
utils<\/code> at the top of the file for other purposes, we can just use the spread operator
...<\/code> to merge the new dynamic routes array into this one, and we\u2019re done. Now our pre-rendering is completely dynamic, only dependent on us adding a new file!<\/p>\n
Webpack Loaders<\/h4>\n
step-4<\/code> branch: <\/p>\n
$ git checkout step-4<\/code><\/pre>\n
$ npm install -D json-loader markdown-it-front-matter-loader markdown-it highlight.js yaml-front-matter<\/code><\/pre>\n
json-loader<\/code> and
markdown-it-front-matter-loader<\/code> from our Webpack config, but the latter has peer dependencies of
markdown-it<\/code> and
highlight.js<\/code>, so we\u2019ll install those at the same time. Also, nothing warns us about this, but
yaml-front-matter<\/code> is also required, so the command above adds that as well.<\/p>\n
\/\/ .\/build\/webpack.base.conf.js\r\n\/\/ ...\r\nmodule.exports = {\r\n \/\/ ...\r\n module: {\r\n rules: [\r\n \/\/ ...\r\n {\r\n test: \/\\.(woff2?|eot|ttf|otf)(\\?.*)?$\/,\r\n loader: 'url-loader',\r\n options: {\r\n limit: 10000,\r\n name: utils.assetsPath('fonts\/[name].[hash:7].[ext]')\r\n }\r\n },\r\n {\r\n test: \/\\.md$\/,\r\n loaders: ['json-loader', 'markdown-it-front-matter-loader'],\r\n },\r\n ]\r\n }\r\n}<\/code><\/pre>\n
.md<\/code> extension, it will use the
front-matter-loader<\/code> (which will correctly parse the metadata block from our articles as well as the code blocks), and take the output JSON and run it through the
json-loader<\/code>. This way, we know we\u2019re ending up with an object for each article that looks like this:<\/p>\n
\/\/ first-article.md [Object]\r\n{\r\n body: \"<p>Here is the text of the article. It's pretty great, isn't it?<\/p>\\n\"\r\n created: \"2017-10-01T06:01:50.000Z\"\r\n description: \"In which the hero starts fresh\"\r\n raw: \"\\n\\nHere is the text of the article. It's pretty great, isn't it?\\n\"\r\n slug: \"first-article\"\r\n status: \"publish\"\r\n title: \"Article One from MD\"\r\n updated: null\r\n}<\/code><\/pre>\n
require<\/code> these in one of our components so that Webpack can find and load it.<\/p>\n
require('..\/posts\/first-article.md')<\/code><\/pre>\n
Dynamic Requiring<\/h4>\n
require.context()<\/code> that we can use to do just what we need. We\u2019ll add it to the script section of our
YesBlog<\/code> component:<\/p>\n
\/\/ .\/src\/components\/YesBlog.vue\r\n\/\/ ...\r\n<script>\r\n const posts = {};\r\n const req = require.context('..\/posts\/', false, \/\\.md$\/);\r\n req.keys().forEach((key) => {\r\n posts[key] = req(key);\r\n });\r\n\r\n export default {\r\n name: 'blog',\r\n computed: {\r\n articles() {\r\n const articleArray = [];\r\n Object.keys(posts).forEach((key) => {\r\n const article = posts[key];\r\n article.slug = key.replace('.\/', '').replace('.md', '');\r\n articleArray.push(article);\r\n });\r\n return articleArray;\r\n },\r\n },\r\n };\r\n<\/script>\r\n\/\/ ...<\/code><\/pre>\n
require.context()<\/code> method accepts three arguments. <\/p>\n
\n
require.context('..\/posts\/', false, \/\\.md$\/);<\/code><\/pre>\n
req.keys()<\/code> will give us an array of the relative paths to each file. If we call
req(key)<\/code>, this will return the article object we want, so we can assign that value to a matching key in our
posts<\/code> object.<\/p>\n
articles()<\/code> method, we\u2019ll auto-generate our slug by adding a
slug<\/code> key to each post, with a value of the file name without a path or extensions. If we wanted to, this could be altered to allow us to set the slug in the Markdown itself, and only fall back to auto-generation. At the same time, we push the article objects into an array, so we have something easy to iterate over in our component.<\/p>\n
Extra Credit<\/h4>\n
return articleArray<\/code>:<\/p>\n
articleArray.filter(post => post.status === 'publish').sort((a, b) => a.created < b.created);<\/code><\/pre>\n
Final Step<\/h4>\n
YesArticle<\/code> component to use the new data we\u2019re receiving along with the route change:<\/p>\n
\/\/ .\/src\/components\/YesArticle.vue\r\n\/\/ ...\r\ndata() {\r\n return {\r\n article: require(`..\/posts\/${this.id}.md`), \/\/ eslint-disable-line global-require, import\/no-dynamic-require\r\n };\r\n},<\/code><\/pre>\n
id<\/code> parameter. This triggers our Webpack Markdown loaders, and we\u2019re all done!<\/p>\n
OMG!<\/h4>\n
$ npm run build && cd dist && python -m SimpleHTTPServer<\/code><\/pre>\n
localhost:8000<\/code>, navigate around and refresh the pages to load the whole app from the new entry point. It works!<\/p>\n
$ git checkout step-complete<\/code><\/pre>\n
Extend functionality with plugins<\/h3>\n
\n
Profit!<\/h3>\n
npm run build<\/code> command and regenerates your site.<\/p>\n
getUserMedia<\/code><\/a> API, or create a shop to make sales.<\/p>\n