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_ast/js_ast.dart'; |
| 8 import 'js_names.dart'; |
| 9 |
| 10 /// Base class for compiling ES6 modules into various ES5 module patterns. |
| 11 /// |
| 12 /// This is a helper class for utilities and state that is shared by several |
| 13 /// module transformers. |
| 14 // TODO(jmesserly): "module transformer" might be a better name than builder. |
| 15 abstract class _ModuleBuilder { |
| 16 final imports = <ImportDeclaration>[]; |
| 17 final exports = <ExportDeclaration>[]; |
| 18 final statements = <Statement>[]; |
| 19 |
| 20 /// Collect [imports], [exports] and [statements] from the ES6 [module]. |
| 21 /// |
| 22 /// For exports, this will also add their body to [statements] in the |
| 23 /// appropriate position. |
| 24 void visitProgram(Program module) { |
| 25 for (var item in module.body) { |
| 26 if (item is ImportDeclaration) { |
| 27 visitImportDeclaration(item); |
| 28 } else if (item is ExportDeclaration) { |
| 29 visitExportDeclaration(item); |
| 30 } else if (item is Statement) { |
| 31 visitStatement(item); |
| 32 } |
| 33 } |
| 34 } |
| 35 |
| 36 visitImportDeclaration(ImportDeclaration node) { |
| 37 imports.add(node); |
| 38 } |
| 39 |
| 40 visitExportDeclaration(ExportDeclaration node) { |
| 41 exports.add(node); |
| 42 statements.add(node.exported.toStatement()); |
| 43 } |
| 44 |
| 45 visitStatement(Statement node) { |
| 46 statements.add(node); |
| 47 } |
| 48 } |
| 49 |
| 50 /// Generates modules for with our legacy `dart_library.js` loading mechanism. |
| 51 // TODO(jmesserly): remove this and replace with something that interoperates. |
| 52 class LegacyModuleBuilder extends _ModuleBuilder { |
| 53 Program build(Program module) { |
| 54 // Collect imports/exports/statements. |
| 55 visitProgram(module); |
| 56 |
| 57 // Build import parameters. |
| 58 var exportsVar = new TemporaryId('exports'); |
| 59 var parameters = <TemporaryId>[exportsVar]; |
| 60 var importNames = <Expression>[]; |
| 61 var importStatements = <Statement>[]; |
| 62 for (var import in imports) { |
| 63 importNames.add(import.from); |
| 64 // TODO(jmesserly): we could use destructuring once Atom supports it. |
| 65 var moduleVar = |
| 66 new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); |
| 67 parameters.add(moduleVar); |
| 68 for (var importName in import.namedImports) { |
| 69 assert(!importName.isStar); // import * not supported in legacy modules. |
| 70 var asName = importName.asName ?? importName.name; |
| 71 importStatements.add(js.statement( |
| 72 'const # = #.#', [asName, moduleVar, importName.name.name])); |
| 73 } |
| 74 } |
| 75 statements.insertAll(0, importStatements); |
| 76 |
| 77 if (exports.isNotEmpty) { |
| 78 statements.add(js.comment('Exports:')); |
| 79 // TODO(jmesserly): make these immutable in JS? |
| 80 for (var export in exports) { |
| 81 var names = export.exportedNames; |
| 82 assert(names != null); // export * not supported in legacy modules. |
| 83 for (var name in names) { |
| 84 statements |
| 85 .add(js.statement('#.# = #;', [exportsVar, name.name, name])); |
| 86 } |
| 87 } |
| 88 } |
| 89 |
| 90 var resultModule = |
| 91 js.call("function(#) { 'use strict'; #; }", [parameters, statements]); |
| 92 |
| 93 var moduleDef = js.statement("dart_library.library(#, #, #, #)", [ |
| 94 js.string(module.name, "'"), |
| 95 new LiteralNull(), |
| 96 js.commentExpression( |
| 97 "Imports", new ArrayInitializer(importNames, multiline: true)), |
| 98 resultModule |
| 99 ]); |
| 100 return new Program(<ModuleItem>[moduleDef]); |
| 101 } |
| 102 } |
| 103 |
| 104 /// Generates node modules. |
| 105 class NodeModuleBuilder extends _ModuleBuilder { |
| 106 Program build(Program module) { |
| 107 var importStatements = [js.statement("'use strict';"),]; |
| 108 |
| 109 for (var import in imports) { |
| 110 // TODO(jmesserly): we could use destructuring once Atom supports it. |
| 111 var moduleVar = |
| 112 new TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes)); |
| 113 importStatements |
| 114 .add(js.statement('const # = require(#);', [moduleVar, import.from])); |
| 115 |
| 116 // TODO(jmesserly): optimize for the common case of a single import. |
| 117 for (var importName in import.namedImports) { |
| 118 assert(!importName.isStar); // import * not supported yet. |
| 119 var asName = importName.asName ?? importName.name; |
| 120 importStatements.add(js.statement( |
| 121 'const # = #.#', [asName, moduleVar, importName.name.name])); |
| 122 } |
| 123 } |
| 124 statements.insertAll(0, importStatements); |
| 125 |
| 126 if (exports.isNotEmpty) { |
| 127 var exportsVar = new Identifier('exports'); |
| 128 statements.add(js.comment('Exports:')); |
| 129 for (var export in exports) { |
| 130 var names = export.exportedNames; |
| 131 assert(names != null); // export * not supported in legacy modules. |
| 132 for (var name in names) { |
| 133 statements |
| 134 .add(js.statement('#.# = #;', [exportsVar, name.name, name])); |
| 135 } |
| 136 } |
| 137 } |
| 138 return new Program(statements); |
| 139 } |
| 140 } |
| 141 |
| 142 /// Escape [name] to make it into a valid identifier. |
| 143 String pathToJSIdentifier(String name) { |
| 144 name = path.basenameWithoutExtension(name); |
| 145 if (name.length == 0) return r'$'; |
| 146 |
| 147 // Escape any invalid characters |
| 148 StringBuffer buffer = null; |
| 149 for (int i = 0; i < name.length; i++) { |
| 150 var ch = name[i]; |
| 151 var needsEscape = ch == r'$' || _invalidCharInIdentifier.hasMatch(ch); |
| 152 if (needsEscape && buffer == null) { |
| 153 buffer = new StringBuffer(name.substring(0, i)); |
| 154 } |
| 155 if (buffer != null) { |
| 156 buffer.write(needsEscape ? '\$${ch.codeUnits.join("")}' : ch); |
| 157 } |
| 158 } |
| 159 |
| 160 var result = buffer != null ? '$buffer' : name; |
| 161 // Ensure the identifier first character is not numeric and that the whole |
| 162 // identifier is not a keyword. |
| 163 if (result.startsWith(new RegExp('[0-9]')) || invalidVariableName(result)) { |
| 164 return '\$$result'; |
| 165 } |
| 166 return result; |
| 167 } |
| 168 |
| 169 // Invalid characters for identifiers, which would need to be escaped. |
| 170 final _invalidCharInIdentifier = new RegExp(r'[^A-Za-z_$0-9]'); |
OLD | NEW |