各位老铁,早上好!今天咱来唠唠 Nuxt.js 源码里的约定式路由和自动导入,保证让你听完直呼 "卧槽,原来是这么回事!"
咱们先热个身,搞清楚啥是约定式路由和自动导入,免得一会儿懵逼。
约定式路由 (Convention-based Routing):
简单来说,就是 Nuxt.js 根据你文件目录的结构,自动生成路由规则。你不用手动配置路由表,只要按照它的规矩放文件,路由就自动搞定了。 就像你住酒店,不用自己铺床叠被,酒店阿姨都给你安排得明明白白。
自动导入 (Auto-imports):
这个更爽,你不用 import
一大堆东西,Nuxt.js 自动帮你把常用的组件、函数、 composables 导入到你的组件里。 就像你用智能家居,不用手动开灯关灯,它自动感应,方便得一匹。
OK,概念搞清楚了,咱们就深入源码,看看 Nuxt.js 是怎么实现这两个骚操作的。
一、约定式路由:文件目录就是你的路由表
Nuxt.js 的约定式路由的核心在于扫描 pages
目录下的文件结构,然后生成对应的路由规则。 咱从入口文件开始,一层一层扒它的皮。
-
nuxt/kit
和@nuxt/kit-builder
:构建和路由配置Nuxt.js 的核心构建逻辑位于
nuxt/kit
和@nuxt/kit-builder
包中。nuxt/kit
提供了各种构建工具函数,而@nuxt/kit-builder
则负责实际的构建过程。 其中,createResolver
用于解析文件路径,useNitro
则用于配置 Nitro 服务器。 -
pages/index.vue
=>/
最简单的例子,
pages
目录下有个index.vue
文件,Nuxt.js 自动生成/
路由。 这就像默认首页一样,你放个index.html
,浏览器自动给你显示。 -
pages/about.vue
=>/about
如果有个
about.vue
文件,Nuxt.js 就生成/about
路由。 这就像你创建了一个新的网页,直接访问你的域名/about
就能看到。 -
pages/users/index.vue
=>/users
如果有个
pages/users/index.vue
文件,Nuxt.js 就生成/users
路由。 这就像你创建了一个users
目录,index.vue
就是这个目录的默认入口。 -
pages/users/[id].vue
=>/users/:id
重点来了!如果有个
pages/users/[id].vue
文件,Nuxt.js 就生成/users/:id
路由。 这里的[id]
表示一个动态路由参数,id
就是参数名。 访问/users/123
,id
的值就是123
。 -
pages/products/[...slug].vue
=>/products/:slug+
如果有个 pages/products/[...slug].vue
文件,Nuxt.js 就生成 /products/:slug+
路由。 这里的 [...slug]
表示一个动态路由参数,是一个可重复的数组,可以匹配多个路径段。访问 /products/electronics/cameras/dslr
,slug
的值就是 ['electronics', 'cameras', 'dslr']
。
代码示例 (简化版,仅用于说明原理):
// 假设这是 Nuxt.js 内部简化版的路由生成函数
async function generateRoutes(pagesDir) {
const routes = [];
// 读取 pages 目录下的所有文件
const files = await readFiles(pagesDir);
for (const file of files) {
// 解析文件路径,提取路由信息
const route = parseRouteFromFile(file);
routes.push(route);
}
return routes;
}
// 解析文件路径,生成路由规则
function parseRouteFromFile(file) {
const path = file.replace(/^pages/, '').replace(/.vue$/, ''); // 提取路径
let routePath = path.replace(/[([^]]+)]/g, ':$1'); // 替换动态参数
if(routePath.includes('[...')){
routePath = routePath.replace(/[...([^]]+)]/g, ':$1+'); // 替换可重复的动态参数
}
if (routePath.endsWith('/index')) {
routePath = routePath.replace(//index$/, ''); // 去掉 index
}
if (routePath === '') {
routePath = '/'; // 根路由
}
return {
path: routePath,
component: file, // 组件路径
};
}
// 模拟读取文件
async function readFiles(dir) {
// 实际 Nuxt.js 会使用更复杂的 glob 匹配和文件遍历
return [
'pages/index.vue',
'pages/about.vue',
'pages/users/index.vue',
'pages/users/[id].vue',
'pages/products/[...slug].vue',
];
}
// 调用函数,生成路由
async function main() {
const routes = await generateRoutes('pages');
console.log(JSON.stringify(routes, null, 2));
}
main();
输出结果 (简化版):
[
{
"path": "/",
"component": "pages/index.vue"
},
{
"path": "/about",
"component": "pages/about.vue"
},
{
"path": "/users",
"component": "pages/users/index.vue"
},
{
"path": "/users/:id",
"component": "pages/users/[id].vue"
},
{
"path": "/products/:slug+",
"component": "pages/products/[...slug].vue"
}
]
总结:
| 文件路径 | 路由路径 | 说明
实际的 Nuxt.js 实现会更加复杂,涉及到文件系统的监听、缓存、路由表的合并等,这里只是为了方便理解,做了大幅简化。
二、自动导入:解放你的手指,告别 import
地狱
自动导入的核心在于 Nuxt.js 在构建时扫描你的项目,找到所有可自动导入的模块,然后生成一个类型声明文件 (通常是 *.d.ts
),这个文件告诉 TypeScript 编译器,这些模块是全局可用的。
-
nuxt.config.js
配置:首先,你需要在
nuxt.config.js
中配置autoImports
选项,指定哪些模块需要自动导入。export default { autoImports: { dirs: [ // 扫描 composables 目录 'composables', // 扫描 utils 目录 'utils', ], global: true, // 是否全局注册 imports: [ // 明确指定需要自动导入的模块 { name: 'useHead', as: 'useCustomHead', from: '#app' } ] }, }
-
扫描目录:
Nuxt.js 会扫描
dirs
选项指定的目录,找到所有的 JavaScript 和 TypeScript 文件。 比如,扫描composables
目录,找到composables/useCounter.ts
文件。 -
生成类型声明文件:
Nuxt.js 会根据扫描结果,生成一个类型声明文件,例如
nuxt.d.ts
。这个文件会声明所有可自动导入的模块。代码示例 (简化版):
// nuxt.d.ts import type { useCustomHead } from '#app' declare module '#app' { interface NuxtApp { $useCounter: () => { count: number; increment: () => void; }; } } declare module '@vue/runtime-core' { interface ComponentCustomProperties { $useCounter: () => { count: number; increment: () => void; }; useCustomHead: typeof useCustomHead; } } export { };
-
使用自动导入的模块:
现在,你可以在你的组件中直接使用
useCounter
和useCustomHead
,而不需要手动import
。<template> <div> <p>Count: {{ count }}</p> <button @click="increment">Increment</button> <button @click="useCustomHead({title:'test'})">useCustomHead</button> </div> </template> <script setup> const { count, increment } = useCounter(); // 直接使用 useCounter </script>
代码示例 (简化版,仅用于说明原理):
// 假设这是 Nuxt.js 内部简化版的自动导入生成函数
async function generateAutoImports(dirs,imports) {
const autoImports = {};
// 扫描目录
for (const dir of dirs) {
const files = await readFiles(dir);
for (const file of files) {
const moduleName = parseModuleNameFromFile(file);
autoImports[moduleName] = file;
}
}
for (const item of imports) {
autoImports[item.name] = item.from;
}
// 生成类型声明文件
const dtsContent = generateDtsContent(autoImports);
await writeFile('nuxt.d.ts', dtsContent);
}
// 解析模块名
function parseModuleNameFromFile(file) {
return file.replace(/^composables//, '').replace(/.ts$/, '');
}
// 生成类型声明文件内容
function generateDtsContent(autoImports) {
let content = ``;
for (const moduleName in autoImports) {
const filePath = autoImports[moduleName];
content += `declare module '#app' {
interface NuxtApp {
$${moduleName}: () => any;
}
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$${moduleName}: () => any;
}
}
export { };
`;
}
return content;
}
// 模拟读取文件
async function readFiles(dir) {
if (dir === 'composables') {
return ['composables/useCounter.ts'];
}
return [];
}
// 模拟写入文件
async function writeFile(file, content) {
console.log(`写入文件: ${file}`);
console.log(content);
}
// 调用函数,生成自动导入
async function main() {
await generateAutoImports(['composables'],[{ name: 'useHead', as: 'useCustomHead', from: '#app' }]);
}
main();
总结:
| 选项 | 作用 构建前端应用。
| 选项 | 作用