Next. Js is no stranger to you, the most familiar of which is reduced-form routing (based on the file system). Now let’s implement this effortless feature subtly in Vite. In this paper, React combined with react-Router is implemented. The implementation idea of VUE is basically the same, with only the difference between the suffix name and vue-router. If necessary, this scheme can be copied.

The routing form

First take a look at what next.js looks like based on file contract routing. When next.js adds a file to the Pages directory, it automatically generates the route. A lot of template code is omitted during development to improve development efficiency.

Features: it will be the index file name js | JSX | | ts TSX at the end of the file, mapping the current directory to the root of the routing:

  • pages/index.js/
  • pages/blog/index.js/blog

Feature 2: Supports nested directory files. If you create a nested folder structure, the file will automatically generate routes in the same way:

  • pages/about.js/about
  • pages/blog/first-post.js/blog/first-post
  • pages/dashboard/settings/username.js/dashboard/settings/username

Property 3: Use parenthesis syntax. Match dynamic named parameters:

  • pages/blog/[slug].js/blog/:slug( /blog/hello-world)
  • pages/[username]/settings.js/:username/settings( /foo/settings)

This route looks very clear, and creating a route is as simple as writing a component. Umijs also supports reduced-form routing, which is basically the same form and should benefit those who have used it. However, Vite as a scaffold provides more general functionality to support VUE and React, and naturally does not couple this routing scheme.

Inspired by the

https://cn.vitejs.dev/guide/features.html#glob-import in Vite official document Glob import is such an introduction:

Vite supports importing multiple modules from the file system using the special import.meta.glob function:

const modules = import.meta.glob('./dir/*.js');
Copy the code

The above will be translated as follows:

const modules = {
  './dir/foo.js': () = > import('./dir/foo.js'),
  './dir/bar.js': () = > import('./dir/bar.js'),};Copy the code

This API is similar to Webpack’s require.context(). Nice. Take a bold approach and use react. lazy with react-router v6 to create a file contract route. Just do it! The only thing we need to do is convert the JSON read from the file to the React-Router configuration.

React-router v6 react-router v6

<Routes>
  <Route path="/" element={<App />} ><Route index element={<Home />} / ><Route path="teams" element={<Teams />} ><Route index element={<LeagueStandings />} / ><Route path=":teamId" element={<Team />} / ><Route path="new" element={<NewTeamForm />} / ></Route>
  </Route>
</Routes>
Copy the code

There is also a useRoutes that configures the route as JSON:

const routes = [
  {
    element: <App />,
    path: '/'.children: [{index: true.element: <Home />}, {path: 'teams'.element: <Teams />,
        children: [
          {
            index: true.element: <LeagueStandings />}, {path: ':teamId'.element: <Team />}, {path: 'new'.element: <NewTeamForm />,},],},],},];// Export the routing component
export function PageRoutes() {
  return useRoutes(routes);
}
Copy the code

All you need to do is convert to the JSON structure above.

Routing rules

We try to keep it consistent with next. Js and implement the contract layout in umiJS form. But avoid one problem: avoid mapping unwanted components to routes. At this point, next. Js must place non-routing related files outside the Pages directory. The umiJS exclusion rule is as follows:

  • A file or directory that begins with a. Or _
  • Type definition file ending in d.ts
  • Ts, spec.ts, e2e. Ts test files (for.js,.jsx,.tsx files)
  • Components and Component directories
  • Utils and util directories
  • Not.js,.jsx,.ts, or.tsx files
  • The file content does not contain JSX elements

Umijs does make this a bit too complicated, and a lot of rules can easily confuse developers. In componentized projects, routing files are often far fewer than page components. We can use some special identifier to indicate that it is a route:

We tentatively set the file beginning with $as the rule for route generation

  • pages/$index.tsx/
  • pages/blog/$index.tsx/blog
  • pages/$about.tsx/about
  • pages/blog/$[foo].tsx/blog/:foo( /blog/hello-world)

Use $.tsx as layout instead of _layout.tsx in umiJS.

In the fast – glob github.com/mrmlnc/fast… We need to read all the ts and TSX files in the pages directory. The wildcard can be written like this:

const modules = import.meta.glob('/src/pages/**/$*.{ts,tsx}');
Copy the code

We have such a catalog

├ ─ pages │ │ $. TSX │ │ $index. The TSX │ │ │ └ ─ demo │ │ $index. The TSX │ │ │ └ ─ child │ $demo - hello - world. TSX │ $index. The TSX │ $[name].tsxCopy the code

Print modules as follows:

implementation

We can convert modules variables to nested JSON for easy understanding (ignore $.tsx for now) :

import { set } from 'lodash-es';

/** * Generate path configuration */ from the pages directory
function generatePathConfig() :Record<string.any> {
  // Scan all routing files under SRC /pages
  const modules = import.meta.glob('/src/pages/**/$*.{ts,tsx}');

  const pathConfig = {};
  Object.keys(modules).forEach((filePath) = > {
    const routePath = filePath
      // remove SRC /pages characters that are not relevant
      .replace('/src/pages/'.' ')
      // Remove the filename suffix
      .replace(/.tsx? /.' ')
      // Convert dynamic route $[foo]. TSX => :foo
      .replace(/$[([\w-]+)]/.': $1')
      // Convert files beginning with $
      .replace(/$([\w-]+)/.'$1')
      // Separate with directories
      .split('/');
    // Use lodash.set to merge into an object
    set(pathConfig, routePath, modules[filePath]);
  });
  return pathConfig;
}
Copy the code

The generatePathConfig() directory structure is printed as follows:

This is now very close to the React-Router configuration.

We just need to wrap the import() syntax slightly () => import(‘./demo/index.tsx’) with a layer of react. lazy to convert it to a component:

** * wraps lazy and Suspense */ for dynamic import
function wrapSuspense(importer: () => PromiseThe < {default: ComponentType }>) {
  if(! importer) {return undefined;
  }
  // Use react.lazy wrap () => import() syntax
  const Component = lazy(importer);
  In Suspense, you can customize loading components
  return (
    <Suspense fallback={null}>
      <Component />
    </Suspense>
  );
}
Copy the code

We recursively convert pathConfig to the React-Router configuration

/** * map file path configuration to react-router route */
function mapPathConfigToRoute(cfg: Record<string.any>) :RouteObject[] {
  // The children of route are arrays
  return Object.entries(cfg).map(([routePath, child]) = > {
    // () => import() syntax judgment
    if (typeof child === 'function') {
      // If the value is index, the route is mapped to the current root route
      const isIndex = routePath === 'index';
      return {
        index: isIndex,
        path: isIndex ? undefined : routePath,
        // Convert to component
        element: wrapSuspense(child),
      };
    }
    // If it is a directory, search the next level
    const{$... rest } = child;return {
      path: routePath,
      / / layout processing
      element: wrapSuspense($),
      / / recursive children
      children: mapPathConfigToRoute(rest),
    };
  });
}
Copy the code

Finally assemble this configuration:

function generateRouteConfig() :RouteObject[] {
  const{$... pathConfig } = generatePathConfig();// Extract the layout of the following route
  return[{path: '/'.element: wrapSuspense($),
      children: mapPathConfigToRoute(pathConfig),
    },
  ];
}

const routeConfig = generateRouteConfig();
Copy the code

Try printing this routeConfig configuration:

Finally, the wrapped component is inserted into the App

export function PageRoutes() {
  return useRoutes(routeConfig);
}
Copy the code

As for why PageRoutes should be a separate component, useRoutes needs the Context of BrowserRouter, otherwise it will report an error.

function App() {
  return (
    <BrowserRouter>
      <PageRoutes />
    </BrowserRouter>
  );
}
Copy the code

And you’re done! Preview:

conclusion

I remember the painful experience of writing the React-Router v2 configuration JSON a few years ago. With file-based routing, you can happily leave work earlier on Vite.

Interested friends can add group communication to study together.