Zurück zum Blog

Folgen und abonnieren

Nur auf Englisch verfügbar

Diese Seite ist momentan nur auf Englisch verfügbar. Wir entschuldigen uns für die Unannehmlichkeiten. Bitte besuchen Sie diese Seite später noch einmal.

Portierung von JavaScript (oder TypeScript) zu AssemblyScript

Aaron Turner

Senior Engineer, Fastly

Hinweis: Das AssemblyScript SDK (@fastly/as-compute) für Compute wurde zugunsten des aktuelleren und funktionsreicheren JavaScript SDK (@fastly/js-compute) eingestellt.

Wir haben erst kürzlich angekündigt, dass unsere Serverless-Compute-Umgebung Compute AssemblyScript unterstützt. Daraufhin haben wir einen Artikel veröffentlicht, in dem erläutert wurde, warum AssemblyScript für Entwickler, die JavaScript und TypeScript nutzen, ein guter Einstieg in Compute und WebAssembly ist. Aber anstatt nur darüber zu sprechen, wie eng AssemblyScript und JavaScript miteinander verwandt sind, möchte ich es Ihnen lieber zeigen. Als Senior Software Engineer für Compute und Mitglied des AssemblyScript Kernteams möchte ich mit Ihnen gerne einen genaueren Blick auf den Portierungsprozess gängiger JavaScript Anwendungen zu AssemblyScript und die damit verbundenen Überlegungen werfen.

Zunächst einmal müssen wir herausfinden, was wir überhaupt portieren wollen. Erst neulich habe ich eine schnelle AssemblyScript Markdown-Demo für Compute erstellt, die auf der experimentellen as-bindundefined Markdown-Parser-Demo basiert. Dabei stellte ich fest, dass ein Round Trip meiner Demo im Homeoffice knapp 25 ms dauerte. Ich wollte wissen, welchen Anteil davon die Ausführung meines Anwendungscodes im Vergleich zum Upload des Markdowns und zum Download des HTML-Codes in Anspruch nahm. Außerdem wollte ich herausfinden, wie viel Zeit im Vergleich zu anderen Komponenten der Anwendung für das Parsen des Markdowns in HTML aufgewendet werden musste. Der Code einer entsprechenden Node.js-Anwendung könnte in etwa so aussehen:

const prettyMilliseconds = require("pretty-ms");

function getPrettyExecutionTime() {
  // Get our start time in milliseconds (Unix Timestamp)
  const start = Date.now();

  // Do a random amount of busy work
  let iterations = 100000 + Math.floor(Math.random() * 10000000);
  for (let i = 0; i < iterations; i++) {
    iterations -= 1;
  }

  // Get our start time in milliseconds (Unix Timestamp)
  const end = Date.now();

  // Log out the Execution time in a human readable format
let responseString = "";
responseString +=
  "Pretty Unix timestamp: " +
  prettyMilliseconds(start, { verbose: true }) +
  "\n";
responseString +=
  "Busy work execution time: " +
  prettyMilliseconds(end - start, {
    verbose: true,
    formatSubMilliseconds: true,
  });

return responseString;
}

console.log(getPrettyExecutionTime());

Wenn wir einen genaueren Blick auf den Node.js-Code werfen, sehen wir, dass er das JavaScript Date global verwendet, um einen dem Unix Zeitstempel ähnlichen Wert zu erhalten (die Anzahl der Millisekunden, die seit dem 1. Januar 1970 00:00:00 UTC vergangen sind), um den Start und das Ende der Ausführung unserer Node.js-Anwendung zu bestimmen. Diese Zeitstempel loggen wir anschließend in einem für das menschliche Auge lesbaren Format mit der Abhängigkeit <u>pretty-ms</u>, die wir auf npm finden. Wir loggen auch die Differenz zwischen diesen beiden Zeitstempeln, was uns die gesamte Ausführungsdauer liefert.

Nehmen wir nun an, wir wollten diese Funktion von unserer Node.js-Anwendung zu unserer AssemblyScript Anwendung portieren. Die Vorgehensweise wäre in etwa wie folgt:

  1. Überprüfen, ob die JavaScript Globals (z. B. Objekte unter window oder global) im Quellcode durch die AssemblyScript Standardbibliothek, WASI-Hostcalls oder AssemblyScript Bibliotheken von Drittanbietern (normalerweise auf npm zu finden) ersetzt werden können.

  2. Ermitteln, ob es für die importierten JavaScript Abhängigkeiten (und Abhängigkeiten der Abhängigkeiten) entsprechende AssemblyScript Abhängigkeiten gibt. Wenn nicht, müssen die Abhängigkeiten portiert werden (siehe Schritt 1).

  3. AssemblyScript Typen zum JavaScript Code hinzufügen (oder die TypeScript Typen durch Typen ersetzen, die mit AssemblyScript kompatibel sind).

  4. JavaScript Code, der Probleme mit der AssemblyScript Syntax oder mit noch in der Entwicklung befindlichen Bibliotheken verursachen könnte, anpassen und verschieben. Zum Beispiel sind meiner Meinung nach (Stand: Ende 2020) die (letzten) beiden großen Entwicklungen für den AssemblyScript Compiler Closures und RegEx. Passen Sie Code an, der mit JavaScript Globals oder Abhängigkeiten interagiert, damit er mit den neuen Ersatz-APIs funktioniert.

Wollen wir diesen Prozess nun auf unsere Node.js-Anwendung übertragen. Im Anschluss an Schritt 1 werden Sie feststellen, dass unsere Node.js-Anwendung <u>Math</u> und <u>Date</u> nutzt. Insbesondere müssen wir folgende Funktionen ersetzen: Math.floor(), Math.random() und Date.now(). In der Standardbibliothek von AssemblyScript sehen wir, dass AssemblyScript seine eigene globale <u>Math.floor</u>-Funktion anbietet. Wir müssen hier also nichts ändern. AssemblyScript bietet auch eine eigene globale <u>Math.random()</u>-Funktion. In den Nutzerhinweisen heißt es jedoch, dass wir auch import “wasi” hinzufügen müssen, da die von Compute unterstützten WASI Bindings verwendet werden, um Zufallszahlen zu generieren. Für die Portierung ist also nur eine einzige Codezeile notwendig. Abschließend müssen wir Date.now() portieren, was nicht ganz so einfach ist. AssemblyScript bietet ein globales Date-Objekt, das allerdings das Date-Objekt von einem JavaScript Host importiert. Compute importiert das JavaScript Date-Objekt nicht in WebAssembly Module. Um dieses Problem zu umgehen, verlassen wir uns auf WASI oder Bibliotheken Dritter. 

Hierfür geben wir in der Google Suche zum Beispiel „assemblyscript wasi date“ ein und halten in den Ergebnissen Ausschau nach <u>as-wasi</u>. as-wasi ist ein übergeordneter AssemblyScript Layer für das WebAssembly System Interface (WASI) Mit anderen Worten bietet es eine schöne übergeordnete API mit allgemeinen Funktionen auf Systemebene für AssemblyScript Anwendungen, die die WASI Bindings von AssemblyScript verwenden. Wenn wir uns die Referenzdokumentation ansehen, stellen wir fest, dass AssemblyScript auch eine eigene <u>Date.now()</u>-Funktion anbietet, die sich hervorragend als Ersatz für unsere Date.now()-Funktion in JavaScript eignet. Wir wissen jetzt also, dass wir den Quellcode unserer Anwendung auf Compute übertragen können. Wir müssen aber denselben Prozess noch einmal für unsere Abhängigkeiten durchführen. Unsere Node.js-Anwendung hat eine einzige Abhängigkeit, nämlich das sehr beliebte, MIT-lizenzierte npm Paket pretty-ms, das wöchentlich über 1 Million Mal heruntergeladen wird. Wenn wir uns den Quellcode von pretty-ms ansehen, stellen wir fest, dass eine Abhängigkeit zu parse-ms besteht und die globalen Funktionen Math und Number verwendet werden. Wenn wir erneut Schritt 1 befolgen, können wir sehen, dass die AssemblyScript Standardbibliothek Math und Number direkt in den aktuellen Quellcode einfügt. 

Bevor wir uns mit parse-ms befassen, möchte ich noch auf einige Dinge in der Syntax hinweisen, die wir ändern müssen. Zunächst werden Sie feststellen, dass der Quellcode Closures verwendet, was an den verschachtelten Funktionen zu erkennen ist. AssemblyScript unterstützt die Übergabe und Verschachtelung von reinen Funktionen. Um Kopfschmerzen zu vermeiden, sollten wir diese jedoch in eigene Funktionen auslagern. Außerdem gilt zu beachten, dass dieser Code die CommonJS Syntax für require() sowie Exportmodule verwendet. AssemblyScript befolgt ähnlich wie Typescript die standardmäßige ES-Modul-Import/Export-Syntax. Wir müssen also noch eine weitere kleine Syntaxänderung vornehmen. Widmen wir uns nun unserer letzten Abhängigkeit: parse-ms.

Ein kurzer Blick auf den Quellcode von <u>parse-ms</u> zeigt, dass diese Abhängigkeit sehr gering ist. Auch hier können alle globalen Funktionen aus der AssemblyScript Standardbibliothek verwendet werden. Es gibt ein Snippet zur Typenprüfung, um sicherzustellen, dass der an die Exportfunktion übergebene Wert eine Zahl ist. Das können wir uns allerdings sparen, da der AssemblyScript Compiler dies für uns erledigt!

Da wir jetzt wissen, dass sich die globalen Funktionen von JavaScript und unsere Abhängigkeiten portieren lassen, können wir mit der Portierung unserer Node.js-Anwendung jetzt endlich loslegen. Hurra – jetzt geht’s ans Programmieren! Ich erstelle mithilfe der Fastly CLI eine neue Compute Anwendung in AssemblyScript und führe fastly compute init aus. Zum Zeitpunkt des Verfassens dieses Blogposts wird unser AssemblyScript Starterkit mit <u>@fastly/as-compute</u> 0.1.3 generiert. Als nächstes beginne ich mit der Portierung des Codes in meine AssemblyScript Anwendung. Die nachfolgenden Codeausschnitte enthalten ausführliche Kommentare, um unser Vorgehen in Bezug auf JavaScript (und die entsprechenden TypeScript Typen) zu erläutern. Werfen wir also einen Blick auf den Code, der sich daraus ergibt. Zunächst beginne ich mit unserer tiefsten Abhängigkeit, parse-ms (JavaScript Quellcode), und erstelle eine assembly/parse-ms.ts:

// This file is a port of:
// https://github.com/sindresorhus/parse-ms/blob/326500f7395fba4f47e73e36e6e770ad47c358d2/index.js
// The file is commented respective to how this is ported.

// As of 2020, AssemblyScript closure support is still in progress, and only supports pure nested functions
// Thus, we will pull out the nested function into its own function.
// This isn't required, but can avoid headaches in the future.
// This function takes in a float, but rounds it to an integer.
// In Typescript, `f64` would have been `number` types, but we must be explicit with the type of number in AS
// Ported from: https://github.com/sindresorhus/parse-ms/blob/326500f7395fba4f47e73e36e6e770ad47c358d2/index.js#L7
function roundTowardsZero(valueToRound: f64): f64 {
  if (valueToRound > 0) {
    return Math.floor(valueToRound);
  } else {
    return Math.ceil(valueToRound);
  }
}

// Define a class that represents the returned object fom the exported function
// We are exporting this as well, that way the type can be used
// Also, the `f64` type in TypeScript would be `number`, but in AS we must be explicit with the number type
// Ported from: https://github.com/sindresorhus/parse-ms/blob/326500f7395fba4f47e73e36e6e770ad47c358d2/index.js#L9
export class ParsedMilliseconds {
  days: f64;
  hours: f64;
  minutes: f64;
  seconds: f64;
  milliseconds: f64;
  microseconds: f64;
  nanoseconds: f64;
}

// Export a function to parse our milliseconds into our return type.
// Also, the `f64` type in TypeScript would be `number`, but in AS we must be explicit with the number type.
// Ported from: https://github.com/sindresorhus/parse-ms/blob/master/index.js#L2
export function parseMilliseconds(milliseconds: f64): ParsedMilliseconds {

  // We don't need to do a type check here, since we are using a typed language!
  // Referring to: https://github.com/sindresorhus/parse-ms/blob/326500f7395fba4f47e73e36e6e770ad47c358d2/index.js#L3

  // We moved roundTowardsZero into its own function
  // Referring to: https://github.com/sindresorhus/parse-ms/blob/326500f7395fba4f47e73e36e6e770ad47c358d2/index.js#L7

  // AssemblyScript will construct an instance of our return type (e.g new ParsedMilliseconds())
  // Since the return object has all the same properties of our return type.
  // This is so that we can pass a float into our `roundTowardsZero` function to handle the special rounding.
  return {
    days: roundTowardsZero(milliseconds / 86400000),
    hours: roundTowardsZero(milliseconds / 3600000) % 24,
    minutes: roundTowardsZero(milliseconds / 60000) % 60,
    seconds: roundTowardsZero(milliseconds / 1000) % 60,
    milliseconds: roundTowardsZero(milliseconds) % 1000,
    microseconds: roundTowardsZero(milliseconds * 1000) % 1000,
    nanoseconds: roundTowardsZero(milliseconds * 1e6) % 1000,
  };
}

Nachdem wir parse-ms nun abgefrühstückt haben, können wir pretty-ms (JavaScript Quellcode) portieren. pretty-ms verfügt über verschiedene Optionen, um die Anzahl der Dezimalstellen zu steuern. Diese sind von der Methode JavaScript Number.prototype.toFixed abhängig. Allerdings gibt es bei AssemblyScript ein aktuelles Problem mit der stdlib. Wir könnten zwar eine Implementierung für dieses Beispiel schreiben, der Kürze halber verzichten wir aber auf diese Funktion. Erstellen wir also eine assembly/pretty-ms.ts:

// This file is a port of:
// https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js
// The file is commented respective to how this is ported.

// Import our ported ported `parse-ms` module
// This ports the `require()` call:
// Ported From: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L2
import { parseMilliseconds } from "./parse-ms";

const SECOND_ROUNDING_EPSILON: f32 = 0.0000001;

// Define our options object that will be passed into our exported `prettyMilliseconds` function
// The options are from the documentation: https://github.com/sindresorhus/pretty-ms#options
// However, we removed the `DecimalDigits` options and `keepDecimalsOnWholeSeconds`, as Float.toFixed is in progress:
// https://github.com/AssemblyScript/assemblyscript/issues/1163
// In Typescript, `f64` and `i32` would have been `number` types, but we must be explicit with the type of number in AS
// Ported from: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L8
export class PrettyMillisecondsOptions {
  compact: boolean = false;
  unitCount: i32 = 0;
  verbose: boolean = false;
  separateMilliseconds: boolean = false;
  formatSubMilliseconds: boolean = false;
  colonNotation: boolean = false;
}

// This function takes in our word (which would be a string),
// and the count of that word (a float), to pluralize it.
// Also, the `i32` type in TypeScript would be `number`, but in AS, we must be explicit with the number type.
// Ported from: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L4
function pluralize(word: string, count: f64): string {

  // Since AssemblyScript is type checked, there is no need for ===
  // We can use the standard ==
  // Referring to: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L4
  if (count == 1) {
    return word;
  }

  return word + "s";
}

// As of 2020, AssemblyScript closure support is still in progress and only supports pure nested functions
// Thus, we will pull out the nested function into its own function.
// We pass in the options and results that were previously accessible by the closure, and return the results
// We also typed all of the parameters, to their respective types they would have been in JS or TS.
// One notable parameter is `valueString`. In JavaScript, optional parameters will default to `undefined`.
// In AssemblyScript, we would want to initialize this parameter with a value of its type and check that value later.
// We could have done a `valueString: string | null = null` to also signify it's an optional high-level typed parameter in a more
// JS-like fashion, but we use an empty string as it is a simpler replacement to check for.
// Ported from: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L33
function add(
  options: PrettyMillisecondsOptions,
  result: Array<string>,
  value: f64,
  long: string,
  short: string,
  valueString: string = ""
): Array<string> {
  if (
    (result.length === 0 || !options.colonNotation) &&
    value === 0 &&
    !(options.colonNotation && short === "m")
  ) {
    return result;
  }

  // AssemblyScript doesn't have `undefined`, so we need to be
  // a bit more explicit with our typecheck here
  // Referring to: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L38
  if (valueString == "") {
    valueString = value.toString();
  }

  // AssemblyScript would normally default types to i32, if the compiler can't figure out what
  // the type is from its initial assignment. So we need to define these types as strings,
  // since they are being used as strings
  // Ported from: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L39
  let prefix: string = "";
  let suffix: string = "";
  if (options.colonNotation) {
    prefix = result.length > 0 ? ":" : "";
    suffix = "";
    const wholeDigits = valueString.includes(".")
      ? valueString.split(".")[0].length
      : valueString.length;
    const minLength = result.length > 0 ? 2 : 1;
    valueString =
      "0".repeat(<i32>Math.max(0, minLength - wholeDigits)) + valueString;
  } else {
    prefix = "";

    // Since we removed the `DecimalDigits` options and `keepDecimalsOnWholeSeconds`, as Float.toFixed is in progress:
    // https://github.com/AssemblyScript/assemblyscript/issues/1163
    // Let's remove the trailing `.0` to clean things up by parsing our f64 into an i32
    valueString = I32.parseInt(valueString).toString();

    suffix = options.verbose ? " " + pluralize(long, value) : short;
  }

  result.push(prefix + valueString + suffix);
  return result;
}

// Export a function to parse our milliseconds into our human readable milliseconds string.
// Also, the `i32` type in TypeScript would be `number`, but in AS we must be explicit with the number type.
// Ported from: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L8
export function prettyMilliseconds(
  milliseconds: f64,
  options: PrettyMillisecondsOptions
): string {
  if (!Number.isFinite(milliseconds)) {
    throw new Error("Expected a finite number");
  }

  if (options.colonNotation) {
    options.compact = false;
    options.formatSubMilliseconds = false;
    options.separateMilliseconds = false;
    options.verbose = false;
  }

  // Since we aren't supporting DecimalDigits options in this port, we don't need to modify them
  // Referring to: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L20

  // It is best to define most high-level types by their object and type
  // Therefore our Array is defined as `new Array<string>()` instead of `[]`
  // Ported from: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L25
  let result = new Array<string>();

  const parsed = parseMilliseconds(milliseconds);

  // As mentioned earlier, we pulled the add function into its own function.
  // Thus, we update our result as we add instead of using the closure.
  // We also updated the other `add()` calls below, but only commenting here for brevity.
  // Also, we don't need the Math.trunc() call since we are doing integer division by default, unlike JavaScript
  // Ported from: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L57
  result = add(options, result, Math.floor(parsed.days / 365), "year", "y");
  result = add(options, result, parsed.days % 365, "day", "d");
  result = add(options, result, parsed.hours, "hour", "h");
  result = add(options, result, parsed.minutes, "minute", "m");

  if (
    options.separateMilliseconds ||
    options.formatSubMilliseconds ||
    (!options.colonNotation && milliseconds < 1000)
  ) {
    result = add(options, result, parsed.seconds, "second", "s");
    if (options.formatSubMilliseconds) {
      result = add(options, result, parsed.milliseconds, "millisecond", "ms");
      result = add(options, result, parsed.microseconds, "microsecond", "µs");
      result = add(options, result, parsed.nanoseconds, "nanosecond", "ns");
    } else {
      const millisecondsAndBelow =
        parsed.milliseconds +
        parsed.microseconds / 1000 +
        parsed.nanoseconds / 1e6;

      // Since we aren't supporting DecimalDigits options in this port, we don't need `millisecondsDecimalDigits`
      // Referring to: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L78

      const roundedMilliseconds =
        millisecondsAndBelow >= 1
          ? Math.round(millisecondsAndBelow)
          : Math.ceil(millisecondsAndBelow);

      // Since we aren't supporting DecimalDigits options in this port, we don't need `millisecondsDecimalDigits`
      // Referring to: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L87
      const millisecondsString: string = roundedMilliseconds.toString();

      result = add(
        options,
        result,
        parseFloat(millisecondsString),
        "millisecond",
        "ms",
        millisecondsString
      );
    }
  } else {
    const seconds = (milliseconds / 1000) % 60;

    // Since we aren't supporting DecimalDigits options in this port, we don't need `secondsDecimalDigits`
    // Referring to: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L78

    const secondsString = seconds.toString();
    result = add(
      options,
      result,
      parseFloat(secondsString),
      "second",
      "s",
      secondsString
    );
  }

  if (result.length === 0) {
    return "0" + (options.verbose ? " milliseconds" : "ms");
  }

  if (options.compact) {
    return result[0];
  }

  // We can replace the type check with a `> 0` check since we are using a typed language!
  // Referring to: https://github.com/sindresorhus/pretty-ms/blob/eda21362097d47ab309dca8cf07dc79b25fb0efa/index.js#L119
  if (options.unitCount > 0) {
    const separator = options.colonNotation ? "" : " ";
    return result.slice(0, <i32>Math.max(options.unitCount, 1)).join(separator);
  }

  return options.colonNotation ? result.join("") : result.join(" ");
}

Großartig! Als Nächstes portieren wir die Hauptlogik der Node.js-Anwendungen zu einer assembly/pretty-execution-time.ts. Zur Erinnerung: Wir hatten uns entschieden, das Date-Objekt aus as-wasi zu nutzen. Der nächste Schritt ist also die Installation von as-wasi in unserem Projekt, indem wir in unserem Terminal npm install --save as-wasi ausführen. Anschließend erstellen wir eine assembly/pretty-execution-time.ts:

import "wasi";
import { Date } from "as-wasi";
import { prettyMilliseconds } from "./pretty-ms";

export function getPrettyExecutionTime(): string {
  // Get our start time in milliseconds (Unix Timestamp)
  // In "as-wasi" this returns the milliseconds as a float
  // However, we will cast this to an integer for prettyMilliseconds
  const start = Date.now();

  // Do a random amount of busy work
  let iterations = 100000 + Math.floor(Math.random() * 10000000);
  for (let i = 0; i < iterations; i++) {
    iterations -= 1;
  }

  // Get our start time in milliseconds (Unix Timestamp)
  const end = Date.now();

  let responseString = "";
  responseString +=
    "Pretty Unix timestamp: " +
    prettyMilliseconds(start, { verbose: true }) +
    "\n";
  responseString +=
    "Busy work execution time: " +
    prettyMilliseconds(end - start, {
      verbose: true,
      formatSubMilliseconds: true,
    });

  return responseString;
}

Zum Schluss rufen wir jetzt noch unsere exportierte Funktion getPrettyExecutionTime in unserer AssemblyScript Datei auf, die als Entrypoint für Compute dient. So modifizieren wir assembly/index.ts:

import { Request, Response, Fastly } from "@fastly/as-compute";

// Import our pretty-execution time
import { getPrettyExecutionTime } from "./pretty-execution-time";

// Remove the unnecessary backend constants for our application
// Referring to: https://github.com/fastly/compute-starter-kit-assemblyscript-default/blob/78e536b046cff9e2a3e81945ef8b02ddc7bf2a75/assembly/index.ts#L3

// The entry point for your application.
//
// Use this function to define your main request handling logic. It could be
// used to route based on the request properties (such as method or path), send
// the request to a backend, make completely new requests, and/or generate
// synthetic responses.
function main(req: Request): Response {
  // Make any desired changes to the client request.
  req.headers().set("Host", "example.com");

  // We can filter requests that have unexpected methods.
  const VALID_METHODS = ["HEAD", "GET", "POST"];
  if (!VALID_METHODS.includes(req.method())) {
    return new Response(String.UTF8.encode("This method is not allowed"), {
      status: 405,
    });
  }

  let method = req.method();
  let urlParts = req.url().split("//").pop().split("/");
  let host = urlParts.shift();
  let path = "/" + urlParts.join("/");

  // If request is a `GET` to the `/` path, send a default response.
  if (method == "GET" && path == "/") {
    return new Response(String.UTF8.encode(getPrettyExecutionTime()), {
      status: 200,
    });
  }

  // Remove the unnecessary routes for our application
  // Referring to: https://github.com/fastly/compute-starter-kit-assemblyscript-default/blob/78e536b046cff9e2a3e81945ef8b02ddc7bf2a75/assembly/index.ts#L42

  // Catch all other requests and return a 404.
  return new Response(
    String.UTF8.encode("The page you requested could not be found"),
    {
      status: 404,
    }
  );
}

// Get the request from the client.
let req = Fastly.getClientRequest();

// Pass the request to the main request handler function.
let resp = main(req);

// Send the response back to the client.
Fastly.respondWith(resp);

Unsere Anwendung ist endlich fertig, und wir können einen Build auslösen und die Anwendung bereitstellen. Das Endprodukt für unser Beispiel finden Sie hier.

Bevor wir zum Schluss kommen, hier noch ein paar abschließende Anmerkungen:

  1. Ein TypeScript Äquivalent zu parse-ms und pretty-ms wäre noch einfacher zu portieren gewesen, da es zwischen den Typen von AssemblyScript und TypeScript einige Überschneidnungen gibt. AssemblyScript Typen sind in der Regel nur etwas spezifischer. Es ist also viel einfacher, die bestehenden Typen zu ändern, als sie völlig neu hinzuzufügen.

  2. Der AssemblyScript Compiler tut sein Bestes, um Annahmen über Typen zu treffen, wenn Variablen angegeben werden. Diese Annahmen ecken sich aber nicht immer mit Ihren eigenen Annahmen. Wenn Sie nach einer Portierung ein merkwürdiges Verhalten feststellen, könnte eine mögliche Lösung darin bestehen, explizit Typen hinzuzufügen, die in den Annahmen des Compilers enthalten waren.

  3. pretty-ms und parse-ms lassen sich viel einfacher portieren als JavaScript Frameworks wie Express oder Apollo. Wir haben uns für diese Packages entschieden, da sie ebenfalls sehr beliebt waren, für die Portierung aber nur ein kurzes Tutorial nötig war. Größere Packages beanspruchen tendenziell zusätzliche Komponenten der globalen Node.js APIs, wie beispielsweise das Dateisystemmodul <u>fs</u>. Bei WASI gibt es dasfs-Äquivalent as-wasi, das dem undefinedNode.js-Modul <u>FileSystem</u> entspricht. Aber nicht alle globalen JavaScript APIs in Node.js bzw. im Browser sind kompatibel, da AssemblyScript, WASI und WebAssembly allesamt relativ neue Technologien sind.

  4. Wenn Sie eine Bibliothek portieren, die in mehreren AssemblyScript Projekten verwendet werden könnte, und die Lizenz dies zulässt, laden Sie sie für die AssemblyScript Community bei npm hoch! Der Ablauf für die Veröffentlichung eines AssemblyScript Packages ist ähnlich wie der für den Upload eines normales JavaScript Packages. Der einzige Unterschied ist, dass AssemblyScript statt des Schlüssels „main“ in Ihrer package.json nach „ascMain“ in der package.json Ihrer Bibliothek sucht, was auf die AssemblyScript Eingabedatei Ihrer Bibliothek verweisen sollte. Wenn die Eingabedatei Ihrer Bibliothek beispielsweise assembly/index.ts ist, könnten Sie package.json: ”ascMain”: “assembly/index.ts” hinzufügen.

AssemblyScript ist eine aufregende neue Sprache, und wir sind gespannt darauf, was Leute damit entwickeln. Wir hoffen, wir konnten Ihnen mit diesem Blogpost verdeutlichen, wie ähnlich die Programmierung in AssemblyScript zu der mit JavaScript oder TypeScript ist, und dass AssemblyScript eine gute Option ist, um JavaScript und TypeScript zu Compute oder WebAssembly im Allgemeinen zu bringen. Melden Sie sich für unseren E-Mail-Newsletter an, um über Compute auf dem Laufenden zu bleiben.