Index: lib/src/compiler/module_builder.dart |
diff --git a/lib/src/compiler/module_builder.dart b/lib/src/compiler/module_builder.dart |
index 6e72217f6d65df982eb17ce7384ef565e94f6e54..0f2dbdd9de8c8558ce18604cf75b3f4959d0f88f 100644 |
--- a/lib/src/compiler/module_builder.dart |
+++ b/lib/src/compiler/module_builder.dart |
@@ -2,11 +2,91 @@ |
// for details. All rights reserved. Use of this source code is governed by a |
// BSD-style license that can be found in the LICENSE file. |
+import 'package:args/args.dart' show ArgParser, ArgResults; |
import 'package:path/path.dart' as path; |
import '../js_ast/js_ast.dart'; |
import 'js_names.dart'; |
+/// The module format to emit. |
+enum ModuleFormat { |
+ /// 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/
|
+ es6, |
+ |
+ /// CommonJS module (used in Node.js) |
+ common, |
+ |
+ /// Asynchronous Module Definition (AMD, used in browsers) |
+ amd, |
+ |
+ /// Dart Dev Compiler's legacy format (deprecated). |
+ legacy |
+} |
+ |
+/// Parses a string into a [ModuleFormat]. |
+ModuleFormat parseModuleFormat(String s) => { |
+ 'es6': ModuleFormat.es6, |
+ 'common': ModuleFormat.common, |
+ 'amd': ModuleFormat.amd, |
+ // Deprecated: |
+ 'node': ModuleFormat.common, |
+ 'legacy': ModuleFormat.legacy |
+ }[s]; |
+ |
+/// Parse the module format option added by [addModuleFormatOptions]. |
+List<ModuleFormat> parseModuleFormatOption(ArgResults argResults) { |
+ var format = argResults['modules']; |
+ if (format is String) { |
+ return [parseModuleFormat(format)]; |
+ } |
+ return (format as List<String>).map(parseModuleFormat).toList(); |
+} |
+ |
+/// Adds an option to the [argParser] for choosing the module format, optionally |
+/// [allowMultiple] formats to be specified, with each emitted into a separate |
+/// file. |
+void addModuleFormatOptions(ArgParser argParser, {bool allowMultiple: false}) { |
+ argParser.addOption('modules', |
+ help: 'module pattern to emit', |
+ allowed: [ |
+ 'es6', |
+ 'common', |
+ 'amd', |
+ 'legacy', // deprecated |
+ 'node', // renamed to commonjs |
+ 'all' // to emit all flavors for the SDK |
+ ], |
+ allowedHelp: { |
+ 'es6': 'EcmaScript 6 modules', |
+ 'common': 'CommonJS/Node.js modules', |
+ 'amd': 'AMD/RequireJS modules' |
+ }, |
+ allowMultiple: allowMultiple, |
+ defaultsTo: 'amd'); |
+} |
+ |
+/// 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.
|
+/// |
+/// If the format is [ModuleFormat.es6] this will return [module] unchanged. |
+/// |
+/// Because JS ASTs are immutable the resulting module will share as much |
+/// structure as possible with the original. The transformation is a shallow one |
+/// that affects the top-level module items, especially [ImportDeclaration]s and |
+/// [ExportDeclaration]s. |
+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
|
+ switch (format) { |
+ case ModuleFormat.legacy: |
+ return new LegacyModuleBuilder().build(module); |
+ case ModuleFormat.common: |
+ return new CommonJSModuleBuilder().build(module); |
+ case ModuleFormat.amd: |
+ return new AmdModuleBuilder().build(module); |
+ case ModuleFormat.es6: |
+ return module; |
+ } |
+ return null; // unreachable. suppresses a bogus analyzer message |
+} |
+ |
/// Base class for compiling ES6 modules into various ES5 module patterns. |
/// |
/// This is a helper class for utilities and state that is shared by several |
@@ -61,7 +141,7 @@ class LegacyModuleBuilder extends _ModuleBuilder { |
var importStatements = <Statement>[]; |
for (var import in imports) { |
importNames.add(import.from); |
- // TODO(jmesserly): we could use destructuring once Atom supports it. |
+ // TODO(jmesserly): we could use destructuring here. |
var moduleVar = |
new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); |
parameters.add(moduleVar); |
@@ -105,8 +185,8 @@ class LegacyModuleBuilder extends _ModuleBuilder { |
} |
} |
-/// Generates node modules. |
-class NodeModuleBuilder extends _ModuleBuilder { |
+/// Generates CommonJS modules (used by Node.js). |
+class CommonJSModuleBuilder extends _ModuleBuilder { |
Program build(Program module) { |
var importStatements = <Statement>[]; |
@@ -114,7 +194,7 @@ class NodeModuleBuilder extends _ModuleBuilder { |
visitProgram(module); |
for (var import in imports) { |
- // TODO(jmesserly): we could use destructuring once Atom supports it. |
+ // TODO(jmesserly): we could use destructuring here. |
var moduleVar = |
new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); |
importStatements |
@@ -122,7 +202,8 @@ class NodeModuleBuilder extends _ModuleBuilder { |
// TODO(jmesserly): optimize for the common case of a single import. |
for (var importName in import.namedImports) { |
- assert(!importName.isStar); // import * not supported yet. |
+ // import * is not emitted by the compiler, so we don't support it here. |
+ assert(!importName.isStar); |
var asName = importName.asName ?? importName.name; |
importStatements.add(js.statement( |
'const # = #.#', [asName, moduleVar, importName.name.name])); |
@@ -135,7 +216,8 @@ class NodeModuleBuilder extends _ModuleBuilder { |
statements.add(js.comment('Exports:')); |
for (var export in exports) { |
var names = export.exportedNames; |
- assert(names != null); // export * not supported in legacy modules. |
+ // export * is not emitted by the compiler, so we don't handle it here. |
+ assert(names != null); |
for (var name in names) { |
statements |
.add(js.statement('#.# = #;', [exportsVar, name.name, name])); |
@@ -152,6 +234,56 @@ class NodeModuleBuilder extends _ModuleBuilder { |
} |
} |
+/// Generates AMD modules (used in browsers with RequireJS). |
+class AmdModuleBuilder extends _ModuleBuilder { |
+ Program build(Program module) { |
+ var importStatements = <Statement>[]; |
+ |
+ // Collect imports/exports/statements. |
+ visitProgram(module); |
+ |
+ var dependencies = <LiteralString>[]; |
+ var fnParams = <Parameter>[]; |
+ for (var import in imports) { |
+ // TODO(jmesserly): we could use destructuring once Atom supports it. |
+ var moduleVar = |
+ new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); |
+ fnParams.add(moduleVar); |
+ dependencies.add(import.from); |
+ |
+ // TODO(jmesserly): optimize for the common case of a single import. |
+ for (var importName in import.namedImports) { |
+ // import * is not emitted by the compiler, so we don't handle it here. |
+ assert(!importName.isStar); |
+ var asName = importName.asName ?? importName.name; |
+ importStatements.add(js.statement( |
+ 'const # = #.#', [asName, moduleVar, importName.name.name])); |
+ } |
+ } |
+ statements.insertAll(0, importStatements); |
+ |
+ if (exports.isNotEmpty) { |
+ var exportedProps = <Property>[]; |
+ for (var export in exports) { |
+ var names = export.exportedNames; |
+ // export * is not emitted by the compiler, so we don't handle it here. |
+ assert(names != null); |
+ for (var name in names) { |
+ exportedProps.add(new Property(js.string(name.name), name)); |
+ } |
+ } |
+ statements.add(js.comment('Exports:')); |
+ statements.add( |
+ new Return(new ObjectInitializer(exportedProps, multiline: true))); |
+ } |
+ |
+ var block = js.statement("define(#, function(#) { 'use strict'; #; });", |
+ [new ArrayInitializer(dependencies), fnParams, statements]); |
+ |
+ return new Program([block]); |
+ } |
+} |
+ |
/// Escape [name] to make it into a valid identifier. |
String pathToJSIdentifier(String name) { |
return toJSIdentifier(path.basenameWithoutExtension(name)); |