Giter Site home page Giter Site logo

nuxt约定式路由实现 about fe-weekly HOT 2 OPEN

tyz98 avatar tyz98 commented on June 24, 2024
nuxt约定式路由实现

from fe-weekly.

Comments (2)

tyz98 avatar tyz98 commented on June 24, 2024

关于生成的routes的顺序:

之前忽略了createRoutes最后的sortRoutes方法:

const DYNAMIC_ROUTE_REGEX = /^\/([:*])/;

const sortRoutes = function sortRoutes (routes) {
  routes.sort((a, b) => {
    if (!a.path.length) {
      return -1
    }
    if (!b.path.length) {
      return 1
    }
    // Order: /static, /index, /:dynamic
    // Match exact route before index: /login before /index/_slug
    if (a.path === '/') {
      return DYNAMIC_ROUTE_REGEX.test(b.path) ? -1 : 1
    }
    if (b.path === '/') {
      return DYNAMIC_ROUTE_REGEX.test(a.path) ? 1 : -1
    }

    let i;
    let res = 0;
    let y = 0;
    let z = 0;
    const _a = a.path.split('/');
    const _b = b.path.split('/');
    for (i = 0; i < _a.length; i++) {
      if (res !== 0) {
        break
      }
      y = _a[i] === '*' ? 2 : _a[i].includes(':') ? 1 : 0;
      z = _b[i] === '*' ? 2 : _b[i].includes(':') ? 1 : 0;
      res = y - z;
      // If a.length >= b.length
      if (i === _b.length - 1 && res === 0) {
        // unless * found sort by level, then alphabetically
        res = _a[i] === '*' ? -1 : (
          _a.length === _b.length ? a.path.localeCompare(b.path) : (_a.length - _b.length)
        );
      }
    }

    if (res === 0) {
      // unless * found sort by level, then alphabetically
      res = _a[i - 1] === '*' && _b[i] ? 1 : (
        _a.length === _b.length ? a.path.localeCompare(b.path) : (_a.length - _b.length)
      );
    }
    return res
  });

  routes.forEach((route) => {
    if (route.children) {
      sortRoutes(route.children);
    }
  });

  return routes
};

使用了sort方法,依据route.path进行排序。

两个同级路由对象a,b的排序按下面的规则:

  • 按level遍历, 在level i中没有比较结果的按下一个level继续比较
    1. 静态路由先于动态路由先于unknown动态路由(*)
    2. 规则i比较结果为相等,但a已经没有下一个level,但b仍有下一个level
      • 若a,b的这一level都是*,则b先于a
      • 否则a先于b
    3. 规则i比较结果为相等,且a,b都没有下一个level,则按字母表顺序
  • 特殊处理:
    • 对于path是'/'的路由,除了第1个level就是动态路由或*的路由排在它的后面,其余路由都先于'/'

注:
关于如何命名文件来代表dynamic routesdynamic nested routesunknown dynamic routes

举例:

(仅作为例子,实际上不会这样写,这里并不是所有路由都能被访问到)
目录结构:

pages/
--| index.vue
--| b.vue
--| detail/
-----| _.vue
-----| _a.vue
-----| _a/
-------| _b.vue
-----| _id.vue
-----| c.vue
-----| d/
-------| e.vue
--| u/
-----| _id.vue

生成routes:

[
  {
    name: 'detail',
    path: '/detail',
    component: '/pages/detail/index.vue',
    chunkName: 'pages/detail/index'
  },
  {
    name: 'detail-c',
    path: '/detail/c',
    component: '/pages/detail/c.vue',
    chunkName: 'pages/detail/c'
  },
  {
    name: 'detail-d-e',
    path: '/detail/d/e',
    component: '/pages/detail/d/e.vue',
    chunkName: 'pages/detail/d/e'
  },
  {
    name: 'detail-a',
    path: '/detail/:a',
    component: '/pages/detail/_a.vue',
    chunkName: 'pages/detail/_a',
    children: [ [Object] ]
  },
  {
    name: 'detail-id',
    path: '/detail/:id',
    component: '/pages/detail/_id.vue',
    chunkName: 'pages/detail/_id'
  },
  {
    name: 'u-id',
    path: '/u/:id?',
    component: '/pages/u/_id.vue',
    chunkName: 'pages/u/_id'
  },
  {
    name: 'detail-all',
    path: '/detail/*',
    component: '/pages/detail/_.vue',
    chunkName: 'pages/detail/_'
  },
  {
    name: 'index',
    path: '/',
    component: '/pages/index.vue',
    chunkName: 'pages/index'
  }
]

from fe-weekly.

dailynodejs avatar dailynodejs commented on June 24, 2024

关于 .nuxt/router.js 的文件是如何生成的

vue-app/template 中有一个对应的 router.js

routerOptions

export const routerOptions = {
  mode: '<%= router.mode %>',
  base: '<%= router.base %>',
  linkActiveClass: '<%= router.linkActiveClass %>',
  linkExactActiveClass: '<%= router.linkExactActiveClass %>',
  scrollBehavior,
  <%= isTest ? '/* eslint-disable array-bracket-spacing, quotes, quote-props, object-curly-spacing, key-spacing */' : '' %>
  routes: [<%= _routes %>],
  <%= isTest ? '/* eslint-enable array-bracket-spacing, quotes, quote-props, object-curly-spacing, key-spacing */' : '' %>
  <% if (router.parseQuery) { %>parseQuery: <%= serializeFunction(router.parseQuery) %>,<% } %>
  <% if (router.stringifyQuery) { %>stringifyQuery: <%= serializeFunction(router.stringifyQuery) %>,<% } %>
  fallback: <%= router.fallback %>
}

转换后的:

import scrollBehavior from './router.scrollBehavior.js'

export const routerOptions = {
  mode: 'history',
  base: decodeURI('/'),
  linkActiveClass: 'nuxt-link-active',
  linkExactActiveClass: 'nuxt-link-exact-active',
  scrollBehavior,
  routes: [],
  fallback: false
}

核心

packages/builder/src/builder.js,依赖了 lodash/template

import template from 'lodash/template'
import TemplateContext from './context/template'

export default class Builder {
  constructor (nuxt, bundleBuilder) {
  }

  async build () {}
}

compileTemplates

import fsExtra from 'fs-extra'

async compileTemplates (templateContext) {
  await Promise.all(
    templateFiles.map(async (templateFile) => {
      const { src, dst, custom } = templateFile
      // ...
      const fileContent = await fsExtra.readFile(src, 'utf8')
      let content
      try {
        const templateFunction = template(fileContent, templateOptions)
      } catch (err) {
        // ...
      }
    })
 )
}

packages/builder/src/context/template

export default class TemplateContext {
  constructor (builder, options) {
    this.templateFiles = Array.from(builder.template.files)
    this.templateVars = {}
  }

  get templateOptions () {
  }
}

generateRoutesAndFiles

async generateRoutesAndFiles () {
  const templateContext = this.createTemplateContext()
}

createTemplateContext

import TemplateContext from './context/template'

createTemplateContext () {
  return new TemplateContext(this, this.options)
}

.nuxt 目录如何生成

import {
  r
} from '@nuxt/utils'

async build () {
  await fsExtra.emptyDir(r(this.options.buildDir))
}

依赖了一个 lodash

import uniqBy from 'lodash/uniqBy'

循环生成:

import { interopDefault } from './utils'

<%= uniqBy(_components, '_name').map((route) => {
  if (!route.component) return ''
  const path = relativeToBuild(route.component)
  const chunkName = wChunk(route.chunkName)
  const name = route._name

  if (splitChunks.pages) {
    return `const ${name} = () => interopDefault(import('${path}' /* webpackChunkName: "${chunkName}" */))`
  } else {
    return `import ${name} from '${path}'`
  }
}).join('\n')%>

输出的:

import { interopDefault } from './utils'

const _1bdf586c = () => interopDefault(import('../pages/a.vue' /* webpackChunkName: "pages/a" */))
const _f963b7de = () => interopDefault(import('../pages/a/b.vue' /* webpackChunkName: "pages/a/b" */))
const _bef819ea = () => interopDefault(import('../pages/a/c.vue' /* webpackChunkName: "pages/a/c" */))

interopDefault

文件:/packages/vue-app/template/utils.js

export function interopDefault (promise) {
  return promise.then(m => m.default || m)
}

hash

import hash from 'hash-sum'

packages/builder 定义了依赖:

"hash-sum": "^2.0.0"

模板里面的

packages/builder/src/context.js

导入几个方法:hashserializeuniqBydevaluewChunk

import hash from 'hash-sum'
import serialize from 'serialize-javascript'
import uniqBy from 'lodash/uniqBy'
import devalue from '@nuxt/devalue'

import { r, wp, wChunk, serializeFunction, isFullStatic } from '@nuxt/utils'

export default class TemplateContext {
  get templateOptions () {
    return {
      imports: {
        uniqBy,
        serialize,
        devalue,
        hash,
        wChunk,
        ...
      },
      interpolate: /<%=([\s\S]+?)%>/g
    }
  }
}

recursiveRoutes

<% function recursiveRoutes(routes, tab, components, indentCount) {
  let res = ''
  const baseIndent = tab.repeat(indentCount)
  const firstIndent = '\n' + tab.repeat(indentCount + 1)
  const nextIndent = ',' + firstIndent
  routes.forEach((route, i) => {
    
  })
  return res
}
const _components = []
const _routes = recursiveRoutes(router.routes, '  ', _components, 1)
%>

循环 routes,这里面就会用到 hash

routes.forEach((route, i) => {
  let resMap = ''
  // If need to handle named views
  if (route.components) {
    let _name = '_' + hash(route.components.default)
    if (splitChunks.pages) {
      resMap += `${firstIndent}${tab}default: ${_name}`
    } else {
      resMap += `${firstIndent}${tab}default: () => ${_name}.default || ${_name}`
    }
    for (const k in route.components) {
      _name = '_' + hash(route.components[k])
      const component = { _name, component: route.components[k] }
      if (k === 'default') {
        components.push({
          ...component,
          name: route.name,
          chunkName: route.chunkName
        })
      } else {
        components.push({
          ...component,
          name: `${route.name}-${k}`,
          chunkName: route.chunkNames[k]
        })
        if (splitChunks.pages) {
          resMap += `${nextIndent}${tab}${k}: ${_name}`
        } else {
          resMap += `${nextIndent}${tab}${k}: () => ${_name}.default || ${_name}`
        }
      }
    }
    route.component = false
  } else {
    route._name = '_' + hash(route.component)
    components.push({ _name: route._name, component: route.component, name: route.name, chunkName: route.chunkName })
  }
  // @see: https://router.vuejs.org/api/#router-construction-options
  res += '{'
  res += firstIndent + 'path: ' + JSON.stringify(route.path)
  res += (route.components) ? nextIndent + 'components: {' + resMap + '\n' + baseIndent + tab + '}' : ''
  res += (route.component) ? nextIndent + 'component: ' + route._name : ''
  res += (route.redirect) ? nextIndent + 'redirect: ' + JSON.stringify(route.redirect) : ''
  res += (route.meta) ? nextIndent + 'meta: ' + JSON.stringify(route.meta) : ''
  res += (typeof route.props !== 'undefined') ? nextIndent + 'props: ' + (typeof route.props === 'function' ? serialize(route.props) : JSON.stringify(route.props)) : ''
  res += (typeof route.caseSensitive !== 'undefined') ? nextIndent + 'caseSensitive: ' + JSON.stringify(route.caseSensitive) : ''
  res += (route.alias) ? nextIndent + 'alias: ' + JSON.stringify(route.alias) : ''
  res += (route.pathToRegexpOptions) ? nextIndent + 'pathToRegexpOptions: ' + JSON.stringify(route.pathToRegexpOptions) : ''
  res += (route.name) ? nextIndent + 'name: ' + JSON.stringify(route.name) : ''
  if (route.beforeEnter) {
    if(isTest) { res += ',\n/* eslint-disable indent, semi */' }
    res += (isTest ? firstIndent : nextIndent) + 'beforeEnter: ' + serialize(route.beforeEnter)
    if(isTest) { res += firstIndent + '/* eslint-enable indent, semi */' }
  }
  res += (route.children) ? nextIndent + 'children: [' + recursiveRoutes(routes[i].children, tab, components, indentCount + 1) + ']' : ''
  res += '\n' + baseIndent + '}' + (i + 1 === routes.length ? '' : ', ')
})

@nuxt/utils

wChunk

export const wChunk = function wChunk (p = '') {
  return p
}

from fe-weekly.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.