The front-end route is automatically generated

General project definition route

Let’s take a look at common vUE single-page projects and what steps are required to define a front-end route:

  1. First we need to find a suitable location in the views or Pages folder of the project to create a new page file, and define a Name1 for this file
  2. We need to import the page file component we are going to use in router.js based on the file path and name it Name2
  3. Define a Route object, set a URL path path, set Component to the imported Name2, and maybe finally define a Name3 for the route

As you can see, in these three steps you need to define the file path, introduce path,url Path has file name, reference component name and routing object name. These paths are the same or different from name, but they all need manual maintenance. When a project is large or multi-person, routing information will generate multiple sets of rules carelessly, which is not conducive to subsequent maintenance

Url: Uniform resource locator. Url Path is the Path of resources on a network server. In the early days of the Web, paths like this represented the location of physical files on the Web server. Today, it’s mostly an abstraction handled by a Web server without any physical reality.

So the URL Path is actually associated with our file Path, we can completely unify the URL Path and the file Path, once we decide to define the URL Path according to the file Path, we can automatically generate a route through a certain method

Advantages of automatically generating routing information

  1. Easy to maintain and reduce mental burden

    The two most important parts of front-end routing are:

    1. Component: represents the component, imported through the file path
    2. Path: indicates the URL route and defines the URL path

    Status:

    Component => file path=> code directory structure URL path=> Route definition pathCopy the code

    Ultimate purpose:

    Directory structure === URL pathCopy the code

    This forces us to reasonably arrange the file hierarchy structure and file name naming, as long as we determine the file directory structure and file name, then the corresponding routing address, URL address is also determined, that is, we combine three paths into one, three names into one

  2. Reduce useless code and reduce code duplication

    If we maintained the router.js file the usual way, we would add a new piece of code to the router.js file for every route we added. As the project grew, more and more routes were added, and soon the router.js file would become a very long text with most of the code repeating

So how to achieve automatic routing information generation?

3. Understand require.context

Require. context is an API provided by Webpack, through which we can dynamically read all file paths in a folder. In other words, we can batch read all file paths under views/

Here is the official demo

require.context('./test'.false./\.test\.js$/);
// create a context where the file is from the test directory and request ends with '.test.js'.
Copy the code
require.context('.. / '.true./\.stories\.js$/);
// create a context in which all files are from the parent folder and all of its children. Request ends with '.stories.js'
Copy the code

Require. context returns an object with three attributes: resolve, keys, and ID.

  • resolveIs a function that returns the module ID after the request was parsed.
  • keysIs also a function that returns an array of all the requests that could be processed by this Context Module.
  • idIs the module ID of the Context Module. It may be used by youmodule.hot.acceptWill be used.
function importAll(r) {
  r.keys().forEach(r);
}

importAll(require.context('.. /components/'.true./\.js$/));
Copy the code
const cache = {};

function importAll(r) {
  r.keys().forEach((key) = > (cache[key] = r(key)));
}

importAll(require.context('.. /components/'.true./\.js$/));
// At build-time, all required modules are populated into the cache object.
Copy the code

Therefore, we can obtain all vue file paths in all views through require.context, and generate array hierarchically through split

const importAll = (r) = > {
  return r.keys().map((key) = > key.slice(2).replace(".vue"."").split("/"));
};
const pages = importAll(require.context(".. /views".true./\.vue$/));
// pages
// 0: (4) ["activity", "activityList", "dataAnalysis", "index"]
// 1: (4) ["activity", "activityList", "details", "index"]
// 2: (3) ["activity", "activityList", "index"]
// 3: (4) ["activity", "activityList", "promote", "index"]
// 4: (3) ["activity", "analysisList", "index"]
// 13: ["index"]
Copy the code

When we get the path to a file, we can import it.

Generate routing objects

Our common route in production should look like this

  {
    name:"" / / routing
    path: ""./ / path
    component: ""./ / component
    meta: {} // Routing information
  }
Copy the code

Let’s create a new functiongetRoutes to retrieve the data we need

First, once we have the path to each component, we can get the component. We can also get the name and meta defined in the Component. For example, name and meta are defined in home.vue

// home.vue <template> <div class="home-wrapper"> <div class="home-content"> <h1>Home</h1> </div> </div> </template> <script> export default {name: "home", // Component name and route name are the same meta: {// Add the meta attribute, which is the same as that in route title: "Home ", keepAlive:false}}; </script>Copy the code
// getRoutes
async function getRoutes() {
  const routesPromise = pages.map(async (path) => {
    const { default: component } = await import(`.. /views/${path.join("/")}`);
    const { name = "", meta = {} } = component; // Name and meta defined in the component
    return {
      name,
      meta,
      component,
    };
  });
  const routes = await Promise.all(routesPromise); // Get all route objects via promise.all
  return {
    routes
  };
}
Copy the code

So far, we have obtained the component,name, and meta properties required by the Route object. Next, we need to obtain the corresponding path. The path situation is complicated, so we add a functiongenerateRoute to handle path generation

// generateRoute 
// If there is a dynamic route, the name of the dynamic route should start with '_'
// For example /list/:id the corresponding file structure should be:
// |--list
// ----_id.vue

const generateRoute = (path) = > {
  // Note: Process the root route
  if (path.length === 1) {
    const shortcut = path[0].toLowerCase();
    return shortcut.startsWith("index")?""
      : // Note: Handle dynamic routing
      shortcut.startsWith("_")? shortcut.replace("_".":")
      : shortcut;
  }
  // Note: Other routes are processed
  const lastElement = path[path.length - 1];
  // Note: Remove the last element starting with index
  if (lastElement.toLowerCase().startsWith("index")) {
    path.pop();
    // Note: Handle dynamic routing
  } else if (lastElement.startsWith("_")) {
    path[path.length - 1] = lastElement.replace("_".":");
  }
  return path.map((p) = > p.toLowerCase()).join("/");
};
Copy the code
// added to getRoutes method
const { default: component } = await import(`.. /views/${path.join("/")}`);
const { name = "", meta = {} } = component; // Name and meta defined in the component
// Add route path
const route = ` /${generateRoute([...path])}`;

return {
  name,
  path:route
  meta,
  component,
};
Copy the code
// The complete router/route.js is as follows

const importAll = (r) = > {
  return r.keys().map((key) = > key.slice(2).replace(".vue"."").split("/"));
};
const pages = importAll(require.context(".. /views".true./\.vue$/));

console.log(pages);

const generateRoute = (path) = > {
  // Note: Remove the first element if the route starts with index
  // Note: Process the root route
  if (path.length === 1) {
    const shortcut = path[0].toLowerCase();
    return shortcut.startsWith("index")?""
      : // Note: Handle dynamic routing
      shortcut.startsWith("_")? shortcut.replace("_".":")
      : shortcut;
  }
  // Note: Other routes are processed
  const lastElement = path[path.length - 1];
  // Note: Remove the last element starting with index
  if (lastElement.toLowerCase().startsWith("index")) {
    path.pop();
    // Note: Handle dynamic routing
  } else if (lastElement.startsWith("_")) {
    path[path.length - 1] = lastElement.replace("_".":");
  }
  return path.map((p) = > p.toLowerCase()).join("/");
};
async function getRoutes() {
  const routesPromise = pages.map(async (path) => {
    const { default: component } = await import(`.. /views/${path.join("/")}`);
    const { name = "", meta = {} } = component;
    // Generate a routing tree
    const route = ` /${generateRoute([...path])}`;
    return {
      path: route,
      name,
      meta,
      component,
    };
  });
  const routes = await Promise.all(routesPromise);
  return {
    routes
  };
}

export default getRoutes();
Copy the code

At this point, all the route attributes have been taken care of and the corresponding Route object has been generated. Next, use our Route object

5. Introduce the route object we generated

So far, we have automatically generated routing objects for the entire project through file pathfinding. The next step is to introduce route objects

// router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import routeObj from "./routes";
Vue.use(VueRouter);

async function getRouter() {
  let { routes } = await routeObj;
  const router = new VueRouter({
    routes,
  });
  router.beforeEach((to, from, next) = > {
  	// TODO
    next()
  });
  router.onError((error) = > {
		//TODO
  });

  return router;
}
export default getRouter();
Copy the code
// main.js
const init = async() = > {const getRouter = await import("./router/index.js");
  const router = await getRouter.default;
  new Vue({
    router,
    store,
    render: (h) = > h(App),
  }).$mount("#app");
};

init();
Copy the code

Generate routeTree and keepAlives array

So far, the effect of automatic routing generation has been achieved, but once we generate the corresponding front-end routing information through the file structure, in addition to not needing to manually maintain the routing information every time, we also determine the relationship between different levels before routing. In other words, we have the ability to generate a routeTree that represents the entire routing hierarchy

// Add the following code to the getRoutes method
  const keepAlives = [];
  const routeTree = {};
  const routesPromise = pages.map(async (path) => {
    const { default: component } = await import(`.. /views/${path.join("/")}`);
    const { name = "", meta = {} } = component;
    // Generate a routing tree
    const route = ` /${generateRoute([...path])}`;
    
    // ------------------ added ------------------------
    route
      .split("/")
      .filter((e) = > e)
      .reduce((tree, routePath, index, arr) = > {
        if (index === arr.length - 1) {
          if(! tree[routePath]) { tree[routePath] = {}; } tree[routePath].path = route; tree[routePath].name = name; tree[routePath].title = meta.title;return tree;
        }
        if(! tree[routePath]) { tree[routePath] = {nextPath: {}}; }return tree[routePath].nextPath;
      }, routeTree);
    // ---------------- added ----------------------------
    
    // Generate a keepAlive page
    if (meta.keepAlive) {
      keepAlives.push(name);
    }
    return {
      path: route,
      name,
      meta,
      component,
    };
  });
  const routes = await Promise.all(routesPromise);
  return {
    keepAlives,
    routeTree,
    routes,
  };
Copy the code

The function of keepAlives is easy to understand and can be determined each time using meta. KeepAlive in route objects instead of using keepAlives

<keep-alive :include="keepAlives" :max="10">
        <router-view></router-view>
</keep-alive>
Copy the code

So what does routeTree do?

Did you all write the breadcrumb component? Every Breadcrumb we write we need to pass in an array of routing objects to make the Breadcrumb clickable. Take the Breadcrumb component of iView for example

<Breadcrumb>
    <BreadcrumbItem to="/">
        <Icon type="ios-home-outline"></Icon> Home
    </BreadcrumbItem>
    <BreadcrumbItem to="/components/breadcrumb">
        <Icon type="logo-buffer"></Icon> Components
    </BreadcrumbItem>
    <BreadcrumbItem>
        <Icon type="ios-cafe"></Icon> Breadcrumb
    </BreadcrumbItem>
</Breadcrumb>
Copy the code

We need to specify the path manually every time. If we had a routeTree, we could have automatically generated the routing information needed by the Breadcrumb based on the hierarchy provided by the routeTree

<! -- Description: Global breadcrumb component -->
<template>
  <div class="wxBreadCrumb">
    <div class="wxBreadCrumbFlex">
      <Breadcrumb separator=">">
        <template v-for="(item, index) in breadcrumbArr">
          <BreadcrumbItem
            v-if="index ! == 0 && index < breadcrumbArr.length - 1"
            :key="item.title"
            :to="item.path"
          >
            {{ item.title }}
          </BreadcrumbItem>
          <BreadcrumbItem v-else :key="item.title">
            {{ item.title }}
          </BreadcrumbItem>
        </template>
      </Breadcrumb>
    </div>
  </div>
</template>

<script>
import routeObj from "@/router/routes";
export default {
  name: "BreadCrumb".// Register the component
  data() {
    return {
      breadcrumbArr: [],}; },// Life cycle - creation complete (access to the current this instance)
  async created() {
    let { routeTree } = await routeObj;
    let breadcrumbArr = [];
    // Get all the routing nodes from the root route to the current path
    this.$route.path
      .split("/")
      .filter((e) = > e)
      .reduce((tree, path) = > {
        breadcrumbArr.push({
          path: tree[path].path,
          name: tree[path].name,
          title: tree[path].title,
        });
        return tree[path].nextPath;
      }, routeTree);
    this.breadcrumbArr = breadcrumbArr; }};</script>
Copy the code

In this way, we can obtain all routing node information from the current routing address from the root node to the current node according to the routeTree, which means we can generate a complete breadcrumb path based on these nodes.

In addition, a common problem of single-page applications is that they cannot go back to the previous page after refreshing the page, because the browser history is cleared after refreshing. In this case, we can still use routeTree to obtain superior routing information to go back to the previous level

What other situations can you use routeTree for? You can try it out in your development

Seven,

Without a plan is perfect, and automatically generate routing are just ordinary a try, if you think of this scheme is applicable to all projects can try might make you relaxed many, of course, the present scheme is relatively simple, has not yet been covered many scenes, such as it is not support embedded routines (is actually can support, you just need to change me Our getRoutes method) and so on, if you teachers generate more new thinking sparks in the process of using, welcome to communicate with me more, after all, everyone adds firewood, the flame is high

reference

[1] VuEJS implementation of intelligent routing

[2] the vue – the router’s official website