Declarative Server & Client Code to Enable Linting and Splitting.
Published: 2022-10-18 22:07:27

This is a proposal for an idea called "FileKinds". It establishes literal eslinting rules for single files or groups of files. As well as optionally rules how clients and servers should utilize the code.

## Part 1: The Purpose

Whats the point? Many JavaScript / TypeScript frameworks require specialized variables to be exported from files. For instance within `Next.js` we have `getServerSideProps` which has a specific type, within Deno Fresh we have `handlers` which has a specific type. Usually these files require the default export to also be a component type.

With the rise of meta-frameworks the union of server + client side code becoming increasingly popular. Frameworks like Next.js popularized named exports like `getServerSideProps` in a file that did specific things within the framework. It's becoming apparent that the union of server + client is only going to become increasingly complex. Currently there are a couple ways to author split code: 

1. separate files like `thing.client.js` and `thing.server.js`
2. one file where you have named exports and split using the AST `server` & `client`
3. use a comment in the AST to define which should be split `// @server` & `// @client`
4. a named export from a package say `universal` from `@reggi/universal` can be tracked by your AST parser of choice and you can pass a function into universal and split the code within the function argument

All of these solutions add complexity for how files / folders need to be authored and having clear rules around how to do that will be essential in getting this next wave of frameworks off the ground.

What about for groups of files? I always found it interesting within the mac ecosystem that the `.app` extension was applied to folders and abstracted out the contents of the folder with executability (when you click on it for instance). I like the idea of a contained component that has server functionality performing something like this as well. By giving the folder an extension a meta²-framework could pickup on how that component works without any configuration. For example for react server components we could use the `rsc` extension could use its own extension, etc.

```
about.rsc
  server.tsx
  client.tsx
```

### Goals

1. To provide linting for files / groups of files that have specific export signatures.
2. To (optionally?) provide plugins on how a server should handle these files in a server framework like [hattip](https://github.com/hattipjs/hattip)

Provide the opportunity to then allow meta-frameworks to allow meta²-frameworks to render server + component code from any meta-framework. (allow a framework to potentially render out `about.rsc` and `about.solid` on the same server, potentially something like `astro` or another project could do this.


{% embed https://twitter.com/thomasreggi/status/1582500182675009537 %}
{% embed https://twitter.com/thomasreggi/status/1582500857660477441 %}


### Example Deno Fresh Route 

As an example I picked Deno fresh, because it uses named exports for routes. A route in Deno requires a default function to return that returns the component (in this case preact) and a `handler` object. With eslint it is possible to create a custom rule that would restrict what exports can be made from this FileKind, for instance it could give a linting error if the `handler` export was assigned a `JSX.Element` instead of an object like it requires. We could create a new FileKind called `deno-fresh-route`.

<details>
  <summary>Example Deno Fresh Route</summary>
  
```tsx
// routes/about.tsx

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

export const handler: Handlers = {
  async GET(req, ctx) {
    const resp = await ctx.render();
    resp.headers.set("X-Custom-Header", "Hello");
    return resp;
  },
};

export default function AboutPage() {
  return (
    <main>
      <h1>About</h1>
      <p>This is the about page.</p>
    </main>
  );
}
```
</details>

## Part 2: Enforcement

When you have a custom FileKind like `deno-fresh-route` what can you do with it? How do you enforce / implement it? Here are two ways people should be able to use it.

### Method one "Extensions"

When `deno-fresh-route` is registered a eslint package is included. I like the idea of FileKinds being able declare a sub-extension like `about.fsh.ts` 

### Method two "Rules"

Understandably library authors may not want to implement file sub-extensions. They may just want to state that all files within a given folder say `routes` are of a certain type. This should be configurable at the framework / editor level and not really seen by users. Unfortunately I can't think of a better way of doing this then a `filekinds.config.json` at the route level something to tell the editor and linter which rules should run for which files / folders. 

### Method three "Comments"

Similar to how we currently juggle JSX runtimes you could use a comment at the top of a file to define its FileType.

<details>
  <summary>Proposal filekinds.config.json</summary>
  
```json
{
  "routes/*": "filekind.deno-fresh-route"
}
```
</details>

### Part 3: The Organization

Part of this project is to create a standards body to claim specific file extension types for example `about.rsc`, along with a vscode extension that can detect these file types and bring them into your project automatically (by requesting access). Right now in order to achieve this level of linting every framework needs to implement their own vscode plugin. I think it would be really interesting if we could just have one that all frameworks could tap into. 

<details>
  <summary>Proposal Registry Definition for single file</summary>
  
```json
{
  // name of the FileKind separate from the eslint-package-name
  "name": "deno-fresh-route",
  // extension of eslint rules with npm modules and options
  "rules": ["deno-fresh-route"],
  // reserved extension
  "extension": "{name}.fsh.ts"
}
```
</details>

<details>
  <summary>Proposal Registry Definition for Folder</summary>
  
```json
{
  // name of the FileKind separate from the eslint-package-name
  "name": "sveltekit-page",
  // extension of eslint rules with npm modules and options
  "folder": {
    "+page.svelte": {
      // possibly nothing not sure what to do with non js / ts files
      // if you really wanted you may need to use vite to compile then
      // run the rule checker
      "rules": ["svelte"]
    },
    "+page.js": {
      "rules": ["svelte-page-js"]
    },
    "+page.server.js": {
      "rules": ["svelte-page-server"]
    },
  // reserved extension
  "extension": "{name}.page.sveltekit"
}
```
</details>

## Possible 🚩

1. People don't care about file / folder linting and the overhead
3. People hate long file extensions like `about.denofreshpreactroute.ts`
4. People hate abstract file extensions like `about.dfpr.ts`
5. Versioning FileKinds
6. Fights over good short useful extension names. (and the problems that come with governance)
7. It's not really feasible to include a multiple meta-frameworks (`qwik`, `solidstart`, `sveltekit`, `next`) in one framework. 
8. Including component + server code in one FileKind is not really desired overall as a practice.
9. Open to other red-flags
10. Security issues with the mono vscode extension
11. It's like babel all over again having to decide what code lives where is like syntax plugins 😵‍💫

---

Imagine a single file that can house web sockets action endpoints, tree-shaken client imports available from the server, and server endpoints. This is a Server FileKind that allows you define exports for different types of callable interactions. The AST tool would parse this file and shake out each part into server handlers using something like [hattip](https://github.com/hattipjs/hattip) inspired by this discussion [here in brillout/vite-plugin-ssr](https://github.com/brillout/vite-plugin-ssr/issues/53).

```ts
export const socketActions = {
  hello () {
    // web socket action endpoint (like liveview)
    console.log('hello on server');
    return { hello: 'true'}
  }
}

export const clientImports = {
  hello () {
    // this is a chunk of js that will be shaken out of this file (with it's external scopes if any?)
    // imported into the client not ajax
    // this isn't loaded at runtime and is loaded on a dom event (like qwick)
    console.log('hello on client');
    return { hello: 'true'}
  }
}

export const serverEndpoints = {
  hello () {
    // this is a server endpoint (like telefunc)
    console.log('hello on server');
    return { hello: 'true'}
  }
}
```

## Argument shaking example:

A different example of static server generation not from exports but from a keyword from a specific module `universal` declarations would compose the server endpoints. I did this in an astro file which would make things extra hard, but the idea is there. Uses web-component to provide globals.

```ts
---
import { staticallyParsedRules, universal } from '@reggi/example'

universal(() => ({
  thing: {
    server: () => {
      console.log('hi from server')
    },
    client: () => {
      console.log('hi from client')
    },
    socket: () => {
      console.log('hi from server socket')
    }
  }
}))

---

<dom-global-rules rules={staticallyParsedRules}></dom-global-rules>
<div onclick="thing.server()">log something on the server</div>
<div onclick="thing.socket()">log something on the socket</div>
<div onclick="thing.client()">log something on the client</div>
```