Skip to content

Commit

Permalink
Initial support for remote imports
Browse files Browse the repository at this point in the history
  • Loading branch information
ry committed May 19, 2018
1 parent 39da69f commit 8886e1b
Show file tree
Hide file tree
Showing 11 changed files with 874 additions and 49 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ TS_FILES = \
os.ts \
runtime.ts \
timers.ts \
url.js \
util.ts

deno: assets.go msg.pb.go main.go
Expand Down
31 changes: 28 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"github.com/golang/protobuf/proto"
"github.com/ry/v8worker2"
"io/ioutil"
"net/http"
"os"
"path"
"runtime"
"strings"
"sync"
"time"
)
Expand All @@ -28,11 +30,34 @@ func CacheFileName(filename string, sourceCodeBuf []byte) string {
return path.Join(CompileDir, cacheKey+".js")
}

func IsRemotePath(filename string) bool {
return strings.HasPrefix(filename, "/$remote$/")
}

func FetchRemoteSource(remotePath string) (buf []byte, err error) {
url := strings.Replace(remotePath, "/$remote$/", "http:https://", 1)
// println("FetchRemoteSource", url)
res, err := http.Get(url)
if err != nil {
return
}
buf, err = ioutil.ReadAll(res.Body)
//println("FetchRemoteSource", err.Error())
res.Body.Close()
return
}

func HandleSourceCodeFetch(filename string) []byte {
res := &Msg{}
sourceCodeBuf, err := Asset("dist/" + filename)
if err != nil {
sourceCodeBuf, err = ioutil.ReadFile(filename)
var sourceCodeBuf []byte
var err error
if IsRemotePath(filename) {
sourceCodeBuf, err = FetchRemoteSource(filename)
} else {
sourceCodeBuf, err = Asset("dist/" + filename)
if err != nil {
sourceCodeBuf, err = ioutil.ReadFile(filename)
}
}
if err != nil {
res.Error = err.Error()
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"fmt": "prettier --write *.ts* *.js *.json"
},
"devDependencies": {
"@types/text-encoding": "^0.0.32",
"http-server": "^0.11.1",
"parcel-bundler": "^1.8.1",
"protobufjs": "^6.8.6",
"text-encoding": "^0.6.4",
"tslint": "^5.10.0",
"typescript": "^2.8.3"
}
},
"dependencies": {}
}
59 changes: 32 additions & 27 deletions runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as path from "path";
import * as util from "./util";
import { log } from "./util";
import * as os from "./os";
import "./url";

const EOL = "\n";

Expand All @@ -26,8 +27,6 @@ export class FileModule {
private constructor(readonly fileName: string) {
FileModule.map.set(fileName, this);

assertValidFileName(this.fileName);

// Load typescript code (sourceCode) and maybe load compiled javascript
// (outputCode) from cache. If cache is empty, outputCode will be null.
const { sourceCode, outputCode } = os.sourceCodeFetch(this.fileName);
Expand All @@ -49,7 +48,6 @@ export class FileModule {
}

static load(fileName: string): FileModule {
assertValidFileName(fileName);
let m = this.map.get(fileName);
if (m == null) {
m = new this(fileName);
Expand All @@ -70,12 +68,6 @@ export class FileModule {
}
}

function assertValidFileName(fileName: string): void {
if (fileName !== "lib.d.ts") {
util.assert(fileName[0] === "/", `fileName must be absolute: ${fileName}`);
}
}

// tslint:disable-next-line:no-any
type AmdFactory = (...args: any[]) => undefined | object;
type AmdDefine = (deps: string[], factory: AmdFactory) => void;
Expand All @@ -94,8 +86,8 @@ export function makeDefine(fileName: string): AmdDefine {
} else if (dep === "exports") {
return localExports;
} else {
dep = resolveModuleName(dep, fileName);
const depModule = FileModule.load(dep);
const resolved = resolveModuleName(dep, fileName);
const depModule = FileModule.load(resolved);
depModule.compileAndRun();
return depModule.exports;
}
Expand All @@ -105,8 +97,26 @@ export function makeDefine(fileName: string): AmdDefine {
return localDefine;
}

function resolveModuleName(fileName: string, contextFileName: string): string {
return path.resolve(path.dirname(contextFileName), fileName);
function resolveModuleName(moduleName: string, containingFile: string): string {
if (isUrl(moduleName)) {
// Remove the "http:https://" from the start of the string.
const u = new URL(moduleName);
const withoutProtocol = u.toString().replace(u.protocol + "//", "");
const name2 = "/$remote$/" + withoutProtocol;
return name2;
} else if (moduleName.startsWith("/")) {
throw Error("Absolute paths not supported");
} else {
// Relative import.
const containingDir = path.dirname(containingFile);
const resolvedFileName = path.join(containingDir, moduleName);
util.log("relative import", {
containingFile,
moduleName,
resolvedFileName
});
return resolvedFileName;
}
}

function execute(fileName: string, outputCode: string): void {
Expand Down Expand Up @@ -231,24 +241,19 @@ class TypeScriptHost implements ts.LanguageServiceHost {
): Array<ts.ResolvedModule | undefined> {
util.log("resolveModuleNames", { moduleNames, reusedNames });
return moduleNames.map((name: string) => {
if (
name.startsWith("/") ||
name.startsWith("http:https://") ||
name.startsWith("https://")
) {
throw Error("Non-relative imports not yet supported.");
} else {
// Relative import.
const containingDir = path.dirname(containingFile);
const resolvedFileName = path.join(containingDir, name);
util.log("relative import", { containingFile, name, resolvedFileName });
const isExternalLibraryImport = false;
return { resolvedFileName, isExternalLibraryImport };
}
const resolvedFileName = resolveModuleName(name, containingFile);
const isExternalLibraryImport = false;
return { resolvedFileName, isExternalLibraryImport };
});
}
}

function isUrl(p: string): boolean {
return (
p.startsWith("//") || p.startsWith("http:https://") || p.startsWith("https://")
);
}

const formatDiagnosticsHost: ts.FormatDiagnosticsHost = {
getCurrentDirectory(): string {
return ".";
Expand Down
19 changes: 18 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,22 @@

const fs = require("fs");
const path = require("path");
const { execFileSync } = require("child_process");
const { spawn, execFileSync } = require("child_process");

// Some tests require an HTTP server. We start one here.
// Because we process tests synchronously in this program we must run
// the server as a subprocess.
// Note that "localhost:4545" is hardcoded into the tests at the moment,
// so if the server runs on a different port, it will fail.
const httpServerArgs = ["node_modules/.bin/http-server", __dirname, "-p 4545"];
const server = spawn(process.execPath, httpServerArgs, {
cwd: __dirname,
stdio: "inherit"
});
// TODO: For some reason the http-server doesn't exit properly
// when this program dies. So we force it with the exit handler here.
server.unref();
process.on("exit", () => server.kill("SIGINT"));

const testdataDir = path.join(__dirname, "testdata");
const denoFn = path.join(__dirname, "deno");
Expand Down Expand Up @@ -33,3 +48,5 @@ ${outFileBuffer.toString()}
`);
}
}

console.log("Tests done");
3 changes: 3 additions & 0 deletions testdata/006_url_imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { printHello } from "http:https://localhost:4545/testdata/subdir/print_hello.ts";
printHello();
console.log("success");
2 changes: 2 additions & 0 deletions testdata/006_url_imports.ts.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hello
success
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"preserveConstEnums": true,
"declaration": true,
"target": "es2017",
"lib": ["es2017"],
"lib": ["es2017", "DOM"],
"noEmit": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
Expand Down
Loading

0 comments on commit 8886e1b

Please sign in to comment.