Index: lib/src/compiler/module_builder.dart |
diff --git a/lib/src/compiler/module_builder.dart b/lib/src/compiler/module_builder.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7a6866c6463c53269bc03c0f817ff11c01b59a5a |
--- /dev/null |
+++ b/lib/src/compiler/module_builder.dart |
@@ -0,0 +1,170 @@ |
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// 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:path/path.dart' as path; |
+ |
+import '../js_ast/js_ast.dart'; |
+import 'js_names.dart'; |
+ |
+/// 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 |
+/// module transformers. |
+// TODO(jmesserly): "module transformer" might be a better name than builder. |
+abstract class _ModuleBuilder { |
+ final imports = <ImportDeclaration>[]; |
+ final exports = <ExportDeclaration>[]; |
+ final statements = <Statement>[]; |
+ |
+ /// Collect [imports], [exports] and [statements] from the ES6 [module]. |
+ /// |
+ /// For exports, this will also add their body to [statements] in the |
+ /// appropriate position. |
+ void visitProgram(Program module) { |
+ for (var item in module.body) { |
+ if (item is ImportDeclaration) { |
+ visitImportDeclaration(item); |
+ } else if (item is ExportDeclaration) { |
+ visitExportDeclaration(item); |
+ } else if (item is Statement) { |
+ visitStatement(item); |
+ } |
+ } |
+ } |
+ |
+ visitImportDeclaration(ImportDeclaration node) { |
+ imports.add(node); |
+ } |
+ |
+ visitExportDeclaration(ExportDeclaration node) { |
+ exports.add(node); |
+ statements.add(node.exported.toStatement()); |
+ } |
+ |
+ visitStatement(Statement node) { |
+ statements.add(node); |
+ } |
+} |
+ |
+/// Generates modules for with our legacy `dart_library.js` loading mechanism. |
+// TODO(jmesserly): remove this and replace with something that interoperates. |
+class LegacyModuleBuilder extends _ModuleBuilder { |
+ Program build(Program module) { |
+ // Collect imports/exports/statements. |
+ visitProgram(module); |
+ |
+ // Build import parameters. |
+ var exportsVar = new TemporaryId('exports'); |
+ var parameters = <TemporaryId>[exportsVar]; |
+ var importNames = <Expression>[]; |
+ var importStatements = <Statement>[]; |
+ for (var import in imports) { |
+ importNames.add(import.from); |
+ // TODO(jmesserly): we could use destructuring once Atom supports it. |
+ var moduleVar = |
+ new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); |
+ parameters.add(moduleVar); |
+ for (var importName in import.namedImports) { |
+ assert(!importName.isStar); // import * not supported in legacy modules. |
+ var asName = importName.asName ?? importName.name; |
+ importStatements.add(js.statement( |
+ 'const # = #.#', [asName, moduleVar, importName.name.name])); |
+ } |
+ } |
+ statements.insertAll(0, importStatements); |
+ |
+ if (exports.isNotEmpty) { |
+ statements.add(js.comment('Exports:')); |
+ // TODO(jmesserly): make these immutable in JS? |
+ for (var export in exports) { |
+ var names = export.exportedNames; |
+ assert(names != null); // export * not supported in legacy modules. |
+ for (var name in names) { |
+ statements |
+ .add(js.statement('#.# = #;', [exportsVar, name.name, name])); |
+ } |
+ } |
+ } |
+ |
+ var resultModule = |
+ js.call("function(#) { 'use strict'; #; }", [parameters, statements]); |
+ |
+ var moduleDef = js.statement("dart_library.library(#, #, #, #)", [ |
+ js.string(module.name, "'"), |
+ new LiteralNull(), |
+ js.commentExpression( |
+ "Imports", new ArrayInitializer(importNames, multiline: true)), |
+ resultModule |
+ ]); |
+ return new Program(<ModuleItem>[moduleDef]); |
+ } |
+} |
+ |
+/// Generates node modules. |
+class NodeModuleBuilder extends _ModuleBuilder { |
+ Program build(Program module) { |
+ var importStatements = [js.statement("'use strict';"),]; |
+ |
+ for (var import in imports) { |
+ // TODO(jmesserly): we could use destructuring once Atom supports it. |
+ var moduleVar = |
+ new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); |
+ importStatements |
+ .add(js.statement('const # = require(#);', [moduleVar, import.from])); |
+ |
+ // TODO(jmesserly): optimize for the common case of a single import. |
+ for (var importName in import.namedImports) { |
+ assert(!importName.isStar); // import * not supported yet. |
+ var asName = importName.asName ?? importName.name; |
+ importStatements.add(js.statement( |
+ 'const # = #.#', [asName, moduleVar, importName.name.name])); |
+ } |
+ } |
+ statements.insertAll(0, importStatements); |
+ |
+ if (exports.isNotEmpty) { |
+ var exportsVar = new Identifier('exports'); |
+ statements.add(js.comment('Exports:')); |
+ for (var export in exports) { |
+ var names = export.exportedNames; |
+ assert(names != null); // export * not supported in legacy modules. |
+ for (var name in names) { |
+ statements |
+ .add(js.statement('#.# = #;', [exportsVar, name.name, name])); |
+ } |
+ } |
+ } |
+ return new Program(statements); |
+ } |
+} |
+ |
+/// Escape [name] to make it into a valid identifier. |
+String pathToJSIdentifier(String name) { |
+ name = path.basenameWithoutExtension(name); |
+ if (name.length == 0) return r'$'; |
+ |
+ // Escape any invalid characters |
+ StringBuffer buffer = null; |
+ for (int i = 0; i < name.length; i++) { |
+ var ch = name[i]; |
+ var needsEscape = ch == r'$' || _invalidCharInIdentifier.hasMatch(ch); |
+ if (needsEscape && buffer == null) { |
+ buffer = new StringBuffer(name.substring(0, i)); |
+ } |
+ if (buffer != null) { |
+ buffer.write(needsEscape ? '\$${ch.codeUnits.join("")}' : ch); |
+ } |
+ } |
+ |
+ var result = buffer != null ? '$buffer' : name; |
+ // Ensure the identifier first character is not numeric and that the whole |
+ // identifier is not a keyword. |
+ if (result.startsWith(new RegExp('[0-9]')) || invalidVariableName(result)) { |
+ return '\$$result'; |
+ } |
+ return result; |
+} |
+ |
+// Invalid characters for identifiers, which would need to be escaped. |
+final _invalidCharInIdentifier = new RegExp(r'[^A-Za-z_$0-9]'); |