| 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 |