Spinning Out the Markdown Engine in the Metron TypeScript Library
If you've been following me at all on this site, or on Twitter, you've heard me mention several times about Metron, which is a JavaScript library I've been writing, written in TypeScript. Metron exists as many things: a convenience library, a routing system, a templating engine, and a full front-end framework.
Any good library, however, often stands on the shoulders of giants--or maybe StackOverflow code and open source GitHub repositories. Metron needs to be compatible with Internet Explorer 11 at the moment, so native Promises aren't available. As such, we've added RSVP as a dependency. In addition, the framework works best with the Milligram CSS library. There is also an optional dependency of Awesomplete until I get around to custom building an autocomplete control.
Another place where Metron borrows from the community is with its markdown engine. A nice bit of compact markdown code is available on GitHub from Mathieu Henri. This did most of what Metron needed, so rather than reinvent the wheel, I incorporated it into the Metron library, and gave appropriate credit.
A couple of modifications needed to be made. One of the most powerful aspects of TypeScript is that it is statically typed. When Henri's code is plugged into the TypeScript language service, and an attempt is made to add appropriate typing, we see some immediate issues where static typing is being less flexible than his initial JavaScript code--specifically, there are a few instances where numbers are later being reassigned to strings, and vice versa.
The first thing I did was clean this up. It meant more code, but it also meant slightly more structure. The next step was to pull out a couple of instances of inline code that was compact, but a little hard to read. For example, these two functions were previously directly in a ternary operator, but I pulled them out, and renamed a bunch of variables to make things more readable and meaningful.
function processWrappedMarkdown(prependType: Array<string>, line: string): string {
return prependType[1] + ("\n" + line)
.split(prependType[0])
.slice(1)
.map(prependType[3] ? escape : inlineEscape)
.join(prependType[3] || "</li>\n<li>") + prependType[2];
}
function processSemanticMarkdown(char: any, line: string): string {
return (char == "#")
? ("<h" + (char = line.indexOf(" ")) + ">" + inlineEscape(line.slice(char + 1)) + "</h" + char + ">")
: (char == "<" ? line : "<p>" + inlineEscape(line) + "</p>")
}
The second function still has complexity with its own ternary operators, but imagine multiple nested ones even beyond this.
Other improvements were made to the markdown engine itself: support for line breaks (converting to <br />
elements) rather than just paragraph tags, and allowing HTML inside of markdown without it escape by using the function override of the replace()
method.
This all worked great inside of Metron, but then to use the Metron CLI as a publishing engine, I also needed the code in there. At first I duplicated the code, which is a clear violation of quality coding, but the CLI is highly experimental. Eventually, I extracted the code from the library, and created a new module specifically for the markdown engine. Although the markdown code is still present in the Metron library, the CLI now has this markdown module as a dependency, and I've published it out to NPM.
To install it, you can run from the command line:
npm install metronical.markdown --save
The module transpiles as a UMD module targeting ES5 (since a lot of my work still needs to function in Internet Explorer 11, as mentioned above). Since this is TypeScript, the installation comes equipped with the Type Definition file, so you can import it into your own TypeScript projects.
There's still more work to do on this module, and most of it will get fleshed out as I complete the publishing engine inside of the Metron CLI, but for now, if you're in need of a lightweight markdown engine, be my guest and download this one. Let me know what you think. You can also submit a pull request too!