OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dev_compiler.src.codegen.module_builder; | 5 library dev_compiler.src.codegen.module_builder; |
6 | 6 |
7 import 'package:path/path.dart' as path; | 7 import 'package:path/path.dart' as path; |
8 | 8 |
9 import '../js/js_ast.dart' as JS; | 9 import '../js/js_ast.dart' as JS; |
10 import '../js/js_ast.dart' show js; | 10 import '../js/js_ast.dart' show js; |
11 import '../options.dart' show ModuleFormat; | 11 import '../options.dart' show ModuleFormat; |
12 | 12 |
13 /// Helper that builds JS modules in a given [ModuleFormat]. | 13 /// Helper that builds JS modules in a given [ModuleFormat]. |
14 abstract class ModuleBuilder { | 14 abstract class ModuleBuilder { |
15 final List<String> _exports = <String>[]; | 15 final _exports = <String, String>{}; |
16 final List<_ModuleImport> _imports = <_ModuleImport>[]; | 16 final _imports = <_ModuleImport>[]; |
17 | 17 |
18 ModuleBuilder._(); | 18 ModuleBuilder._(); |
19 | 19 |
20 /// Returns a [format]-specific [ModuleBuilder]. | 20 /// Returns a [format]-specific [ModuleBuilder]. |
21 /// - [jsPath] is the path of the module being built. | 21 /// - [jsPath] is the path of the module being built. |
22 /// - [jsModuleValue] is the default value to use for the library, in case of | 22 /// - [jsModuleValue] is the default value to use for the library, in case of |
23 /// js interop (comes from the @js.JS(jsModuleValue) annotation on the | 23 /// js interop (comes from the @js.JS(jsModuleValue) annotation on the |
24 /// library directive). It is null in any other case. | 24 /// library directive). It is null in any other case. |
25 /// - [exportsVar] is the name of the object on which items are exported. Lazy | 25 /// - [exportsVar] is the name of the object on which items are exported. Lazy |
26 /// variables and constants are assumed to be declared on this instance. | 26 /// variables and constants are assumed to be declared on this instance. |
27 factory ModuleBuilder(ModuleFormat format) { | 27 factory ModuleBuilder(ModuleFormat format) { |
28 switch (format) { | 28 switch (format) { |
29 case ModuleFormat.legacy: | 29 case ModuleFormat.legacy: |
30 return new LegacyModuleBuilder(); | 30 return new LegacyModuleBuilder(); |
31 case ModuleFormat.es6: | 31 case ModuleFormat.es6: |
32 return new ES6ModuleBuilder(); | 32 return new ES6ModuleBuilder(); |
33 case ModuleFormat.node: | |
34 return new NodeModuleBuilder(); | |
33 } | 35 } |
34 } | 36 } |
35 | 37 |
36 /// Adds [name] to the list of names to be exported from the module. | 38 /// Adds [name] to the list of names to be exported from the module. |
37 void addExport(String name) { | 39 void addExport(String name, String exportName) { |
38 _exports.add(name); | 40 _exports[name] = exportName; |
39 } | 41 } |
40 | 42 |
41 /// Adds an import from a module named [name] and locally aliased as [libVar]. | 43 /// Adds an import from a module named [name] and locally aliased as [libVar]. |
42 /// When [isLazy] is true, the import should be lazy (i.e. there is some | 44 /// When [isLazy] is `true`, the import should be lazy (i.e. there is some |
43 /// cyclic dependency of imports). | 45 /// cyclic dependency of imports). |
46 /// When [libVar] is `null`, the import is there just to force the import | |
47 /// order. | |
44 void addImport(String name, JS.Identifier libVar, {bool isLazy: false}) { | 48 void addImport(String name, JS.Identifier libVar, {bool isLazy: false}) { |
45 _imports.add(new _ModuleImport(name, libVar, isLazy)); | 49 _imports.add(new _ModuleImport(name, libVar, isLazy)); |
46 } | 50 } |
47 | 51 |
48 /// Builds a program out of menu items. | 52 /// Builds a program out of menu items. |
49 JS.Program build(String jsPath, String jsModuleValue, | 53 JS.Program build(String jsPath, String jsModuleValue, |
50 JS.Identifier exportsVar, Iterable<JS.ModuleItem> moduleItems); | 54 JS.Identifier exportsVar, Iterable<JS.ModuleItem> moduleItems); |
51 } | 55 } |
52 | 56 |
53 class _ModuleImport { | 57 class _ModuleImport { |
54 final String name; | 58 final String name; |
55 final JS.Identifier libVar; | 59 final JS.Identifier libVar; |
56 // TODO(jmesserly): Assess whether we can remove this (we shouldn't need it | 60 // TODO(jmesserly): Assess whether we can remove this (we shouldn't need it |
57 // even in our legacy module format, but it might still be useful for Closure | 61 // even in our legacy module format, but it might still be useful for Closure |
58 // with ES6 modules). | 62 // with ES6 modules). |
59 final bool isLazy; | 63 final bool isLazy; |
60 _ModuleImport(this.name, this.libVar, this.isLazy); | 64 _ModuleImport(this.name, this.libVar, this.isLazy); |
61 } | 65 } |
62 | 66 |
63 /// Generates modules for with DDC's `dart_library.js` loading mechanism. | 67 /// Generates modules for with DDC's `dart_library.js` loading mechanism. |
64 class LegacyModuleBuilder extends ModuleBuilder { | 68 class LegacyModuleBuilder extends ModuleBuilder { |
65 LegacyModuleBuilder() : super._(); | 69 LegacyModuleBuilder() : super._(); |
66 | 70 |
67 JS.Program build(String jsPath, String jsModuleValue, | 71 JS.Program build(String jsPath, String jsModuleValue, |
68 JS.Identifier exportsVar, List<JS.ModuleItem> moduleItems) { | 72 JS.Identifier exportsVar, Iterable<JS.ModuleItem> moduleItems) { |
69 // TODO(jmesserly): it would be great to run the renamer on the body, | 73 // TODO(jmesserly): it would be great to run the renamer on the body, |
70 // then figure out if we really need each of these parameters. | 74 // then figure out if we really need each of these parameters. |
71 // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34 | 75 // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34 |
72 var params = [exportsVar]; | 76 var params = [exportsVar]; |
73 var lazyParams = []; | 77 var lazyParams = []; |
74 | 78 |
75 var imports = <JS.Expression>[]; | 79 var imports = <JS.Expression>[]; |
76 var lazyImports = <JS.Expression>[]; | 80 var lazyImports = <JS.Expression>[]; |
77 var moduleStatements = <JS.Statement>[]; | 81 var moduleStatements = <JS.Statement>[]; |
78 | 82 |
79 for (var i in _imports) { | 83 for (var i in _imports) { |
84 // No need to force the import order for the legacy library mechanism. | |
85 if (i.libVar == null) continue; | |
80 (i.isLazy ? lazyImports : imports).add(js.string(i.name, "'")); | 86 (i.isLazy ? lazyImports : imports).add(js.string(i.name, "'")); |
81 (i.isLazy ? lazyParams : params).add(i.libVar); | 87 (i.isLazy ? lazyParams : params).add(i.libVar); |
82 } | 88 } |
83 params.addAll(lazyParams); | 89 params.addAll(lazyParams); |
84 | 90 |
85 moduleStatements.addAll(_flattenBlocks(moduleItems)); | 91 moduleStatements.addAll(_flattenBlocks(moduleItems)); |
86 | 92 |
87 if (_exports.isNotEmpty) { | 93 if (_exports.isNotEmpty) { |
88 moduleStatements.add(js.comment('Exports:')); | 94 moduleStatements.add(js.comment('Exports:')); |
89 // TODO(jmesserly): make these immutable in JS? | 95 // TODO(jmesserly): make these immutable in JS? |
90 for (var name in _exports) { | 96 _exports.forEach((name, exportName) { |
91 moduleStatements | 97 moduleStatements |
92 .add(js.statement('#.# = #;', [exportsVar, name, name])); | 98 .add(js.statement('#.# = #;', [exportsVar, exportName, name])); |
93 } | 99 }); |
94 } | 100 } |
95 | 101 |
96 var module = | 102 var module = |
97 js.call("function(#) { 'use strict'; #; }", [params, moduleStatements]); | 103 js.call("function(#) { 'use strict'; #; }", [params, moduleStatements]); |
98 | 104 |
99 var moduleDef = js.statement("dart_library.library(#, #, #, #, #)", [ | 105 var moduleDef = js.statement("dart_library.library(#, #, #, #, #)", [ |
100 js.string(jsPath, "'"), | 106 js.string(jsPath, "'"), |
101 jsModuleValue ?? new JS.LiteralNull(), | 107 jsModuleValue ?? new JS.LiteralNull(), |
102 js.commentExpression( | 108 js.commentExpression( |
103 "Imports", new JS.ArrayInitializer(imports, multiline: true)), | 109 "Imports", new JS.ArrayInitializer(imports, multiline: true)), |
(...skipping 12 matching lines...) Expand all Loading... | |
116 } | 122 } |
117 | 123 |
118 /// Generates ES6 modules. | 124 /// Generates ES6 modules. |
119 // TODO(ochafik): Break strong dep cycles to accommodate the Closure Compiler. | 125 // TODO(ochafik): Break strong dep cycles to accommodate the Closure Compiler. |
120 class ES6ModuleBuilder extends ModuleBuilder { | 126 class ES6ModuleBuilder extends ModuleBuilder { |
121 ES6ModuleBuilder() : super._(); | 127 ES6ModuleBuilder() : super._(); |
122 | 128 |
123 JS.Program build(String jsPath, String jsModuleValue, | 129 JS.Program build(String jsPath, String jsModuleValue, |
124 JS.Identifier exportsVar, Iterable<JS.ModuleItem> moduleItems) { | 130 JS.Identifier exportsVar, Iterable<JS.ModuleItem> moduleItems) { |
125 var moduleStatements = <JS.ModuleItem>[ | 131 var moduleStatements = <JS.ModuleItem>[ |
126 // Lazy declarations may reference exports. | |
127 js.statement("const # = {};", [exportsVar]) | 132 js.statement("const # = {};", [exportsVar]) |
128 ]; | 133 ]; |
129 | 134 |
130 // TODO(jmesserly): it would be great to run the renamer on the body, | 135 // TODO(jmesserly): it would be great to run the renamer on the body, |
131 // then figure out if we really need each of these parameters. | 136 // then figure out if we really need each of these parameters. |
132 // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34 | 137 // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34 |
133 for (var i in _imports) { | 138 for (var i in _imports) { |
134 var moduleName = js.string(_relativeModuleName(i.name, from: jsPath)); | 139 var moduleName = js.string(_relativeModuleName(i.name, from: jsPath)); |
135 // TODO(ochafik): laziness, late binding, etc, to support Closure... | 140 // TODO(ochafik): laziness, late binding, etc, to support Closure... |
136 moduleStatements.add( | 141 if (i.libVar == null) { |
137 new JS.ImportDeclaration(defaultBinding: i.libVar, from: moduleName)); | 142 moduleStatements |
143 .add(new JS.ImportDeclaration(namedImports: [], from: moduleName)); | |
144 } else { | |
145 moduleStatements.add(new JS.ImportDeclaration( | |
146 defaultBinding: i.libVar, from: moduleName)); | |
147 } | |
138 } | 148 } |
139 | 149 |
140 moduleStatements.addAll(_flattenBlocks(moduleItems)); | 150 moduleStatements.addAll(_flattenBlocks(moduleItems)); |
141 | 151 |
142 if (_exports.isNotEmpty) { | 152 if (_exports.isNotEmpty) { |
143 moduleStatements.add(js.comment('Exports:')); | 153 moduleStatements.add(js.comment('Exports:')); |
144 // TODO(jmesserly): make these immutable in JS? | 154 // TODO(jmesserly): make these immutable in JS? |
145 for (var name in _exports) { | 155 _exports.forEach((name, exportName) { |
146 moduleStatements | 156 moduleStatements |
147 .add(js.statement('#.# = #;', [exportsVar, name, name])); | 157 .add(js.statement('#.# = #;', [exportsVar, exportName, name])); |
148 } | 158 }); |
149 moduleStatements | 159 moduleStatements |
150 .add(new JS.ExportDeclaration(exportsVar, isDefault: true)); | 160 .add(new JS.ExportDeclaration(exportsVar, isDefault: true)); |
151 } | 161 } |
152 // TODO(ochafik): What to do of jsModuleValue? | 162 // TODO(ochafik): What to do of jsModuleValue? |
153 return new JS.Program(moduleStatements); | 163 return new JS.Program(moduleStatements); |
154 } | 164 } |
155 } | 165 } |
156 | 166 |
167 /// Generates node modules. | |
168 class NodeModuleBuilder extends ModuleBuilder { | |
169 NodeModuleBuilder() : super._(); | |
170 | |
171 JS.Program build(String jsPath, String jsModuleValue, | |
172 JS.Identifier exportsVar, Iterable<JS.ModuleItem> moduleItems) { | |
173 var moduleStatements = <JS.ModuleItem>[js.statement("'use strict';"),]; | |
174 | |
175 for (var i in _imports) { | |
176 if (i.libVar == null) { | |
177 moduleStatements.add(js.statement('require(#);', [js.string(i.name)])); | |
178 } else { | |
179 moduleStatements.add( | |
180 js.statement('let # = require(#);', [i.libVar, js.string(i.name)])); | |
181 } | |
182 } | |
183 | |
184 moduleStatements.addAll(_flattenBlocks(moduleItems)); | |
185 | |
186 if (_exports.isNotEmpty) { | |
187 moduleStatements.add(js.comment('Exports:')); | |
188 _exports.forEach((name, exportName) { | |
189 moduleStatements | |
190 .add(js.statement('#.# = #;', [exportsVar, exportName, name])); | |
191 }); | |
192 } | |
193 // TODO(ochafik): What to do of jsModuleValue? | |
Jennifer Messerly
2016/01/29 00:32:31
Wording suggestion: "what to do with jsModuleValue
ochafik
2016/01/29 09:38:17
Thanks for the explanation! Upgraded the comment
| |
194 return new JS.Program(moduleStatements); | |
195 } | |
196 } | |
197 | |
157 /// Flattens blocks in [stats] to a single list of module items. | 198 /// Flattens blocks in [stats] to a single list of module items. |
158 /// Note that in general, blocks should not be flattened, because it can | 199 /// Note that in general, blocks should not be flattened, because it can |
159 /// mess up with block-level scoping (let, const). | 200 /// mess up with block-level scoping (let, const). |
160 // TODO(ochafik): Remove this / find better pattern (adding statements as they | 201 // TODO(ochafik): Remove this / find better pattern (adding statements as they |
161 // are generated from [JSCodegenVisitor], instead of composing them with | 202 // are generated from [JSCodegenVisitor], instead of composing them with |
162 // [_statements]). | 203 // [_statements]). |
163 Iterable<JS.ModuleItem> _flattenBlocks(List<JS.ModuleItem> stats) => | 204 Iterable<JS.ModuleItem> _flattenBlocks(List<JS.ModuleItem> stats) => |
164 stats.expand( | 205 stats.expand( |
165 (item) => item is JS.Block ? _flattenBlocks(item.statements) : [item]); | 206 (item) => item is JS.Block ? _flattenBlocks(item.statements) : [item]); |
OLD | NEW |