Serialize your TypeScript functions!
ts-serialize-closures
can serialize and deserialize arbitrary TypeScript/JavaScript object graphs. That includes:
- functions, which may have captured data,
Date
andRegExp
objects,- cyclic graphs,
- prototypes,
- references to built-in objects,
- etc.
The idea is that serialize
creates a self-contained snapshot of all program state relevant to the object being serialized. The deserialize
function decodes that snapshot back to a JavaScript object graph.
This tool might be useful in a number of scenarios:
-
Exchanging functions between different processes. That's often a useful tool for building distributed systems.
-
Lightweight remote post-mortem debugging: have failing processes create a neat little snapshot of their current state and send yourself that snapshot for analysis.
The serializer (serialize-closures
) requires a preprocessing step (ts-closure-transform
). Therefore, the typical usage of this library is to configure webpack to automatically transform the source code using a hook in the TypeScript compiler (tsc
). Take the following steps to set up a stand-alone example:
- Prepare project and install dev-dependencies
mkdir example && cd example
npm init
npm install --save-dev ts-closure-transform serialize-closures webpack webpack-cli typescript ts-loader util
2.1 Configure tsconfig.json
:
{
"compileOnSave": true,
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"declaration": true,
"moduleResolution": "node",
"stripInternal": true,
"jsx": "react",
"outDir": "dist"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
2.2 Configure webpack.config.js
:
const tsClosureTransform = require('ts-closure-transform');
const path = require('path');
module.exports = {
entry: {
example: './src/example.ts',
},
mode: 'development',
module: {
rules: [
{
test: /.tsx?$/,
loader: 'ts-loader', // or 'awesome-typescript-loader'
options: {
getCustomTransformers: () => ({
before: [tsClosureTransform.beforeTransform()],
after: [tsClosureTransform.afterTransform()]
})
}
}
]
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
fallback: {
"util": require.resolve("util/"),
},
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].bundle.js',
}
}
- Write code
src/example.ts
to serialize and deserialize arbitrary functions:
import { serialize, deserialize } from 'serialize-closures';
// Just about anything can be serialized by calling `serialize`.
let capturedVariable = 5;
let serialized = serialize(() => capturedVariable);
// Serialized representations can be stringified and parsed.
let text = JSON.stringify(serialized);
let parsed = JSON.parse(text);
// Serialized representations can be deserialized by calling `deserialize`.
console.log(deserialize(serialized)()); // Prints '5'.
console.log(deserialize(parsed)()); // Prints '5'.
- Compile with webpack and run the sample
npx webpack
node dist/example.bundle.js
The serializer consists of two components.
-
ts-closure-transform
: a transformation to inject in the TypeScript compiler's pass pipeline. This transformation will rewrite all function definitions to include a special__closure
property. The serializer uses that__closure
property to figure out which variables are captured by the function.How you inject this transform depends on the webpack loader you're using. For
ts-loader
andawesome-typescript-loader
, you can do the following:import { beforeTransform, afterTransform } from 'ts-closure-transform'; // ... loader: 'ts-loader', options: { getCustomTransformers: () => ({ before: [beforeTransform()], after: [afterTransform()] }) } // ...
Note that
ts-closure-transform
is strictly a dev dependency: there's no need to package it with your application. -
serialize-closures
: a runtime library that defines theserialize
anddeserialize
functions. These should work for any object graph as long as all source code has first been processed byts-closure-transform
.
ts-serialize-closures
works fairly well for modest object graphs, but it does have a number of limitations you should be aware of:
-
Variable-capturing functions defined in files that have not been transformed by
ts-closure-transform
cannot be deserialized correctly. -
Serializing class definitions works, but only if they are first lowered to function definitions by the TypeScript compiler, i.e., the target is ES5 or lower.
-
Functions can only be serialized and deserialized once. There is no support for serializing a deserialized function.