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
ast-compiler
- 552