npm.io
1.2.1 • Published 2d ago

@m4rsh/cones

Licence
Version
1.2.1
Deps
0
Size
31 kB
Vulns
0
Weekly
0

@m4rsh/cones

tools to roll js

NPM Version npm bundle size


using tagged templates to generate files with ease

  • jstag: template string with indents & object strings & automatic imports
  • fmt: template string with indents & object strings
  • ranges: find matching ranges with regex
  • changes: apply edited ranges
  • objstr: print javascript objects (like JSON but less verbose)
  • jsonstr: object to JSON with better spacing

Examples

Automatic barrel files

ID Compressor
Generate Markdown
Custom naming scheme for jstag
Annotating Log Statements

API

jstag

function to create a custom js tagged template function to compose javascript code with automatic imports

import { jstag, Ref } from '@m4rsh/cones'

const js = jstag()

let code = js`
    let x = ${ Ref("@ui/Button", ['styles']) };
    let y = ${ Ref("@ui/Button", ['render']) };

    ${js`
        let fn = () => ${ Ref("@ui/Button", ["handlers"]) };
    `}
`

Output

import { styles as $__0, render as $__1, handlers as $__2 } from "@ui/Button"

let x = $__0;
let y = $__1;

let fn = () => $__2;
Details on using Ref

Ref objects are basically pointers to an imported resource - defined by path + exports. They are converted to strings at the very end, once all Refs in the template are known. This allows for generating optimized import statements when the template is stringified.

Imports are generated with the following heuristics:

  • References to the same path + export will reuse the same identifier
  • Importing * alongside named exports will generate 2 different imports to allow for tree-shaking
  • Not specifying an export will default to the 'default' export
  • Refs within nested jstag templates will automatically merge with the 'root' template that gets stringifed.
  • Paths are treated as raw strings, not resolved or changed

fmt
import { fmt } from '@m4rsh/cones'

let code = fmt`
    <h1>left-aligned</h1>
    <div>
        ${fmt`
            <h2>preserved indent</h2>
        `}
    </div>
`

Output

<h1>left-aligned</h1>
<div>
    <h2>preserved indent</h2>
<div>

(without fmt)


    <h1>left-aligned</h1>
    <div>
        
            <h2>preserved indent</h2>

    </div>
How indents work
  • When parsing a template string, if a Hole is on a line that's preceded by tabs or spaces, save that as 'padding'
  • If the value for that Hole contains newlines, insert that 'padding' at the beginning of every newline (after the first)
  • Trim the empty lines before the first non-whitespace character
  • On the first line with content, count the number of tabs/spaces on that line
  • For every subsequent line, remove up to that much whitespace on subsequent lines

If everything goes smoothly:

  • Indenting templates with a newline at top/bottom are nicely trimmed away for the output
  • Inserting values within a template preserve the indentation of the opening ${
  • Nested indentation is consistent

ranges

Find selections within a source text that match the regex pattern. Returns ideal format for running changes

(For serious file manipulation, I recommend using tree-sitter instead of this regex method)

changes

Apply changes (in reverse order to preserve correctness) to source text at given range indices, returning the new string.

import { fmt, ranges, changes } from '@m4rsh/cones'
let src = fmt`
    /*<context>*/
    function a(){}
    function b(){}
    /*</context>*/

    function c(){}

    /*<context>*/
    function d(){}
    /*</context>*/
`

let zones = ranges(src,/^\/\*<\/?context>\*\/$/gm)

console.log(zones)
// [ 0, 13, "/*<context>*/" ],
// [ 44, 58, "/*</context>*/" ],
// [ 76, 89, "/*<context>*/" ],
// [ 105, 119, "/*</context>*/" ]

let group_i = 1
let modified = changes(src, zones.map(([b,e,str]) => {
  if(str.includes("</")){
    return [b,e,"// --- CLOSE " + (group_i++)]
  }
  return [b,e,"// --- OPEN " + (group_i)]
}))

console.log(modified == fmt`
  // --- OPEN 1
  function a(){}
  function b(){}
  // --- CLOSE 1
  
  function c(){}

  // --- OPEN 2
  function d(){}
  // --- CLOSE 2
`) // true

objstr

Pulled from stringify-object

import { objstr } from '@m4rsh/cones'

let obj = {
    x: 30,
    y: "test",
    z: {
        "int-er/es*ting": [9.2291]
    }
}

let str = objstr(obj, {
    indent:'  ', // two spaces is default
    singleQuotes:false, // ' vs "
    inlineCharacterLimit:80, // line wrap limit
    pad:'', // used internally to preserve indents
    valueTransform: (v)=>v, // undefined by default
})

console.log(str)
// {x: 30, y: "test", z: {"int-er/es*ting": [9.2291]}} 

jsonstr

Pulled from json-stringify-pretty-compact

import { jsonstr } from '@m4rsh/cones'

let list = [
    { id: 1, $: ["home", "about"] },
    { id: 2, $: ["faq", "contact"] },
    { id: 3, $: ["test", "app"] },
]

let str = jsonstr(obj, {
    indent:'  ', // two spaces is default
    inlineCharacterLimit:80, // line wrap limit
    pad:'', // used internally to preserve indents
})

console.log(str)

Output

[                                     
  {"id": 1, "$": ["home", "about"]},  
  {"id": 2, "$": ["faq", "contact"]}, 
  {"id": 3, "$": ["test", "app"]}     
]                                     

With JSON.stringify()

[
 {
  "id": 1,
  "$": [
   "home",
   "about"
  ]
 },
 {
  "id": 2,
  "$": [
   "faq",
   "contact"
  ]
 },
 {
  "id": 3,
  "$": [
   "test",
   "app"
  ]
 }
]

References

Keywords