/jsdotmd

Literate JavaScript within Markdown

Primary LanguageJavaScriptMIT LicenseMIT

jsdotmd

What is it?

jsdotmd takes all JavaScript inline code tags from Github Flavored Markdown file and compiles them into JavaScript file.

Why would I use it?

You can use it if you want to write extensive code documentation. See Literate programming

Why did you do that?

I wanted to make a language, whose compiler is written on itself and is also its README. Just for fun.

How to use the CLI?

Install the package globally:

npm install --global jsdotmd
# or
yarn global add jsdotmd

After installing, use the following syntax:

jsdotmd [source file] [destination file]

How to use the parser in my code?

Install the package locally:

npm install --save jsdotmd
# or
yarn add jsdotmd
import {parse} from 'jsdotmd'

const src = []
  .concat('# Hello, Markdown!')
  .concat('```javascript')
  .concat('console.log("Hello, JavaScript!")')
  .concat('```')
  .join('\n')

const result = parse(source)
//=> 'console.log("Hello, JavaScript!")'

Since working with JavaScript code in Markdown code tags in JavaScript strings is a bit messy, you can also use file-to-file compiler:

import path from 'path'
import {compile} from 'jsdotmd'

const sourcePath = path.join(__dirname, 'index.js.md')
const destinationPath = path.join(__dirname, 'index.js')

compile(sourcePath, destinationPath)
  .catch((err) => console.log('Something went wrong!', err))

How does the parser work?

The code is very simple.

It uses only two dependencies:

  • marked for parsing Markdown source file. We don't need the HTML output, so we use only lexer.

    const {lexer} = require('marked')
  • fs-promise to work with the file system easier.

    const fs = require('fs-promise')

By default, jsdotmd compiles into JavaScript code only those Markdown inline code tags, which are marked javascript, js or node.

const defaultLangCodes = ['javascript', 'js', 'node']

From CLI, you can specify tags like this:

jsdotmd [source file] [destination file] -l js,javascript,node,jsx,typescript

parse function

function parse (source, options) {
  options = options || {}

  const langCodes = options.langCodes || defaultLangCodes
  const separator = options.separator || '\n\n'

parse function joins code blocks using separator specified in the second argments options which is two line breaks by default.

  const tokens = lexer(source)

Lexer returns an array of tokens. We are only interested in tokens with type 'code'. They look like this:

{
  type: 'code',
  lang: 'javascript',
  text: '  const tokens = lexer(source)'
}

Next we filter only the tokens we need, retrieve text from them and join them with a separator.

  const code = tokens
    .filter(({type, lang}) => type === 'code' && langCodes.includes(lang))
    .map(({text}) => text)
    .join(separator)

  return code
}

compile function

Compile function is even simpler. It just reads file, specified in the first argument, parses it with parse and writes it to file, specified in the second argument. Done!

function compile (sourcePath, destinationPath, options) {
  options = options || {}

  const encoding = options.encoding || 'utf-8'

  return fs.readFile(sourcePath, {encoding})
    .then((contents) => parse(contents, options))
    .then((code) => fs.writeFile(destinationPath, code, {encoding}))
}

jsdotmd exports both parse and compile functions for you to use.

module.exports = {parse, compile}

How does the CLI work?

It uses commander. Check bin/jsdotmd.js. I haven't written it in jsdotmd for simplicity.

I've made changes to the code. How to recompile the compiler?

Run npm run build. It compiles README.md into lib/index.js.

How to test if the code works?

Run npm test. It runs npm run build two times. If the compiler compiles itself after the recompiling, it probably works. If not, well, no luck.

Should I use it in the real project?

Probably not.

That's super weird.

Thank you.