├── .gitignore ├── .travis.yml ├── README.md ├── __fixtures__ ├── css.md ├── file-with-errors.md ├── front-matter.md ├── graphql.md ├── javascript-lines.md ├── javascript-multiple-same.md ├── javascript-multiple.md ├── javascript.md ├── json.md ├── kitchen-sink.md ├── less.md ├── react.md ├── scss.md ├── typescript-with-highlighting.md └── typescript.md ├── __mocks__ └── yargs.ts ├── demo └── sample.gif ├── jest.config.js ├── package.json ├── src ├── __tests__ │ ├── __snapshots__ │ │ └── index.ts.snap │ ├── cli.ts │ └── index.ts ├── cli.ts ├── index.ts ├── interfaces │ └── program-opts.ts ├── prettify-code.ts └── remark.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | 5 | cache: 6 | yarn: true 7 | 8 | script: yarn test 9 | 10 | before_deploy: 11 | - yarn deploy 12 | - cd dist 13 | deploy: 14 | - provider: releases 15 | overwrite: true 16 | api_key: $GITHUB_TOKEN 17 | skip_cleanup: true 18 | on: 19 | tags: true 20 | - provider: npm 21 | email: dustinschau@gmail.com 22 | api_key: $NPM_API_KEY 23 | skip_cleanup: true 24 | on: 25 | tags: true 26 | after_deploy: 27 | - cd ../ 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # prettier-markdown 2 | 3 | [![Build Status](https://travis-ci.org/DSchau/prettier-markdown.svg?branch=master)](https://travis-ci.org/DSchau/prettier-markdown) [![NPM version](https://img.shields.io/npm/v/@dschau/prettier-markdown.svg)](https://www.npmjs.com/package/@dschau/prettier-markdown) 4 | 5 | **Prettier now includes support for prettifying Markdown (including code blocks!), so as such, I'd recommend using the official Prettier instead. See the [release notes](https://github.com/prettier/prettier/releases/tag/1.8.0) for more information** 6 | 7 | A simple utility and CLI to run [prettier][prettier] on code blocks within Markdown, leaving any non-code blocks untouched. 8 | 9 | Currently works on the following languages (basically everything prettier supports!): 10 | 11 | - JavaScript 12 | - TypeScript 13 | - JSON 14 | - CSS 15 | - SASS 16 | - LESS 17 | - GraphQL 18 | 19 | ## Install 20 | 21 | ```bash 22 | yarn global add @dschau/prettier-markdown 23 | ``` 24 | 25 | ## Example 26 | 27 | ![Prettier Markdown](demo/sample.gif) 28 | 29 | ## Usage 30 | 31 | ### CLI 32 | 33 | Command line usage is simple. All options (besides `--dry`, which will not write files to disk) are passed directly through to prettier. 34 | 35 | ```bash 36 | prettier-markdown src/**/*.md README.md --single-quote --trailing-comma es5 37 | ``` 38 | 39 | ### Programatically 40 | 41 | #### `prettierMarkdown(files, prettierOpts = {}, programOpts = {})` 42 | 43 | Usage is fairly simple. An array of markdown files are passed, as well as any prettier options, and prettier is run on the specified files. 44 | 45 | ```javascript 46 | const path = require('path'); 47 | const { prettierMarkdown } = require('@dschau/prettier-markdown'); 48 | 49 | prettierMarkdown( 50 | ['README.md', 'blog/posts/2017-01-01-hello-world/index.md'].map(file => 51 | path.join(process.cwd(), file) 52 | ) 53 | ).then(files => { 54 | // array of files that were written 55 | }); 56 | 57 | ``` 58 | 59 | ### Advanced Functionality 60 | 61 | #### Line highlights 62 | 63 | Note that line highlights (e.g. like the below) are kept intact _and_ the block is still prettified! 64 | 65 |
66 | ```javascript {1-2}
67 | const a =   'b';
68 | const b =   'c';
69 | 
70 |   alert('hello world');
71 | ```
72 | 
73 | 74 | #### Frontmatter 75 | 76 | Frontmatter, i.e. in a [Gastby][gatsby] blog post, is preserved as authored. 77 | 78 |
79 | ---
80 | title: Hello World
81 | tags:
82 |   - Some Tag
83 |   - Another Tag
84 | ---
85 | 
86 | ```javascript
87 | // this will be prettified
88 | var a =    'a';
89 | 
90 | ```
91 | 
92 | 93 | [prettier]: https://github.com/prettier/prettier 94 | [gatsby]: https://gatsbyjs.org 95 | -------------------------------------------------------------------------------- /__fixtures__/css.md: -------------------------------------------------------------------------------- 1 | ```css 2 | p { 3 | color: red; 4 | } 5 | 6 | body { 7 | font-family: 'Georgia', sans-serif; 8 | } 9 | 10 | ``` 11 | -------------------------------------------------------------------------------- /__fixtures__/file-with-errors.md: -------------------------------------------------------------------------------- 1 | This file has errors goober 2 | 3 | ```typescript 4 | class Hello extends React.Component { 5 | render() { 6 | ... 7 | } 8 | } 9 | 10 | ``` -------------------------------------------------------------------------------- /__fixtures__/front-matter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Hello World' 3 | tags: ["asdf", "asdf1"] 4 | --- 5 | 6 | Some content 7 | 8 | ```javascript 9 | var a = 'a'; 10 | 11 | ``` 12 | -------------------------------------------------------------------------------- /__fixtures__/graphql.md: -------------------------------------------------------------------------------- 1 | ```graphql 2 | { 3 | empireHero: hero(episode: EMPIRE) { 4 | name 5 | } 6 | jediHero: hero( episode: JEDI) { 7 | name 8 | } 9 | } 10 | ``` 11 | -------------------------------------------------------------------------------- /__fixtures__/javascript-lines.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | var lines = 3; 3 | 4 | 5 | 6 | ``` 7 | -------------------------------------------------------------------------------- /__fixtures__/javascript-multiple-same.md: -------------------------------------------------------------------------------- 1 | ```javascript{1-2} 2 | const a = 'b'; 3 | const b = 'c'; 4 | 5 | alert('hello world'); 6 | ``` 7 | 8 | Content 9 | 10 | ```javascript{1-2} 11 | const a = 'b'; 12 | const b = 'c'; 13 | 14 | alert('hello world'); 15 | ``` 16 | 17 | Content 18 | 19 | ```javascript{1-2} 20 | const a = 'b'; 21 | const b = 'c'; 22 | 23 | alert('hello world'); 24 | ``` 25 | -------------------------------------------------------------------------------- /__fixtures__/javascript-multiple.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | const path = require('path'); 3 | const { prettierMarkdown } = require('@dschau/prettier-markdown'); 4 | 5 | prettierMarkdown( 6 | ['README.md', 'blog/posts/2017-01-01-hello-world/index.md'].map(file => 7 | path.join(process.cwd(), file) 8 | ) 9 | ).then(files => { 10 | // array of files that were written 11 | }); 12 | 13 | ``` 14 | 15 | ```javascript{1-2} 16 | const a = 'b'; 17 | const b = 'c'; 18 | 19 | alert('hello world'); 20 | ``` 21 | -------------------------------------------------------------------------------- /__fixtures__/javascript.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | var a = 'i am ugly' 3 | var b = "super ugly"; 4 | 5 | alert('sup') 6 | ``` 7 | -------------------------------------------------------------------------------- /__fixtures__/json.md: -------------------------------------------------------------------------------- 1 | ```json 2 | { 3 | "asdf": "asdf", 4 | 5 | "value": true, 6 | 7 | "other_value": false 8 | } 9 | ``` 10 | -------------------------------------------------------------------------------- /__fixtures__/kitchen-sink.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Creating a Blog with Gatsby 3 | path: "/getting-started-with-gatsby" 4 | date: "2017-07-17T17:12:33.962Z" 5 | tags: ["gatsby", "react", "javascript"] 6 | image: "preview.png" 7 | excerpt: "Gatsby is an incredible static site generator that lets you build a static site that still has all the benefits expected from a modern web application…" 8 | --- 9 | 10 | *This blog post was originally published at [Object Partners, Inc.](https://objectpartners.com/2017/07/19/creating-a-static-blog-with-gatsby/), and has since been cross-posted at the [official gatsby blog](https://www.gatsbyjs.org/blog/2017-07-19-creating-a-blog-with-gatsby/)* 11 | 12 | Gatsby is an incredible static site generator that allows for React to be used as the underlying rendering engine to scaffold out a static site that truly has all the benefits expected in a modern web application. It does this by rendering dynamic React components into static HTML content via [server side rendering][react-dom-server] at build time. This means that your users get all the benefits of a static site such as the ability to work without JavaScript, search engine friendliness, speedy load times, etc. without losing the dynamism and interactivity that is expected of the modern web. Once rendered to static HTML, client-site React/JavaScript _can_ take over (if creating stateful components or logic in `componentDidMount`) and add dynamism to the statically generated content. 13 | 14 | Gatsby [recently released][gatsby-release] a v1.0.0 with a bunch of new features, including (but not limited to) the ability to create content queries with GraphQL, integration with various CMSs--including WordPress, Contentful, Drupal, etc., and route based code splitting to keep the end-user experience as snappy as possible. In this post, we'll take a deep dive into Gatsby and some of these new features by creating a static blog. Let's get on it! 15 | 16 | ## Getting started 17 | 18 | ### Installing the CLI 19 | 20 | `npm install -g gatsby` 21 | 22 | Gatsby ships with a great CLI (command line interface) that contains the functionality of scaffolding out a working site, as well as commands to help develop the site once created. 23 | 24 | `gatsby new personal-blog && cd $_` 25 | 26 | This command will create the folder `personal-blog` and then change into that directory. A working `gatsby` statically generated application can now be developed upon. The Gatsby CLI includes many common development features such as `gatsby build` (build a production, statically generated version of the project), `gatsby develop` (launch a hot-reload enabled web development server), etc. 27 | 28 | We can now begin the exciting task of *actually* developing on the site, and creating a functional, modern blog. You'll generally want to use `gatsby develop` to launch the local development server to validate functionality as we progress through the steps. 29 | 30 | ## Adding necessary plugins 31 | 32 | Gatsby supports a [rich plugin interface][gatsby-plugins], and many incredibly useful plugins have been authored to make accomplishing common tasks a breeze. Plugins can be broken up into three main categories: **functional** plugins, **source** plugins, and **transformer** plugins. 33 | 34 | ### Functional plugins 35 | 36 | Functional plugins either implement some functionality (e.g. offline support, generating a sitemap, etc.) _or_ they extend Gatsby's webpack configuration adding support for typescript, sass, etc. 37 | 38 | For this particular blog post, we want a single page app-like feel (without page reloads), as well as the ability to dynamically change the `title` tag within the `head` tags. As noted, the Gatsby plugin ecosystem is rich, vibrant, and growing, so oftentimes a plugin already exists that solves the particular problem you're trying to solve. To address the functionality we want for _this_ blog, we'll use the following plugins: 39 | 40 | - [`gatsby-plugin-catch-links`][gatsby-plugin-catch-links] 41 | - implements the history `pushState` API, and does not require a page reload on navigating to a different page in the blog 42 | - [`gatsby-plugin-react-helmet`][gatsby-plugin-react-helmet] 43 | - [react-helmet][react-helmet] is a tool that allows for modification of the `head` tags; Gatsby statically renders any of these `head` tag changes 44 | 45 | with the following command: 46 | 47 | ```bash 48 | yarn add gatsby-plugin-catch-links gatsby-plugin-react-helmet 49 | ``` 50 | 51 | We're using [yarn][yarn], but npm can just as easily be used with `npm i --save [deps]`. 52 | 53 | After installing each of these functional plugins, we'll edit `gatsby-config.js`, which Gatsby loads at build-time to implement the exposed functionality of the specified plugins. 54 | 55 | ```javascript{6-9} 56 | module.exports = { 57 | siteMetadata: { 58 | title: `Your Name - Blog`, 59 | author: `Your Name`, 60 | }, 61 | plugins: [ 62 | 'gatsby-plugin-catch-links', 63 | 'gatsby-plugin-react-helmet', 64 | ], 65 | } 66 | ``` 67 | 68 | Without any additional work besides a `yarn install` and editing a config file, we now have the ability to edit our site's head tags, as well as implement a single page app feel without reloads. Now let's enhance the base functionality by implementing a source plugin which can load blog posts from our local file system. 69 | 70 | ### Source plugins 71 | 72 | Source plugins create _nodes_ which can then be transformed into a usable format (if not already usable) by a transformer plugin. For instance, a typical workflow often involves using [`gatsby-source-filesystem`][gatsby-source-filesystem], which loads files off of disk--e.g. Markdown files--and then specifying a Markdown transformer to transform the Markdown into HTML. 73 | 74 | Since the bulk of the blog's content, and each article, will be authored in Markdown, let's add that [`gatsby-source-filesystem`][gatsby-source-filesystem] plugin. Similarly to our previous step, we'll install the plugin and then inject into our `gatsby-config.js`, like so: 75 | 76 | ```bash 77 | yarn add gatsby-source-filesystem 78 | ``` 79 | 80 | ```javascript{6-12} 81 | module.exports = { 82 | // previous configuration 83 | plugins: [ 84 | 'gatsby-plugin-catch-links', 85 | 'gatsby-plugin-react-helmet', 86 | { 87 | resolve: `gatsby-source-filesystem`, 88 | options: { 89 | path: `${__dirname}/src/pages`, 90 | name: 'pages', 91 | }, 92 | } 93 | ] 94 | } 95 | ``` 96 | 97 | Some explanation will be helpful here! An `options` object can be passed to a plugin, and we're passing the filesystem `path` (i.e. where our Markdown files will be located), and then a `name` for the source files. Now that Gatsby knows about our source files, we can begin applying some useful transformers to convert those files into usable data! 98 | 99 | ### Transformer plugins 100 | 101 | As mentioned, a transformer plugin takes some underlying data format that is not inherently usable in its current form (e.g. Markdown, json, yaml, etc.), and transforms it into a format that Gatsby can understand, and that we can query against with GraphQL. Jointly, the filesystem source plugin will load file nodes (as Markdown) off of our filesystem, and then the Markdown transformer will take over and convert to usable HTML. 102 | 103 | We'll only be using one transformer plugin (for Markdown), so let's get that installed. 104 | 105 | - [gatsby-transformer-remark][gatsby-transformer-remark] 106 | - Uses the [remark][remark] Markdown parser to transform .md files on disk into HTML; additionally this transformer can optionally take plugins to further extend functionality--e.g. add syntax highlighting with `gatsby-remark-prismjs`, `gatsby-remark-copy-linked-files` to copy relative files specified in markdown, `gatsby-remark-images` to compress images and add responsive images with `srcset`, etc. 107 | 108 | The process should be familiar by now, install and then add to config. 109 | 110 | ```bash 111 | yarn add gatsby-transformer-remark 112 | ``` 113 | 114 | and editing `gatsby-config.js` 115 | 116 | ```javascript{13-18} 117 | module.exports = { 118 | // previous setup 119 | plugins: [ 120 | 'gatsby-plugin-catch-links', 121 | 'gatsby-plugin-react-helmet', 122 | { 123 | resolve: `gatsby-source-filesystem`, 124 | options: { 125 | path: `${__dirname}/src/pages`, 126 | name: 'pages', 127 | }, 128 | }, 129 | { 130 | resolve: 'gatsby-transformer-remark', 131 | options: { 132 | plugins: [] // just in case those previously mentioned remark plugins sound cool :) 133 | } 134 | }, 135 | ] 136 | }; 137 | ``` 138 | 139 | Whew! Seems like a lot of set up, but collectively these plugins are going to super charge Gatsby, and give us an incredibly powerful (yet relatively simple!) development environment. We have one more set up step and it's an easy one. We're simply going to create a Markdown file that will contain the content of our first blog post. Let's get to it. 140 | 141 | ## Writing our first Markdown blog post 142 | 143 | The `gatsby-source-filesystem` plugin we configured earlier expects our content to be in `src/pages`, so that's exactly where we'll put it! 144 | 145 | Gatsby is not at all prescriptive in naming conventions, but a typical practice for blog posts is to name the folder something like `MM-DD-YYYY-title`, e.g. `07-12-2017-hello-world`. Let's do just that, and create the folder `src/pages/07-12-2017-getting-started`, and place an `index.md` inside! 146 | 147 | The content of this Markdown file will be our blog post, authored in Markdown (of course!). Here's what it'll look like: 148 | 149 | ```markdown 150 | --- 151 | path: "/hello-world" 152 | date: "2017-07-12T17:12:33.962Z" 153 | title: "My First Gatsby Post" 154 | --- 155 | 156 | Oooooh-weeee, my first blog post! 157 | 158 | ``` 159 | 160 | _Fairly_ typical stuff, except for the block surrounded in dashes. What is that? That is what is referred to as [`frontmatter`][frontmatter], and the contents of the block can be used to inject React components with the specified data, e.g. path, date, title, etc. Any piece of data can be injected here (e.g. tags, sub-title,draft, etc.), so feel free to experiment and find what necessary pieces of frontmatter are required to achieve an ideal blogging system for your usage. One important note is that `path` will be used when we dynamically create our pages to specify the URL/path to render the file (in a later step!). In this instance, `http://localhost:8000/hello-world` will be the path to this file. 161 | 162 | Now that we have created a blog post with frontmatter and some content, we can begin actually writing some React components that will display this data! 163 | 164 | ## Creating the (React) template 165 | 166 | As Gatsby supports server side rendering (to string) of React components, we can write our template in... you guessed it, React! (Or [Preact][gatsby-plugin-preact], if that's more your style) 167 | 168 | We'll want to create the file `src/templates/blog-post.js` (please create the `src/templates` folder if it does not yet exist!). 169 | 170 | ```javascript 171 | import React from 'react'; 172 | import Helmet from 'react-helmet'; 173 | 174 | // import '../css/blog-post.css'; // make it pretty! 175 | 176 | export default function Template({ 177 | data // this prop will be injected by the GraphQL query we'll write in a bit 178 | }) { 179 | const { markdownRemark: post } = data; // data.markdownRemark holds our post data 180 | return ( 181 |
182 | 183 |
184 |

{post.frontmatter.title}

185 |
186 |
187 |
188 | ); 189 | } 190 | ``` 191 | 192 | Whoa, neat! This React component will be rendered to a static HTML string (for each route/blog post we define), which will serve as the basis of our routing/navigation for our blog. 193 | 194 | At this point, there is a reasonable level of confusion and "magic" occuring, particularly with the props injection. What is `markdownRemark`? Where is this `data` prop injected from? All good questions, so let's answer them by writing a GraphQL query to seed our `