Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(63)

Side by Side Diff: lib/src/compiler/module_builder.dart

Issue 2249233002: fix #626, add AMD module format and make it default (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: merged Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/src/compiler/compiler.dart ('k') | lib/src/js_ast/module_transform.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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.
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 /// Transforms an ES6 [module] into a given module [format].
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 transformModuleFormat(ModuleFormat format, Program module) {
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
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
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
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]');
OLDNEW
« no previous file with comments | « lib/src/compiler/compiler.dart ('k') | lib/src/js_ast/module_transform.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698