Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreScripted, a JavaScript editor from VMWare was announced on this blog last month. In this article we'll take a look under the hood at Scripted's Dependency Analysis Engine. But before diving into the details, lets motivate why we need it.
[caption id="attachment_12178" align="aligncenter" width="533" caption="Cross-file Content Assist"][/caption]
Two components work together to achieve this goal:
If you wanted to simply put all your code into one big file, then a good quality inferencer alone would be sufficient to provide some pretty good content assist. In reality, projects will be divided into modules. In the example above, the 'main' module imports a 'utils' module. While you edit the main module, Scripted proposes functions from 'utils' as appropriate. The dependency analysis engine is what makes this possible.
[caption id="attachment_12180" align="aligncenter" width="518" caption="Error Marker for an Unresolved Module"][/caption]
Scripted also uses dependency analysis to support easy navigation. A shift or ctrl click on a resolved dependency name will take you to the corresponding file.
In the future, dependency analysis might also allow us to implement refactoring tools. For example, if you drag-and-drop a .js file to a different directory, Scripted could automatically update relative path references as needed.
getDGraph :: <path-to-js-file> -> <dependency-graph>This function computes a JSON representation of the dependency graph. This graph contains a node for all files that the target file directly or transitively depends on. If we pass it our 'main' module, it will return something like the following:
getDGraph('/sample/main.js') ==> { ... "/NODE-NATIVES/stream.js": { "kind": "commonjs", "refs": { ... } }, "/NODE-NATIVES/fs.js": { "kind": "commonjs", "refs": { "stream": { "kind": "commonjs", "name": "stream", "path": "/NODE-NATIVES/stream.js" }, ... } }, "/sample/utils.js": { "kind": "commonjs", "refs": {} }, "/sample/main.js": { "kind": "commonjs", "refs": { "fs": { "kind": "commonjs", "name": "fs", "path": "/NODE-NATIVES/fs.js" }, "./utils": { "kind": "commonjs", "name": "./utils", "path": "/sample/utils.js" } } } }Each property in this JSON object represents a node in the graph. The 'refs' property contains the edges. Each edge corresponds to a module import.
An interesting detail is that the dependency analyzer returns special path strings for native node modules. When the inference engine request the source code for such a path, the Scripted server, which is written in JavaScript and running on Node.js, extracts the source code from its very own Node.js process. The inference engine analyzes it just like an ordinary JavaScript file. The result is nice content assist for built-in node modules:
[caption id="attachment_12184" align="aligncenter" width="587" caption="Inferred Suggestions for Built-in Node Modules"][/caption]
Steps 2 and 3 are dispatched to different support modules depending on the module type from step 1. It should be relatively easy to add support for additional module types (provided the step 1 detector can be made to recognize the new module type).
For Node/CommonJS this works very well, mainly because there really is not much that needs to be configured. I.e. if we assume the standard Node.js loader algorithm is used, that is really all the information we need.
For AMD the situation is unfortunately more complicated. The typical AMD loader (e.g. requirejs) is highly configurable. Moreover the way this configuration is expressed in the project's source code tends to vary from project to project. This makes it a challenge to determine where to find the required information in a random project.
The approach we have taken is to look at some example projects and the 'typical' patterns they use. Discovery works by recognizing these patterns. The hope is that if we support enough common patterns, eventually discovery will work for most projects. We may also add some manual configuration options as a last resort, for those cases where it fails.
To give an idea how AMD discovery works, here's an example of one of the patterns we currently detect:
The pattern here is a script tag with a data-main attribute... If Scripted finds this, it will go looking for a requirejs style configuration block in the data-main file:
AMD configuration discovery is a work in progress. As we are presented with more examples of how people setup their AMD loaders we try to add detectors for them. If Scripted incorrectly flags many dependencies as errors, it probably failed to discover your AMD configuration. You can help us by raising a bug request. If you attach a code sample illustrating your 'typical pattern'. This will help us extend our 'pattern catalog'.
Want to try out Scripted? Grab it from GitHub. It is easy to install. Download and installation instructions are in the readme file.