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