Skip to content

Commit

Permalink
fix: error when using top level await and outputting commonjs module
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Nov 5, 2021
1 parent fd8fbe2 commit 533ba8d
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 18 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ There are several steps done in a pipeline:
- Allows mapping any specifier to an npm package.
1. Type checks the output.
1. Emits ESM, CommonJS, and TypeScript declaration files along with a _package.json_ file.
1. Runs the final output in Node through a test runner running all `Deno.test` calls. Deletes the test files when complete.
1. Runs the final output in Node through a test runner calling all `Deno.test` calls.

## Setup

Expand Down
24 changes: 23 additions & 1 deletion lib/compiler.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { getCompilerScriptTarget, ScriptTarget } from "./compiler.ts";
import {
getCompilerScriptTarget,
getTopLevelAwait,
ScriptTarget,
} from "./compiler.ts";
import { ts } from "./mod.deps.ts";
import { assertEquals, assertThrows } from "./test.deps.ts";

Expand Down Expand Up @@ -26,3 +30,21 @@ Deno.test("script target should have expected outputs", () => {
assertEquals(getCompilerScriptTarget(undefined), ts.ScriptTarget.ES2021);
assertThrows(() => getCompilerScriptTarget("invalid" as any));
});

Deno.test("get has top level await", () => {
runTest("const some = code;class SomeOtherCode {}", undefined);
runTest("async function test() { await 5; }", undefined);
runTest("await test();", {
line: 0,
character: 0,
});

function runTest(code: string, expected: ts.LineAndCharacter | undefined) {
const sourceFile = ts.createSourceFile(
"file.ts",
code,
ts.ScriptTarget.Latest,
);
assertEquals(getTopLevelAwait(sourceFile), expected);
}
});
14 changes: 14 additions & 0 deletions lib/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,17 @@ export function getCompilerScriptTarget(target: ScriptTarget | undefined) {
throw new Error(`Unknown target compiler option: ${target}`);
}
}

export function getTopLevelAwait(sourceFile: ts.SourceFile) {
for (const statement of sourceFile.statements) {
if (
ts.isExpressionStatement(statement) &&
ts.isAwaitExpression(statement.expression)
) {
return sourceFile.getLineAndCharacterOfPosition(
statement.expression.getStart(sourceFile),
);
}
}
return undefined;
}
24 changes: 21 additions & 3 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

import { getCompilerScriptTarget, outputDiagnostics } from "./lib/compiler.ts";
import {
getCompilerScriptTarget,
getTopLevelAwait,
outputDiagnostics,
} from "./lib/compiler.ts";
import { colors, createProjectSync, path, ts } from "./lib/mod.deps.ts";
import { PackageJsonObject } from "./lib/types.ts";
import { glob, runNpmCommand } from "./lib/utils.ts";
Expand Down Expand Up @@ -153,7 +157,7 @@ export async function build(options: BuildOptions): Promise<void> {
jsxFactory: "React.createElement",
jsxFragmentFactory: "React.Fragment",
importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Remove,
module: ts.ModuleKind.ES2015,
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
target: getCompilerScriptTarget(options.compilerOptions?.target),
allowSyntheticDefaultImports: true,
Expand Down Expand Up @@ -182,11 +186,25 @@ export async function build(options: BuildOptions): Promise<void> {
const outputFileText = binaryEntryPointPaths.has(outputFile.filePath)
? `#!/usr/bin/env node\n${outputFile.fileText}`
: outputFile.fileText;
project.createSourceFile(
const sourceFile = project.createSourceFile(
outputFilePath,
outputFileText,
);

const tlaLocation = getTopLevelAwait(sourceFile);
if (tlaLocation) {
warn(
`Top level await cannot be used when targeting CommonJS ` +
`(See ${outputFile.filePath} ${tlaLocation.line + 1}:${
tlaLocation.character + 1
}). ` +
`Please re-organize your code to not use a top level await or only distribute an ESM module.`,
);
throw new Error(
"Build failed due to top level await when creating CommonJS module.",
);
}

if (!options.skipSourceOutput) {
writeFile(outputFilePath, outputFileText);
}
Expand Down
43 changes: 31 additions & 12 deletions tests/integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
import {
assertEquals,
assertRejects,
} from "https://deno.land/[email protected]/testing/asserts.ts";
import { build, BuildOptions } from "../mod.ts";

Deno.test("should build", async () => {
await runTest({
await runTest("test_project", {
entryPoints: ["mod.ts"],
outDir: "./npm",
package: {
Expand Down Expand Up @@ -55,7 +58,7 @@ test_runner.js
});

Deno.test("should build bin project", async () => {
await runTest({
await runTest("test_project", {
entryPoints: [{
kind: "bin",
name: "add",
Expand Down Expand Up @@ -98,30 +101,46 @@ Deno.test("should build bin project", async () => {
});
});

Deno.test("error for project with TLA and creating a CommonJS", async () => {
await assertRejects(() =>
runTest("tla_project", {
entryPoints: ["mod.ts"],
outDir: "./npm",
package: {
name: "add",
version: "1.0.0",
},
})
);
});

export interface Output {
packageJson: any;
npmIgnore: string;
getFileText(filePath: string): string;
}

async function runTest(
project: "test_project" | "tla_project",
options: BuildOptions,
checkOutput: (output: Output) => (Promise<void> | void),
checkOutput?: (output: Output) => (Promise<void> | void),
) {
const originalCwd = Deno.cwd();
Deno.chdir("./tests/test_project");
Deno.chdir(`./tests/${project}`);
try {
await build(options);
const getFileText = (filePath: string) => {
return Deno.readTextFileSync(options.outDir + "/" + filePath);
};
const packageJson = JSON.parse(getFileText("package.json"));
const npmIgnore = getFileText(".npmignore");
await checkOutput({
packageJson,
npmIgnore,
getFileText,
});
if (checkOutput) {
const packageJson = JSON.parse(getFileText("package.json"));
const npmIgnore = getFileText(".npmignore");
await checkOutput({
packageJson,
npmIgnore,
getFileText,
});
}
} finally {
Deno.removeSync(options.outDir, { recursive: true });
Deno.chdir(originalCwd);
Expand Down
9 changes: 9 additions & 0 deletions tests/tla_project/mod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

import { add } from "./mod.ts";

Deno.test("should add in test project", () => {
if (add(1, 2) !== 3) {
throw new Error("FAIL");
}
});
7 changes: 7 additions & 0 deletions tests/tla_project/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

await new Promise<void>((resolve) => resolve());

export function add(a: number, b: number) {
return a + b;
}
1 change: 0 additions & 1 deletion todo.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
- tests for `build`
- Handle deno-types when it specifies a declaration for a `.ts` file (maybe transform the .ts file to .js?)
- Handle dynamic imports (at least ones that are statically analyzable and maybe warn on others)
- Support lockfile
Expand Down

0 comments on commit 533ba8d

Please sign in to comment.