VSCode extension to generate a router based on file based routing and nested layouts

vscode-router-generator

VSCode Extension to generate a router based on a file structure and returning the correct nested layouts.

There can be optional loader function to return data to the component before the build is done.

You can check out the examples for more details.

Inspired by https://remix.run

Features

  • File based routing (with regex)
  • Nested layouts
  • Optional loader function (to pass data to a component)
  • Works on https://vscode.dev

Platforms

  • Flutter (.dart)
  • Lit (.js, .ts)
  • React (.tsx, .jsx)
  • JSON (.json)

JSON

Generate JSON for Routes

This will generate a json file at the route containing all the metadata found after crawling the pages directory.

{
  "pages": {
    "/projects/:id": {
      "hasLoader": true,
      "name": "ProjectDetails",
      "route": "/projects/:id",
      "args": [
        "id"
      ],
      "ext": "tsx",
      "alias": "route1",
      "relativePath": "pages/projects/:id",
      "implicitIndex": false,
      "parentRoute": "/projects"
    },
    "/projects/": {
      "hasLoader": true,
      "name": "ProjectList",
      "route": "/projects/",
      "args": [],
      "ext": "tsx",
      "alias": "route2",
      "relativePath": "pages/projects/index",
      "parentRoute": "/projects"
    },
    "/projects": {
      "hasLoader": false,
      "name": "ProjectBase",
      "route": "/projects",
      "args": [],
      "ext": "tsx",
      "alias": "route3",
      "relativePath": "pages/projects",
      "implicitIndex": true,
      "parentRoute": ""
    },
    "/": {
      "hasLoader": false,
      "name": "Home",
      "route": "/",
      "args": [],
      "ext": "tsx",
      "alias": "route0",
      "relativePath": "pages/index",
      "parentRoute": ""
    },
    "": {
      "hasLoader": false,
      "name": "Root",
      "route": "",
      "args": [],
      "ext": "tsx",
      "alias": "route4",
      "relativePath": "pages/root"
    }
  },
  "routes": [
    "/projects/:id",
    "/projects/",
    "/projects",
    "/",
    ""
  ]
}

Lit

Generate Lit Router

Generates a router that can be used both server side and browser side.

Generate Lit Component

Generates a Lit web component that can consume the generated router and listen for hash changes.

<body>
    <generated-app> </generated-app>
    <script type="module" src="/src/generated-app.ts"></script>
</body>

Generate Lit SSR Server

Generates a express.js application that consumes the router and returns html rendered on the server.

To generate a route just define a web component and an optional loader method:

import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";

export async function loader(
  route: string,
  args: { [key: string]: any }
): Promise<AccountData> {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  const id = args["id"]!;
  return {
    id,
    name: "Name: " + id,
    email: route,
  };
}

@customElement("account-details")
export class AccountDetails extends LitElement {
  static styles = css``;

  @property({ type: String }) id = "";
  @property({ type: Object }) data!: AccountData;

  render() {
    return html`<section>User ID: ${this.data.id}</section>`;
  }
}

interface AccountData {
  id: string;
  name: string;
  email: string;
}

Flutter

Generate Flutter Router

Generates a Flutter MaterialApp with a generated router using Navigator 2.0 and will listen for hash changes and return the correct layout for the route.

import 'package:flutter/material.dart';

import 'router.dart';

void main() {
  runApp(GeneratedApp(
    themeMode: ThemeMode.system,
    theme: ThemeData.light(),
    darkTheme: ThemeData.dark(),
  ));
}

To build a class extend UiRoute and override the methods (including an optional loader):

import 'package:flutter/material.dart';

import '../../router.dart';

class AccountPage extends UiRoute<Map<String, String>> {
  @override
  loader(route, args) => args;

  @override
  Widget builder(
      BuildContext context, Map<String, String> data, Widget? child) {
    return Center(
      child: Text('ID: ${data['id']}'),
    );
  }
}

To update the route in a widget just dispatch the following:

RoutingRequest('ROUTE_HERE').dispatch(context)

React

Generate React Router

Generates a async React component that can be used to render a layout based on the url.

You can import the generated router and run it at the top level index.js:

import * as ReactDOM from 'react-dom';
import App from './router';

const root = document.getElementById('root');

async function loadApp() {
  const AppRoot = await App();
  ReactDOM.render(AppRoot, root);
}

window.addEventListener("hashchange", () => {
  loadApp();
});

loadApp();

You can define a layout and an optional loader for a given page:

import * as React from "react";

export function loader(route: string, args: { [key: string]: any }) {
    const id = args['id'];
    return {
        id: `${id}`,
        name: `Project ${id}`,
    };
}

export default function ProjectDetails({
    data,
    children,
}: {
    id: string;
    data: Project;
    children?: React.ReactNode;
}) {
    const project = data;
    return (
        <div>
            <h3>{project.name}</h3>

            {children}
        </div>
    );
}

interface Project {
    id: string;
    name: string;
}

To build with nested layouts you can use the children from the props to pass down the tree:

import * as React from "react";
import Header from "../components/Header";

export default function ProjectBase({ children }: { children?: React.ReactNode }) {
    return (
        <div>
            <Header title="Projects" />
            <section style={{
                padding: '1rem',
            }}>
                {children}
            </section>
        </div>
    );
}

GitHub

View Github