Skip to content

Commit 19d380d

Browse files
authored
fix(build): ensure the dist files' directories exist before writing them (#54)
1 parent fd5eeea commit 19d380d

File tree

6 files changed

+149
-30
lines changed

6 files changed

+149
-30
lines changed

bundle_util.ts

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import { resolve, toFileUrl } from "./deps.ts";
2-
import { build, stop } from "https://deno.land/x/esbuild@v0.14.51/mod.js";
3-
import { denoPlugin } from "https://deno.land/x/esbuild_deno_loader@0.5.2/mod.ts";
1+
import { CommonOptions } from "https://deno.land/x/esbuild@v0.14.51/mod.js";
2+
import {
3+
build,
4+
denoPlugin,
5+
exists,
6+
parseJsonC,
7+
resolve,
8+
stop,
9+
toFileUrl,
10+
} from "./deps.ts";
411

512
export async function bundleByEsbuild(
613
path: string,
@@ -11,6 +18,39 @@ export async function bundleByEsbuild(
1118
importMapURL = toFileUrl(resolve(importMapFile));
1219
}
1320

21+
// @NekoMaru76: option tsconfig in esbuild's build doesn't work, so I did this
22+
let jsx: CommonOptions["jsx"];
23+
let jsxFactory: CommonOptions["jsxFactory"];
24+
let jsxFragment: CommonOptions["jsxFragment"];
25+
let jsxDev: CommonOptions["jsxDev"];
26+
let jsxImportSource: CommonOptions["jsxImportSource"];
27+
28+
const tsconfigFile = await getTsconfig();
29+
30+
if (tsconfigFile) {
31+
const config = <{
32+
compilerOptions: {
33+
jsx: CommonOptions["jsx"];
34+
jsxFactory: CommonOptions["jsxFactory"];
35+
jsxFragmentFactory: CommonOptions["jsxFragment"];
36+
jsxDev: CommonOptions["jsxDev"];
37+
jsxImportSource: CommonOptions["jsxImportSource"];
38+
};
39+
}> parseJsonC(await Deno.readTextFile(tsconfigFile));
40+
41+
if (
42+
config && typeof config === "object" &&
43+
config.compilerOptions &&
44+
typeof config.compilerOptions === "object"
45+
) {
46+
jsx = config.compilerOptions.jsx;
47+
jsxDev = config.compilerOptions.jsxDev;
48+
jsxFactory = config.compilerOptions.jsxFactory;
49+
jsxFragment = config.compilerOptions.jsxFragmentFactory;
50+
jsxImportSource = config.compilerOptions.jsxImportSource;
51+
}
52+
}
53+
1454
const bundle = await build({
1555
entryPoints: [toFileUrl(resolve(path)).href],
1656
plugins: [
@@ -20,6 +60,11 @@ export async function bundleByEsbuild(
2060
],
2161
bundle: true,
2262
write: false,
63+
jsx,
64+
jsxFactory,
65+
jsxDev,
66+
jsxFragment,
67+
jsxImportSource,
2368
});
2469

2570
stop();
@@ -36,3 +81,40 @@ export function setImportMap(importMap: string) {
3681
export function getImportMap() {
3782
return _importMap;
3883
}
84+
85+
let _tsconfig: string | undefined;
86+
87+
export function setTsconfig(tsconfig: string) {
88+
_tsconfig = tsconfig;
89+
}
90+
91+
export async function getTsconfig() {
92+
if (!_tsconfig) {
93+
if (
94+
await exists("./deno.json", {
95+
isReadable: true,
96+
isDirectory: false,
97+
})
98+
) {
99+
return "./deno.json";
100+
}
101+
if (
102+
await exists("./deno.jsonc", {
103+
isReadable: true,
104+
isDirectory: false,
105+
})
106+
) {
107+
return "./deno.jsonc";
108+
}
109+
if (
110+
await exists("./tsconfig.json", {
111+
isReadable: true,
112+
isDirectory: false,
113+
})
114+
) {
115+
return "./tsconfig.json";
116+
}
117+
}
118+
119+
return _tsconfig;
120+
}

cli.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
dirname,
23
ensureDir,
34
join,
45
NAME,
@@ -16,7 +17,7 @@ import {
1617
import { livereloadServer } from "./livereload_server.ts";
1718
import { byteSize, checkUniqueEntrypoints, mux } from "./util.ts";
1819
import { logger, setLogLevel } from "./logger_util.ts";
19-
import { setImportMap } from "./bundle_util.ts";
20+
import { setImportMap, setTsconfig } from "./bundle_util.ts";
2021

2122
function usage() {
2223
logger.log(`
@@ -48,6 +49,7 @@ Options:
4849
--static-dist-prefix <prefix> The prefix for static files in the destination.
4950
--public-url <prefix> The path prefix for urls. Default is ".".
5051
-i, --import-map <file> The path to an import map file.
52+
-c, --config <file> The path to a tsconfig file.
5153
-o, --open Automatically opens in specified browser.
5254
TODO --https Serves files over HTTPS.
5355
TODO --cert <path> The path to certificate to use with HTTPS.
@@ -69,6 +71,7 @@ Options:
6971
--static-dist-prefix <prefix> The prefix for static files in the destination.
7072
--public-url <prefix> The path prefix for urls. Default is ".".
7173
-i, --import-map <file> The path to an import map file.
74+
-c, --config <file> The path to a tsconfig file.
7275
-L, --log-level <level> Set the log level (choices: "none", "error", "warn", "info", "verbose")
7376
-h, --help Display help for command
7477
`.trim());
@@ -87,6 +90,7 @@ type CliArgs = {
8790
"static-dir": string;
8891
"static-dist-prefix": string;
8992
"import-map": string;
93+
"config": string;
9094
};
9195

9296
/**
@@ -106,6 +110,7 @@ export async function main(cliArgs: string[] = Deno.args): Promise<number> {
106110
"public-url": publicUrl = ".",
107111
"livereload-port": livereloadPort = 35729,
108112
"import-map": importMap,
113+
"config": config,
109114
} = parseFlags(cliArgs, {
110115
string: [
111116
"log-level",
@@ -114,6 +119,7 @@ export async function main(cliArgs: string[] = Deno.args): Promise<number> {
114119
"static-dir",
115120
"public-url",
116121
"import-map",
122+
"config",
117123
],
118124
boolean: ["help", "version", "open"],
119125
alias: {
@@ -124,6 +130,7 @@ export async function main(cliArgs: string[] = Deno.args): Promise<number> {
124130
L: "log-level",
125131
p: "port",
126132
i: "import-map",
133+
c: "config",
127134
},
128135
}) as CliArgs;
129136

@@ -191,6 +198,7 @@ export async function main(cliArgs: string[] = Deno.args): Promise<number> {
191198
publicUrl,
192199
staticDistPrefix,
193200
importMap,
201+
config,
194202
});
195203
return 0;
196204
}
@@ -218,6 +226,7 @@ export async function main(cliArgs: string[] = Deno.args): Promise<number> {
218226
publicUrl,
219227
staticDistPrefix,
220228
importMap,
229+
config,
221230
});
222231
return 0;
223232
}
@@ -227,6 +236,7 @@ type BuildAndServeCommonOptions = {
227236
staticDistPrefix: string;
228237
publicUrl: string;
229238
importMap: string;
239+
config: string;
230240
};
231241

232242
type BuildOptions = {
@@ -244,10 +254,12 @@ async function build(
244254
publicUrl,
245255
staticDistPrefix,
246256
importMap,
257+
config,
247258
}: BuildOptions & BuildAndServeCommonOptions,
248259
) {
249260
checkUniqueEntrypoints(paths);
250261
setImportMap(importMap);
262+
setTsconfig(config);
251263

252264
logger.log(`Writing the assets to ${distDir}`);
253265
await ensureDir(distDir);
@@ -267,6 +279,7 @@ async function build(
267279
const bytes = new Uint8Array(await asset.arrayBuffer());
268280
// TODO(kt3k): Print more structured report
269281
logger.log("Writing", filename, byteSize(bytes.byteLength));
282+
await ensureDir(dirname(filename));
270283
await Deno.writeFile(filename, bytes);
271284
}
272285
}
@@ -290,10 +303,12 @@ async function serve(
290303
publicUrl,
291304
staticDistPrefix,
292305
importMap,
306+
config,
293307
}: ServeOptions & BuildAndServeCommonOptions,
294308
) {
295309
checkUniqueEntrypoints(paths);
296310
setImportMap(importMap);
311+
setTsconfig(config);
297312

298313
// This is used for propagating onBuild event to livereload server.
299314
const buildEventHub = new EventTarget();

deps.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ export {
88
resolve,
99
toFileUrl,
1010
} from "https://deno.land/std@0.155.0/path/mod.ts";
11+
export { parse as parseJsonC } from "https://deno.land/std@0.193.0/jsonc/mod.ts";
1112
import { join } from "https://deno.land/std@0.155.0/path/posix.ts";
1213
export { join as posixPathJoin };
14+
export { denoPlugin } from "https://deno.land/x/esbuild_deno_loader@0.5.2/mod.ts";
15+
export { build, stop } from "https://deno.land/x/esbuild@v0.14.51/mod.js";
16+
export { exists } from "https://deno.land/std@0.194.0/fs/mod.ts";
1317
export { ensureDir } from "https://deno.land/std@0.155.0/fs/ensure_dir.ts";
1418
export { parse as parseFlags } from "https://deno.land/std@0.155.0/flags/mod.ts";
1519
export { red } from "https://deno.land/std@0.155.0/fmt/colors.ts";

generate_static_assets.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ async function createStaticAssetFromPath(
7777
): Promise<File> {
7878
logger.debug("Reading", path);
7979
const bytes = await Deno.readFile(path);
80+
const webkitRelativePath = relative(root, path);
8081
return Object.assign(new Blob([bytes]), {
81-
name: join(distPrefix, relative(root, path)),
82+
name: join(distPrefix, webkitRelativePath),
8283
lastModified: 0,
84+
webkitRelativePath,
8385
});
8486
}

serve_test.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { assertEquals } from "./test_deps.ts";
22

33
Deno.test("cli.ts serve <entrypoint> --port <port> --livereload-port <port> -- serves the site at the given port and livereload port", async () => {
4-
const p = Deno.run({
5-
cmd: [
6-
Deno.execPath(),
4+
const cmd = new Deno.Command(Deno.execPath(), {
5+
args: [
76
"run",
87
"-A",
98
"cli.ts",
@@ -14,20 +13,36 @@ Deno.test("cli.ts serve <entrypoint> --port <port> --livereload-port <port> -- s
1413
"--livereload-port",
1514
"34567",
1615
],
16+
stdout: "piped",
1717
});
18-
await new Promise((resolve) => setTimeout(resolve, 10000));
19-
let res = await fetch("http://localhost:4567/index.html");
20-
assertEquals(
21-
await res.text(),
22-
`<!DOCTYPE html><html><head></head><body><div>aaa</div>\n<script src="http://localhost:34567/livereload.js"></script></body></html>`,
23-
);
18+
const p = cmd.spawn();
19+
const textDecoder = new TextDecoder();
20+
let done = false;
2421

25-
// Non existent path returns the same response as the main html.
26-
// This is useful for apps which use client side routing.
27-
res = await fetch("http://localhost:4567/asdf");
28-
assertEquals(
29-
await res.text(),
30-
`<!DOCTYPE html><html><head></head><body><div>aaa</div>\n<script src="http://localhost:34567/livereload.js"></script></body></html>`,
31-
);
32-
p.close();
22+
for await (const a of p.stdout) {
23+
if (textDecoder.decode(a).includes("Server running")) {
24+
let res = await fetch("http://localhost:4567/index.html");
25+
assertEquals(
26+
await res.text(),
27+
`<!DOCTYPE html><html><head></head><body><div>aaa</div>\n<script src="http://localhost:34567/livereload.js"></script></body></html>`,
28+
);
29+
30+
// Non existent path returns the same response as the main html.
31+
// This is useful for apps which use client side routing.
32+
res = await fetch("http://localhost:4567/asdf");
33+
assertEquals(
34+
await res.text(),
35+
`<!DOCTYPE html><html><head></head><body><div>aaa</div>\n<script src="http://localhost:34567/livereload.js"></script></body></html>`,
36+
);
37+
38+
done = true;
39+
40+
break;
41+
}
42+
}
43+
44+
p.kill();
45+
await p.status;
46+
47+
assertEquals(done, true);
3348
});

util.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,22 @@ export function md5(data: string | ArrayBuffer): string {
1717
}
1818

1919
export async function getDependencies(path: string): Promise<string[]> {
20-
const p = Deno.run({
21-
cmd: [Deno.execPath(), "info", "--json", path],
20+
const p = new Deno.Command(Deno.execPath(), {
21+
args: ["info", "--json", path],
2222
stdout: "piped",
2323
stderr: "piped",
24-
});
24+
}).spawn();
2525
const [status, output, stderrOutput] = await Promise.all([
26-
p.status(),
26+
p.status,
2727
p.output(),
28-
p.stderrOutput(),
28+
p.stderr,
2929
]);
3030
if (status.code !== 0) {
31-
throw new Error(decoder.decode(stderrOutput));
31+
throw new Error(
32+
decoder.decode((await stderrOutput.getReader().read()).value),
33+
);
3234
}
33-
const denoInfo = JSON.parse(decoder.decode(output)) as DenoInfo;
34-
p.close();
35+
const denoInfo = JSON.parse(decoder.decode(output.stdout)) as DenoInfo;
3536
return denoInfo.modules.map((m) => m.specifier);
3637
}
3738

0 commit comments

Comments
 (0)