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 import 'package:args/args.dart' show ArgParser, ArgResults; | |
5 import 'package:path/path.dart' as path; | 6 import 'package:path/path.dart' as path; |
6 | 7 |
7 import '../js_ast/js_ast.dart'; | 8 import '../js_ast/js_ast.dart'; |
8 import 'js_names.dart'; | 9 import 'js_names.dart'; |
9 | 10 |
11 /// The module format to emit. | |
12 enum ModuleFormat { | |
13 /// EcmaScript 6 module using import and export. | |
nweiz
2016/08/24 23:33:26
Nit: "ECMAScript" (also below)
My editor blood ru
Jennifer Messerly
2016/08/25 16:21:39
done :) fixed all occurrences under dev_compiler/
| |
14 es6, | |
15 | |
16 /// CommonJS module (used in Node.js) | |
17 common, | |
18 | |
19 /// Asynchronous Module Definition (AMD, used in browsers) | |
20 amd, | |
21 | |
22 /// Dart Dev Compiler's legacy format (deprecated). | |
23 legacy | |
24 } | |
25 | |
26 /// Parses a string into a [ModuleFormat]. | |
27 ModuleFormat parseModuleFormat(String s) => { | |
28 'es6': ModuleFormat.es6, | |
29 'common': ModuleFormat.common, | |
30 'amd': ModuleFormat.amd, | |
31 // Deprecated: | |
32 'node': ModuleFormat.common, | |
33 'legacy': ModuleFormat.legacy | |
34 }[s]; | |
35 | |
36 /// Parse the module format option added by [addModuleFormatOptions]. | |
37 List<ModuleFormat> parseModuleFormatOption(ArgResults argResults) { | |
38 var format = argResults['modules']; | |
39 if (format is String) { | |
40 return [parseModuleFormat(format)]; | |
41 } | |
42 return (format as List<String>).map(parseModuleFormat).toList(); | |
43 } | |
44 | |
45 /// Adds an option to the [argParser] for choosing the module format, optionally | |
46 /// [allowMultiple] formats to be specified, with each emitted into a separate | |
47 /// file. | |
48 void addModuleFormatOptions(ArgParser argParser, {bool allowMultiple: false}) { | |
49 argParser.addOption('modules', | |
50 help: 'module pattern to emit', | |
51 allowed: [ | |
52 'es6', | |
53 'common', | |
54 'amd', | |
55 'legacy', // deprecated | |
56 'node', // renamed to commonjs | |
57 'all' // to emit all flavors for the SDK | |
58 ], | |
59 allowedHelp: { | |
60 'es6': 'EcmaScript 6 modules', | |
61 'common': 'CommonJS/Node.js modules', | |
62 'amd': 'AMD/RequireJS modules' | |
63 }, | |
64 allowMultiple: allowMultiple, | |
65 defaultsTo: 'amd'); | |
66 } | |
67 | |
68 /// Transform an ES6 [module] into a given module [format]. | |
nweiz
2016/08/24 23:33:26
Nit: "Transforms"
Jennifer Messerly
2016/08/25 16:21:39
Done.
| |
69 /// | |
70 /// If the format is [ModuleFormat.es6] this will return [module] unchanged. | |
71 /// | |
72 /// Because JS ASTs are immutable the resulting module will share as much | |
73 /// structure as possible with the original. The transformation is a shallow one | |
74 /// that affects the top-level module items, especially [ImportDeclaration]s and | |
75 /// [ExportDeclaration]s. | |
76 Program lowerModuleFormat(ModuleFormat format, Program module) { | |
nweiz
2016/08/24 23:33:26
I think "lower" is confusing here. Maybe "change"
Jennifer Messerly
2016/08/25 16:21:39
hmmm, yeah that's tricky. "lower" is a bit of comp
| |
77 switch (format) { | |
78 case ModuleFormat.legacy: | |
79 return new LegacyModuleBuilder().build(module); | |
80 case ModuleFormat.common: | |
81 return new CommonJSModuleBuilder().build(module); | |
82 case ModuleFormat.amd: | |
83 return new AmdModuleBuilder().build(module); | |
84 case ModuleFormat.es6: | |
85 return module; | |
86 } | |
87 return null; // unreachable. suppresses a bogus analyzer message | |
88 } | |
89 | |
10 /// Base class for compiling ES6 modules into various ES5 module patterns. | 90 /// Base class for compiling ES6 modules into various ES5 module patterns. |
11 /// | 91 /// |
12 /// This is a helper class for utilities and state that is shared by several | 92 /// This is a helper class for utilities and state that is shared by several |
13 /// module transformers. | 93 /// module transformers. |
14 // TODO(jmesserly): "module transformer" might be a better name than builder. | 94 // TODO(jmesserly): "module transformer" might be a better name than builder. |
15 abstract class _ModuleBuilder { | 95 abstract class _ModuleBuilder { |
16 final imports = <ImportDeclaration>[]; | 96 final imports = <ImportDeclaration>[]; |
17 final exports = <ExportDeclaration>[]; | 97 final exports = <ExportDeclaration>[]; |
18 final statements = <Statement>[]; | 98 final statements = <Statement>[]; |
19 | 99 |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
54 // Collect imports/exports/statements. | 134 // Collect imports/exports/statements. |
55 visitProgram(module); | 135 visitProgram(module); |
56 | 136 |
57 // Build import parameters. | 137 // Build import parameters. |
58 var exportsVar = new TemporaryId('exports'); | 138 var exportsVar = new TemporaryId('exports'); |
59 var parameters = <TemporaryId>[exportsVar]; | 139 var parameters = <TemporaryId>[exportsVar]; |
60 var importNames = <Expression>[]; | 140 var importNames = <Expression>[]; |
61 var importStatements = <Statement>[]; | 141 var importStatements = <Statement>[]; |
62 for (var import in imports) { | 142 for (var import in imports) { |
63 importNames.add(import.from); | 143 importNames.add(import.from); |
64 // TODO(jmesserly): we could use destructuring once Atom supports it. | 144 // TODO(jmesserly): we could use destructuring here. |
65 var moduleVar = | 145 var moduleVar = |
66 new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); | 146 new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); |
67 parameters.add(moduleVar); | 147 parameters.add(moduleVar); |
68 for (var importName in import.namedImports) { | 148 for (var importName in import.namedImports) { |
69 assert(!importName.isStar); // import * not supported in legacy modules. | 149 assert(!importName.isStar); // import * not supported in legacy modules. |
70 var asName = importName.asName ?? importName.name; | 150 var asName = importName.asName ?? importName.name; |
71 importStatements.add(js.statement( | 151 importStatements.add(js.statement( |
72 'const # = #.#', [asName, moduleVar, importName.name.name])); | 152 'const # = #.#', [asName, moduleVar, importName.name.name])); |
73 } | 153 } |
74 } | 154 } |
(...skipping 23 matching lines...) Expand all Loading... | |
98 js.string(module.name, "'"), | 178 js.string(module.name, "'"), |
99 new LiteralNull(), | 179 new LiteralNull(), |
100 js.commentExpression( | 180 js.commentExpression( |
101 "Imports", new ArrayInitializer(importNames, multiline: true)), | 181 "Imports", new ArrayInitializer(importNames, multiline: true)), |
102 resultModule | 182 resultModule |
103 ]); | 183 ]); |
104 return new Program(<ModuleItem>[moduleDef]); | 184 return new Program(<ModuleItem>[moduleDef]); |
105 } | 185 } |
106 } | 186 } |
107 | 187 |
108 /// Generates node modules. | 188 /// Generates CommonJS modules (used by Node.js). |
109 class NodeModuleBuilder extends _ModuleBuilder { | 189 class CommonJSModuleBuilder extends _ModuleBuilder { |
110 Program build(Program module) { | 190 Program build(Program module) { |
111 var importStatements = <Statement>[]; | 191 var importStatements = <Statement>[]; |
112 | 192 |
113 // Collect imports/exports/statements. | 193 // Collect imports/exports/statements. |
114 visitProgram(module); | 194 visitProgram(module); |
115 | 195 |
116 for (var import in imports) { | 196 for (var import in imports) { |
117 // TODO(jmesserly): we could use destructuring once Atom supports it. | 197 // TODO(jmesserly): we could use destructuring here. |
118 var moduleVar = | 198 var moduleVar = |
119 new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); | 199 new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); |
120 importStatements | 200 importStatements |
121 .add(js.statement('const # = require(#);', [moduleVar, import.from])); | 201 .add(js.statement('const # = require(#);', [moduleVar, import.from])); |
122 | 202 |
123 // TODO(jmesserly): optimize for the common case of a single import. | 203 // TODO(jmesserly): optimize for the common case of a single import. |
124 for (var importName in import.namedImports) { | 204 for (var importName in import.namedImports) { |
125 assert(!importName.isStar); // import * not supported yet. | 205 // import * is not emitted by the compiler, so we don't support it here. |
206 assert(!importName.isStar); | |
126 var asName = importName.asName ?? importName.name; | 207 var asName = importName.asName ?? importName.name; |
127 importStatements.add(js.statement( | 208 importStatements.add(js.statement( |
128 'const # = #.#', [asName, moduleVar, importName.name.name])); | 209 'const # = #.#', [asName, moduleVar, importName.name.name])); |
129 } | 210 } |
130 } | 211 } |
131 statements.insertAll(0, importStatements); | 212 statements.insertAll(0, importStatements); |
132 | 213 |
133 if (exports.isNotEmpty) { | 214 if (exports.isNotEmpty) { |
134 var exportsVar = new Identifier('exports'); | 215 var exportsVar = new Identifier('exports'); |
135 statements.add(js.comment('Exports:')); | 216 statements.add(js.comment('Exports:')); |
136 for (var export in exports) { | 217 for (var export in exports) { |
137 var names = export.exportedNames; | 218 var names = export.exportedNames; |
138 assert(names != null); // export * not supported in legacy modules. | 219 // export * is not emitted by the compiler, so we don't handle it here. |
220 assert(names != null); | |
139 for (var name in names) { | 221 for (var name in names) { |
140 statements | 222 statements |
141 .add(js.statement('#.# = #;', [exportsVar, name.name, name])); | 223 .add(js.statement('#.# = #;', [exportsVar, name.name, name])); |
142 } | 224 } |
143 } | 225 } |
144 } | 226 } |
145 | 227 |
146 // TODO(vsm): See https://github.com/dart-lang/dev_compiler/issues/512 | 228 // TODO(vsm): See https://github.com/dart-lang/dev_compiler/issues/512 |
147 // This extra level of indirection should be unnecessary. | 229 // This extra level of indirection should be unnecessary. |
148 var block = | 230 var block = |
149 js.statement("(function() { 'use strict'; #; })()", [statements]); | 231 js.statement("(function() { 'use strict'; #; })()", [statements]); |
150 | 232 |
151 return new Program([block]); | 233 return new Program([block]); |
152 } | 234 } |
153 } | 235 } |
154 | 236 |
237 /// Generates AMD modules (used in browsers with RequireJS). | |
238 class AmdModuleBuilder extends _ModuleBuilder { | |
239 Program build(Program module) { | |
240 var importStatements = <Statement>[]; | |
241 | |
242 // Collect imports/exports/statements. | |
243 visitProgram(module); | |
244 | |
245 var dependencies = <LiteralString>[]; | |
246 var fnParams = <Parameter>[]; | |
247 for (var import in imports) { | |
248 // TODO(jmesserly): we could use destructuring once Atom supports it. | |
249 var moduleVar = | |
250 new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); | |
251 fnParams.add(moduleVar); | |
252 dependencies.add(import.from); | |
253 | |
254 // TODO(jmesserly): optimize for the common case of a single import. | |
255 for (var importName in import.namedImports) { | |
256 // import * is not emitted by the compiler, so we don't handle it here. | |
257 assert(!importName.isStar); | |
258 var asName = importName.asName ?? importName.name; | |
259 importStatements.add(js.statement( | |
260 'const # = #.#', [asName, moduleVar, importName.name.name])); | |
261 } | |
262 } | |
263 statements.insertAll(0, importStatements); | |
264 | |
265 if (exports.isNotEmpty) { | |
266 var exportedProps = <Property>[]; | |
267 for (var export in exports) { | |
268 var names = export.exportedNames; | |
269 // export * is not emitted by the compiler, so we don't handle it here. | |
270 assert(names != null); | |
271 for (var name in names) { | |
272 exportedProps.add(new Property(js.string(name.name), name)); | |
273 } | |
274 } | |
275 statements.add(js.comment('Exports:')); | |
276 statements.add( | |
277 new Return(new ObjectInitializer(exportedProps, multiline: true))); | |
278 } | |
279 | |
280 var block = js.statement("define(#, function(#) { 'use strict'; #; });", | |
281 [new ArrayInitializer(dependencies), fnParams, statements]); | |
282 | |
283 return new Program([block]); | |
284 } | |
285 } | |
286 | |
155 /// Escape [name] to make it into a valid identifier. | 287 /// Escape [name] to make it into a valid identifier. |
156 String pathToJSIdentifier(String name) { | 288 String pathToJSIdentifier(String name) { |
157 return toJSIdentifier(path.basenameWithoutExtension(name)); | 289 return toJSIdentifier(path.basenameWithoutExtension(name)); |
158 } | 290 } |
159 | 291 |
160 /// Escape [name] to make it into a valid identifier. | 292 /// Escape [name] to make it into a valid identifier. |
161 String toJSIdentifier(String name) { | 293 String toJSIdentifier(String name) { |
162 if (name.length == 0) return r'$'; | 294 if (name.length == 0) return r'$'; |
163 | 295 |
164 // Escape any invalid characters | 296 // Escape any invalid characters |
(...skipping 13 matching lines...) Expand all Loading... | |
178 // Ensure the identifier first character is not numeric and that the whole | 310 // Ensure the identifier first character is not numeric and that the whole |
179 // identifier is not a keyword. | 311 // identifier is not a keyword. |
180 if (result.startsWith(new RegExp('[0-9]')) || invalidVariableName(result)) { | 312 if (result.startsWith(new RegExp('[0-9]')) || invalidVariableName(result)) { |
181 return '\$$result'; | 313 return '\$$result'; |
182 } | 314 } |
183 return result; | 315 return result; |
184 } | 316 } |
185 | 317 |
186 // Invalid characters for identifiers, which would need to be escaped. | 318 // Invalid characters for identifiers, which would need to be escaped. |
187 final _invalidCharInIdentifier = new RegExp(r'[^A-Za-z_$0-9]'); | 319 final _invalidCharInIdentifier = new RegExp(r'[^A-Za-z_$0-9]'); |
OLD | NEW |