Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/loilo/yufka
🌯 Transform JavaScript ASTs the easy way
https://github.com/loilo/yufka
Last synced: 5 days ago
JSON representation
🌯 Transform JavaScript ASTs the easy way
- Host: GitHub
- URL: https://github.com/loilo/yufka
- Owner: loilo
- License: other
- Created: 2019-04-29T21:33:51.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2024-05-06T04:57:11.000Z (6 months ago)
- Last Synced: 2024-10-07T10:43:43.223Z (about 1 month ago)
- Language: TypeScript
- Homepage:
- Size: 491 KB
- Stars: 9
- Watchers: 3
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
Yufka
Transform JavaScript [AST](http://en.wikipedia.org/wiki/Abstract_syntax_tree)s the easy way.
[![Tests](https://badgen.net/github/checks/loilo/yufka/master)](https://github.com/loilo/yufka/actions)
[![npm](https://badgen.net/npm/v/yufka)](https://npmjs.com/package/yufka)Yufka aims to be the unofficial successor to [Falafel](https://www.npmjs.com/package/falafel) and fixes most of its [outstanding issues](falafel.md#resolved-issues).
Some benefits over Falafel are:
* Written in TypeScript (so you get type hints in your IDE)
* Future-proof design (no modification of third-party objects)
* [Predictable and controllable asynchronous behavior through Promises](#asynchronous-manipulations)
* [Creation of source maps](#source-maps)
* Actively maintainedHowever, Yufka is not a drop-in replacement for Falafel — see its [list of differences](falafel.md#differences).
## Installation
```
npm install --save yufka
```## Motivation
Yufka is the ideal tool for programmatically making small & simple modifications to your JavaScript code. (For complex use cases, you probably want to head for more sophisticated solutions like [`@babel/traverse`](https://babeljs.io/docs/en/next/babel-traverse.html).)As an introducing example, let's put a function wrapper around all array literals:
```js
const yufka = require('yufka')const source = `
const xs = [1, 2, [3, 4]]
const ys = [5, 6]
console.log([xs, ys])
`const result = yufka(source, (node, { update, source }) => {
if (node.type === 'ArrayExpression') {
update(`fn(${source()})`)
}
})console.log(result.toString())
```Output:
```js
const xs = fn([1, 2, fn([3, 4])])
const ys = fn([5, 6])
console.log(fn([xs, ys]))
```## Usage
### How it Works
```ts
function yufka(source, options = {}, manipulator)
```Transform the string `source` with the function `manipulator`, returning an output object.
For every node in the AST, `manipulator(node, helpers)` fires. The recursive walk is an
in-order traversal, so children get called before their parents. This makes it easier to write nested transforms since transforming parents often requires transforming their children first anyway.The `yufka()` return value is an object with two properties:
* `code` – contains the transformed source code
* `map` – holds the resulting source map object, [as generated by `magic-string`](https://www.npmjs.com/package/magic-string#sgeneratemap-options-)Calling `.toString()` on a Yufka result object will return its source `code`.
> **Pro Tip:**
> Don't know how a JavaScript AST looks like? Have a look at [astexplorer.net](https://astexplorer.net/) to get an idea.### Options
All options are, as the name says, optional. If you want to provide an options object, its place is between the `source` code and the `manipulator` function.#### Acorn Options
Any options for the underlying [`acorn`](https://npmjs.com/package/acorn) parser can be passed to `options.acorn`:```js
yufka(source, { acorn: { sourceType: 'module' } }, (node, helpers) => {
// Parse the `source` as an ES module
})
```#### Custom Parser
You may pass a custom acorn parser as `options.parser` to use that
instead of the default acorn version coming with this library:```js
const acorn = require('acorn')
const jsx = require('acorn-jsx')
const parser = acorn.Parser.extend(jsx())yufka(source, { parser }, (node, helpers) => {
// Parse the `source` as JSX
})
```#### Source Maps
Yufka uses [`magic-string`](https://www.npmjs.com/package/magic-string) under the hood to generate [source maps](https://developer.mozilla.org/docs/Tools/Debugger/How_to/Use_a_source_map) for your code modifications. You can pass its [source map options](https://www.npmjs.com/package/magic-string#sgeneratemap-options-) as `options.sourceMap`:```js
yufka(source, { sourceMap: { hires: true } }, (node, helpers) => {
// Create a high-resolution source map
})
```### Helpers
The `helpers` object passed to the `manipulator` function exposes the following methods. All of these methods handle the *current AST node* (the one that's passed to the manipulator as its first argument).However, all of these methods take an AST node as an optional first parameter if you want to access other nodes.
> **Example:**
> ```js
> yufka('x = 1', (node, { source }) => {
> if (node.type === 'AssignmentExpression') {
> // `node` refers to the `x = 1` Expression
> source() // returns "x = 1"
> source(node.right) // returns "1"
> }
> })
> ```#### `source()`
Return the source code for the given node, including any modifications made to
child nodes:```js
yufka('true', (node, { source, update }) => {
if (node.type === 'Literal') {
source() // returns "true"
update('false')
source() // returns "false"
}
})
```#### `update(replacement)`
Replace the source of the affected node with the `replacement` string:```js
const result = yufka('4 + 2', (node, { source, update }) => {
if (node.type === 'BinaryExpression') {
update(source(node.left) + source(node.right))
}
})console.log(result.toString())
```Output:
```js
42
```#### `parent(levels = 1)`
From the starting node, climb up the syntax tree `levels` times. Getting an ancestor node of the program root yields `undefined`.```js
yufka('x = [1]', (node, { parent }) => {
if (node.type === 'Literal') {
// `node` refers to the `1` literal
parent() // same as parent(1), refers to the `[1]` expression
parent(2) // refers to the `x = [1]` assignment expression
parent(3) // refers to the `x = [1]` statement
parent(4) // refers to the program as a whole (root node)
parent(5) // yields `undefined`, same as parent(6), parent(7) etc.
}
})
```#### External Helper Access
**Tip:** If you want to extract manipulation behavior into standalone functions, you can access the helpers directly on the `yufka` instance (e.g. `yufka.source()`) where they are not bound to a specific node:
```js
// Standalone function, increments node's value if it's a number
const increment = node => {
if (node.type === 'Literal' && typeof node.value === 'number') {
yufka.update(node, String(node.value + 1))
}
}const result = yufka('x = 1', node => {
increment(node)
})console.log(result.toString())
```
Output:
```js
x = 2
```### Asynchronous Manipulations
The `manipulator` function may return a [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise). If it does, Yufka will wait for that to resolve, making the whole `yufka()` function return a Promise resolving to the result object (instead of returning the result object directly):```js
const got = require('got') // see www.npmjs.com/package/gotconst source = `
const content = curl("https://example.com")
`
const deferredResult = yufka(source, async (node, { source, update }) => {
if (node.type === 'CallExpression' && node.callee.name === 'curl') {
// Replace all cUrl calls with their actual content// Get the URL (will only work for simple string literals)
const url = node.arguments[0].value// Fetch the URL's contents
const contents = (await got(url)).body// Replace the cUrl() call with the fetched contents
update(JSON.stringify(contents))
}
})// Result is not available immediately, we need to await it
deferredResult.then(result => {
console.log(result.toString())
})
```Output:
```js
const content = "\n\n[...]\n"
```> **Note:** You *have* to return a promise if you want to commit updates asynchronously. Once the manipulator function is done running, any `update()` calls originating from it will throw an error.
## Credit
While the source code of this package has virtually nothing left from the [Falafel](https://www.npmjs.com/package/falafel) codebase, Yufka actually started out as a fork and its concepts stem from there.