Open tyz98 opened 3 years ago
之前忽略了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的排序按下面的规则:
注: 关于如何命名文件来代表dynamic routes,dynamic nested routes和unknown 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'
}
]
在 vue-app/template
中有一个对应的 router.js
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 () {}
}
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) {
// ...
}
})
)
}
export default class TemplateContext {
constructor (builder, options) {
this.templateFiles = Array.from(builder.template.files)
this.templateVars = {}
}
get templateOptions () {
}
}
async generateRoutesAndFiles () {
const templateContext = this.createTemplateContext()
}
import TemplateContext from './context/template'
createTemplateContext () {
return new TemplateContext(this, this.options)
}
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" */))
文件:/packages/vue-app/template/utils.js
export function interopDefault (promise) {
return promise.then(m => m.default || m)
}
import hash from 'hash-sum'
在 packages/builder
定义了依赖:
"hash-sum": "^2.0.0"
packages/builder/src/context.js
导入几个方法:hash
、serialize
、uniqBy
、devalue
、wChunk
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
}
}
}
<% 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 ? '' : ', ')
})
export const wChunk = function wChunk (p = '') {
return p
}
目录结构: pages/ --| a.vue --| b.vue --| b/ -----| c.vue -----| c/ -------| d.vue 生成的router:
@nuxt/builder/dist/builder.js Builder的build方法
generateRoutesAndFiles方法
resolveRoutes方法
resolveFiles使用glob库返回an array of filenames
@nuxt/utils/dist/utils.js
举例说明createRoutes方法:
遍历files,每个file最终会得到一个route对象,并且会按照嵌套关系放在正确的“parent”中(可能是routes列表本身,或者某一个route的children列表) 每个file生成一个keys列表,keys即每个.vue文件的“路径”, 之后在遍历keys的过程中要更新route对象并更新最后要插入其中的parent对象。
遍历keys前初始化route为一个name、path均为空的对象,parent为最外层的routes: route = { name: '', path: '', component: r(srcDir, file) }; parent = routes
后面的例子中route中省略path和component
所以最终生成的routes为[{ name: 'a' }, { name: 'b', children: [{ name: 'b-c', chilren: [{ name: 'b-c-d' }] }] }]