Skip to content

Commit 118242e

Browse files
major: build islands + scan routeDir without importing user code (#3122)
This fixes a bunch of long standing issues in our tracker where a build wouldn't finish because of importing user code. It's common for backend apps to spawn some open ended tasks like subscribing to Deno KV or other things. This addresses another problem of having to set up ENV vars just for the build, even though they are technically not required to build islands. This also gives a roughly ~30% performance boost on cold starts (at least on `deno.com`). Unfortunately, this does require some breaking changes. But I believe the performance improvements + removing the footgun of loading user code during build is well worth it. This work is also a precursor in case we want to exploring moving to vite at some point in the future. ## Breaking changes 1. Build options have moved to the builder. ```diff // main.ts - const app = new App({ build: { outDir: "dist/" } }) + const app = new App() // dev.ts - const builder = new Builder(); + const builder = new Builder({ outDir: "dist/" }); ``` 2. Another breaking change is how file based routes are loaded. Since they're prepared and loaded by the builder the API changed. ```diff // main.ts const app = new App() .use(staticFiles()) - await fsRoutes(app, { - loadIsland: (path) => import(`./islands/${path}`), - loadRoute: (path) => import(`./routes/${path}`), - }); + app.fsRoutes() ``` You can optionally change the `islandDir` or `routeDir` by passing that to the `Builder`: ```ts // dev.ts const builder = new Builder({ islandDir: "/path/to/islands", routeDir: "/path/to/routes" }); ``` 3. Import `app` lazily in `dev.ts`. Passing `app` to `builder.build()` is not necessary anymore. ```diff - import { app } from "./main.ts"; const builder = new Builder({ target: "safari12" }); if (Deno.args.includes("build")) { - await builder.build(app); + await builder.build(); } else { - await builder.listen(app); + await builder.listen(() => import("./main.ts")); } ``` 4. The production run command has changed. It's now using `deno serve` ```diff // deno.json { "tasks": { "dev": "deno run -A --watch=static/,routes/ dev.ts", "dev": "deno run -A dev.ts build", - "start": "deno run -A main.ts", + "start": "deno serve -A _fresh/server.js", } } ``` Remove this bit in `main.ts`: ```diff // main.ts - if (import.meta.main) { - await app.listen() - } ``` Fixes #2240 Fixes #2640 Fixes #2592 Fixes #1843
1 parent 69af00e commit 118242e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1910
-1769
lines changed

.github/workflows/deploy.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ jobs:
2222
uses: denoland/setup-deno@v2
2323
with:
2424
cache: true
25-
deno-version: rc
2625

2726
- name: Build step
2827
working-directory: ./www
@@ -32,5 +31,5 @@ jobs:
3231
uses: denoland/deployctl@v1
3332
with:
3433
project: "fresh"
35-
entrypoint: "./www/main.ts"
34+
entrypoint: "./www/_fresh/server.js"
3635
root: "."

deno.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
".": "./src/mod.ts",
1515
"./runtime": "./src/runtime/shared.ts",
1616
"./dev": "./src/dev/mod.ts",
17-
"./compat": "./src/compat.ts"
17+
"./compat": "./src/compat.ts",
18+
"./do-not-use": "./src/internals.ts"
1819
},
1920
"tasks": {
2021
"test": "deno test -A --parallel",

docs/canary/deployment/production.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ To run Fresh in production mode, run the `start` task:
2929
```sh Terminal
3030
deno task start
3131
# or
32-
deno run -A main.ts
32+
deno serve -A _fresh/server.js
3333
```
3434
3535
Fresh will automatically pick up the optimized assets in the `_fresh` directory.

docs/canary/examples/migration-guide.md

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,20 @@ The full `dev.ts` file for newly generated Fresh 2 projects looks like this:
5353
```ts
5454
import { Builder } from "fresh/dev";
5555
import { tailwind } from "@fresh/plugin-tailwind";
56-
import { app } from "./main.ts";
5756

5857
// Pass development only configuration here
5958
const builder = new Builder({ target: "safari12" });
6059

6160
// Example: Enabling the tailwind plugin for Fresh
62-
tailwind(builder, app);
61+
tailwind(builder);
6362

6463
// Create optimized assets for the browser when
6564
// running `deno run -A dev.ts build`
6665
if (Deno.args.includes("build")) {
67-
await builder.build(app);
66+
await builder.build();
6867
} else {
6968
// ...otherwise start the development server
70-
await builder.listen(app);
69+
await builder.listen(() => import("./main.ts"));
7170
}
7271
```
7372

@@ -82,18 +81,9 @@ import { App, fsRoutes, staticFiles } from "fresh";
8281

8382
export const app = new App()
8483
// Add static file serving middleware
85-
.use(staticFiles());
86-
87-
// Enable file-system based routing
88-
await fsRoutes(app, {
89-
loadIsland: (path) => import(`./islands/${path}`),
90-
loadRoute: (path) => import(`./routes/${path}`),
91-
});
92-
93-
// If this module is called directly, start the server
94-
if (import.meta.main) {
95-
await app.listen();
96-
}
84+
.use(staticFiles())
85+
// Enable file-system based routing
86+
.fsRoutes();
9787
```
9888

9989
## Merging error pages
@@ -226,9 +216,9 @@ Same is true for handlers:
226216

227217
All the various context interfaces have been consolidated and simplified:
228218

229-
| Fresh 1.x | Fresh 2.x |
230-
| --------------------------------------------- | -------------- |
231-
| `AppContext`, `LayoutContext`, `RouteContext` | `FreshContext` |
219+
| Fresh 1.x | Fresh 2.x |
220+
| --------------------------------------------- | ------------------------------------------ |
221+
| `AppContext`, `LayoutContext`, `RouteContext` | [`Context`](/docs/canary/concepts/context) |
232222

233223
### Context methods
234224

examples/README.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,6 @@ import { DemoIsland } from "jsr:@fresh/examples/island";
1515
export const app = new App({ root: import.meta.url })
1616
.use(staticFiles());
1717

18-
// Register the island
19-
app.island(
20-
// Module specifier for esbuild, could also be a file path
21-
"jsr:@fresh/examples/island",
22-
// Name of the island
23-
"DemoIsland",
24-
// Island component function
25-
DemoIsland,
26-
);
27-
2818
// Use the island somewhere in your components
2919
app.get("/", (ctx) => ctx.render(<DemoIsland />));
3020

init/src/init.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,11 @@ ENV DENO_DEPLOYMENT_ID=\${GIT_REVISION}
158158
WORKDIR /app
159159
160160
COPY . .
161-
RUN deno cache main.ts
161+
RUN deno cache _fresh/server.js
162162
163163
EXPOSE 8000
164164
165-
CMD ["run", "-A", "main.ts"]
165+
CMD ["serve", "-A", "_fresh/server.js"]
166166
167167
`;
168168
await writeFile("Dockerfile", DOCKERFILE_TEXT);
@@ -349,7 +349,7 @@ ${GRADIENT_CSS}`;
349349
// Skip this and be silent if there is a network issue.
350350
}
351351

352-
const MAIN_TS = `import { App, fsRoutes, staticFiles } from "fresh";
352+
const MAIN_TS = `import { App, staticFiles } from "fresh";
353353
import { define, type State } from "./utils.ts";
354354
355355
export const app = new App<State>();
@@ -371,14 +371,8 @@ const exampleLoggerMiddleware = define.middleware((ctx) => {
371371
});
372372
app.use(exampleLoggerMiddleware);
373373
374-
await fsRoutes(app, {
375-
loadIsland: (path) => import(\`./islands/\${path}\`),
376-
loadRoute: (path) => import(\`./routes/\${path}\`),
377-
});
378-
379-
if (import.meta.main) {
380-
await app.listen();
381-
}`;
374+
// Include file-system based routes here
375+
app.fsRoutes();`;
382376
await writeFile("main.ts", MAIN_TS);
383377

384378
const COMPONENTS_BUTTON_TSX =
@@ -489,14 +483,13 @@ export default function Counter(props: CounterProps) {
489483
const DEV_TS = `#!/usr/bin/env -S deno run -A --watch=static/,routes/
490484
${useTailwind ? `import { tailwind } from "@fresh/plugin-tailwind";\n` : ""}
491485
import { Builder } from "fresh/dev";
492-
import { app } from "./main.ts";
493486
494487
const builder = new Builder();
495-
${useTailwind ? "tailwind(builder, app);" : ""}
488+
${useTailwind ? "tailwind(builder);" : ""}
496489
if (Deno.args.includes("build")) {
497-
await builder.build(app);
490+
await builder.build();
498491
} else {
499-
await builder.listen(app);
492+
await builder.listen(() => import("./main.ts"));
500493
}`;
501494
await writeFile("dev.ts", DEV_TS);
502495

@@ -506,7 +499,7 @@ if (Deno.args.includes("build")) {
506499
check: "deno fmt --check . && deno lint . && deno check",
507500
dev: "deno run -A --watch=static/,routes/ dev.ts",
508501
build: "deno run -A dev.ts build",
509-
start: "deno run -A main.ts",
502+
start: "deno serve -A _fresh/server.js",
510503
update: "deno run -A -r jsr:@fresh/update .",
511504
},
512505
lint: {

init/src/init_test.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -139,28 +139,31 @@ Deno.test({
139139
},
140140
});
141141

142-
Deno.test("init with tailwind - fmt, lint, and type check project", async () => {
143-
await using tmp = await withTmpDir();
144-
const dir = tmp.dir;
145-
using _promptStub = stubPrompt(".");
146-
using _confirmStub = stubConfirm({
147-
[CONFIRM_TAILWIND_MESSAGE]: true,
148-
});
142+
Deno.test(
143+
"init with tailwind - fmt, lint, and type check project",
144+
async () => {
145+
await using tmp = await withTmpDir();
146+
const dir = tmp.dir;
147+
using _promptStub = stubPrompt(".");
148+
using _confirmStub = stubConfirm({
149+
[CONFIRM_TAILWIND_MESSAGE]: true,
150+
});
149151

150-
await initProject(dir, [], {});
151-
await expectProjectFile(dir, "main.ts");
152-
await expectProjectFile(dir, "dev.ts");
152+
await initProject(dir, [], {});
153+
await expectProjectFile(dir, "main.ts");
154+
await expectProjectFile(dir, "dev.ts");
153155

154-
await patchProject(dir);
156+
await patchProject(dir);
155157

156-
const check = await new Deno.Command(Deno.execPath(), {
157-
args: ["task", "check"],
158-
cwd: dir,
159-
stderr: "inherit",
160-
stdout: "inherit",
161-
}).output();
162-
expect(check.code).toEqual(0);
163-
});
158+
const check = await new Deno.Command(Deno.execPath(), {
159+
args: ["task", "check"],
160+
cwd: dir,
161+
stderr: "inherit",
162+
stdout: "inherit",
163+
}).output();
164+
expect(check.code).toEqual(0);
165+
},
166+
);
164167

165168
Deno.test("init - can start dev server", async () => {
166169
await using tmp = await withTmpDir();
@@ -240,5 +243,5 @@ Deno.test("init - errors on missing build cache in prod", async () => {
240243
const { stderr } = getStdOutput(cp);
241244
expect(cp.code).toEqual(1);
242245

243-
expect(stderr).toMatch(/Found 1 islands, but did not/);
246+
expect(stderr).toMatch(/Module not found/);
244247
});

plugin-tailwindcss/src/mod.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import type { Builder } from "fresh/dev";
2-
import type { App } from "fresh";
32
import twPostcss from "@tailwindcss/postcss";
43
import postcss from "postcss";
54
import type { TailwindPluginOptions } from "./types.ts";
65

76
// Re-export types for public API
87
export type { TailwindPluginOptions } from "./types.ts";
98

10-
export function tailwind<T>(
9+
export function tailwind(
1110
builder: Builder,
12-
app: App<T>,
1311
options: TailwindPluginOptions = {},
1412
): void {
1513
const { exclude, ...tailwindOptions } = options;
1614
const instance = postcss(twPostcss({
17-
optimize: app.config.mode === "production",
15+
optimize: builder.config.mode === "production",
1816
...tailwindOptions,
1917
}));
2018

0 commit comments

Comments
 (0)