## A Brief Background When I started working on this site, my first hunch was to use that [classic static website builder, Jekyll](https://jekyllrb.com/). It's simplicity and tight github integration seemed great. However, there's just one issue: it's not easily integrated with [Webpack](https://webpack.github.io/) and the rest of the modern front-end dev stack. In fact, it seems written for Ruby on Rails devs--but let's save that for another post. Shortly after starting to look beyond Jekyll, I started fiddling around with the [`html-webpack-plugin`](https://github.com/ampedandwired/html-webpack-plugin) and realized: wait a minute I think I can make a perfectly suitable blog with just this. ## Configuring Webpack So if you're not already familiar with webpack, it relies on a `webpack.config.js` (or whatever you wanna name it) file that usually takes a form along the lines of: ```javascript const path = require("path"); module.exports = { // Your precompiled JS file(s) for bundling. entry: { // A main entry bundle. main: "./src/main.js", // Some other random bundle. secondary: "./src/something-else.js", }, // Your post-compiled assets path. output: path.join(__dirname, "dist"), // Your module-loading configuration. module: { // Loader/transpiler config. loaders: [ // A sample babel loader that processes your fancy, ES2019-next-rc-11 or whatever. { test: /\.js$/, loader: "babel", }, ], }, // And this mysterious array. plugins: [ // Why am I here?????? ], }; ``` Well the bulk of this file doesn't need much changing. In fact, you can leave most of it as is. The part that matters is that ever-so-mysterious `Array` of `plugins`. ## Plugins for `plugins` So for starters, let's come back to the [`html-webpack-plugin`](https://github.com/ampedandwired/html-webpack-plugin). If you aren't familiar with what it does, here's what it does: 1. Injests an `Object` of options, such as `title`, metadata etc. 2. Injects the proper `<link>` and/or `<script>` tags associated with your webpack bundle. 3. Spits out a `.html` file based on your options. So given a template like this: ```jade html head title= htmlWebpackPlugin.options.title meta(charset='utf-8') body #main-container Hello world! ``` And a `webpack.config.js` (using [`pug`](https://pugjs.org/api/getting-started.html) for templates) that looks something like this: ```javascript const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { // ... plugins: [ new HtmlWebpackPlugin({ // A string identifying where the template is located. // and optionally which webpack loaders to use. template: "pug!templates/index.pug", // Do you wanna make some cache? $$ cache: true, // Specifying that only the "main" bundle should be inserted. chunks: ["main"], // The contents of your <title></title> tag. title: "The Main Page", // The location and source of your output file. filename: "pages/index.html", }), ], // ... }; ``` Sounds pretty simple right? It's deceptively simple. Like webpack itself, this plugin's [minimal API surface area](https://www.youtube.com/watch?v=4anAwXYqLG8) is where its power lies--and also in its ability to receive (and potentially render) arbitrary options. ## Arbitrary Options and Loops The secret to making a blog from all this is is learning to combine two features of the `html-webpack-plugin`: 1. The fact that you can pass as many of them as you want. 2. The arbitrary options object. So feature #1 is best harnessed with [the `Array` spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) which is available in most post-ES6 flavors of JavaScript: ```javascript const arr = [...[1, 2, 3], ...[4, 5, 6]]; ``` _NOTE: For the remainder of this process, I recommend a relatively modern version of NodeJS (I'm using 6.5.0 at the time of writing this)._ Given the above `webpack.config.js`, you can create a bunch of HTML pages simply by list generation and iteration. Here's an example using [`lodash.times`](https://lodash.com/docs/4.17.4#times): ```javascript const HtmlWebpackPlugin = require("html-webpack-plugin"); // https://lodash.com/docs/4.17.4#times const times = require("lodash/times"); const makeHtmlConfig = n => ({ template: "pug!templates/index.pug", cache: true, chunks: ["main"], title: `Page Number ${n}`, filename: `pages/page_${n}.html`, }); module.exports = { // ... plugins: [ // Instead of one HtmlWebpackPlugin, how about a hundred? ...times(100, makeHtmlConfig), ], // ... }; ``` The above code should spit out 100 pages based on your `pug` template, each with a different `<title></title>` and filename. That's obviously pretty useless. However, by harnessing feature #2 from the list above, you can do some useful stuff. ## Injecting Arbitrary HTML In A Template Because `html-webpack-plugin` takes an plain `Object` as its input, you can add additional key/value pairs very easily using our above looping system. However, the nicest part of all is that this data is all available inside the template from the `htmlWebpackPlugin.options` object. (Along with a few others outlined in [its documention](https://github.com/ampedandwired/html-webpack-plugin#writing-your-own-templates)). So if I were to add the following changes to my `makeHtmlConfig` function: ```javascript const makeHtmlConfig = n => ({ template: "pug!templates/index.pug", cache: true, chunks: ["main"], title: `Page Number ${n}`, filename: `pages/page_${n}.html`, bodyText: `The number is ${n}`, }); ``` And make the following changes to my source template: ```jade html head title= htmlWebpackPlugin.options.title meta(charset='utf-8') body #main-container= htmlWebpackPlugin.options.bodyText ``` It would create a bunch of HTML pages with `<div>` elements in their `<body>` node's taking the following form: ```html // pages/page_1.html <div id="main-container">The number is 1</div> // pages/page_2.html <div id="main-container">The number is 2</div> // pages/page_3.html <div id="main-container">The number is 3</div> ``` This alone a blog does not make, though. ## Adding Markdown Now adding markdown is one of the easier parts. To do this, just add several markdown files in the `./md` directory. Then make the following modifications to the `webpack.config.js` file: ```javascript const HtmlWebpackPlugin = require("html-webpack-plugin"); const marked = require("marked"); const fs = require("fs"); // Assuming I add a bunch of .md files in my ./md dir. const MARKDOWN_FILE_DIR = "./md"; /* * Generates an Array with the following data: * [ * { * filename: '{markdownFilename}.md', * markdown: '{ markdownString }` * } * ] */ const markdownFilesData = fs // Read directory contents .readdirSync(MARKDOWN_FILE_DIR) // Take only .md files .filter(filename => /\.md$/.test(filename)) // Normalize file data. .map(filename => { return { markdown: fs.readFileSync(path.join(MARKDOWN_FILE_DIR, mdFilename)), filename, }; }); const makeHtmlConfig = ({ filename, markdown }) => ({ template: "pug!templates/index.pug", cache: true, chunks: ["main"], title: `Page Number ${n}`, filename: `pages/${filename}.html`, // Parses the markdown string and converts to HTML string bodyHTML: marked(markdown), }); module.exports = { // ... plugins: [ // map the above function to the array of file data ...markdownFiles.map(makeHtmlConfig), ], // ... }; ``` Afterwards, let's make a simple modifcation to the `pug` template to allow it to receive the HTML produced with the previous process and render the unescaped HTML string in the `div.#main-container`: ```jade html head title= htmlWebpackPlugin.options.title meta(charset='utf-8') body #main-container= !{htmlWebpackPlugin.options.bodyHTML} ``` And voila! You should now be able to add posts in Markdown to the `md/` directory and build the blog into static HTML pages using: ```shell $ webpack --config webpack.config.js ``` ## Examples To see a working example of this, just check out the following files in the source code of this very website: - [`build/base.webpack.config.js`](https://github.com/omardelarosa/omardelarosa.github.io/blob/master/build/base.webpack.config.js) - [`build/prod.webpack.config.js`](https://github.com/omardelarosa/omardelarosa.github.io/blob/master/build/prod.webpack.config.js) - [`_posts/`](https://github.com/omardelarosa/omardelarosa.github.io/tree/master/_posts) - [`posts/`](https://github.com/omardelarosa/omardelarosa.github.io/tree/master/posts) - [`templates/blog.pug`](https://github.com/omardelarosa/omardelarosa.github.io/blob/master/templates/blog.pug) Although there are a couple other things going on in the webpack configs for this project (such as a distinction between static `pages` and `posts`), the principles are basically the same as the above examples. #webpack, #webdev, #js, #md