Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
Add JsInitializerGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
justinfagnani committed Sep 30, 2014
1 parent 0c17586 commit b5ef7f4
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 152 deletions.
199 changes: 199 additions & 0 deletions lib/src/transformer/dart_initializer_generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Copyright (c) 2014, 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.

library js.transformer.dart_initializer_generator;

import 'package:js/src/js_elements.dart';
import 'package:logging/logging.dart';
import 'package:quiver/iterables.dart';

final _logger = new Logger('js.transformer.dart_initializer_generator');

const JS_PREFIX = '__package_js_impl__';
const JS_THIS_REF = '__js_this_ref__';

class DartInitializerGenerator {
final String libraryName;
final String libraryPath;
final JsElements jsElements;

final buffer = new StringBuffer();

DartInitializerGenerator(this.libraryName, this.libraryPath, this.jsElements);

/**
* Returns the transformed source.
*/
String generate() {
buffer.write(
'''
library ${libraryName}__init_js__;
import 'dart:js' as js;
import '${libraryPath}';
import 'package:js/src/js_impl.dart' as $JS_PREFIX;
final _obj = js.context['Object'];
initializeJavaScriptLibrary() {
''');

jsElements.proxies.forEach((Proxy proxy) {
if (proxy.constructor == null) return;
var name = proxy.name;
buffer.writeln(" $JS_PREFIX.registerFactoryForJsConstructor("
"$JS_PREFIX.context['${proxy.constructor}'], "
"($JS_PREFIX.JsObject o) => new $name.created(o));");
});

buffer.writeln(" var lib = $JS_PREFIX.context['dart'];");

jsElements.exportedLibraries.values.forEach(_generateLibraryExportCall);

buffer.writeln('}');

jsElements.exportedLibraries.values.forEach(_generateLibraryExportMethod);

return buffer.toString();
}

_generateLibraryExportCall(ExportedLibrary library) {
buffer.writeln(" _export_${library.getPath('_')}(lib);");
}

_generateLibraryExportMethod(ExportedLibrary library) {
buffer.writeln(
'''
void _export_${library.getPath('_')}($JS_PREFIX.JsObject parent) {
js.JsObject lib = parent['${library.name}'];
''');

library.declarations.values.forEach(_generateDeclarationExportCall);
library.children.values.forEach(_generateLibraryExportCall);

buffer.writeln('}');

library.children.values.forEach(_generateLibraryExportMethod);
library.declarations.values.forEach(_generateDeclarationExport);
}

void _generateDeclarationExportCall(ExportedElement element) {
if (element is ExportedClass) {
buffer.writeln(" _export_${element.getPath('_')}(lib);");
}
}

void _generateDeclarationExport(ExportedElement element) {
if (element is ExportedClass) {
_generateClass(element);
}
}

void _generateClass(ExportedClass c) {
buffer.writeln(
'''
void _export_${c.getPath('_')}($JS_PREFIX.JsObject parent) {
var constructor = parent['${c.name}'];
$JS_PREFIX.registerJsConstructorForType(${c.name}, constructor['_wrapDartObject']);
var prototype = constructor['prototype'];
''');

c.children.values.forEach(_generateClassMember);

buffer.writeln("}");
}

void _generateClassMember(ExportedElement e) {
if (e is ExportedConstructor) {
_generateConstructor(e);
} else if (e is ExportedMethod) {
_generateMethod(e);
} else if (e is ExportedField) {
_generateField(e);
}
}

void _generateConstructor(ExportedConstructor c) {
var constructorName = c.name == '' ? '_new' : '_new_${c.name}';
var dartParameters = _getDartParameters(c.parameters);
var jsParameters = _getJsParameters(c.parameters);
var namedPart = c.name == '' ? '' : '.${c.name}';
buffer.writeln(" constructor['$constructorName'] = ($jsParameters) => "
"new ${c.parent.name}$namedPart($dartParameters);");
}

void _generateMethod(ExportedMethod c) {
var dartParameters = _getDartParameters(c.parameters);
var jsParameters = _getJsParameters(c.parameters, withThis: true);
buffer.write(
'''
// method ${c.name}
prototype['${c.name}'] = new js.JsFunction.withThis(($jsParameters) {
return $JS_THIS_REF.${c.name}($dartParameters);
});
''');
}

void _generateField(ExportedField f) {
var name = f.name;
var className = f.parent.name;
buffer.write(
'''
// field $name
_obj.callMethod('defineProperty', [prototype, '$name',
new js.JsObject.jsify({
'get': new js.JsFunction.withThis((o) => (o[$JS_PREFIX.DART_OBJECT_PROPERTY] as $className).$name),
'set': new js.JsFunction.withThis((o, v) => (o[$JS_PREFIX.DART_OBJECT_PROPERTY] as $className).$name = v),
})]);
''');
}


String _getJsParameters(List<ExportedParameter> parameters,
{bool withThis: false}) {
var requiredParameters = parameters
.where((p) => p.kind == ParameterKind.REQUIRED)
.map((p) => p.name);
var positionalParameters = parameters
.where((p) => p.kind == ParameterKind.POSITIONAL)
.map((p) => p.name);
var namedParameters = parameters
.where((p) => p.kind == ParameterKind.NAMED)
.map((p) => p.name);
var jsParameterList = withThis ? [JS_THIS_REF] : [];
jsParameterList.addAll(requiredParameters);
var jsParameters = jsParameterList.join(', ');
if (positionalParameters.isNotEmpty) {
jsParameters += ', [' + positionalParameters.join(', ') + ']';
} else if (namedParameters.isNotEmpty) {
jsParameters += ', [__js_named_parameters_map__]';
}
return jsParameters;
}

String _getDartParameters(List<ExportedParameter> parameters) {
var requiredParameters = parameters
.where((p) => p.kind == ParameterKind.REQUIRED)
.map((p) => p.name);
var positionalParameters = parameters
.where((p) => p.kind == ParameterKind.POSITIONAL)
.map((p) => p.name);
var namedParameters = parameters
.where((p) => p.kind == ParameterKind.NAMED)
.map((p) => p.name);
var dartNamedParameters = namedParameters.map((name) =>
"${name}: _getOptionalArg(__js_named_parameters_map__, '${name}')");
var dartParameters = concat([
requiredParameters,
positionalParameters,
dartNamedParameters])
.join(', ');

return dartParameters;
}

}
123 changes: 77 additions & 46 deletions lib/src/transformer/entry_point_transformer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:barback/barback.dart' show Asset, AssetId, Transform,
import 'package:code_transformers/resolver.dart' show Resolver,
ResolverTransformer, Resolvers, isPossibleDartEntryId;
import 'package:logging/logging.dart' show Logger;
import 'package:path/path.dart' as path;

import 'utils.dart';

Expand Down Expand Up @@ -46,51 +45,83 @@ class EntryPointTransformer extends Transformer with ResolverTransformer {
.map((lib) {
// look for initializer library
var libAssetId = resolver.getSourceAssetId(lib);
var initializerAssetId = libAssetId.addExtension(INITIALIZER_SUFFIX);
return transform
.getInput(initializerAssetId)
.catchError((e) => null,
test: (e) => e is AssetNotFoundException);
var dartInitializerAssetId =
libAssetId.addExtension(DART_INITIALIZER_SUFFIX);
var jsInitializerAssetId =
libAssetId.addExtension(JS_INITIALIZER_SUFFIX);
return [
transform
.getInput(dartInitializerAssetId)
.catchError((e) => null,
test: (e) => e is AssetNotFoundException),
transform
.getInput(jsInitializerAssetId)
.catchError((e) => null,
test: (e) => e is AssetNotFoundException),
];
});

var dartInitializerFutures = initializerFutures.map((l) => l[0]);
var jsInitializerFutures = initializerFutures.map((l) => l[1]);

var dartImports = new StringBuffer('\n');
var dartInitializerCalls = new StringBuffer();

var dartFuture = Future.wait(dartInitializerFutures)
.then((initializerAssets) {
for (Asset asset in initializerAssets.where((a) => a != null)) {
var id = asset.id;
var importUri = getImportUri(id, input.id);
if (importUri == null) continue;
var prefix = assetIdToPrefix(id);
dartImports.writeln("import '$importUri' as $prefix;");
dartInitializerCalls
.writeln(" $prefix.initializeJavaScriptLibrary();");
}
})
.where((f) => f != null);
return Future.wait(initializerFutures).then((initializerAssets) {
var imports = new StringBuffer('\n');
var calls = new StringBuffer();
for (Asset asset in initializerAssets.where((a) => a != null)) {
var id = asset.id;
var importUri = getImportUri(id, input.id);
if (importUri == null) continue;
var prefix = assetIdToPrefix(id);
imports.writeln("import '$importUri' as $prefix;");
calls.writeln(" $prefix.initializeJavaScriptLibrary();");
}

var initMethod = 'initializeJavaScript() {\n$calls}\n';
var insertImportOffset = getInsertImportOffset(library);
var initMethodOffset = library.source.contents.data.length;
transaction.edit(insertImportOffset, insertImportOffset, '$imports');
transaction.edit(initMethodOffset, initMethodOffset, initMethod);
var printer = transaction.commit();
printer.build(input.id.path);
transform.addOutput(new Asset.fromString(input.id, printer.text));
});
.then((_) {
var initMethod = 'initializeJavaScript() {\n$dartInitializerCalls}\n';
var insertImportOffset = getInsertImportOffset(library);
var initMethodOffset = library.source.contents.data.length;
transaction.edit(insertImportOffset, insertImportOffset,
'$dartImports');
transaction.edit(initMethodOffset, initMethodOffset, initMethod);
var printer = transaction.commit();
printer.build(input.id.path);
transform.addOutput(new Asset.fromString(input.id, printer.text));
});

var jsInitializerScript = new StringBuffer('''
window.dart = window.dart || {};
window.dart.Object = function DartObject() {
throw "not allowed";
};
window.dart.Object._wrapDartObject = function(dartObject) {
var o = Object.create(window.dart.Object.prototype);
o.__dart_object__ = dartObject;
return o;
};
''');

var jsFuture =
Future.wait(jsInitializerFutures)
.then((assets) => Future.wait(assets
.where((a) => a != null)
.map((Asset a) => a.readAsString())))
.then((initializerSources) {
for (String source in initializerSources) {
jsInitializerScript.writeln(source);
}
}).then((_) {
var jsInitializerId = input.id.addExtension('_initialize.js');
var asset = new Asset.fromString(jsInitializerId,
jsInitializerScript.toString());
transform.addOutput(asset);
});
return Future.wait([dartFuture, jsFuture]);
}
}

final illegalIdRegex = new RegExp(r'[^a-zA-Z0-9_]');

String assetIdToPrefix(AssetId id) =>
'_js__${id.package}__${id.path.replaceAll(illegalIdRegex, '_')}';

// TODO(justinfagnani): put this in code_transformers ?
String getImportUri(AssetId importId, AssetId from) {
if (importId.path.startsWith('lib/')) {
// we support package imports
return "package:${importId.package}/${importId.path.substring(4)}";
} else if (importId.package == from.package) {
// we can support relative imports
return path.relative(importId.path, from: path.dirname(from.path));
}
// cannot import
return null;
}
Loading

0 comments on commit b5ef7f4

Please sign in to comment.