dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.06k stars 1.55k forks source link

DDC interop: allow me to control which APIs are exported from a module #27692

Open nex3 opened 7 years ago

nex3 commented 7 years ago

When I'm using DDC to compile a module for use as a JavaScript module, I want to be able to control exactly what API is exposed, just as I would be able to if I were writing JavaScript natively. I don't want to expose all Dart types and fields that are transitively used by my module.

I propose that a flag, --entrypoint-only be added to DDC. This flag causes the generated module to export only those names that are exported by the Dart library(s) passed directly to the dev compiler, without any extra prefixes. It could also cause the compiler not to emit a summary file, since this JS module won't be able to be imported by other DDC-generated modules.

For example, consider the following two files:

// test1.dart
export 'test2.dart' show bar;
final foo = "foo";

// test2.dart
final bar = "bar";
final baz = "baz";

Currently, dartdevc --modules es6 -o test1.dart.js test1.dart emits

export const test1 = Object.create(null);
export const test2 = Object.create(null);
import { core, dart, dartx } from 'dart_sdk';
test1.foo = "foo";
test2.bar = "bar";
test1.bar = test2.bar;
test2.baz = "baz";

I'd like dartdevc --modules es6 -o test1.dart.js --entrypoint-only test1.dart to emit something like:

const test1 = Object.create(null);
export default test1;
const test2 = Object.create(null);
import { core, dart, dartx } from 'dart_sdk';
test1.foo = "foo";
test2.bar = "bar";
test1.bar = test2.bar;
test2.baz = "baz";
nex3 commented 7 years ago

Eventually, this could also be used to seed tree-shaking for the module in question.

jmesserly commented 7 years ago

this is a great idea. We absolutely need a way to expose APIs directly on the module (instead of prefixed via library). I think in ES6 it will end up as the "default export" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export)

nex3 commented 7 years ago

Currently I'm working around this by writing something like the following:

// # bin/sass.dart
export 'package:sass/src/executable.dart'
    if (node) 'package:sass/src/node.dart';

// #  lib/src/executable.dart
void main(List<String> args) {
  // ...
}

// # lib/src/node.dart
import 'package:js/js.dart';

import 'executable.dart' as executable;

@JS()
class _Exports {
  external set run_(function);
  // ...
}

@JS()
external _Exports get exports;

void main() {
  exports.run_ = allowInterop(executable.main);
  // ...
}

// # sass.js
var index = require('./sass.dart.js');

index.run_(process.argv.slice(2));

However, this seems to make the entire app run about 10% slower across the board than just directly compiling and running lib/src/executable.dart. That's a pretty serious perf hit for us.

jmesserly commented 7 years ago

However, this seems to make the entire app run about 10% slower across the board than just directly compiling and running lib/src/executable.dart. That's a pretty serious perf hit for us.

that's with dart2js?