Skip to content

RxJS esprima AST js code modification library using aster, esprima and grasp

License

Notifications You must be signed in to change notification settings

mandricore/rx-aster

Repository files navigation

rx-aster

[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url]

aster - AST-based code builder

What's that

aster is reactive builder specialized for code processing and transformations. It's built with debugging in mind and makes building JavaScript code more reliable and faster.

Why one more

Source maps are a great invention that is meant to simplify life by allowing developers to debug the original code (the one that they actually write, whatever language it is) on production.

However, using them is pretty hard in any of existing build systems whenever you have at least one plugin in your build pipeline - and you most likely do - that doesn't support emitting them or even consuming from previous step; some plugins even treat code as simple strings discarding all it's inner logic and structure.

Your code isn't just a string. It has a soul and rich inner world and aster is built to treat it like that. As result, it provides complex yet easy and fast transformations that are transparent for browser debugger out of the box.

You can think of aster for JS as of rework for CSS.

But I like X builder! Should I throw it out?

Definitely no! aster doesn't try to fight your favorite build system. It has only one specific area that it's exceptionally good at - code processing. Everything else (CSS, images, publishing to CDN, etc.) is left for generic builders, and you can use them together.

Currently there are following bindings:

If you wish, you can define aster pipeline as custom task in any existing build system on your own since aster uses RxJS under the hood, which is interoperable with events, streams, promises, callbacks and any other asynchronous primitives and patterns out of the box.

API

aster is completely modular and main package is just a centralized API wrapper for core parts published as separate modules (check out their documentations for details):

  • src - Single-pass source files reader.
  • watch - Continuous source files reader.
  • dest - File writer.
  • runner - Build pipeline runner with built-in logger.
  • equery - Example query and modify, via grasp-equery
  • squery - Standard (CSS like) query and modify, via grasp-squery
  • traverse - AST traverser
  • parse - Parser
  • parse-js - JS Parser
  • parse-esnext - ES.next Parser

Usage

First, install rx-aster as a development dependency:

npm i --save rx-aster

or via yarn

yarn add rx-aster

Check the tests in /tests folder and run them. The tests demonstrate how to:

  • select and remove/rename class, function and method by id
  • find a node and add a class, function or method after it

Read the docs

Notice how the tests use selectors described in javascript syntax

Example (from squery)

Putting a couple things we have learned together, here is the selector for an immediately-invoked function expression.

These can take for form of:

(function(){ ... })();
(function(){ ... }).call(...);
(function(){ ... }).apply(...);

The selector we use to match these is:

call[callee=(func-exp, member[obj=func-exp][prop=(#call, #apply)])]

At the top level, in all cases we are matching a call. The callee (the function being called) of the call is either a function expression in the first case, or a member expression in the second and third cases. In those cases, the object that is being accessed is a function expression, and the property is either the identifier call in the second case, or apply in the third case.

As this is a common pattern, you can simply use iife to match it instead.

See the javascript syntax for an overview of available selectors such as func-dec and class-dec.

Creating a refactor pipeline

Create build script and use it.

aster.watch([ // watch these files for changes
  'src/**/*.js',
  'src/**/*.coffee',
  'src/**/*.jsx'
])
.throttle(500) // every 500ms
.map(changed(function (src) {
  return src.map(equery({
    'if ($cond) return $expr1; else return $expr2;':
      'return <%= cond %> ? <%= expr1 %> : <%= expr2 %>'
  }));
}))
.map(concat('built.js'))
.map(umd({exports: 'superLib'}))
.map(aster.dest('dist', {sourceMap: true}))
.subscribe(aster.runner);

Using custom sources:

const sources = ['var a = 1', 'var b = a + 2']

const srcObserver = Rx.Observable.of(sources);
const srcObserver2 = Rx.Observable.just(sources[0]);

aster.src({
  srcObserver
})
.map(equery({
  'if ($cond) return $expr1; else return $expr2;': 'return <%= cond %> ? <%= expr1 %> : <%= expr2 %>'
  // , ...
}))
.map(aster.dest((options) => {
  // return a function that when run returns an Observable
  return function(sinks) {
    // do something with the sinks...
    // ie. use incoming event streams from previous step (ie. .map(equery(...)), write to a destination
    return sinks
  }
}))
.subscribe(aster.runner({
  onNext: (item) => {
    console.log('>> %s'.yellow, item)
  }
}));

## Creating plugins

Check out aster's [Yeoman generator](https://github.com/asterjs/generator-aster).

It automizes the process of creating basic skeleton and Github repo for your plugin in few easy steps. When created, you just need to modify [`index.js`](https://github.com/asterjs/generator-aster/blob/master/app/templates/index.js) and [`test.js`](https://github.com/asterjs/generator-aster/blob/master/app/templates/test/test.js) files to reflect your intended plugin's functionality (detailed hints included right in code).

## License

[MIT License](http://en.wikipedia.org/wiki/MIT_License)

[npm-url]: https://npmjs.org/package/aster
[npm-image]: https://badge.fury.io/js/aster.png

[travis-url]: http://travis-ci.org/asterjs/aster
[travis-image]: https://secure.travis-ci.org/asterjs/aster.png?branch=master

About

RxJS esprima AST js code modification library using aster, esprima and grasp

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published