Skip to content

Instantly share code, notes, and snippets.

@markusfisch
Last active Aug 21, 2022
Embed
What would you like to do?
Render some Markdown file to HTML and show it in your browser

Preview Markdown files locally

Render some Markdown file to HTML and show it in your browser.

This is useful when you want to check a Markdown file before pushing it onto GitHub/GitLab/etc or if you want to simply print a nice looking Markdown file. Just print it from your browser.

The look is broadly similar to that on GitHub but stripped down to the bare minimum. It can also be changed easily. Just have a look at the source, Luke. It's really quite simple.

Requires

This script does use CommonMarker to render the Markdown files to HTML. Install it with:

$ [sudo] gem install commonmarker

According to github.com/github/markup, this is the very same component GitHub is using.

Get it

Download viewmd.rb, save it somewhere within your PATH and make it executable:

$ mv Downloads/viewmd.rb /usr/local/bin/viewmd
$ chmod a+x /usr/local/bin/viewmd

Run it

When there's a README.md in the current folder:

$ viewmd

If you want to view another file:

$ viewmd path/to/another/file.md

Your default web browser should open immediately and display the rendered Markdown.

Because this script mimicks a web server, it won't terminate instantly but continue to serve a new render of the Markdown file each time you refresh the page in your browser.

To stop the script, just hit CTRL+C.

You may want to put the script in the background while you edit the Markdown file in your favorite editor:

$ viewmd &
$ vim README.md

Now make some changes and reload the page in your browser.

Motivation & Alternatives

There are a couple of tools out there than can do the same. Somehow, they just didn't quite cut the mustard for me.

I wanted a simple tool I can use to pretty print a Markdown file. Or generate a PDF. Your browser most certainly can do so.

At the time of writing, grip renders the Markdown file on a GitHub server. This service has a quota that quickly dries up. Also, grip has this annoying bar above the document you just can't get rid of.

Alternatively one can use pandoc with browser (on macOS, both available with brew) like this:

$ pandoc -f markdown_github < README.md | browser

But this won't update when you reload the page.

Another handy option for macOS is QLMarkdown that shows a preview of a Markdown file by simply pressing space if it's selected in Finder.

Also, there are plug-ins for Emacs and Vim that can do the same and more.

#!/usr/bin/env ruby
require 'commonmarker'
require 'socket'
file = ARGV[0] || 'README.md'
if not File.exists?(file)
puts "#{file} doesn't exist"
exit 1
end
port = ARGV[1] || Process.pid % 32000 + 32000
address = "http://127.0.0.1:#{port}/"
server = TCPServer.new port
open_command = ARGV[2] || RUBY_PLATFORM =~ /darwin/i ? 'open' : 'xdg-open'
if not system("#{open_command} #{address}")
puts "#{open_command} not found, please open #{address} in your browser"
end
while socket = server.accept
request = socket.gets
_, full_path = request.split(' ')
path, _ = full_path.split('?')
path[0] = ''
content = if File.file?(path)
content_type = case File.extname(path)
when '.gif'
'image/gif'
when '.jpg', '.jpeg'
'image/jpeg'
when '.mpg'
'video/mpeg'
when '.png'
'image/png'
when '.svg'
'image/svg+xml'
else
'application/octet-stream'
end
File.read(path)
else
content_type = 'text/html'
%{<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width:device-width"/>
<title>#{file}</title>
<style>
html, body { background: #fff; color: #333; }
body { margin: 0 auto 2em; padding: 1em; max-width: 50em; font: 110% sans-serif; line-height: 1.45; }
h1, h2 { font-weight: normal; border-bottom: 1px solid #eaecef; }
a { color: #0366d6; text-decoration: none; }
pre, code { background: #f6f8fa; -webkit-print-color-adjust: exact; }
code { display: inline-block; padding: .25em .5em; }
pre { padding: 1em; }
pre > code { padding: 0; }
table { border-spacing: 0; border-collapse: collapse; }
td, th { padding: 6px 13px; border: 1px solid #eaecef; }
tbody tr:nth-child(odd) { background: #f6f8fa; }
</style>
</head>
<body>
#{CommonMarker.render_html(File.read(file), :UNSAFE, [:table])}
</body>
</html>
}
end
socket.puts %{HTTP/1.1 200 OK\r
Content-Type: #{content_type}\r
\r
#{content}
}
socket.close
end
@rassie
Copy link

rassie commented May 17, 2020

Webserver seems overkill, especially since you still need to refresh the browser (could be more interesting with hot reload). And you really don't need to use Ruby here: CommonMark's official C implementation is called cmark in every distribution, including brew, so that you could hack a way smaller inotify-based shell script for the heavy lifting.

Of course, every Emacs user does this inside Emacs (https://www.youtube.com/watch?v=Pd0JwOqh-gI), but Vim users obviously can do that too: https://nickjanetakis.com/blog/writing-and-previewing-markdown-in-real-time-with-vim-8.

@markusfisch
Copy link
Author

markusfisch commented May 18, 2020

Well, this is probably a classic sample for easy versus simple 😉 And a matter of taste, of course.

First, this isn't really a web server. This script just mimicks a web server as far as necessary to show the Markdown preview. It's the bare minimum of what's required to do that. I chose to mimick a web server because it's a very simple and convenient way to repeatedly hand the rendered Markdown directly to the browser. Of course, it's possible to render in a temporary file too. But I wanted to avoid a temporary file.

Now, that Vim plug-in you mentioned does exactly the same thing 😉 It just uses node (along with a lot of dependencies, see here: https://github.com/iamcco/markdown-preview.nvim/blob/master/app/package.json) to serve the rendered Markdown by implementing a more complete web server (see https://github.com/iamcco/markdown-preview.nvim/blob/master/app/server.js). Of course, this is fine! It's just multiple times the code of this script. And many many more dependencies that just the commonmarker gem.

Of course, the plug-in can do hot reloading. But personally I don't need or want hot reloading because I'm very much used to Ctrl/Cmd+R. It's just enough for me. YMMV 🤷‍♂️

So if you're fine without hot reloading and like simple things, this script is for you. If you rather want an easy solution with more features, better go with some of the other options 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment