Tomi Chen

js-yaml-source-map

March 2022 Source Live on the web

Package

js-yaml-source-map is an NPM package for finding YAML source locations after parsing with js-yaml.

Why?

Duosmium Results uses js-yaml to parse YAML files, since sciolyff (the results file format) is a subset of YAML. While working on the SciolyFF validator, I thought it would be nice to emit the source location of each error, since files are often quite large.

While I could’ve switched to using yaml and yaml-source-map, I thought building something myself would be an interesting challenge. (Also I think js-yaml is slightly smaller? idk bundlephobia is throwing an error right now and I can’t remember my reasoning)

Challenges

The first part of this project was a way to get js-yaml to reveal some source locations. Luckily, this GitHub issue asking about sourcemaps has the answer! Someone pointed out a listener property that dumps some information as it parses the document.

What followed was more than a few hours of reverse engineering in order to read the emitted events and build up another representation of the YAML document, mapping parsed values back to the original location. While not trivial, it was strangely relaxing.

What was definitely NOT relaxing was figuring out how to package and publish the darn thing properly. With ES modules kinda becoming more standard but CommonJS still widely used, it was incredibly frustrating to figure out how to build and pubish the package for both formats. I ended up compromising on the CommonJS version (requiring users to access the default key). I would hope there’s a better way, but I couldn’t find it even after hours of frustration.

Lessons Learned

This project taught me to break large problems down into more managable chunks and that some problems aren’t as difficult as they seem. I was intimidated by this task at first, but I eventually figured it out. 😋

I also learned how terrible it is to publish packages as ES modules and CommonJS. Hopefully everyone switches to ES modules soon because oh my that was not fun.

---
# file: example.yaml
fruits:
  - apple
  - banana
  - orange
people:
  - name: Eric
    age: 26
  - name: Lily
    age: 22
states:
  CA: California
  NY: New York
    capital: Albany
  TX: Texas
import fs from 'fs'
import yaml from 'js-yaml'
import SourceMap from 'js-yaml-source-map'

const data = fs.readFileSync('./example.yaml', 'utf8')

const map = new SourceMap()
// pass map.listen() to the listener option
const loaded = yaml.load(data, { listener: map.listen() })

console.log(loaded) // { fruits: [ 'apple', 'banana', 'orange' ], ... }

// different syntaxes supported
console.log(map.lookup('fruits')) // { line: 4, column: 10, position: 42 }
console.log(map.lookup('people.0.age')) // { line: 9, column: 8, position: 95 }
console.log(map.lookup('.people[1].name')) // { line: 10, column: 9, position: 108}
console.log(map.lookup(['states', 'NY', 'capital'])) // { line: 16, column: 12, position: 188 }