使用dart创建一个javascript库

Lu4*_*Lu4 5 dart dart-js-interop

问题

我目前正在开发一个JavaScript库,为了减少错误的数量,我认为我的库可能会受益于使用Dart的静态类型机制.首先,因为我的lib没有与HTML或其他JavaScript库进行任何互操作,只有纯javascript对象操作的东西.但是我没有在网上找到任何关于如何使用dart构建JS库的信息.所以我自己尝试这样做,创建了初始的dart文件:

library Repo;

class Type {
  final String name;
  final TypeCategory category;

  Type(String name, TypeCategory category) : name = name, category = category {
    category.types[name] = this;
  }
}

class TypeCategory {
  final String name;
  final Map<String, Type> types = new Map();

  TypeCategory(this.name);
}

class Branch {

}

class Descriptor {

}

class TableDescriptor extends Descriptor {
  TableDescriptor.ctor() {

  }
}

class Repo {
  Descriptor descriptor(String name) {

  }

  Branch branch(String name) {

  }

  void Merge() {

  }
}


main() {
  return Repo;
}
Run Code Online (Sandbox Code Playgroud)

使用dart2js将其编译为JavaScript以查看我正在做的事情:

// Generated by dart2js, the Dart to JavaScript compiler version: 1.3.6.
// The code supports the following hooks:
// dartPrint(message):
//    if this function is defined it is called instead of the Dart [print]
//    method.
//
// dartMainRunner(main, args):
//    if this function is defined, the Dart [main] method will not be invoked
//    directly. Instead, a closure that will invoke [main], and its arguments
//    [args] is passed to [dartMainRunner].
(function($) {
function dart(){ this.x = 0 }var A = new dart;
delete A.x;
var B = new dart;
delete B.x;
var C = new dart;
delete C.x;
var D = new dart;
delete D.x;
var E = new dart;
delete E.x;
var F = new dart;
delete F.x;
var G = new dart;
delete G.x;
var H = new dart;
delete H.x;
var J = new dart;
delete J.x;
var K = new dart;
delete K.x;
var L = new dart;
delete L.x;
var M = new dart;
delete M.x;
var N = new dart;
delete N.x;
var O = new dart;
delete O.x;
var P = new dart;
delete P.x;
var Q = new dart;
delete Q.x;
var R = new dart;
delete R.x;
var S = new dart;
delete S.x;
var T = new dart;
delete T.x;
var U = new dart;
delete U.x;
var V = new dart;
delete V.x;
var W = new dart;
delete W.x;
var X = new dart;
delete X.x;
var Y = new dart;
delete Y.x;
var Z = new dart;
delete Z.x;
function Isolate() {}
init();

$ = Isolate.$isolateProperties;
var $$ = {};

(function (reflectionData) {
  "use strict";
  function map(x){x={x:x};delete x.x;return x}
    function processStatics(descriptor) {
      for (var property in descriptor) {
        if (!hasOwnProperty.call(descriptor, property)) continue;
        if (property === "^") continue;
        var element = descriptor[property];
        var firstChar = property.substring(0, 1);
        var previousProperty;
        if (firstChar === "+") {
          mangledGlobalNames[previousProperty] = property.substring(1);
          var flag = descriptor[property];
          if (flag > 0) descriptor[previousProperty].$reflectable = flag;
          if (element && element.length) init.typeInformation[previousProperty] = element;
        } else if (firstChar === "@") {
          property = property.substring(1);
          $[property]["@"] = element;
        } else if (firstChar === "*") {
          globalObject[previousProperty].$defaultValues = element;
          var optionalMethods = descriptor.$methodsWithOptionalArguments;
          if (!optionalMethods) {
            descriptor.$methodsWithOptionalArguments = optionalMethods = {}
          }
          optionalMethods[property] = previousProperty;
        } else if (typeof element === "function") {
          globalObject[previousProperty = property] = element;
          functions.push(property);
          init.globalFunctions[property] = element;
        } else if (element.constructor === Array) {
          addStubs(globalObject, element, property, true, descriptor, functions);
        } else {
          previousProperty = property;
          var newDesc = {};
          var previousProp;
          for (var prop in element) {
            if (!hasOwnProperty.call(element, prop)) continue;
            firstChar = prop.substring(0, 1);
            if (prop === "static") {
              processStatics(init.statics[property] = element[prop]);
            } else if (firstChar === "+") {
              mangledNames[previousProp] = prop.substring(1);
              var flag = element[prop];
              if (flag > 0) element[previousProp].$reflectable = flag;
            } else if (firstChar === "@" && prop !== "@") {
              newDesc[prop.substring(1)]["@"] = element[prop];
            } else if (firstChar === "*") {
              newDesc[previousProp].$defaultValues = element[prop];
              var optionalMethods = newDesc.$methodsWithOptionalArguments;
              if (!optionalMethods) {
                newDesc.$methodsWithOptionalArguments = optionalMethods={}
              }
              optionalMethods[prop] = previousProp;
            } else {
              var elem = element[prop];
              if (prop !== "^" && elem != null && elem.constructor === Array && prop !== "<>") {
                addStubs(newDesc, elem, prop, false, element, []);
              } else {
                newDesc[previousProp = prop] = elem;
              }
            }
          }
          $$[property] = [globalObject, newDesc];
          classes.push(property);
        }
      }
    }
  function addStubs(descriptor, array, name, isStatic, originalDescriptor, functions) {
    var f, funcs = [originalDescriptor[name] = descriptor[name] = f = array[0]];
    f.$stubName = name;
    functions.push(name);
    for (var index = 0; index < array.length; index += 2) {
      f = array[index + 1];
      if (typeof f != "function") break;
      f.$stubName = array[index + 2];
      funcs.push(f);
      if (f.$stubName) {
        originalDescriptor[f.$stubName] = descriptor[f.$stubName] = f;
        functions.push(f.$stubName);
      }
    }
    for (var i = 0; i < funcs.length; index++, i++) {
      funcs[i].$callName = array[index + 1];
    }
    var getterStubName = array[++index];
    array = array.slice(++index);
    var requiredParameterInfo = array[0];
    var requiredParameterCount = requiredParameterInfo >> 1;
    var isAccessor = (requiredParameterInfo & 1) === 1;
    var isSetter = requiredParameterInfo === 3;
    var isGetter = requiredParameterInfo === 1;
    var optionalParameterInfo = array[1];
    var optionalParameterCount = optionalParameterInfo >> 1;
    var optionalParametersAreNamed = (optionalParameterInfo & 1) === 1;
    var isIntercepted = requiredParameterCount + optionalParameterCount != funcs[0].length;
    var functionTypeIndex = array[2];
    var unmangledNameIndex =  2 * optionalParameterCount + requiredParameterCount + 3;
    var isReflectable = array.length > unmangledNameIndex;

    if (getterStubName) {
      f = tearOff(funcs, array, isStatic, name, isIntercepted);
      f.getterStub = true;
      if (isStatic) init.globalFunctions[name] = f;
      originalDescriptor[getterStubName] = descriptor[getterStubName] = f;
      funcs.push(f);
      if (getterStubName) functions.push(getterStubName);
      f.$stubName = getterStubName;
      f.$callName = null;
      if (isIntercepted) init.interceptedNames[getterStubName] = true;
    }
    if (isReflectable) {
      for (var i = 0; i < funcs.length; i++) {
        funcs[i].$reflectable = 1;
        funcs[i].$reflectionInfo = array;
      }
      var mangledNames = isStatic ? init.mangledGlobalNames : init.mangledNames;
      var unmangledName = array[unmangledNameIndex];
      var reflectionName = unmangledName;
      if (getterStubName) mangledNames[getterStubName] = reflectionName;
      if (isSetter) {
        reflectionName += "=";
      } else if (!isGetter) {
        reflectionName += ":" + requiredParameterCount + ":" + optionalParameterCount;
      }
      mangledNames[name] = reflectionName;
      funcs[0].$reflectionName = reflectionName;
      funcs[0].$metadataIndex = unmangledNameIndex + 1;
      if (optionalParameterCount) descriptor[unmangledName + "*"] = funcs[0];
    }
  }
  function tearOffGetterNoCsp(funcs, reflectionInfo, name, isIntercepted) {
    return isIntercepted
        ? new Function("funcs", "reflectionInfo", "name", "H", "c",
            "return function tearOff_" + name + (functionCounter++)+ "(x) {" +
              "if (c === null) c = H.closureFromTearOff(" +
                  "this, funcs, reflectionInfo, false, [x], name);" +
              "return new c(this, funcs[0], x, name);" +
            "}")(funcs, reflectionInfo, name, H, null)
        : new Function("funcs", "reflectionInfo", "name", "H", "c",
            "return function tearOff_" + name + (functionCounter++)+ "() {" +
              "if (c === null) c = H.closureFromTearOff(" +
                  "this, funcs, reflectionInfo, false, [], name);" +
              "return new c(this, funcs[0], null, name);" +
            "}")(funcs, reflectionInfo, name, H, null)
  }
  function tearOffGetterCsp(funcs, reflectionInfo, name, isIntercepted) {
    var cache = null;
    return isIntercepted
        ? function(x) {
            if (cache === null) cache = H.closureFromTearOff(this, funcs, reflectionInfo, false, [x], name);
            return new cache(this, funcs[0], x, name)
          }
        : function() {
            if (cache === null) cache = H.closureFromTearOff(this, funcs, reflectionInfo, false, [], name);
            return new cache(this, funcs[0], null, name)
          }
  }
  function tearOff(funcs, reflectionInfo, isStatic, name, isIntercepted) {
    var cache;
    return isStatic
        ? function() {
            if (cache === void 0) cache = H.closureFromTearOff(this, funcs, reflectionInfo, true, [], name).prototype;
            return cache;
          }
        : tearOffGetter(funcs, reflectionInfo, name, isIntercepted);
  }
  var functionCounter = 0;
  var tearOffGetter = (typeof dart_precompiled == "function")
      ? tearOffGetterCsp : tearOffGetterNoCsp;
  if (!init.libraries) init.libraries = [];
  if (!init.mangledNames) init.mangledNames = map();
  if (!init.mangledGlobalNames) init.mangledGlobalNames = map();
  if (!init.statics) init.statics = map();
  if (!init.typeInformation) init.typeInformation = map();
  if (!init.globalFunctions) init.globalFunctions = map();
  if (!init.interceptedNames) init.interceptedNames = map();
  var libraries = init.libraries;
  var mangledNames = init.mangledNames;
  var mangledGlobalNames = init.mangledGlobalNames;
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  var length = reflectionData.length;
  for (var i = 0; i < length; i++) {
    var data = reflectionData[i];
    var name = data[0];
    var uri = data[1];
    var metadata = data[2];
    var globalObject = data[3];
    var descriptor = data[4];
    var isRoot = !!data[5];
    var fields = descriptor && descriptor["^"];
    var classes = [];
    var functions = [];
    processStatics(descriptor);
    libraries.push([name, uri, classes, functions, metadata, fields, isRoot,
                    globalObject]);
  }
})
([
["Repo", "repo.dart", , F, {
  "^": "",
  main: function() {
    return C.Type_Jeh;
  }
},
1],
["_js_helper", "dart:_js_helper", , H, {
  "^": "",
  createRuntimeType: function($name) {
    return new H.TypeImpl($name, null);
  },
  TypeImpl: {
    "^": "Object;_typeName,_unmangledName"
  }
}],
["dart.core", "dart:core", , P, {
  "^": "",
  Null: {
    "^": "Object;"
  },
  Object: {
    "^": ";"
  }
}],
]);
Isolate.$finishClasses($$, $, null);
$$ = null;

// Runtime type support
// getInterceptor methods
C.Type_Jeh = H.createRuntimeType('Repo');
$.libraries_to_load = {};
$.Closure_functionCounter = 0;
$.BoundClosure_selfFieldNameCache = null;
$.BoundClosure_receiverFieldNameCache = null;

init.functionAliases = {};
;
init.metadata = [];
$ = null;
Isolate = Isolate.$finishIsolateConstructor(Isolate);
$ = new Isolate();
function convertToFastObject(properties) {
  function MyClass() {};
  MyClass.prototype = properties;
  new MyClass();
  return properties;
}
A = convertToFastObject(A);
B = convertToFastObject(B);
C = convertToFastObject(C);
D = convertToFastObject(D);
E = convertToFastObject(E);
F = convertToFastObject(F);
G = convertToFastObject(G);
H = convertToFastObject(H);
J = convertToFastObject(J);
K = convertToFastObject(K);
L = convertToFastObject(L);
M = convertToFastObject(M);
N = convertToFastObject(N);
O = convertToFastObject(O);
P = convertToFastObject(P);
Q = convertToFastObject(Q);
R = convertToFastObject(R);
S = convertToFastObject(S);
T = convertToFastObject(T);
U = convertToFastObject(U);
V = convertToFastObject(V);
W = convertToFastObject(W);
X = convertToFastObject(X);
Y = convertToFastObject(Y);
Z = convertToFastObject(Z);
// BEGIN invoke [main].
;(function (callback) {
  if (typeof document === "undefined") {
    callback(null);
    return;
  }
  if (document.currentScript) {
    callback(document.currentScript);
    return;
  }

  var scripts = document.scripts;
  function onLoad(event) {
    for (var i = 0; i < scripts.length; ++i) {
      scripts[i].removeEventListener("load", onLoad, false);
    }
    callback(event.target);
  }
  for (var i = 0; i < scripts.length; ++i) {
    scripts[i].addEventListener("load", onLoad, false);
  }
})(function(currentScript) {
  init.currentScript = currentScript;

  if (typeof dartMainRunner === "function") {
    dartMainRunner(F.main, []);
  } else {
    F.main([]);
  }
});
// END invoke [main].
function init() {
  Isolate.$isolateProperties = {};
  function generateAccessor(fieldDescriptor, accessors, cls) {
    var fieldInformation = fieldDescriptor.split("-");
    var field = fieldInformation[0];
    var len = field.length;
    var code = field.charCodeAt(len - 1);
    var reflectable;
    if (fieldInformation.length > 1)
      reflectable = true;
    else
      reflectable = false;
    code = code >= 60 && code <= 64 ? code - 59 : code >= 123 && code <= 126 ? code - 117 : code >= 37 && code <= 43 ? code - 27 : 0;
    if (code) {
      var getterCode = code & 3;
      var setterCode = code >> 2;
      var accessorName = field = field.substring(0, len - 1);
      var divider = field.indexOf(":");
      if (divider > 0) {
        accessorName = field.substring(0, divider);
        field = field.substring(divider + 1);
      }
      if (getterCode) {
        var args = getterCode & 2 ? "receiver" : "";
        var receiver = getterCode & 1 ? "this" : "receiver";
        var body = "return " + receiver + "." + field;
        var property = cls + ".prototype.get$" + accessorName + "=";
        var fn = "function(" + args + "){" + body + "}";
        if (reflectable)
          accessors.push(property + "$reflectable(" + fn + ");\n");
        else
          accessors.push(property + fn + ";\n");
      }
      if (setterCode) {
        var args = setterCode & 2 ? "receiver, value" : "value";
        var receiver = setterCode & 1 ? "this" : "receiver";
        var body = receiver + "." + field + " = value";
        var property = cls + ".prototype.set$" + accessorName + "=";
        var fn = "function(" + args + "){" + body + "}";
        if (reflectable)
          accessors.push(property + "$reflectable(" + fn + ");\n");
        else
          accessors.push(property + fn + ";\n");
      }
    }
    return field;
  }
  Isolate.$isolateProperties.$generateAccessor = generateAccessor;
  function defineClass(name, cls, fields) {
    var accessors = [];
    var str = "function " + cls + "(";
    var body = "";
    for (var i = 0; i < fields.length; i++) {
      if (i != 0)
        str += ", ";
      var field = generateAccessor(fields[i], accessors, cls);
      var parameter = "parameter_" + field;
      str += parameter;
      body += "this." + field + " = " + parameter + ";\n";
    }
    str += ") {\n" + body + "}\n";
    str += cls + ".builtin$cls=\"" + name + "\";\n";
    str += "$desc=$collectedClasses." + cls + ";\n";
    str += "if($desc instanceof Array) $desc = $desc[1];\n";
    str += cls + ".prototype = $desc;\n";
    if (typeof defineClass.name != "string") {
      str += cls + ".name=\"" + cls + "\";\n";
    }
    str += accessors.join("");
    return str;
  }
  var inheritFrom = function() {
    function tmp() {
    }
    var hasOwnProperty = Object.prototype.hasOwnProperty;
    return function(constructor, superConstructor) {
      tmp.prototype = superConstructor.prototype;
      var object = new tmp();
      var properties = constructor.prototype;
      for (var member in properties)
        if (hasOwnProperty.call(properties, member))
          object[member] = properties[member];
      object.constructor = constructor;
      constructor.prototype = object;
      return object;
    };
  }();
  Isolate.$finishClasses = function(collectedClasses, isolateProperties, existingIsolateProperties) {
    var pendingClasses = {};
    if (!init.allClasses)
      init.allClasses = {};
    var allClasses = init.allClasses;
    var hasOwnProperty = Object.prototype.hasOwnProperty;
    if (typeof dart_precompiled == "function") {
      var constructors = dart_precompiled(collectedClasses);
    } else {
      var combinedConstructorFunction = "function $reflectable(fn){fn.$reflectable=1;return fn};\n" + "var $desc;\n";
      var constructorsList = [];
    }
    for (var cls in collectedClasses) {
      if (hasOwnProperty.call(collectedClasses, cls)) {
        var desc = collectedClasses[cls];
        if (desc instanceof Array)
          desc = desc[1];
        var classData = desc["^"], supr, name = cls, fields = classData;
        if (typeof classData == "string") {
          var split = classData.split("/");
          if (split.length == 2) {
            name = split[0];
            fields = split[1];
          }
        }
        var s = fields.split(";");
        fields = s[1] == "" ? [] : s[1].split(",");
        supr = s[0];
        split = supr.split(":");
        if (split.length == 2) {
          supr = split[0];
          var functionSignature = split[1];
          if (functionSignature)
            desc.$signature = function(s) {
              return function() {
                return init.metadata[s];
              };
            }(functionSignature);
        }
        if (typeof dart_precompiled != "function") {
          combinedConstructorFunction += defineClass(name, cls, fields);
          constructorsList.push(cls);
        }
        if (supr)
          pendingClasses[cls] = supr;
      }
    }
    if (typeof dart_precompiled != "function") {
      combinedConstructorFunction += "return [\n  " + constructorsList.join(",\n  ") + "\n]";
      var constructors = new Function("$collectedClasses", combinedConstructorFunction)(collectedClasses);
      combinedConstructorFunction = null;
    }
    for (var i = 0; i < constructors.length; i++) {
      var constructor = constructors[i];
      var cls = constructor.name;
      var desc = collectedClasses[cls];
      var globalObject = isolateProperties;
      if (desc instanceof Array) {
        globalObject = desc[0] || isolateProperties;
        desc = desc[1];
      }
      allClasses[cls] = constructor;
      globalObject[cls] = constructor;
    }
    constructors = null;
    var finishedClasses = {};
    init.interceptorsByTag = Object.create(null);
    init.leafTags = {};
    function finishClass(cls) {
      var hasOwnProperty = Object.prototype.hasOwnProperty;
      if (hasOwnProperty.call(finishedClasses, cls))
        return;
      finishedClasses[cls] = true;
      var superclass = pendingClasses[cls];
      if (!superclass || typeof superclass != "string")
        return;
      finishClass(superclass);
      var constructor = allClasses[cls];
      var superConstructor = allClasses[superclass];
      if (!superConstructor)
        superConstructor = existingIsolateProperties[superclass];
      var prototype = inheritFrom(constructor, superConstructor);
    }
    for (var cls in pendingClasses)
      finishClass(cls);
  };
  Isolate.$lazy = function(prototype, staticName, fieldName, getterName, lazyValue) {
    var sentinelUndefined = {};
    var sentinelInProgress = {};
    prototype[fieldName] = sentinelUndefined;
    prototype[getterName] = function() {
      var result = $[fieldName];
      try {
        if (result === sentinelUndefined) {
          $[fieldName] = sentinelInProgress;
          try {
            result = $[fieldName] = lazyValue();
          } finally {
            if (result === sentinelUndefined) {
              if ($[fieldName] === sentinelInProgress) {
                $[fieldName] = null;
              }
            }
          }
        } else {
          if (result === sentinelInProgress)
            H.throwCyclicInit(staticName);
        }
        return result;
      } finally {
        $[getterName] = function() {
          return this[fieldName];
        };
      }
    };
  };
  Isolate.$finishIsolateConstructor = function(oldIsolate) {
    var isolateProperties = oldIsolate.$isolateProperties;
    function Isolate() {
      var hasOwnProperty = Object.prototype.hasOwnProperty;
      for (var staticName in isolateProperties)
        if (hasOwnProperty.call(isolateProperties, staticName))
          this[staticName] = isolateProperties[staticName];
      function ForceEfficientMap() {
      }
      ForceEfficientMap.prototype = this;
      new ForceEfficientMap();
    }
    Isolate.prototype = oldIsolate.prototype;
    Isolate.prototype.constructor = Isolate;
    Isolate.$isolateProperties = isolateProperties;
    Isolate.$finishClasses = oldIsolate.$finishClasses;
    return Isolate;
  };
}
})()

//# sourceMappingURL=out.js.map
//@ sourceMappingURL=out.js.map
Run Code Online (Sandbox Code Playgroud)

And that's it, I've thrown away Dart because I didn't knew what to do with generated JS file, also I was frightened of potentially high amount of time required for keeping the resulting library interface clean and similar to one I'm using with JavaScript.

The question(s)

  1. How do I expose class definitions created in Dart and later use them in JavaScript?
  2. Do you think it's worth it going into Dart when nearly all potential library users will be using JS version instead? (Using dart is already not good for me due to difference in community sizes, this means that less people will find it easy to contribute to my library)
  3. In your opinion, what should I do?

Gün*_*uer 5

尽管Dart支持这个用例,但如果你定位JavaScript开发人员,我会坚持使用JavaScript.

@AlexandreArdhuin在回答Expose Dart函数的答案中向javascript展示了如何为JavaScript提供Dart函数.
有很多例子如何在Dart和JavaScript之间进行函数调用和传递数据.