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 talk
|
12 | [LET's CONST together, right now (with ES3)](http://vimeo.com/66501924)
|
13 | from Front-Trends 2013
|
14 | ([slides](http://blog.lassus.se/files/lets_const_together_ft2013.pdf)) includes
|
15 | more 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 |
|
22 | Then run it as `defs file.js`. The errors (if any) will go to stderr,
|
23 | the transpiled source to `stdout`, so redirect it like `defs file.js > output.js`.
|
24 | More command line options are coming.
|
25 |
|
26 | There's also a [Grunt](http://gruntjs.com/) plugin, see [grunt-defs](https://npmjs.org/package/grunt-defs).
|
27 |
|
28 | See [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
|
35 | See [CHANGES.md](CHANGES.md).
|
36 |
|
37 |
|
38 | ## Configuration
|
39 | `defs` looks for a `defs-config.json` configuration file in your current
|
40 | directory. It will search for it in parent directories soon as you'd expect.
|
41 |
|
42 | Example `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
|
57 | writable (`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
|
64 | usage 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
|
70 | unknown 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 |
|
81 | Input `example.js`:
|
82 |
|
83 | ```javascript
|
84 | "use strict";
|
85 | function 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 | }
|
93 | fn();
|
94 | ```
|
95 |
|
96 | Output from running `defs example.js`:
|
97 |
|
98 | ```javascript
|
99 | "use strict";
|
100 | function 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 | }
|
108 | fn();
|
109 | ```
|
110 |
|
111 |
|
112 | ## defs.js used as a library
|
113 | `npm install defs`, then:
|
114 |
|
115 | ```javascript
|
116 | const defs = require("defs");
|
117 | const options = {};
|
118 | const res = defs("const x = 1", options);
|
119 | assert(res.src === "var x = 1");
|
120 | ```
|
121 |
|
122 | res 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
|
134 | possible, while being as maximally non-intrusive as possible. The only textual
|
135 | differences you'll find between your original and transpiled program is that the latter
|
136 | uses `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
|
141 | as the following example:
|
142 |
|
143 | ```javascript
|
144 | for (let x = 0; x < 10; x++) {
|
145 | let y = x;
|
146 | arr.push(function() { return y; });
|
147 | }
|
148 | ```
|
149 |
|
150 | With ES6 semantics `y` is bound fresh per loop iteration, so each closure captures a separate
|
151 | instance of `y`, unlike if `y` would have been a `var`. [Actually, even `x` is bound per
|
152 | iteration, but v8 (so node) has an
|
153 | [open bug](https://code.google.com/p/v8/issues/detail?id=2560) for that].
|
154 |
|
155 | To transpile this example, an IIFE or `try-catch` must be inserted, which isn't maximally
|
156 | non-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 |
|
160 | You need to manually handle this the way we've always done pre-`ES6`,
|
161 | for instance like so:
|
162 |
|
163 | ```javascript
|
164 | for (let x = 0; x < 10; x++) {
|
165 | (function(y) {
|
166 | arr.push(function() { return y; });
|
167 | })(x);
|
168 | }
|
169 | ```
|
170 |
|
171 | I'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
|
176 | its declaration. The one case it cannot detect is the following:
|
177 |
|
178 | ```javascript
|
179 | function printx() { console.log(x); }
|
180 | printx(); // illegal
|
181 | let x = 1;
|
182 | printx(); // legal
|
183 | ```
|
184 |
|
185 | The first call to `printx` is not legal because `x` hasn't been initialized at that point
|
186 | of *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
|
188 | happily transpile this example (`let` => `var` and that's it), and the transpiled code
|
189 | will print `undefined` on the first call to `printx`. This difference should be a very
|
190 | minor problem in practice.
|