UNPKG

5.69 kBMarkdownView Raw
1# defs.js
2Static scope analysis and transpilation of ES6 block scoped `const` and `let`
3variables, to ES3.
4
5Node already supports `const` and `let` so you can use that today
6(run `node --harmony` and `"use strict"`). `defs.js` enables you to do the same
7for browser code. While developing you can rely on the experimental support
8in Chrome (chrome://flags, check Enable experimental JavaScript). `defs.js` is
9also a pretty decent static scope analyzer/linter.
10
11The talk
12[LET's CONST together, right now (with ES3)](http://vimeo.com/66501924)
13from Front-Trends 2013
14([slides](http://blog.lassus.se/files/lets_const_together_ft2013.pdf)) includes
15more information about `let`, `const` and `defs.js`. See also the blog post
16[ES3 <3 block scoped const and let => defs.js](http://blog.lassus.se/2013/05/defsjs.html).
17
18
19## Installation and usage
20 npm install -g defs
21
22Then run it as `defs file.js`. The errors (if any) will go to stderr,
23the transpiled source to `stdout`, so redirect it like `defs file.js > output.js`.
24More command line options are coming.
25
26There's also a [Grunt](http://gruntjs.com/) plugin, see [grunt-defs](https://npmjs.org/package/grunt-defs).
27
28See [BUILD.md](BUILD.md) for a description of the self-build and the browser bundle.
29
30## License
31`MIT`, see [LICENSE](LICENSE) file.
32
33
34## Changes
35See [CHANGES.md](CHANGES.md).
36
37
38## Configuration
39`defs` looks for a `defs-config.json` configuration file in your current
40directory. It will search for it in parent directories soon as you'd expect.
41
42Example `defs-config.json`:
43
44 {
45 "environments": ["node", "browser"],
46
47 "globals": {
48 "my": false,
49 "hat": true
50 },
51 "disallowVars": false,
52 "disallowDuplicated": true,
53 "disallowUnknownReferences": true
54 }
55
56`globals` lets you list your program's globals, and indicate whether they are
57writable (`true`) or read-only (`false`), just like `jshint`.
58
59`environments` lets you import a set of pre-defined globals, here `node` and
60`browser`. These default environments are borrowed from `jshint` (see
61[jshint_globals/vars.js](https://github.com/olov/defs/blob/master/jshint_globals/vars.js)).
62
63`disallowVars` (defaults to `false`) can be enabled to make
64usage of `var` an error.
65
66`disallowDuplicated` (defaults to `true`) errors on duplicated
67`var` definitions in the same function scope.
68
69`disallowUnknownReferences` (defaults to `true`) errors on references to
70unknown global variables.
71
72`ast` (defaults to `false`) produces an AST instead of source code
73(experimental).
74
75`stats` (defaults to `false`) prints const/let statistics and renames
76(experimental).
77
78
79## Example
80
81Input `example.js`:
82
83```javascript
84"use strict";
85function fn() {
86 const y = 0;
87 for (let x = 0; x < 10; x++) {
88 const y = x * 2;
89 const z = y;
90 }
91 console.log(y); // prints 0
92}
93fn();
94```
95
96Output from running `defs example.js`:
97
98```javascript
99"use strict";
100function fn() {
101 var y = 0;
102 for (var x = 0; x < 10; x++) {
103 var y$0 = x * 2;
104 var z = y$0;
105 }
106 console.log(y); // prints 0
107}
108fn();
109```
110
111
112## defs.js used as a library
113`npm install defs`, then:
114
115```javascript
116const defs = require("defs");
117const options = {};
118const res = defs("const x = 1", options);
119assert(res.src === "var x = 1");
120```
121
122res object:
123
124 {
125 src: string // on success
126 errors: array of error messages // on errors
127 stats: statistics object (toStringable)
128 ast: transformed ast // when options.ast is set
129 }
130
131
132## Compatibility
133`defs.js` strives to transpile your program as true to the ES6 block scope semantics as
134possible, while being as maximally non-intrusive as possible. The only textual
135differences you'll find between your original and transpiled program is that the latter
136uses `var` and occasional variable renames.
137
138
139### Loop closures limitation
140`defs.js` won't transpile a closure-that-captures-a-block-scoped-variable-inside-a-loop, such
141as the following example:
142
143```javascript
144for (let x = 0; x < 10; x++) {
145 let y = x;
146 arr.push(function() { return y; });
147}
148```
149
150With ES6 semantics `y` is bound fresh per loop iteration, so each closure captures a separate
151instance of `y`, unlike if `y` would have been a `var`. [Actually, even `x` is bound per
152iteration, but v8 (so node) has an
153[open bug](https://code.google.com/p/v8/issues/detail?id=2560) for that].
154
155To transpile this example, an IIFE or `try-catch` must be inserted, which isn't maximally
156non-intrusive. `defs.js` will detect this case and spit out an error instead, like so:
157
158 line 3: can't transform closure. y is defined outside closure, inside loop
159
160You need to manually handle this the way we've always done pre-`ES6`,
161for instance like so:
162
163```javascript
164for (let x = 0; x < 10; x++) {
165 (function(y) {
166 arr.push(function() { return y; });
167 })(x);
168}
169```
170
171I'm interested in feedback on this based on real-world usage of `defs.js`.
172
173
174### Referenced (inside closure) before declaration
175`defs.js` detects the vast majority of cases where a variable is referenced prior to
176its declaration. The one case it cannot detect is the following:
177
178```javascript
179function printx() { console.log(x); }
180printx(); // illegal
181let x = 1;
182printx(); // legal
183```
184
185The first call to `printx` is not legal because `x` hasn't been initialized at that point
186of *time*, which is impossible to catch reliably with statical analysis.
187`v8 --harmony` will detect and error on this via run-time checking. `defs.js` will
188happily transpile this example (`let` => `var` and that's it), and the transpiled code
189will print `undefined` on the first call to `printx`. This difference should be a very
190minor problem in practice.