Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions packages/fresh/src/commands.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { setAdditionalStyles } from "./context.ts";
import { HttpError } from "./error.ts";
import { isHandlerByMethod, type PageResponse } from "./handlers.ts";
import {
Expand Down Expand Up @@ -74,31 +73,36 @@ export function newErrorCmd<State>(
export interface AppCommand<State> {
type: CommandType.App;
component: RouteComponent<State>;
css?: string[];
}
export function newAppCmd<State>(
component: RouteComponent<State>,
css?: string[],
): AppCommand<State> {
return { type: CommandType.App, component };
return { type: CommandType.App, component, css };
}

export interface LayoutCommand<State> {
type: CommandType.Layout;
pattern: string;
component: RouteComponent<State>;
config?: LayoutConfig;
css?: string[];
includeLastSegment: boolean;
}
export function newLayoutCmd<State>(
pattern: string,
component: RouteComponent<State>,
config: LayoutConfig | undefined,
includeLastSegment: boolean,
css?: string[],
): LayoutCommand<State> {
return {
type: CommandType.Layout,
pattern,
component,
config,
css,
includeLastSegment,
};
}
Expand Down Expand Up @@ -253,7 +257,7 @@ function applyCommandsInner<State>(
break;
}
case CommandType.App: {
root.app = cmd.component;
root.app = { component: cmd.component, css: cmd.css ?? null };
break;
}
case CommandType.Layout: {
Expand All @@ -265,6 +269,7 @@ function applyCommandsInner<State>(
segment.layout = {
component: cmd.component,
config: cmd.config ?? null,
css: cmd.css ?? null,
};
break;
}
Expand All @@ -290,10 +295,6 @@ function applyCommandsInner<State>(
def = await route();
}

if (def.css !== undefined) {
setAdditionalStyles(ctx, def.css);
}

return renderRoute(ctx, def);
});

Expand Down
44 changes: 36 additions & 8 deletions packages/fresh/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,11 @@ export type ServerIslandRegistry = Map<ComponentType, Island>;
export const internals: unique symbol = Symbol("fresh_internal");

export interface UiTree<Data, State> {
app: AnyComponent<PageProps<Data, State>> | null;
layouts: ComponentDef<Data, State>[];
app: {
component: AnyComponent<PageProps<Data, State>>;
css: string[] | null;
} | null;
layouts: (ComponentDef<Data, State> & { css: string[] | null })[];
}

/**
Expand All @@ -109,7 +112,10 @@ export type FreshContext<State = unknown> = Context<State>;

export let getBuildCache: <T>(ctx: Context<T>) => BuildCache<T>;
export let getInternals: <T>(ctx: Context<T>) => UiTree<unknown, T>;
export let setAdditionalStyles: <T>(ctx: Context<T>, css: string[]) => void;
export let setAdditionalStyles: <T>(
ctx: Context<T>,
css: string[] | null | undefined,
) => void;

/**
* The context passed to every middleware. It is unique for every request.
Expand Down Expand Up @@ -182,8 +188,25 @@ export class Context<State> {
// deno-lint-ignore no-explicit-any
getInternals = <T>(ctx: Context<T>) => ctx.#internal as any;
getBuildCache = <T>(ctx: Context<T>) => ctx.#buildCache;
setAdditionalStyles = <T>(ctx: Context<T>, css: string[]) =>
ctx.#additionalStyles = css;
setAdditionalStyles = <T>(
ctx: Context<T>,
css: string[] | null | undefined,
) => {
if (css == null) return;

if (ctx.#additionalStyles === null) {
ctx.#additionalStyles = css.slice();
return;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: includes() is O(n) per insertion. For the small CSS lists in practice this is fine, but a Set would be more idiomatic if you ever want to tighten this up. Low priority.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I added a FIXME comment below. I think it could be use Set instead of css: Array<string>.It seems that at least all the tests pass in the following branch.

Hajime-san/fresh@fix-ssr-css-modules...Hajime-san:fresh:perf-css

I'm planning it as a follow up PR.

}

for (let i = 0; i < css.length; i++) {
const href = css[i];
// FIXME: consider to use `Set` instead of `css: string[]` for entire codebase
if (!ctx.#additionalStyles.includes(href)) {
ctx.#additionalStyles.push(href);
}
}
};
}

constructor(
Expand Down Expand Up @@ -283,6 +306,7 @@ export class Context<State> {
props.Component = () => child;

const def = defs[i];
setAdditionalStyles(this, def.css);

const result = await renderRouteComponent(this, def, () => child);
if (result instanceof Response) {
Expand All @@ -298,16 +322,20 @@ export class Context<State> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: appDef?.css evaluates to undefined when appDef is null, which the new setAdditionalStyles handles via the css == null guard. Works correctly, but it's a subtle null-propagation chain. An explicit guard might be clearer:

if (appDef !== null) {
  setAdditionalStyles(this, appDef.css);
}

Especially since the appDef !== null checks already exist a few lines below.

let hasApp = true;

if (isAsyncAnyComponent(appDef)) {
if (appDef !== null) {
setAdditionalStyles(this, appDef.css);
}

if (appDef !== null && isAsyncAnyComponent(appDef.component)) {
props.Component = () => appChild;
const result = await renderAsyncAnyComponent(appDef, props);
const result = await renderAsyncAnyComponent(appDef.component, props);
if (result instanceof Response) {
return result;
}

appVNode = result;
} else if (appDef !== null) {
appVNode = h(appDef, {
appVNode = h(appDef.component, {
Component: () => appChild,
config: this.config,
data: null,
Expand Down
2 changes: 1 addition & 1 deletion packages/fresh/src/dev/dev_build_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const WINDOWS_SEPARATOR = pathWin32.SEPARATOR;
/** Normalize a path to use forward slashes so that generated files
* are portable across operating systems (e.g. build on Windows,
* deploy on Linux). */
function toPosix(p: string): string {
export function toPosix(p: string): string {
return p.replaceAll(WINDOWS_SEPARATOR, "/");
}

Expand Down
4 changes: 2 additions & 2 deletions packages/fresh/src/dev/fs_crawl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type FsAdapter, fsAdapter } from "../fs.ts";
import type { WalkEntry } from "@std/fs/walk";
import type { FsRouteFileNoMod } from "./dev_build_cache.ts";
import { type FsRouteFileNoMod, toPosix } from "./dev_build_cache.ts";
import * as path from "@std/path";
import { pathToPattern } from "../router.ts";
import { CommandType } from "../commands.ts";
Expand Down Expand Up @@ -86,7 +86,7 @@ export async function crawlRouteDir<State>(

files.push({
id,
filePath: entry.path,
filePath: toPosix(entry.path),
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suddenly, several tests failed on Windows. It's likely that a potential bug was occured by this PR.

vite dev - css modules => ./packages/fresh/src/test_utils.ts:198:8
vite dev - css modules in _app/_layout/_error non-island component are injected => ./packages/fresh/src/test_utils.ts:198:8
vite dev - route css import => ./packages/fresh/src/test_utils.ts:198:8
debug console on Windows
------- post-test output -------
👺 > fresh:route-css > name _index
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  </head>
  <body>
    <div class="green">
      <h1 class="_root_1mdiz_1">
        green text
      </h1>
    </div>
    <div class="red">
      <h1 class="_root_xlv1v_1">
        red text
      </h1>
    </div>
    <h1>
      ok
    </h1>
  </body>
</html>

[
  "Port 5173 is in use, trying another one...",
  "",
  "  VITE v7.3.1  ready in 1276 ms",
  "",
  "  ➜  Local:   http://127.0.0.1:5174/"
]
[
  "Port 5173 is in use, trying another one...",
  "",
  "  VITE v7.3.1  ready in 1276 ms",
  "",
  "  ➜  Local:   http://127.0.0.1:5174/",
  "\x1b[2m  \x1b[32m➜\x1b[39m  \x1b[1mNetwork\x1b[22m\x1b[2m: use \x1b[22m\x1b[1m--host\x1b[22m\x1b[2m to expose\x1b[22m",
  "👺 > fresh:route-css > route {",
  '  id: "/_app",',
  '  filePath: "D:\\\\a\\\\fresh\\\\fresh\\\\packages\\\\plugin-vite\\\\tests\\\\fixtures\\\\non_island_css_modules\\\\routes\\\\_app.tsx",',
  '  type: "app",',
  '  pattern: "*",',
  '  routePattern: "*",',
  "  lazy: false,",
  "  css: [",
  '    "/@fs/D:/a/fresh/fresh/packages/plugin-vite/demo/components/CssModulesNonIsland.module.css"',
  "  ],",
  "  overrideConfig: undefined",
  "}",
  "👺 > fresh:route-css > name __app",
  "👺 > fresh:route-css > route {",
  '  id: "/_layout",',
  '  filePath: "D:\\\\a\\\\fresh\\\\fresh\\\\packages\\\\plugin-vite\\\\tests\\\\fixtures\\\\non_island_css_modules\\\\routes\\\\_layout.tsx",',
  '  type: "layout",',
  '  pattern: "/",',
  '  routePattern: "/",',
  "  lazy: false,",
  "  css: [",
  '    "/@fs/D:/a/fresh/fresh/packages/plugin-vite/demo/components/CssModulesNonIsland3.module.css"',
  "  ],",
  "  overrideConfig: undefined",
  "}",
  "👺 > fresh:route-css > name __layout",
  "👺 > fresh:route-css > route {",
  '  id: "/_error",',
  '  filePath: "D:\\\\a\\\\fresh\\\\fresh\\\\packages\\\\plugin-vite\\\\tests\\\\fixtures\\\\non_island_css_modules\\\\routes\\\\_error.tsx",',
  '  type: "error",',
  '  pattern: "/",',
  '  routePattern: "/",',
  "  lazy: false,",
  "  css: [",
  '    "/@fs/D:/a/fresh/fresh/packages/plugin-vite/demo/components/CssModulesNonIsland2.module.css"',
  "  ],",
  "  overrideConfig: undefined",
  "}",
  "👺 > fresh:route-css > name __error",
  "👺 > fresh:route-css > route {",
  '  id: "/index",',
  '  filePath: "D:\\\\a\\\\fresh\\\\fresh\\\\packages\\\\plugin-vite\\\\tests\\\\fixtures\\\\non_island_css_modules\\\\routes\\\\index.tsx",',
  '  type: "route",',
  '  pattern: "/",',
  '  routePattern: "/",',
  "  lazy: true,",
  "  css: [],",
  '  overrideConfig: { methods: "ALL" }',
  "}",
  "👺 > fresh:route-css > name _index",
  "👺 > renderRoute > setAdditionalStyles []",
  "👺 > render Compose VNode > setAdditionalStyles []",
  "👺 > render compose App > setAdditionalStyles []"
]
./packages/plugin-vite/tests/dev_server_test.ts => vite dev - css modules in _app/_layout/_error non-island component are injected ...----- post-test output end -----
./packages/plugin-vite/tests/dev_server_test.ts => vite dev - css modules in _app/_layout/_error non-island component are injected ... FAILED (5s)
./packages/plugin-vite/tests/build_test.ts => vite build - tailwind _app ... ok (6s)
./packages/plugin-vite/tests/build_test.ts => vite build - partial island ... ok (1s)
------- post-test output -------

type,
pattern,
routePattern,
Expand Down
24 changes: 23 additions & 1 deletion packages/fresh/src/dev/fs_crawl_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from "@std/expect/expect";
import { createFakeFs } from "../test_utils.ts";
import { walkDir } from "./fs_crawl.ts";
import { crawlRouteDir, walkDir } from "./fs_crawl.ts";

Deno.test("walkDir - ", async () => {
const fs = createFakeFs({
Expand Down Expand Up @@ -43,3 +43,25 @@ Deno.test("walkDir - respects skip patterns", async () => {
"routes/api/users.ts",
]);
});

Deno.test({
name: "crawlRouteDir.filePath - normalized Windows paths",
ignore: Deno.build.os !== "windows",
fn: async () => {
const fs = createFakeFs({
"foo\\bar\\baz.txt": "foo",
"D:\\foo\\bar.tsx": "foo",
});

const rawFiles = await crawlRouteDir(fs, "foo", [], () => {});

expect(rawFiles).toEqual(expect.arrayContaining([
expect.objectContaining({
filePath: "foo/bar/baz.txt",
}),
expect.objectContaining({
filePath: "D:/foo/bar.tsx",
}),
]));
},
});
8 changes: 6 additions & 2 deletions packages/fresh/src/fs_routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ export function fsItemsToCommands<State>(
}
if (!mod.default) continue;

commands.push(newLayoutCmd(pattern, mod.default, mod.config, true));
commands.push(
newLayoutCmd(pattern, mod.default, mod.config, true, mod.css),
);
continue;
}
case CommandType.Error: {
Expand All @@ -116,6 +118,7 @@ export function fsItemsToCommands<State>(
{
component: mod.default ?? undefined,
config: mod.config ?? undefined,
css: mod.css,
// deno-lint-ignore no-explicit-any
handler: (handlers as any) ?? undefined,
},
Expand All @@ -128,6 +131,7 @@ export function fsItemsToCommands<State>(
commands.push(newNotFoundCmd({
config: mod.config,
component: mod.default,
css: mod.css,
// deno-lint-ignore no-explicit-any
handler: handlers as any ?? undefined,
}));
Expand All @@ -137,7 +141,7 @@ export function fsItemsToCommands<State>(
const { mod } = validateFsMod<State>(filePath, rawMod, type);
if (mod.default === undefined) continue;

commands.push(newAppCmd(mod.default));
commands.push(newAppCmd(mod.default, mod.css));
continue;
}
case CommandType.Route: {
Expand Down
1 change: 1 addition & 0 deletions packages/fresh/src/internals_dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
type IslandModChunk,
type PendingStaticFile,
prepareStaticFile,
toPosix,
writeCompiledEntry,
} from "./dev/dev_build_cache.ts";
export { specToName } from "./dev/builder.ts";
Expand Down
16 changes: 13 additions & 3 deletions packages/fresh/src/segments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { AnyComponent } from "preact";
import type { MaybeLazyMiddleware, Middleware } from "./middlewares/mod.ts";
import { type Method, patternToSegments } from "./router.ts";
import type { LayoutConfig, Route } from "./types.ts";
import { type Context, getInternals } from "./context.ts";
import { type Context, getInternals, setAdditionalStyles } from "./context.ts";
import { recordSpanError, tracer } from "./otel.ts";
import { type HandlerFn, isHandlerByMethod } from "./handlers.ts";
import {
Expand All @@ -22,10 +22,14 @@ export interface Segment<State> {
layout: {
component: RouteComponent<State>;
config: LayoutConfig | null;
css: string[] | null;
} | null;
errorRoute: Route<State> | null;
notFound: Middleware<State> | null;
app: RouteComponent<State> | null;
app: {
component: RouteComponent<State>;
css: string[] | null;
} | null;
children: Map<string, Segment<State>>;
parent: Segment<State> | null;
}
Expand Down Expand Up @@ -105,7 +109,11 @@ export function segmentToMiddlewares<State>(
internals.app = null;
}

const def = { props: null, component: layout.component };
const def = {
props: null,
component: layout.component,
css: layout.css,
};
if (layout.config?.skipInheritedLayouts) {
internals.layouts = [def];
} else {
Expand Down Expand Up @@ -145,6 +153,8 @@ export async function renderRoute<State>(
route: Route<State>,
status = 200,
): Promise<Response> {
setAdditionalStyles(ctx, route.css);

const internals = getInternals(ctx);
if (route.config?.skipAppWrapper) {
internals.app = null;
Expand Down
6 changes: 5 additions & 1 deletion packages/fresh/src/test_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { DEFAULT_CONN_INFO } from "./app.ts";
import type { Command } from "./commands.ts";
import { fsItemsToCommands, type FsRouteFile } from "./fs_routes.ts";
import * as path from "@std/path";
import { toPosix } from "./dev/dev_build_cache.ts";

const STUB = {} as unknown as Deno.ServeHandlerInfo;

Expand Down Expand Up @@ -123,7 +124,10 @@ export function createFakeFs(files: Record<string, unknown>): FsAdapter {
},
// deno-lint-ignore require-await
async isDirectory(dir) {
return Object.keys(files).some((file) => file.startsWith(dir + "/"));
return Object.keys(files).some((file) =>
// normalize path to posix before comparing
toPosix(file).startsWith(dir + "/")
);
},
async mkdirp(_dir: string) {
},
Expand Down
9 changes: 9 additions & 0 deletions packages/plugin-vite/demo/components/CssModuleNonIsland2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styles from "./CssModulesNonIsland2.module.css";

export function CssModulesNonIsland2() {
return (
<div class="blue">
<h1 class={styles.root}>blue text</h1>
</div>
);
}
9 changes: 9 additions & 0 deletions packages/plugin-vite/demo/components/CssModuleNonIsland3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styles from "./CssModulesNonIsland3.module.css";

export function CssModulesNonIsland3() {
return (
<div class="red">
<h1 class={styles.root}>red text</h1>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.root {
color: blue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.root {
color: red;
}
Loading