summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/compiler.ts122
-rw-r--r--js/compiler_test.ts72
2 files changed, 150 insertions, 44 deletions
diff --git a/js/compiler.ts b/js/compiler.ts
index 20ddbae9f..88a383e4b 100644
--- a/js/compiler.ts
+++ b/js/compiler.ts
@@ -79,7 +79,7 @@ export interface Ts {
*/
export class ModuleMetaData implements ts.IScriptSnapshot {
public deps?: ModuleFileName[];
- public readonly exports = {};
+ public exports = {};
public factory?: AmdFactory;
public gatheringDeps = false;
public hasRun = false;
@@ -129,6 +129,15 @@ function getExtension(
}
}
+/** Generate output code for a provided JSON string along with its source. */
+export function jsonAmdTemplate(
+ jsonString: string,
+ sourceFileName: string
+): OutputCode {
+ // tslint:disable-next-line:max-line-length
+ return `define([], function() { return JSON.parse(\`${jsonString}\`); });\n//# sourceURL=${sourceFileName}`;
+}
+
/** A singleton class that combines the TypeScript Language Service host API
* with Deno specific APIs to provide an interface for compiling and running
* TypeScript and JavaScript modules.
@@ -153,11 +162,12 @@ export class DenoCompiler
>();
// TODO ideally this are not static and can be influenced by command line
// arguments
- private readonly _options: Readonly<ts.CompilerOptions> = {
+ private readonly _options: ts.CompilerOptions = {
allowJs: true,
checkJs: true,
module: ts.ModuleKind.AMD,
outDir: "$deno$",
+ resolveJsonModule: true,
sourceMap: true,
stripComments: true,
target: ts.ScriptTarget.ESNext
@@ -198,7 +208,15 @@ export class DenoCompiler
);
assert(moduleMetaData.hasRun === false, "Module has already been run.");
// asserts not tracked by TypeScripts, so using not null operator
- moduleMetaData.factory!(...this._getFactoryArguments(moduleMetaData));
+ const exports = moduleMetaData.factory!(
+ ...this._getFactoryArguments(moduleMetaData)
+ );
+ // For JSON module support and potential future features.
+ // TypeScript always imports `exports` and mutates it directly, but the
+ // AMD specification allows values to be returned from the factory.
+ if (exports != null) {
+ moduleMetaData.exports = exports;
+ }
moduleMetaData.hasRun = true;
}
}
@@ -421,50 +439,74 @@ export class DenoCompiler
if (!recompile && moduleMetaData.outputCode) {
return moduleMetaData.outputCode;
}
- const { fileName, sourceCode, moduleId } = moduleMetaData;
+ const { fileName, sourceCode, mediaType, moduleId } = moduleMetaData;
console.warn("Compiling", moduleId);
const service = this._service;
- const output = service.getEmitOutput(fileName);
-
- // Get the relevant diagnostics - this is 3x faster than
- // `getPreEmitDiagnostics`.
- const diagnostics = [
- ...service.getCompilerOptionsDiagnostics(),
- ...service.getSyntacticDiagnostics(fileName),
- ...service.getSemanticDiagnostics(fileName)
- ];
- if (diagnostics.length > 0) {
- const errMsg = this._ts.formatDiagnosticsWithColorAndContext(
- diagnostics,
- this
+ // Instead of using TypeScript to transpile JSON modules, we will just do
+ // it directly.
+ if (mediaType === MediaType.Json) {
+ moduleMetaData.outputCode = jsonAmdTemplate(sourceCode, fileName);
+ } else {
+ assert(
+ mediaType === MediaType.TypeScript || mediaType === MediaType.JavaScript
);
- console.log(errMsg);
- // All TypeScript errors are terminal for deno
- this._os.exit(1);
- }
+ // TypeScript is overly opinionated that only CommonJS modules kinds can
+ // support JSON imports. Allegedly this was fixed in
+ // Microsoft/TypeScript#26825 but that doesn't seem to be working here,
+ // so we will trick the TypeScript compiler.
+ this._options.module = ts.ModuleKind.AMD;
+ const output = service.getEmitOutput(fileName);
+ this._options.module = ts.ModuleKind.CommonJS;
+
+ // Get the relevant diagnostics - this is 3x faster than
+ // `getPreEmitDiagnostics`.
+ const diagnostics = [
+ ...service.getCompilerOptionsDiagnostics(),
+ ...service.getSyntacticDiagnostics(fileName),
+ ...service.getSemanticDiagnostics(fileName)
+ ];
+ if (diagnostics.length > 0) {
+ const errMsg = this._ts.formatDiagnosticsWithColorAndContext(
+ diagnostics,
+ this
+ );
+ console.log(errMsg);
+ // All TypeScript errors are terminal for deno
+ this._os.exit(1);
+ }
- assert(!output.emitSkipped, "The emit was skipped for an unknown reason.");
+ assert(
+ !output.emitSkipped,
+ "The emit was skipped for an unknown reason."
+ );
- assert(
- output.outputFiles.length === 2,
- `Expected 2 files to be emitted, got ${output.outputFiles.length}.`
- );
+ assert(
+ output.outputFiles.length === 2,
+ `Expected 2 files to be emitted, got ${output.outputFiles.length}.`
+ );
+
+ const [sourceMapFile, outputFile] = output.outputFiles;
+ assert(
+ sourceMapFile.name.endsWith(".map"),
+ "Expected first emitted file to be a source map"
+ );
+ assert(
+ outputFile.name.endsWith(".js"),
+ "Expected second emitted file to be JavaScript"
+ );
+ moduleMetaData.outputCode = `${
+ outputFile.text
+ }\n//# sourceURL=${fileName}`;
+ moduleMetaData.sourceMap = sourceMapFile.text;
+ }
- const [sourceMapFile, outputFile] = output.outputFiles;
- assert(
- sourceMapFile.name.endsWith(".map"),
- "Expected first emitted file to be a source map"
- );
- assert(
- outputFile.name.endsWith(".js"),
- "Expected second emitted file to be JavaScript"
- );
- const outputCode = (moduleMetaData.outputCode = `${
- outputFile.text
- }\n//# sourceURL=${fileName}`);
- const sourceMap = (moduleMetaData.sourceMap = sourceMapFile.text);
moduleMetaData.scriptVersion = "1";
- this._os.codeCache(fileName, sourceCode, outputCode, sourceMap);
+ this._os.codeCache(
+ fileName,
+ sourceCode,
+ moduleMetaData.outputCode,
+ moduleMetaData.sourceMap
+ );
return moduleMetaData.outputCode;
}
diff --git a/js/compiler_test.ts b/js/compiler_test.ts
index d7a5c877c..729d6b4a7 100644
--- a/js/compiler_test.ts
+++ b/js/compiler_test.ts
@@ -6,7 +6,7 @@ import * as ts from "typescript";
// We use a silly amount of `any` in these tests...
// tslint:disable:no-any
-const { DenoCompiler } = (deno as any)._compiler;
+const { DenoCompiler, jsonAmdTemplate } = (deno as any)._compiler;
interface ModuleInfo {
moduleName: string | undefined;
@@ -118,6 +118,11 @@ const fooBazTsOutput = `define(["require", "exports", "./bar.ts"], function (req
// This is not a valid map, just mock data
const fooBazTsSourcemap = `{"version":3,"file":"baz.js","sourceRoot":"","sources":["file:///root/project/foo/baz.ts"],"names":[],"mappings":""}`;
+
+const loadConfigSource = `import * as config from "./config.json";
+console.log(config.foo.baz);
+`;
+const configJsonSource = `{"foo":{"bar": true,"baz": ["qat", 1]}}`;
// tslint:enable:max-line-length
const moduleMap: {
@@ -148,6 +153,14 @@ const moduleMap: {
"console.log();",
null,
null
+ ),
+ "loadConfig.ts": mockModuleInfo(
+ "/root/project/loadConfig.ts",
+ "/root/project/loadConfig.ts",
+ MediaType.TypeScript,
+ loadConfigSource,
+ null,
+ null
)
},
"/root/project/foo/baz.ts": {
@@ -166,6 +179,16 @@ const moduleMap: {
"/root/project/modB.ts": {
"./modA.ts": modAModuleInfo
},
+ "/root/project/loadConfig.ts": {
+ "./config.json": mockModuleInfo(
+ "/root/project/config.json",
+ "/root/project/config.json",
+ MediaType.Json,
+ configJsonSource,
+ null,
+ null
+ )
+ },
"/moduleKinds": {
"foo.ts": mockModuleInfo(
"foo",
@@ -280,7 +303,7 @@ const osMock = {
return mockModuleInfo(null, null, null, null, null, null);
},
exit(code: number): never {
- throw new Error(`os.exit(${code})`);
+ throw new Error(`Unexpected call to os.exit(${code})`);
}
};
const tsMock = {
@@ -289,9 +312,9 @@ const tsMock = {
},
formatDiagnosticsWithColorAndContext(
diagnostics: ReadonlyArray<ts.Diagnostic>,
- host: ts.FormatDiagnosticsHost
+ _host: ts.FormatDiagnosticsHost
): string {
- return "";
+ return JSON.stringify(diagnostics.map(({ messageText }) => messageText));
}
};
@@ -374,6 +397,23 @@ function teardown() {
Object.assign(compilerInstance, originals);
}
+test(function testJsonAmdTemplate() {
+ let deps: string[];
+ let factory: Function;
+ function define(d: string[], f: Function) {
+ deps = d;
+ factory = f;
+ }
+
+ const code = jsonAmdTemplate(`{ "hello": "world", "foo": "bar" }`);
+ const result = eval(code);
+ assert(result == null);
+ assertEqual(deps && deps.length, 0);
+ assert(factory != null);
+ const factoryResult = factory();
+ assertEqual(factoryResult, { hello: "world", foo: "bar" });
+});
+
test(function compilerInstance() {
assert(DenoCompiler != null);
assert(DenoCompiler.instance() != null);
@@ -479,6 +519,29 @@ test(function compilerRunCircularDependency() {
teardown();
});
+test(function compilerLoadJsonModule() {
+ setup();
+ const factoryStack: string[] = [];
+ const configJsonDeps: string[] = [];
+ const configJsonFactory = () => {
+ factoryStack.push("configJson");
+ return JSON.parse(configJsonSource);
+ };
+ const loadConfigDeps = ["require", "exports", "./config.json"];
+ const loadConfigFactory = (_require, _exports, _config) => {
+ factoryStack.push("loadConfig");
+ assertEqual(_config, JSON.parse(configJsonSource));
+ };
+
+ mockDepsStack.push(configJsonDeps);
+ mockFactoryStack.push(configJsonFactory);
+ mockDepsStack.push(loadConfigDeps);
+ mockFactoryStack.push(loadConfigFactory);
+ compilerInstance.run("loadConfig.ts", "/root/project");
+ assertEqual(factoryStack, ["configJson", "loadConfig"]);
+ teardown();
+});
+
test(function compilerResolveModule() {
setup();
const moduleMetaData = compilerInstance.resolveModule(
@@ -544,6 +607,7 @@ test(function compilerGetCompilationSettings() {
"checkJs",
"module",
"outDir",
+ "resolveJsonModule",
"sourceMap",
"stripComments",
"target"