Skip to content
This repository has been archived by the owner on Aug 29, 2024. It is now read-only.

Commit

Permalink
Views intellisense (#23)
Browse files Browse the repository at this point in the history
* feat: generate typescript types for svelte component props definition file
  • Loading branch information
joshamaju authored Mar 18, 2024
1 parent 67b88f1 commit 81f2c04
Show file tree
Hide file tree
Showing 15 changed files with 162 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/red-drinks-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@leanweb/fullstack": minor
---

Add views and props intellisense plugin
2 changes: 1 addition & 1 deletion packages/core/src/runtime/Render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export function unsafeRenderToString(
return internal.unsafeRenderToString(componentOrOutput, props);
}

export {SSRComponentExport, resolveComponent, makeFactory} from './internal/render.js'
export {SSRComponentExport, resolveComponent, makeFactory, Views} from './internal/render.js'
15 changes: 10 additions & 5 deletions packages/core/src/runtime/internal/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,22 @@ export function resolveComponent<T>(
return isLazy(entry) ? entry().then((_) => _.default) : entry.default;
}

export interface Views {
// [k:string]: SSRComponentProps
}

export function makeFactory<T extends SSRComponent | Promise<SSRComponent>>(
f: (name: string) => T
) {
return (
name: string,
props?: SSRComponentProps
return <V extends Views, K extends keyof V>(
name: K,
props?: V[K]
): T extends Promise<SSRComponent> ? Promise<string> : string => {
// @ts-expect-error
const output = f(name);
// @ts-expect-error
return output instanceof Promise
? output.then((_) => unsafeRenderToString(_, props))
: unsafeRenderToString(output, props);
? output.then((_) => unsafeRenderToString(_, props as SSRComponentProps))
: unsafeRenderToString(output, props as SSRComponentProps);
};
}
3 changes: 3 additions & 0 deletions packages/core/src/vite/Plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import * as Island from "./Island.js";
import { devServer } from "./devServer/DevServer.js";
import * as AssetRef from "./devServer/assetRef/AssetRef.js";
import { previewServer } from "./preview/Server.js";
import {views as pluginViews} from './views.js'
import { dedent } from "ts-dedent";
// import { compressFile } from "./Compress.js";

// interface Manifest {
Expand Down Expand Up @@ -611,6 +613,7 @@ export default function fullstack(userConfig?: Options) {
// We need island preprocessing to run before our inner build during production build
pluginIsland,
pluginBuild,
pluginViews,
pluginPreview,
svelte(svelteOptions),
];
Expand Down
107 changes: 107 additions & 0 deletions packages/core/src/vite/views.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import path from "node:path";
import fs from "node:fs";
import type { Plugin } from "vite";
import { globSync as glob } from "glob";
import { dedent } from "ts-dedent";

const cwd = process.cwd();

const outDir = cwd;
const outFile = path.join(outDir, "views.d.ts");

const prefix = path.join(cwd, "src/views/");

let cache = new Set<string>();

const write = (views: string[]) => {
fs.writeFileSync(
outFile,
dedent`
import {ComponentProps} from 'svelte';
${views
.map((file, i) => `import $${i} from "${relative_path(outDir, file)}";`)
.join("\n")}
declare module "@leanweb/fullstack/runtime/Render" {
interface Views {
${views
.map((file, i) => {
const file_ = file.replace(prefix, "");
const { dir, name } = path.parse(file_);
const key = path.join(dir, name);
const value = `ComponentProps<$${i}>`;
return [`"${key}": ${value}`, `"${file_}": ${value}`];
})
.flat()
.join(",\n")}
}
}
`
);
};

export const views: Plugin = {
name: "fullstack:views",
config() {
return {
server: {
watch: {
ignored: [outFile],
},
},
};
},
buildStart() {
const files = glob("**/*.svelte", {
cwd,
absolute: true,
root: "/src/views",
});

write(files);

cache = new Set(files);
},
configureServer(server) {
return () => {
server.watcher.on("all", (e, file) => {
if (!file.endsWith(".svelte")) return;

if (e == "unlink") {
cache.delete(file);
}

if (e == "add") {
cache.add(file);
}

write([...cache]);
});
};
},
};

function posixify(str: string) {
return str.replace(/\\/g, "/");
}

/**
* Like `path.join`, but posixified and with a leading `./` if necessary
*/
function join_relative(...str: string[]) {
let result = posixify(path.join(...str));
if (!result.startsWith(".")) {
result = `./${result}`;
}
return result;
}

/**
* Like `path.relative`, but always posixified and with a leading `./` if necessary.
* Useful for JS imports so the path can safely reside inside of `node_modules`.
* Otherwise paths could be falsely interpreted as package paths.
*/
function relative_path(from: string, to: string) {
return join_relative(path.relative(from, to));
}
3 changes: 2 additions & 1 deletion playground/basic/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
node_modules
.env
public/assets
env.d.ts
env.d.ts
views.d.ts
1 change: 1 addition & 0 deletions playground/basic/fullstack-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="svelte" />
/// <reference path="env.d.ts" />
/// <reference path="views.d.ts" />
/// <reference types="vite/client" />
/// <reference types="@leanweb/fullstack/ssr" />
/// <reference types="vite-plugin-simple-scope/types" />
2 changes: 1 addition & 1 deletion playground/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"build": "tsc && vite build",
"preview": "vite preview"
},
"keywords": [],
Expand Down
18 changes: 18 additions & 0 deletions playground/basic/src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ import Async from "./views/async.svelte?ssr";
import Home from "./views/home.svelte?ssr";
// import About from "./views/about.svx?ssr";

import {
makeFactory,
resolveComponent,
type SSRComponentExport,
} from "@leanweb/fullstack/runtime/Render";

const components = import.meta.glob<SSRComponentExport>(
"./views/**/*.svelte",
{ query: { ssr: true }, eager: true }
);

export const render = makeFactory((name) => {
return resolveComponent(`./views/${name}.svelte`, components);
});

console.log(render('home', {}));


type SessionData = {
userId: string;
};
Expand Down
1 change: 1 addition & 0 deletions playground/basic/src/views/home.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
export let count: number;
export let name: string
</script>

<!DOCTYPE html>
Expand Down
2 changes: 1 addition & 1 deletion playground/basic/src/views/inner.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
<title>Document</title>
</head>
<body>
<Footer __island />
<Footer />
</body>
</html>
3 changes: 3 additions & 0 deletions playground/basic/src/views/users/user.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<script lang='ts'>
export let count: number
</script>
Empty file.
9 changes: 8 additions & 1 deletion playground/basic/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
"isolatedModules": true,

"strict": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte", "fullstack-env.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
Expand Down
2 changes: 1 addition & 1 deletion playground/basic/tsconfig.node.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler"
"moduleResolution": "bundler",
},
"include": ["vite.config.ts"]
}

0 comments on commit 81f2c04

Please sign in to comment.