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

fix: tailwind supports routes and islands provided by plugins #2266

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5da3315
fix: tailwind now scans dependencies used in plugins for class names
deer Jan 19, 2024
51e7f6f
fix: formatting
deer Jan 19, 2024
22a099a
fix: tests pass locally
deer Jan 22, 2024
c809bbb
Merge branch 'main' into 2159_plugin_routes_tailwind
deer Jan 22, 2024
b0ca59e
fix: windows failures
deer Jan 23, 2024
8424c17
chore: add better test case
deer Jan 23, 2024
f9d7895
fix: support project root imports
deer Jan 24, 2024
064796b
fix: remote plugins
deer Jan 24, 2024
b4ebab5
Merge branch 'main' into 2159_plugin_routes_tailwind
deer Jan 24, 2024
727d0e7
test: try hacking a remote plugin into the commit history
deer Jan 24, 2024
6c39f5f
refactor plugins to be separate files, incorporate hackyRemotePlugin …
deer Jan 25, 2024
d3cf08a
fix nesting hack, support jsx, add tests for both, standardize testsi…
deer Jan 25, 2024
9801425
further simplifications
deer Jan 26, 2024
ddbfb2e
remove handler from plugins
deer Jan 28, 2024
50bb190
remove createCustomResolver
deer Jan 29, 2024
18dd8b5
Merge branch 'main' into 2159_plugin_routes_tailwind
deer Jan 29, 2024
8466dca
uptake latest import_map
deer Jan 29, 2024
844370c
support deno.jsonc files
deer Jan 30, 2024
61f8bf6
Merge branch 'main' into 2159_plugin_routes_tailwind
deer Jan 31, 2024
3b1fc08
rename imports to importMap
deer Jan 31, 2024
235ee88
refactor how to determine project location and add documentation
deer Feb 1, 2024
c953b15
fix windows
deer Feb 1, 2024
3cb107b
Merge branch 'main' into 2159_plugin_routes_tailwind
deer Feb 19, 2024
3a8ff4a
Merge branch 'main' into 2159_plugin_routes_tailwind
deer Feb 25, 2024
60220dd
remove upNLevels
deer Feb 25, 2024
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
1 change: 1 addition & 0 deletions .vscode/import_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"THIS FILE EXISTS ONLY FOR VSCODE! IT IS NOT USED AT RUNTIME": {}
},
"imports": {
"@vscode_787_hack/": "../tests/fixture_tailwind_remote_classes/",
deer marked this conversation as resolved.
Show resolved Hide resolved
"$fresh/": "../",
"twind": "https://esm.sh/twind@0.16.19",
"twind/": "https://esm.sh/twind@0.16.19/",
Expand Down
49 changes: 49 additions & 0 deletions plugins/tailwind/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import cssnano from "npm:cssnano@6.0.3";
import autoprefixer from "npm:autoprefixer@10.4.17";
import * as path from "https://deno.land/std@0.216.0/path/mod.ts";
import { TailwindPluginOptions } from "./types.ts";
import {
createGraph,
type ModuleGraphJson,
} from "https://deno.land/x/deno_graph@0.63.5/mod.ts";
import { parseFromJson } from "https://deno.land/x/import_map@v0.18.3/mod.ts";
import { parse as jsoncParse } from "https://deno.land/std@0.213.0/jsonc/mod.ts";

const CONFIG_EXTENSIONS = ["ts", "js", "mjs"];

Expand Down Expand Up @@ -62,6 +68,39 @@ export async function initTailwind(
return pattern;
});

let importMap;
if (path.extname(config.denoJsonPath) === ".json") {
importMap = (await import(path.toFileUrl(config.denoJsonPath).href, {
with: { type: "json" },
})).default;
} else if (path.extname(config.denoJsonPath) === ".jsonc") {
const fileContents = Deno.readTextFileSync(config.denoJsonPath);
importMap = jsoncParse(fileContents);
} else {
throw Error("deno config must be either .json or .jsonc");
}
iuioiua marked this conversation as resolved.
Show resolved Hide resolved
for (const plugin of config.plugins ?? []) {
if (plugin.location === undefined) continue;
// if the plugin is declared in a separate place than the project, the plugin developer should have specified a projectLocation
// otherwise, we assume the plugin is in the same directory as the project
const projectLocation = plugin.projectLocation ??
path.dirname(plugin.location);
const resolvedImportMap = await parseFromJson(
path.toFileUrl(config.denoJsonPath),
importMap,
);

const moduleGraph = await createGraph(plugin.location, {
resolve: resolvedImportMap.resolve.bind(resolvedImportMap),
});

for (const file of extractSpecifiers(moduleGraph, projectLocation)) {
const response = await fetch(file);
deer marked this conversation as resolved.
Show resolved Hide resolved
const content = await response.text();
tailwindConfig.content.push({ raw: content });
}
}

// PostCSS types cause deep recursion
const plugins = [
// deno-lint-ignore no-explicit-any
Expand All @@ -76,3 +115,13 @@ export async function initTailwind(

return postcss(plugins);
}

function extractSpecifiers(graph: ModuleGraphJson, projectLocation: string) {
return graph.modules
.filter((module) =>
(module.specifier.endsWith(".tsx") ||
module.specifier.endsWith(".jsx")) &&
module.specifier.startsWith(projectLocation)
)
.map((module) => module.specifier);
}
1 change: 1 addition & 0 deletions src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export async function getInternalFreshState(
router: config.router,
server: config.server ?? {},
basePath,
denoJsonPath,
};

if (config.cert) {
Expand Down
14 changes: 14 additions & 0 deletions src/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export interface ResolvedFreshConfig {
router?: RouterOptions;
server: Partial<Deno.ServeTlsOptions>;
basePath: string;
denoJsonPath: string;
}

export interface RouterOptions {
Expand Down Expand Up @@ -495,6 +496,19 @@ export interface Plugin<State = Record<string, unknown>> {
middlewares?: PluginMiddleware<State>[];

islands?: PluginIslands;

/**
* This should always be set to `import.meta.url`.
* Required if you want tailwind to scan your routes for classes.
*/
location?: string;

/**
* If the plugin is declared in a separate place than the project root, specify the root here.
* This is necessary if your plugin is declared in a `src/` folder.
* Required if you want tailwind to scan your routes for classes, and your plugin is not in the root.
*/
projectLocation?: string;
deer marked this conversation as resolved.
Show resolved Hide resolved
}

export interface PluginRenderContext {
Expand Down
13 changes: 13 additions & 0 deletions tests/fixture_tailwind_remote_classes/basicPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Plugin } from "../../src/server/types.ts";
import PluginComponent from "./components/PluginComponent.tsx";

export const basicPlugin = {
name: "basic plugin",
routes: [
{
path: "routeFromPlugin",
component: PluginComponent,
},
],
location: import.meta.url,
} satisfies Plugin;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AtPluginComponent() {
return <div class="text-slate-500">AtPluginComponent</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function JsxPluginComponent() {
return <div class="text-emerald-500">JsxPluginComponent</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function NestedPluginComponent() {
return <div class="text-amber-500">NestedPluginComponent</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function PluginComponent() {
return <div class="text-purple-500">PluginComponent</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function VeryNestedPluginComponent() {
return <div class="text-cyan-500">VeryNestedPluginComponent</div>;
}
18 changes: 18 additions & 0 deletions tests/fixture_tailwind_remote_classes/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"lock": false,
"tasks": {
"start": "deno run -A --watch=static/,routes/ dev.ts"
},
"imports": {
"$fresh/": "../../",
"preact": "https://esm.sh/preact@10.15.1",
"preact/": "https://esm.sh/preact@10.15.1/",
"tailwindcss": "npm:tailwindcss@3.3.5",
"$std/": "https://deno.land/std@0.216.0/",
"@vscode_787_hack/": "./"
deer marked this conversation as resolved.
Show resolved Hide resolved
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
6 changes: 6 additions & 0 deletions tests/fixture_tailwind_remote_classes/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env -S deno run -A --watch=static/,routes/

import dev from "$fresh/dev.ts";
import config from "./fresh.config.ts";

await dev(import.meta.url, "./main.ts", config);
18 changes: 18 additions & 0 deletions tests/fixture_tailwind_remote_classes/fresh.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig, Plugin } from "$fresh/server.ts";
import tailwind from "$fresh/plugins/tailwind.ts";
import { basicPlugin } from "./basicPlugin.ts";
import { nestedPlugin } from "./nestedPlugins/nestedPlugin.ts";
import { hackyRemotePlugin } from "https://raw.githubusercontent.com/denoland/fresh/727d0e729e154323b99b319c04f9805f382949f0/tests/fixture_tailwind_remote_classes/hackyRemotePlugin.tsx";
deer marked this conversation as resolved.
Show resolved Hide resolved
import { veryNestedPlugin } from "./nestedPlugins/nested/nested/nested/veryNestedPlugin.ts";
import { jsxPlugin } from "./jsxPlugin.ts";

export default defineConfig({
plugins: [
tailwind(),
basicPlugin,
nestedPlugin,
hackyRemotePlugin as Plugin,
veryNestedPlugin,
jsxPlugin,
],
});
21 changes: 21 additions & 0 deletions tests/fixture_tailwind_remote_classes/fresh.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// DO NOT EDIT. This file is generated by Fresh.
// This file SHOULD be checked into source version control.
// This file is automatically updated during development when running `dev.ts`.

import * as $_app from "./routes/_app.tsx";
import * as $_middleware from "./routes/_middleware.ts";
import * as $index from "./routes/index.tsx";

import { type Manifest } from "$fresh/server.ts";

const manifest = {
routes: {
"./routes/_app.tsx": $_app,
"./routes/_middleware.ts": $_middleware,
"./routes/index.tsx": $index,
},
islands: {},
baseUrl: import.meta.url,
} satisfies Manifest;

export default manifest;
13 changes: 13 additions & 0 deletions tests/fixture_tailwind_remote_classes/jsxPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Plugin } from "../../src/server/types.ts";
import JsxPluginComponent from "./components/JsxPluginComponent.jsx";

export const jsxPlugin = {
name: "jsx plugin",
routes: [
{
path: "routeFromJsxPlugin",
component: JsxPluginComponent,
},
],
location: import.meta.url,
} satisfies Plugin;
11 changes: 11 additions & 0 deletions tests/fixture_tailwind_remote_classes/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// <reference no-default-lib="true" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />

import { start } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";
import config from "./fresh.config.ts";

await start(manifest, config);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { normalize } from "$std/url/normalize.ts";
import type { Plugin } from "../../../../../../src/server/types.ts";
import VeryNestedPluginComponent from "../../../../components/VeryNestedPluginComponent.tsx";

export const veryNestedPlugin = {
name: "very nested plugin",
location: import.meta.url,
projectLocation: normalize(import.meta.url + "../../../../../../").href,
routes: [{
path: "routeFromVeryNestedPlugin",
component: VeryNestedPluginComponent,
}],
} satisfies Plugin;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { normalize } from "$std/url/normalize.ts";
import type { Plugin } from "../../../src/server/types.ts";
import NestedPluginComponent from "../components/NestedPluginComponent.tsx";
import AtPluginComponent from "@vscode_787_hack/components/AtPluginComponent.tsx";

export const nestedPlugin = {
name: "nested plugin",
location: import.meta.url,
projectLocation: normalize(import.meta.url + "../../../").href,
routes: [{
path: "routeFromNestedPlugin",
component: NestedPluginComponent,
}, {
path: "atRouteFromNestedPlugin",
component: AtPluginComponent,
}],
} satisfies Plugin;
17 changes: 17 additions & 0 deletions tests/fixture_tailwind_remote_classes/routes/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PageProps } from "$fresh/server.ts";

export default function App({ Component }: PageProps) {
return (
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Fresh app</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<Component />
</body>
</html>
);
}
15 changes: 15 additions & 0 deletions tests/fixture_tailwind_remote_classes/routes/_middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FreshContext } from "$fresh/server.ts";

export async function handler(
_req: Request,
ctx: FreshContext,
) {
if (ctx.url.pathname === "/middleware-only.css") {
return new Response(".foo-bar { color: red }", {
headers: {
"Content-Type": "text/css",
},
});
}
return await ctx.next();
}
3 changes: 3 additions & 0 deletions tests/fixture_tailwind_remote_classes/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <h1 class="text-red-600 block">foo</h1>;
}
3 changes: 3 additions & 0 deletions tests/fixture_tailwind_remote_classes/static/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
7 changes: 7 additions & 0 deletions tests/fixture_tailwind_remote_classes/tailwind.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Config } from "tailwindcss";

export default {
content: [
"{routes,islands}/**/*.{ts,tsx}", // deliberately ignore components
],
} satisfies Config;
2 changes: 2 additions & 0 deletions tests/main_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,7 @@ Deno.test("Expose config in ctx", async () => {
"safari15",
],
},
denoJsonPath: join(Deno.cwd(), "tests", "fixture", "deno.json"),
dev: false,
plugins: [],
render: "Function",
Expand Down Expand Up @@ -1191,6 +1192,7 @@ Deno.test("Expose config in ctx", async () => {
"safari15",
],
},
denoJsonPath: join(Deno.cwd(), "tests", "fixture", "deno.json"),
dev: false,
plugins: [],
render: "Function",
Expand Down
8 changes: 7 additions & 1 deletion tests/server_components_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertEquals } from "./deps.ts";
import { assertEquals, join } from "./deps.ts";
import {
assertSelector,
assertTextMany,
Expand Down Expand Up @@ -93,6 +93,12 @@ Deno.test("passes context to server component", async () => {
"safari15",
],
},
denoJsonPath: join(
Deno.cwd(),
"tests",
"fixture_server_components",
"deno.json",
),
dev: false,
plugins: [
{ entrypoints: {}, name: "twind", renderAsync: "AsyncFunction" },
Expand Down
Loading
Loading