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