Skip to content

Instantly share code, notes, and snippets.

@chrisnicola
Last active April 11, 2018 11:52
Show Gist options
  • Save chrisnicola/db7eaf95fd39579c8eff8c2f6759798e to your computer and use it in GitHub Desktop.
Save chrisnicola/db7eaf95fd39579c8eff8c2f6759798e to your computer and use it in GitHub Desktop.
export function parseName(path) {
const name = path.toLowerCase()
// Remove starting ./
.replace(/^\.\//, '')
// Parameters
.replace(/\/_(\w+)(\/|\.vue$)/, '/:$1/')
// Remove file extension and 'index'
.replace(/(index)?\.vue$/, '')
// Remove trailing '/'
.replace(/\/$/, '');
// Replace beginning '/'
if (name === '') return 'root';
return name;
}
export function parsePathTemplate(path) {
const template = path.toLowerCase()
// Remove starting ./
.replace(/^\.\//, '')
// Conditional parameters
.replace(/\/_(\w+).vue$/, '/:$1?')
// Required parameters
.replace(/\/_(\w+)\//, '/:$1/')
// Remove file extension and 'index'
.replace(/(index)?\.vue$/, '')
// Remove trailing '/'
.replace(/\/$/, '');
// Replace beginning '/'
return `/${template}`;
}
export function hasChildren(path) {
return !path.match(/(index)\.vue$/i) &&
!path.match(/\/_(\w+).vue$/i);
}
export function generateRoute(key, component) {
const route = { ...component.route, component };
if (!('name' in route)) route.name = parseName(key);
if (!('path' in route)) route.path = parsePathTemplate(key);
if (hasChildren(key)) route.children = [];
return route;
}
export function nestRoutes([route, ...rest]) {
let routes = rest;
if (!route) return [];
if (route.children) {
const children = routes.filter(r => r.path.startsWith(route.path));
routes = routes.filter(r => !r.path.startsWith(route.path));
if (children.length === 0) {
delete route.children;
} else {
route.children = nestRoutes(children);
}
}
return [route, ...nestRoutes(routes)];
}
export function buildRoutes(context) {
const routes = context.keys().map(key => {
const component = context(key).default;
return generateRoute(key, component);
});
return nestRoutes(routes);
}
import assert from 'assert';
import sinon from 'sinon';
import {
parsePathTemplate,
parseName,
generateRoute,
buildRoutes,
} from './build_routes';
describe('build_routes', () => {
it('parsePathTempalte', async () => {
const expectations = {
'./index.vue': '/',
'./clients.vue': '/clients',
'./clients/_id.vue': '/clients/:id?',
'./clients/_name.vue': '/clients/:name?',
'./clients/_id/index.vue': '/clients/:id',
'./clients/_name/index.vue': '/clients/:name',
'./clients/_id/accounts.vue': '/clients/:id/accounts',
'./my_account.vue': '/my_account',
'./my-account.vue': '/my-account',
};
Object.entries(expectations).forEach(([path, expected]) => {
const template = parsePathTemplate(path);
assert.equal(template, expected, `parses '${path}' as '${expected}'`);
});
});
it('parseName', async () => {
const expectations = {
'./index.vue': 'root',
'./clients.vue': 'clients',
'./clients/_id.vue': 'clients/:id',
'./clients/_name.vue': 'clients/:name',
'./clients/_id/index.vue': 'clients/:id',
'./clients/_name/index.vue': 'clients/:name',
'./clients/_id/accounts.vue': 'clients/:id/accounts',
'./my_account.vue': 'my_account',
'./my-account.vue': 'my-account',
};
Object.entries(expectations).forEach(([path, expected]) => {
const template = parseName(path);
assert.equal(template, expected, `parses '${path}' as '${expected}'`);
});
});
it('generateRoute', async () => {
const component = {};
let route = generateRoute('index.vue', component);
assert.equal(route.path, '/', 'Generates a path for the route if there is no route.path.');
assert.equal(route.name, 'root', 'Generates a name for the route if there is no route.name.');
assert.equal(route.component, component, 'Sets the component for the route.');
component.route = { path: '/path', name: 'name' };
route = generateRoute('index.vue', component);
assert.equal(route.path, '/path', 'Uses component route.path if there is one');
assert.equal(route.name, 'name', 'Uses component route.name if there is one.');
});
it('buildRoutes', async () => {
const context = sinon.stub();
const component = {};
context.returns({ default: component });
context.keys = () => ['./index.vue', './clients.vue'];
let routes = buildRoutes(context);
assert.equal(routes.length, 2, 'creates 2 routes for two non-child keys in the context');
context.keys = () => ['./index.vue', './clients.vue', './clients/_id.vue'];
routes = buildRoutes(context);
const parent = routes.find(r => r.name === 'clients');
assert(parent.children[0].path === '/clients/:id?', 'creates a nested child route');
});
});
import Router from 'vue-router';
import routes from './routes';
import { installRouterPlugins } from '@/lib/vue';
const router = new Router({
mode: 'history',
routes,
});
/* istanbul ignore next */
installRouterPlugins(router);
export default router;
import { buildRoutes } from './build_routes';
const context = require.context('./', true, /\.vue$/);
export default buildRoutes(context);
@chrisnicola
Copy link
Author

Note: routes.js goes in the root of your /views folder and from that point any .vue file dropped in that folder (or subfolders) will get mapped to a route. The export of routes.js is the route configuration which gets passed to the vue router.

@chrisnicola
Copy link
Author

chrisnicola commented Apr 10, 2018

Note 2: Also this flat file layout is just for the sake of the Gist but it would work. I would typically export build_routes from @/lib/vue. The fully confiugred router would be exported by /router/index.js.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment