-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: fourceInstantiateWasm * update jsdoc * fix test * add coverage * fix lint error
- Loading branch information
Showing
6 changed files
with
251 additions
and
163 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,173 +1,24 @@ | ||
import { | ||
serveDir, | ||
type ServeDirOptions, | ||
serveFile, | ||
type ServeFileOptions, | ||
} from "https://deno.land/std@0.151.0/http/file_server.ts"; | ||
import { contentType } from "https://deno.land/std@0.151.0/media_types/mod.ts"; | ||
import type { Context } from "https://deno.land/x/oak@v10.6.0/mod.ts"; | ||
import { convertBodyToBodyInit } from "https://deno.land/x/oak@v10.6.0/response.ts"; | ||
import { MediaType, transpile } from "./utils/transpile.ts"; | ||
|
||
const decoder = new TextDecoder(); | ||
const tsType = new Set<string | undefined>( | ||
["ts", ".ts", "mts", ".mts", "video/mp2t"], | ||
); | ||
const tsxType = new Set<string | undefined>(["tsx", ".tsx"]); | ||
const jsxType = new Set<string | undefined>(["jsx", ".jsx", "text/jsx"]); | ||
const jsContentType = contentType(".js")!; | ||
export * from "./src/oak.ts"; | ||
export * from "./src/file_server.ts"; | ||
export * from "./utils/transpile.ts"; | ||
import { transpile } from "./utils/transpile.ts"; | ||
|
||
/** | ||
* This can be used in the same way as the [serveFile](https://doc.deno.land/https://deno.land/std@0.151.0/http/file_server.ts/~/serveFile) function of the standard library, but if the file is TypeScript, it will be rewritten to JavaScript. | ||
* **Calling this function has no effect whether it is called or not.** | ||
* Calling this function will force the loading of the wasm file used internally. | ||
* For performance sensitive servers, etc., call this function first to tell it to load wasm. | ||
* There is no need to call this function where performance is not important. In that case, the wasm file will be automatically loaded in about 3 seconds when you transpile for the first time. | ||
* | ||
* ```ts | ||
* import { serve } from "https://deno.land/std@0.151.0/http/mod.ts"; | ||
* import { serveFileWithTs } from "https://deno.land/x/ts_serve@$VERSION/mod.ts"; | ||
* | ||
* serve((request) => serveFileWithTs(request, "./mod.ts")); | ||
* ``` | ||
*/ | ||
export async function serveFileWithTs( | ||
request: Request, | ||
filePath: string, | ||
options?: ServeFileOptions, | ||
): Promise<Response> { | ||
const response = await serveFile(request, filePath, options); | ||
|
||
let url; | ||
try { | ||
url = new URL(request.url, "file:///"); | ||
} catch { | ||
url = new URL("file:///src"); | ||
} | ||
// if range request, skip | ||
if (response.status === 200) { | ||
if (filePath.endsWith(".ts")) { | ||
return rewriteTsResponse(response, url, MediaType.TypeScript); | ||
} else if (filePath.endsWith(".tsx")) { | ||
return rewriteTsResponse(response, url, MediaType.Tsx); | ||
} else if (filePath.endsWith(".jsx")) { | ||
return rewriteTsResponse(response, url, MediaType.Jsx); | ||
} | ||
} | ||
return response; | ||
} | ||
|
||
/** | ||
* This can be used in the same way as the [serveDir](https://doc.deno.land/https://deno.land/std@0.151.0/http/file_server.ts/~/serveDir) function of the standard library, but if the file is TypeScript, it will be rewritten to JavaScript. | ||
* | ||
* ```ts | ||
* import { serve } from "https://deno.land/std@0.151.0/http/mod.ts"; | ||
* import { serveDirWithTs } from "https://deno.land/x/ts_serve@$VERSION/mod.ts"; | ||
* import { serveDirWithTs, fourceInstantiateWasm } from "https://deno.land/x/ts_serve@$VERSION/mod.ts"; | ||
* | ||
* fourceInstantiateWasm(); | ||
* serve((request) => serveDirWithTs(request)); | ||
* ``` | ||
*/ | ||
export async function serveDirWithTs( | ||
request: Request, | ||
options?: ServeDirOptions, | ||
): Promise<Response> { | ||
const response = await serveDir(request, options); | ||
|
||
let url; | ||
export async function fourceInstantiateWasm() { | ||
try { | ||
url = new URL(request.url, "file:///"); | ||
} catch { | ||
return response; | ||
} | ||
// if range request, skip | ||
if (response.status === 200) { | ||
if (url.pathname.endsWith(".ts")) { | ||
return rewriteTsResponse(response, url); | ||
} else if (url.pathname.endsWith(".tsx")) { | ||
return rewriteTsResponse(response, url); | ||
} else if (url.pathname.endsWith(".jsx")) { | ||
return rewriteTsResponse(response, url); | ||
} | ||
} | ||
return response; | ||
} | ||
|
||
async function rewriteTsResponse( | ||
response: Response, | ||
url: URL, | ||
mediaType?: MediaType, | ||
) { | ||
const tsCode = await response.text(); | ||
const jsCode = await transpile(tsCode, url, mediaType); | ||
const { headers } = response; | ||
headers.set("content-type", jsContentType); | ||
headers.delete("content-length"); | ||
|
||
return new Response(jsCode, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers, | ||
}); | ||
await transpile("", new URL("file:///src")); | ||
} catch (_) { /* ignore error*/ } | ||
} | ||
|
||
/** | ||
* Oak middleware that rewrites TypeScript response to JavaScript response. | ||
* | ||
* ```ts | ||
* import { Application } from "https://deno.land/x/oak@v10.6.0/mod.ts"; | ||
* import { tsMiddleware } from "https://deno.land/x/ts_serve@$VERSION/mod.ts"; | ||
* | ||
* const app = new Application(); | ||
* | ||
* // use middleware and transpile TS code | ||
* app.use(tsMiddleware); | ||
* | ||
* // serve static file | ||
* app.use(async (ctx, next) => { | ||
* try { | ||
* await ctx.send({ root: "./" }); | ||
* } catch { | ||
* await next(); | ||
* } | ||
* }); | ||
* await app.listen({ port: 8000 }); | ||
* ``` | ||
*/ | ||
export async function tsMiddleware( | ||
ctx: Context, | ||
next: () => Promise<unknown>, | ||
) { | ||
await next(); | ||
const mediaType = tsType.has(ctx.response.type) | ||
? MediaType.TypeScript | ||
: tsxType.has(ctx.response.type) | ||
? MediaType.Tsx | ||
: jsxType.has(ctx.response.type) | ||
? MediaType.Jsx | ||
: undefined; | ||
|
||
if (mediaType == undefined) { | ||
return; | ||
} | ||
|
||
const specifier = ctx.request.url; | ||
|
||
if (ctx.response.body == null) { | ||
// skip | ||
} else if (typeof ctx.response.body === "string") { | ||
// major fast path | ||
const tsCode = ctx.response.body; | ||
const jsCode = await transpile(tsCode, specifier, mediaType); | ||
ctx.response.body = jsCode; | ||
} else if (ctx.response.body instanceof Uint8Array) { | ||
// major fast path | ||
const tsCode = decoder.decode(ctx.response.body); | ||
const jsCode = await transpile(tsCode, specifier, mediaType); | ||
ctx.response.body = jsCode; | ||
} else { | ||
// fallback | ||
const [responseInit] = await convertBodyToBodyInit(ctx.response.body); | ||
const tsCode = await new Response(responseInit).text(); | ||
const jsCode = await transpile(tsCode, specifier, mediaType); | ||
ctx.response.body = jsCode; | ||
} | ||
ctx.response.type = ".js"; | ||
} | ||
|
||
export { MediaType, type ServeDirOptions, type ServeFileOptions, transpile }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { assert } from "https://deno.land/std@0.152.0/testing/asserts.ts"; | ||
import { | ||
assertSpyCalls, | ||
stub, | ||
} from "https://deno.land/std@0.152.0/testing/mock.ts"; | ||
|
||
import { fourceInstantiateWasm, transpile } from "./mod.ts"; | ||
|
||
Deno.test({ | ||
name: "fourceInstantiateWasm", | ||
async fn() { | ||
await fourceInstantiateWasm(); | ||
const start = Date.now(); | ||
await transpile( | ||
"function foo(arg: string): string {return arg}", | ||
new URL("file:///src.ts"), | ||
); | ||
const time = Date.now() - start; | ||
assert(time < 100, `transpile() took ${time} ms`); | ||
}, | ||
}); | ||
|
||
Deno.test({ | ||
name: "fourceInstantiateWasm - failed to load wasm", | ||
async fn() { | ||
// Don't throw an error when transpile() throws | ||
const fetchStub = stub( | ||
URL.prototype, | ||
"toString", | ||
() => { | ||
throw new Error("load fail!!"); | ||
}, | ||
); | ||
try { | ||
await fourceInstantiateWasm(); | ||
assertSpyCalls(fetchStub, 1); | ||
} finally { | ||
fetchStub.restore(); | ||
} | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { | ||
serveDir, | ||
type ServeDirOptions, | ||
serveFile, | ||
type ServeFileOptions, | ||
} from "https://deno.land/std@0.151.0/http/file_server.ts"; | ||
import { contentType } from "https://deno.land/std@0.151.0/media_types/mod.ts"; | ||
import { MediaType, transpile } from "../utils/transpile.ts"; | ||
|
||
const jsContentType = contentType(".js"); | ||
|
||
/** | ||
* This can be used in the same way as the [serveFile](https://doc.deno.land/https://deno.land/std@0.151.0/http/file_server.ts/~/serveFile) function of the standard library, but if the file is TypeScript, it will be rewritten to JavaScript. | ||
* | ||
* ```ts | ||
* import { serve } from "https://deno.land/std@0.151.0/http/mod.ts"; | ||
* import { serveFileWithTs, fourceInstantiateWasm } from "https://deno.land/x/ts_serve@$VERSION/mod.ts"; | ||
* | ||
* fourceInstantiateWasm(); | ||
* serve((request) => serveFileWithTs(request, "./mod.ts")); | ||
* ``` | ||
*/ | ||
export async function serveFileWithTs( | ||
request: Request, | ||
filePath: string, | ||
options?: ServeFileOptions, | ||
): Promise<Response> { | ||
const response = await serveFile(request, filePath, options); | ||
|
||
let url; | ||
try { | ||
url = new URL(request.url, "file:///"); | ||
} catch { | ||
url = new URL("file:///src"); | ||
} | ||
// if range request, skip | ||
if (response.status === 200) { | ||
if (filePath.endsWith(".ts")) { | ||
return rewriteTsResponse(response, url, MediaType.TypeScript); | ||
} else if (filePath.endsWith(".tsx")) { | ||
return rewriteTsResponse(response, url, MediaType.Tsx); | ||
} else if (filePath.endsWith(".jsx")) { | ||
return rewriteTsResponse(response, url, MediaType.Jsx); | ||
} | ||
} | ||
return response; | ||
} | ||
|
||
/** | ||
* This can be used in the same way as the [serveDir](https://doc.deno.land/https://deno.land/std@0.151.0/http/file_server.ts/~/serveDir) function of the standard library, but if the file is TypeScript, it will be rewritten to JavaScript. | ||
* | ||
* ```ts | ||
* import { serve } from "https://deno.land/std@0.151.0/http/mod.ts"; | ||
* import { serveDirWithTs, fourceInstantiateWasm } from "https://deno.land/x/ts_serve@$VERSION/mod.ts"; | ||
* | ||
* fourceInstantiateWasm(); | ||
* serve((request) => serveDirWithTs(request)); | ||
* ``` | ||
*/ | ||
export async function serveDirWithTs( | ||
request: Request, | ||
options?: ServeDirOptions, | ||
): Promise<Response> { | ||
const response = await serveDir(request, options); | ||
|
||
let url; | ||
try { | ||
url = new URL(request.url, "file:///"); | ||
} catch { | ||
return response; | ||
} | ||
// if range request, skip | ||
if (response.status === 200) { | ||
if (url.pathname.endsWith(".ts")) { | ||
return rewriteTsResponse(response, url); | ||
} else if (url.pathname.endsWith(".tsx")) { | ||
return rewriteTsResponse(response, url); | ||
} else if (url.pathname.endsWith(".jsx")) { | ||
return rewriteTsResponse(response, url); | ||
} | ||
} | ||
return response; | ||
} | ||
|
||
async function rewriteTsResponse( | ||
response: Response, | ||
url: URL, | ||
mediaType?: MediaType, | ||
) { | ||
const tsCode = await response.text(); | ||
const jsCode = await transpile(tsCode, url, mediaType); | ||
const { headers } = response; | ||
headers.set("content-type", jsContentType); | ||
headers.delete("content-length"); | ||
|
||
return new Response(jsCode, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers, | ||
}); | ||
} | ||
|
||
export { type ServeDirOptions, type ServeFileOptions }; |
Oops, something went wrong.