using.macro

babel-plugin-macro for automatic try-finally blocks and C++ like RAII resource management

Usage no npm install needed!

<script type="module">
  import usingMacro from 'https://cdn.skypack.dev/using.macro';
</script>

README

using.macro

Npm

Babel Macro Build Status Coverage Status TypeScript

Just plain old C++ RAII (or C# using declaration) implementation for TypeScipt via 'babel-plugin-macros'.

:zap: Rationale

Suppose you have a local resource handle that you must manually release in the same scope.

With this plugin the cleanup is done automatically for you.

import using from 'using.macro';

const resource = using (new Resource());
using (new AutoVarResource());
doStuffWithResource(resource);

    ↓ ↓ ↓ ↓ ↓ ↓

const resource = new Resource();
try {
    const _handle = new AutoVarResource(); // variable is created for you
    try {
        doStuffWithResource(resource);
    } finally {
        _handle.delete(); // this part is fully customizable!
    }
} finally {
    resource.delete();    // this part is fully customizable!
}

:dvd: Installation

Make sure babel-plugin-macros, is added as "macros" item in "plugins" array of your babel config file.

Afterwards just install this package as dev dependency and use it in your code:

npm i -D using.macro

:sunglasses: Syntax

The way you import this macro doesn't matter (as long as TypeScript eats it):

import using from 'using.macro';
import kek   from 'using.macro';
import { using } from 'using.macro';
import { using as kek } from 'using.macro';

There are only two options for invoking this macro:

  • Near variable declaration var/let/const varId = using(expresssion);
  • As a single expression statement using(expression); so that a variable will be created automatically to release it for you.

:hammer: Customize

By default when you do

import using from 'using.macro';

you get .delete() method called on your handles.

If you want to change this behaviour to e.g. call free() function on your handle, you can wrap this macro with your own via createUsingMacro() from 'using.macro/create':

// using-ptr.macro.ts  # .macro extension matters! See babel-plugin-macros docs
import { createMacro } from 'babel-plugin-macros';
import { createUsingMacro, createFunctionCallFactory } from 'using.macro/create';

export default createMacro<Fn>(createUsingMacro(createFunctionCallFactory('free')));

// Pass this noop function type to createMacro<>() for TypeScript support.
type Fn = <T>(val: T) => T;

and use it in your code this way:

import usingPtr from './using-ptr.macro';

const ptr = usingPtr (malloc(42));
// stuff

    ↓ ↓ ↓ ↓ ↓ ↓

const ptr = malloc(42);
try {
    // stuff
} finally {
    free(ptr);
}

create[Function/Method]CallFactory() are simply two helper functions. You can actually pass your own destructor codegen function instead:

import * as T from '@babel/types'; // default import is not supported by babel!

createUsingMacro(garbageVariableIdentifier => {
    // you may return a simple expression or a statement or an array of statements here
    return T.callExpression(T.identifier('free'), [garbageVariableIdentifier.node]);
})

:warning: Caveats

Unfortunately 'babel-plugin-macros' doesn't natively support TypeScript macros in runtime, so you need to build your custom *.macro.ts to *.macro.js file, and put in the same directory as ts file.

:arrow_upper_right: References

If you are looking for a native using binding syntax, please support this stage 2 proposal and upvote this proposition to it.

This macro was originally created for simplifying the usage of 'embind' C++ object handles