Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support more import specifiers in dev #259

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/basic/client.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import hydrate from "ultra/hydrate.js";
import App from "./src/app.tsx";
import App from "@/app.tsx";

hydrate(document, <App />);
2 changes: 1 addition & 1 deletion examples/basic/deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"tasks": {
"dev": "deno run -A --no-check --watch ./server.tsx",
"dev": "ULTRA_MODE=development deno run -A --no-check --watch ./server.tsx",
"test": "deno test --allow-all",
"build": "deno run -A ./build.ts",
"start": "ULTRA_MODE=production deno run -A --no-remote ./server.js"
Expand Down
3 changes: 2 additions & 1 deletion examples/basic/importMap.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"react-dom/server": "https://esm.sh/v122/react-dom@18.2.0/server?dev",
"react-dom/client": "https://esm.sh/v122/react-dom@18.2.0/client?dev",
"ultra/": "https://deno.land/x/ultra@v2.2.4/",
"@ultra/qrcode/": "https://deno.land/x/qrcode@v2.0.0/"
"@ultra/qrcode/": "https://deno.land/x/qrcode@v2.0.0/",
"@/": "./src/"
}
}
6 changes: 2 additions & 4 deletions examples/basic/server.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { serve } from "https://deno.land/std@0.176.0/http/server.ts";
import { createServer } from "ultra/server.ts";
import App from "./src/app.tsx";
import App from "@/app.tsx";

const server = await createServer({
importMapPath: Deno.env.get("ULTRA_MODE") === "development"
? import.meta.resolve("./importMap.dev.json")
: import.meta.resolve("./importMap.json"),
importMapPath: import.meta.resolve("./importMap.json"),
browserEntrypoint: import.meta.resolve("./client.tsx"),
});

Expand Down
2 changes: 2 additions & 0 deletions examples/basic/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import useAsset from "ultra/hooks/use-asset.js";
import useEnv from "ultra/hooks/use-env.js";
import Button from "@/components/Button.tsx";

export default function App() {
// Read our environment variable from '.env' or the host environment
Expand Down Expand Up @@ -37,6 +38,7 @@ export default function App() {
customise your routing, data fetching, and styling with popular
libraries.
</p>
<Button>Button</Button>
</main>
</body>
</html>
Expand Down
10 changes: 10 additions & 0 deletions examples/basic/src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react";

type ButtonProps = React.DetailedHTMLProps<
React.HTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>;

export default function Button(props: ButtonProps) {
return <button className="btn" {...props} />;
}
5 changes: 3 additions & 2 deletions lib/middleware/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ULTRA_COMPILER_PATH } from "../constants.ts";
import { extname, join, sprintf, toFileUrl } from "../deps.ts";
import { log } from "../logger.ts";
import type { CompilerOptions, Context, Next } from "../types.ts";
import { isCompilerTarget } from "../utils/compiler.ts";

const { transform } = await createCompiler();

Expand All @@ -26,9 +27,9 @@ export const compiler = (options: CompilerOptions) => {
const path = !isRemoteSource ? join(root, pathname) : pathname;
const url = !isRemoteSource ? toFileUrl(path) : new URL(pathname);

const isCompilerTarget = [".ts", ".tsx", ".js", ".jsx"].includes(extension);
const shouldCompile = isCompilerTarget(pathname);

if (method === "GET" && isCompilerTarget) {
if (method === "GET" && shouldCompile) {
const bytes = await fetch(url).then((response) => response.arrayBuffer());
let source = new TextDecoder().decode(bytes);

Expand Down
14 changes: 11 additions & 3 deletions lib/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
*/
import type { ReactElement } from "react";
import * as ReactDOMServer from "react-dom/server";
import { readableStreamFromReader, StringReader } from "./deps.ts";
import { log } from "./logger.ts";
import { ImportMap, Mode, RenderedReadableStream } from "./types.ts";
import { isCompilerTarget } from "./utils/compiler.ts";
import { nonNullable } from "./utils/non-nullable.ts";
import { log } from "./logger.ts";
import { readableStreamFromReader, StringReader } from "./deps.ts";

export function encodeText(input: string) {
return new TextEncoder().encode(input);
Expand Down Expand Up @@ -160,13 +161,20 @@ export function createImportMapInjectionStream(
log.debug("Stream inject importMap");
let injected = false;

function isSpecialPrefix(specifier: string) {
return specifier.startsWith("@ultra/") || specifier.startsWith("@/");
}

return createHeadInsertionTransformStream(() => {
if (injected) return Promise.resolve("");

if (mode === "development") {
importMap.imports = Object.fromEntries(
Object.entries(importMap.imports).map(([key, value]) => {
if (key.startsWith("@ultra/")) {
if (
value.startsWith("/_ultra/compiler/") === false &&
(isSpecialPrefix(key) || isCompilerTarget(value))
) {
value = value.endsWith("/") ? value.slice(0, -1) : value;
value = `/_ultra/compiler/${encodeURIComponent(value)}/`;
}
Expand Down
6 changes: 6 additions & 0 deletions lib/utils/compiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { extname } from "../deps.ts";

export function isCompilerTarget(path: string) {
const extension = extname(path);
return [".ts", ".tsx", ".js", ".jsx"].includes(extension);
}
8 changes: 4 additions & 4 deletions test/fixture/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Deno.test(

assertEquals(ignoredOutput.size, 0);
assertEquals(result.outputSources.size > 0, true);
assertEquals(result.dynamicImports.size, 2);
assertEquals(result.dynamicImports.size, 1);

// Test that the built output starts correctly
const command = new Deno.Command(Deno.execPath(), {
Expand Down Expand Up @@ -62,7 +62,7 @@ Deno.test(
const result = await builder.build();

assertEquals(result.outputSources.size > 0, true);
assertEquals(result.dynamicImports.size, 2);
assertEquals(result.dynamicImports.size, 1);

// Test that the built output starts correctly
const command = new Deno.Command(Deno.execPath(), {
Expand Down Expand Up @@ -99,7 +99,7 @@ Deno.test(
const result = await builder.build();

assertEquals(result.outputSources.size > 0, true);
assertEquals(result.dynamicImports.size, 2);
assertEquals(result.dynamicImports.size, 1);

// Test that the built output starts correctly
const command = new Deno.Command(Deno.execPath(), {
Expand Down Expand Up @@ -136,7 +136,7 @@ Deno.test(
const result = await builder.build();

assertEquals(result.outputSources.size > 0, true);
assertEquals(result.dynamicImports.size, 2);
assertEquals(result.dynamicImports.size, 1);

// Test that the built output starts correctly
const command = new Deno.Command(Deno.execPath(), {
Expand Down
17 changes: 9 additions & 8 deletions test/fixture/client.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { cssomSheet } from "twind";
import { setup } from "@twind/core";
import hydrate from "ultra/hydrate.js";
import App from "./src/app.tsx";
import { TWProvider } from "./src/context/twind.tsx";
import { TRPCClientProvider } from "./src/trpc/client.tsx";
import { theme } from "./theme.ts";
import App from "@/app.tsx";
import { TRPCClientProvider } from "@/trpc/client.tsx";
import { sheet } from "@/twind.ts";
import config from "@/twind.config.js";

//@ts-ignore twind types issue
setup(config, sheet);

hydrate(
document,
<TRPCClientProvider>
<TWProvider sheet={cssomSheet()} theme={theme}>
<App />
</TWProvider>
<App />
</TRPCClientProvider>,
);
4 changes: 1 addition & 3 deletions test/fixture/deno.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"lock": false,
"test": {
"files": {
"exclude": [".ultra/"]
}
"exclude": [".ultra/"]
},
"tasks": {
"dev": "deno run -A --no-check --watch ./server.tsx",
Expand Down
30 changes: 16 additions & 14 deletions test/fixture/importMap.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
{
"imports": {
"react": "https://esm.sh/react@18.2.0?dev",
"react/": "https://esm.sh/react@18.2.0&dev/",
"react-dom": "https://esm.sh/react-dom@18.2.0?dev",
"react-dom/": "https://esm.sh/react-dom@18.2.0&dev/",
"@tanstack/react-query": "https://esm.sh/@tanstack/react-query@4.13.0?external=react",
"@tanstack/react-query-devtools": "https://esm.sh/@tanstack/react-query-devtools@4.13.0?external=react,@tanstack/react-query&dev",
"@trpc/server": "https://esm.sh/@trpc/server@10.0.0-rc.1",
"@trpc/server/": "https://esm.sh/@trpc/server@10.0.0-rc.1/",
"@trpc/client": "https://esm.sh/*@trpc/client@10.0.0-rc.1",
"@trpc/client/": "https://esm.sh/*@trpc/client@10.0.0-rc.1/",
"@trpc/react-query": "https://esm.sh/*@trpc/react-query@10.0.0-rc.1",
"react": "https://esm.sh/v125/react@18.2.0?dev",
"react/": "https://esm.sh/v125/react@18.2.0&dev/",
"react-dom": "https://esm.sh/v125/react-dom@18.2.0?dev",
"react-dom/": "https://esm.sh/v125/react-dom@18.2.0&dev/",
"@tanstack/react-query": "https://esm.sh/v125/@tanstack/react-query@4.13.0?external=react",
"@tanstack/react-query-devtools": "https://esm.sh/v125/@tanstack/react-query-devtools@4.13.0?external=react,@tanstack/react-query&dev",
"@trpc/server": "https://esm.sh/v125/@trpc/server@10.0.0-rc.1",
"@trpc/server/": "https://esm.sh/v125/@trpc/server@10.0.0-rc.1/",
"@trpc/client": "https://esm.sh/v125/*@trpc/client@10.0.0-rc.1",
"@trpc/client/": "https://esm.sh/v125/*@trpc/client@10.0.0-rc.1/",
"@trpc/react-query": "https://esm.sh/v125/*@trpc/react-query@10.0.0-rc.1",
"zod": "https://deno.land/x/zod@v3.19.1/mod.ts",
"twind": "https://esm.sh/twind@0.16.17",
"twind/sheets": "https://esm.sh/twind@0.16.17/sheets",
"@twind/core": "https://esm.sh/v125/@twind/core@1.0.1",
"@twind/preset-autoprefix": "https://esm.sh/v125/@twind/preset-autoprefix@1.0.1",
"@twind/preset-tailwind": "https://esm.sh/v125/*@twind/preset-tailwind@1.0.1",
"ultra/": "https://denopkg.com/exhibitionist-digital/ultra@main/",
"@ultra/qrcode/": "https://deno.land/x/qrcode@v2.0.0/"
"@ultra/qrcode/": "https://deno.land/x/qrcode@v2.0.0/",
"@/": "./src/"
}
}
26 changes: 18 additions & 8 deletions test/fixture/server.no-browser.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { serve } from "https://deno.land/std@0.176.0/http/server.ts";
import { createServer } from "ultra/server.ts";
import { createHeadInsertionTransformStream } from "ultra/stream.ts";
import App from "./src/app.tsx";
import { queryClient } from "./src/query-client.tsx";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter } from "./src/server/router.ts";
import { TRPCServerProvider } from "./src/trpc/server.tsx";
import { serverSheet, TWProvider } from "./src/context/twind.tsx";
import { theme } from "./theme.ts";
import { stringify, tw } from "./src/twind.ts";

const server = await createServer();

Expand All @@ -20,7 +20,6 @@ server.all("/api/trpc/:path", (context) => {
});

server.get("*", async (context) => {
const sheet = serverSheet();
// clear query cache
queryClient.clear();

Expand All @@ -29,20 +28,31 @@ server.get("*", async (context) => {
*/
const result = await server.render(
<TRPCServerProvider>
<TWProvider sheet={sheet} theme={theme}>
<App />
</TWProvider>
<App />
</TRPCServerProvider>,
);

return context.body(result, 200, {
// Inject the style tag into the head of the streamed response
const stylesInject = createHeadInsertionTransformStream(() => {
if (Array.isArray(tw.target)) {
return Promise.resolve(stringify(tw.target));
}

throw new Error("Expected tw.target to be an instance of an Array");
});

const transformed = result.pipeThrough(stylesInject);

return context.body(transformed, 200, {
"content-type": "text/html; charset=utf-8",
});
});

if (import.meta.main) {
serve(server.fetch, {
onListen() {
// We exit onListen so we know the server started successfully
// in our tests
Deno.exit(0);
},
});
Expand Down
32 changes: 20 additions & 12 deletions test/fixture/server.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import App from "@/app.tsx";
import { queryClient } from "@/query-client.tsx";
import { appRouter } from "@/server/router.ts";
import { TRPCServerProvider } from "@/trpc/server.tsx";
import { stringify, tw } from "@/twind.ts";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { serve } from "https://deno.land/std@0.176.0/http/server.ts";
import { createServer } from "ultra/server.ts";
import App from "./src/app.tsx";
import { queryClient } from "./src/query-client.tsx";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter } from "./src/server/router.ts";
import { TRPCServerProvider } from "./src/trpc/server.tsx";
import { serverSheet, TWProvider } from "./src/context/twind.tsx";
import { theme } from "./theme.ts";
import { createHeadInsertionTransformStream } from "ultra/stream.ts";

const server = await createServer({
importMapPath: import.meta.resolve("./importMap.json"),
Expand All @@ -23,7 +23,6 @@ server.all("/api/trpc/:path", (context) => {
});

server.get("*", async (context) => {
const sheet = serverSheet();
// clear query cache
queryClient.clear();

Expand All @@ -32,13 +31,22 @@ server.get("*", async (context) => {
*/
const result = await server.render(
<TRPCServerProvider>
<TWProvider sheet={sheet} theme={theme}>
<App />
</TWProvider>
<App />
</TRPCServerProvider>,
);

return context.body(result, 200, {
// Inject the style tag into the head of the streamed response
const stylesInject = createHeadInsertionTransformStream(() => {
if (Array.isArray(tw.target)) {
return Promise.resolve(stringify(tw.target));
}

throw new Error("Expected tw.target to be an instance of an Array");
});

const transformed = result.pipeThrough(stylesInject);

return context.body(transformed, 200, {
"content-type": "text/html; charset=utf-8",
});
});
Expand Down
4 changes: 2 additions & 2 deletions test/fixture/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { lazy, Suspense } from "react";
import useAsset from "ultra/hooks/use-asset.js";
import Post from "./components/Post.tsx";
import Post from "@/components/Post.tsx";

const LazyPost = lazy(() => import("./components/Post.tsx"));
const LazyPost = lazy(() => import("@/components/Post.tsx"));

export default function App() {
return (
Expand Down
3 changes: 1 addition & 2 deletions test/fixture/src/components/Post.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { trpc } from "../trpc/trpc.ts";
import { useTw } from "../hooks/useTw.ts";
import { tw } from "../twind.ts";

export default function Post({ id }: { id: number }) {
const { data } = trpc.post.get.useQuery({ id });
const tw = useTw();
return (
<div>
<div className={tw("p-3")}>{data?.title}</div>
Expand Down