Prints Babel AST to readable JavaScript. Use 🐊Putout to parse your code.
You may also use Babel 8 with estree-to-babel for ESTree and Babel AST to put .extra.raw to .raw (which is simpler for transforms, no need to use Optional Chaining and add extra values every time).
- ☝️ Similar to Recast, but twice faster, also simpler and easier in maintenance, since it supports only Babel.
- ☝️ As opinionated as Prettier, but has more user-friendly output and works directly with AST.
- ☝️ Like ESLint but works directly with Babel AST.
- ☝️ Easily extendable with help of Overrides.
Supports:
- ✅ ES2026;
- ✅ JSX;
- ✅ TypeScript;
- ✅ JSON;
npm i @putout/printer
Printer has first class support from 🐊Putout with help of @putout/plugin-printer. So install:
npm i @putout/plugin-printer -aDAnd update .putout.json:
{
"printer": "putout",
"plugins": ["printer"]
}To benefit from it.
import{print}from'@putout/printer';import{parse}from'putout';constast=parse('const a = (b, c) => {const d = 5; return a;}');print(ast);// returns`const a = (b, c) => { const d = 5; return a;};`;When you need to extend syntax of @putout/printer just pass a function which receives:
path, Babel Pathprint, a function to output result of printing into token array;
When path contains to dashes __ and name, it is the same as: write(path.get('right')), and this is
actually traverse(path.get('right')) shortened to simplify read and process.
Here is how you can override AssignmentPattern:
const{print}=printer;constast=parse('const {a = 5} = b');print(ast,{format: {indent: ' ',newline: '\n',space: ' ',splitter: '\n',quote: `'`,endOfFile: '\n',},semantics: {comments: true,maxSpecifiersInOneLine: 2,maxElementsInOneLine: 3,maxElementLengthInOneLine: 15,maxLogicalsInOneLine: 3,maxVariablesInOneLine: 4,maxTypesInOneLine: 3,maxPropertiesInOneLine: 2,maxPropertiesLengthInOneLine: 15,trailingComma: true,escapeSingleQuote: true,escapeDoubleQuote: false,roundBraces: {arrow: true,sequence: true,assign: false,new: true,},},visitors: {AssignmentPattern(path,{print}){print('/* [hello world] */= ');print('__right');},},});// returns'const {a/* [hello world] */= 5} = b;\n';Options related to visuals and not related to logic of output can be changed with help of format,
you can override next options:
constoverrides={format: {indent: ' ',newline: '\n',space: ' ',splitter: '\n',endOfFile: '\n',},};indent- use two spaces, tabs, or anything you want;newline- symbol used for line separation;space- default symbol used for space character;splitter- mandatory symbol that used inside of statements like this:
Default options produce:
if(a>3)console.log('ok');elseconsole.log('not ok');But you can override them with:
constoverrides={format: {indent: '',newline: '',space: '',splitter: ' ',},};And have minified code:
if(a>3)console.log('ok');else console.log('not ok');
Options used to configure logic of output, similar to ESLint rules:
- ✅
maxElementsInOneLine- count ofArrayExpressionandArrayPatternelements placed in one line. - ✅
maxLogicalsInOneLine- count ofLogicalExpressionelements placed in one line. - ✅
maxVariablesInOneLine- count ofVariableDeclaratorsin one line. - ✅
maxPropertiesInOneLine- count ofObjectPropertiesin one line. - ✅
maxPropertiesLengthInOneLine- maximum length ofObject Property, when violated splits event ifmaxPropertiesInOneLinesatisfies; - ✅
roundBracesto output braces or not
When you want to improve support of existing visitor or extend Printer with a new ones, you need next base operations:
When you need to override behavior of existing visitor use:
import{print,visitorsasv,}from'@putout/printer';print(ast,{visitors: {CallExpression(path,printer,semantics){const{print}=printer;if(!path.node.goldstein)returnv.CallExpression(path,printer,semantics);print('__goldstein');},},});Used in previous example print can be used for a couple purposes:
- to write
string; - to write
nodewhenobjectpassed; - to write
nodewhenstringstarted with__;
const{print}=printer;print(ast,{visitors: {AssignmentPattern(path,{print, maybe}){maybe.write.newline(path.parentPath.isCallExpression());print('/* [hello world] */= ');print('__right');},},});When you need some condition use maybe. For example, to add newline only when parent node is CallExpression you
can use maybe.write.newline(condition):
const{print}=printer;print(ast,{visitors: {AssignmentPattern(path,{write, maybe}){maybe.write.newline(path.parentPath.isCallExpression());write(' /* [hello world] */= ');write('__right');},},});When you going to output string you can use low-level function write:
const{print}=printer;print(ast,{visitors: {BlockStatement(path,{write}){write('hello');},},});When you need to add indentation use indent, for example when you output body,
you need to increment indentation, and then decrement it back:
const{print}=printer;print(ast,{visitors: {BlockStatement(path,{write, indent}){write('{');indent.inc();indent();write('some;');indent.dec();write('{');},},});When you need to traverse node path, you can use traverse:
const{print}=printer;print(ast,{visitors: {AssignmentExpression(path,{traverse}){traverse(path.get('left'));},},});This is the same as print('__left') but more low-level, and supports only objects.
About speed, for file speed.js:
import{readFileSync}from'node:fs';import{putout}from'putout';importparserfrom'@babel/parser';constcode=readFileSync('./lib/tokenize/tokenize.js','utf8');constast=parser.parse(code);speed('recast');speed('putout');functionspeed(printer){console.time(printer);for(leti=0;i<1000;i++){putout(code,{
printer,plugins: ['remove-unused-variables'],});}console.timeEnd(printer);}With contents of tokenize.js, we have:
MIT
