Code Conversion


function SimpleClass() {
	var test = new my.long.name.space.Field();
	this.aValue = my.other.name.space.Factory.callExpression('A Literal Value');
	other.name.space.ClassName.callExpression(42);
}

my.extend(SimpleClass, my.long.name.space.SuperClass);

Conversion Goal

To take scripts that contain global references to their dependencies and convert them into...


var my = require("my");
var Field = require("my/long/name/space/Field");
var Factory = require("my/other/name/space/Factory");
var ClassName = require("other/name/space/ClassName");
var SuperClass = require("my/long/name/space/SuperClass");

function SimpleClass() {
	var test = new Field();
	this.aValue = Factory.callExpression('A Literal Value');
	ClassName.callExpression(42);
}

my.extend(SimpleClass, SuperClass);

...modules which import/require their dependencies via a module loader.

Why?

  • Easier for developers to reason about code.
  • Easier for tools to statically analyse code.
  • Modules are the future of web application development.
  • Customers can update their codebase

How?

  • RegExp
  • String manipulation

Too low level, insufficient level of abstraction. Error prone and can end up extremely complex to deal with all edge cases.

  • Parse the code and mutate the AST

Abstract syntax tree

Data structure representing abstract syntactic code structure.

So code like this...


my.long.name.space.Field

is represented like...

"type": "MemberExpression",
"computed": false,
"object": {
    "type": "MemberExpression",
    "computed": false,
    "object": {
        "type": "MemberExpression",
        "computed": false,
        "object": {
            "type": "MemberExpression",
            "computed": false,
            "object": {
                "type": "Identifier",
                "name": "my"
            },
            "property": {
                "type": "Identifier",
                "name": "long"
            }
        },
        "property": {
            "type": "Identifier",
            "name": "name"
        }
    },
    "property": {
        "type": "Identifier",
        "name": "space"
    }
},
"property": {
    "type": "Identifier",
    "name": "Field"
}

Parsing

Esprima is the most popular https://github.com/ariya/esprima

Generates SpiderMonkey ASTs

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API

Modifying AST

Recast allows mutation of AST*

https://github.com/benjamn/recast


const {builders} = require('ast-types');

const moduleIdentifier = builders.identifier('Field');

nodePathToTransform.replace(moduleIdentifier)

my.long.name.space.Field

Field

From

to

Tree traversal


export const moduleIdVisitor = {
	/**
	 * @param {Map<string, string>} moduleIdsToConvert - The module Ids to convert.
	 */
	initialize(moduleIdsToConvert) {
		this._moduleIdsToConvert = moduleIdsToConvert;
	},

	/**
	 * @param {NodePath} identifierNodePath - Identifier NodePath.
	 */
	visitIdentifier(identifierNodePath) {
		this.traverse(identifierNodePath);
	}
};

/**
 * @param {OptionsObject} options - Options to configure transforms.
 */
export function compileSourceFiles(options) {
    vinylFs.src('src/**/*.js')
        .pipe(parseJSFile())
        .pipe(expandVarNamespaceAliases(options.namespaces))
        .pipe(through2.obj(flattenIIFEClass))
        .pipe(through2.obj(flattenClass))
        .pipe(convertGlobalsToRequires(options.namespaces))
        .pipe(removeCJSModuleRequires(options.moduleIDsToRemove))
        .pipe(addRequiresForLibraries(options.libraryIdentifiersToRequire))
        .pipe(transformI18nUsage())
        .pipe(convertASTToBuffer())
        .pipe(vinylFs.dest(options.outputDirectory))
        .on('end', createJSStyleFiles());
}

Using transforms

Future work

Automate the conversion of code to match our code style and linting rules.

 

Upgrade deprecated code usage to new code.

 

Convert the codebase to ES6.

 

Replace proxied code with direct reference to new code.

 

Could be part of an upgrade script for new versions of our code.

https://github.com/briandipalma/global-compiler

 

https://github.com/briandipalma/gc-cli

 

https://confluence.caplin.com/display/ENG/Converting+code+to+commonjs+automatically

ast-compiler

By briandipalma