使用:
//路由自动加载
// route/index.js
const autoLoadRoutes = []
const reqRouter = require.context('./', true, /\.js$/)
reqRouter.keys().forEach(name => {
if (name !== './index.js') {
autoLoadRoutes.push(...reqRouter(name).default)
}
})
const routes = [
{
path: '/',
component: () => import('@/views/home/home'),
},
...autoLoadRoutes,
{
path: '*', //匹配不上的路由统一走404
component: () => import('@/views/NotFound')
}
]
export default routes
// main.js
/* router */
import VueRouter from "vue-router"
import routes from "./router";
Vue.use(VueRouter);
const router = new VueRouter({
mode: "history",
base: __dirname,
routes: routes
});
=========================================
//vuex的module自动加载
// store/index.js
const autoLoadStores = {}
const reqStore = require.context('./', true, /\.js$/)
reqStore.keys().forEach(name => {
if (name !== './index.js') {
const key = name.replace('./', '').replace('/', '').replace('.js', '')
autoLoadStores[key] = {
...reqStore(name).default,
namespaced: true,
};
}
})
export default autoLoadStores
// main.js
/* vuex */
import Vuex from 'vuex'
import stores from "./store";
Vue.use(Vuex)
const store = new Vuex.Store({
modules: stores
})
组件篇:
1.简介
require.context是Webpack中用来管理依赖的一个函数,此方法会生成一个上下文模块,包含目录下所有模块的引用,通过正则表达式匹配,然后require进来
2.使用方法
require.context('.', false, /\.vue$/)
此方法有三个参数,
- 参数一:要查询的目录,上述代码指的是当前目录
- 参数二: 是否要查询子孙目录,方法默认的值为false
- 参数三:要匹配的文件的后缀,是一个正则表达式,上述我要查询的是.vue文件
require.context模块返回一个函数,这个函数可以接收一个参数
导出的方法有 3 个属性: resolve, keys, id。
- resolve 是一个函数,它返回请求被解析后得到的模块 id。
- keys 也是一个函数,它返回一个数组,由所有可能被上下文模块处理的请求组成。
- id 是上下文模块里面所包含的模块 id. 它可能在你使用 module.hot.accept 的时候被用到
3.应用场景
import Vue from 'vue'
let contexts = require.context('.', false, /\.vue$/)
contexts.keys().forEach(component => {
// debugger;
let componentEntity = contexts(component).default
// 使用内置的组件名称 进行全局组件注册
Vue.component(componentEntity.name, componentEntity)
})
上述代码中写的是一个全局注册的组件,用到了keys属性,返回一个数组,通过遍历,来完成组件注册,
contexts方法,内部就是返回引用webpack_require来加载模块,
使用componentEntity.name,来作为组件名,
即上述图片组件中的name,
文件目录
最后在main.js中引入index.js,该目录下的组件就全部被全局注册,可以在任意vue中使用这些组件,
注意
很多组件可能只是单个页面需要用到,我们在加载页面的过程中,希望组件也是按需加载的。组件全部注册,那么当页面需要引用其中某些组件时,是不是将所有的组件都打包引入了呢?全局注册的意思是不是已经全部被引入?
对于所有组件都在项目中被用到的情况来说,全局注册和按需引入的方式在打包和运行效率上并没有什么区别,只是相对来说按需引入可读性更强一点。
可根据自己的需求,合理使用
改良版:(文件名命名组件名,和组件内default.name解耦)
// autoLoadCpt.js
import Vue from 'vue'
const reqCpt = require.context('./', false, /\.vue$/)
const install = () => {
reqCpt.keys().forEach(name => {
const cpt = reqCpt(name)
const cptName = name.replace(/^\.\//, '').replace(/\.vue$/, '')
Vue.component(cptName, cpt.default)
})
}
export default install
// main.js
import autoLoadCpt from "./components/autoLoadCpt";
Vue.use(autoLoadCpt)
// app.vue
<template>
<div id="app">
<page1/>
<page2/>
</div>
</template>
路由篇:
// home.route.js
export default [
{
path: '/h1',
component: () => import('../components/Home1')
},
{
path: '/h2',
component: () => import('../components/Home2'),
children: [
{
path: 'h3',
component: () => import('../components/Home3')
}
]
}
]
// login.route.js
export default [
{
path: '/l1',
component: () => import('../components/Login1')
},
{
path: '/l2',
component: () => import('../components/Login2')
}
]
普通引入:
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import home from './home.route'
import login from './login.route'
const routes = [
{
path: '/',
component: () => import('../components/Welcome')
},
...home,
...login
]
const router = new VueRouter({
routes,
mode: 'history'
})
export default router
自动加载:
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const autoLoadRoutes = []
const reqRouter = require.context('./', true, /\.route\.js$/)
reqRouter.keys().forEach(name => {
autoLoadRoutes.push(...reqRouter(name).default)
})
const routes = [
{
path: '/',
component: () => import('../components/Welcome')
},
...autoLoadRoutes
]
const router = new VueRouter({
routes,
mode: 'history'
})
export default router
原理:
require.context是什么
一个webpack的api,通过执行require.context函数获取一个特定的上下文,主要用来实现自动化导入模块,在前端工程中,如果遇到从一个文件夹引入很多模块的情况,可以使用这个api,它会遍历文件夹中的指定文件,然后自动导入,使得不需要每次显式的调用import导入模块
什么时候需要用到require.context
如果有以下情况,可以考虑使用require.context替换
在Vue写的项目中,我把路由通过不同的功能划分成不同的模块,在index.js中一个个导入(原谅ide的警告-.-),但是如果项目变大了之后,每次手动import会显得有些力不从心,这里可以使用require.context函数遍历modules文件夹的所有文件一次性导入到index.js中
分析require.context
require.context函数接受三个参数
directory {String} -读取文件的路径
useSubdirectories {Boolean} -是否遍历文件的子目录
regExp {RegExp} -匹配文件的正则
语法: require.context(directory, useSubdirectories = false, regExp = /^.//);
借用webpakc官网的例子
require.context('./test', false, /.test.js$/);
上面的代码遍历当前目录下的test文件夹的所有.test.js结尾的文件,不遍历子目录
大概用图片来表示的话就是这样子的
在index.js中调用 require.context('./test', false, /.test.js$/);会得到test文件下3个文件的执行环境
值得注意的是require.context函数执行后返回的是一个函数,并且这个函数有3个属性
resolve {Function} -接受一个参数request,request为test文件夹下面匹配文件的相对路径,返回这个匹配文件相对于整个工程的相对路径
keys {Function} -返回匹配成功模块的名字组成的数组
id {String} -执行环境的id,返回的是一个字符串,主要用在module.hot.accept,应该是热加载?
这三个都是作为函数的属性(注意是作为函数的属性,函数也是对象,有对应的属性)
talk is cheap ,show me the code
结合工程看一下这3个属性返回了什么
我们在里层的modules文件夹新建一个index.js,用来收集所有的模块然后一次性导出给外层的index.js
这里我们先上代码,代码是写在里层的index.js中的(代码借鉴于加快Vue项目的开发速度)
这里我把require.context函数执行后的代码赋值给了files变量,files中保存了图一的以.js结尾的文件,files是个函数,我们分别调用者3个属性看看会返回什么
可以看到
执行了keys方法返回了一个由匹配文件的文件名组成的数组
id属性返回了匹配的文件夹的相对于工程的相对路径,是否遍历子目录,匹配正则组成的字符串
对于resolve方法可以看到它是一个函数接受req参数,经过实践我发现这个req参数的值是keys方法返回的数组的元素,接着我们传入其中一个元素执行resolve函数
resolve方法返回了一个字符串代表着传入参数的文件相对于整个工程的相对路径
同时files作为一个函数,也接受一个req参数,这个和resolve方法的req参数是一样的,即匹配的文件名的相对路径,而files函数返回的是一个模块,这个模块才是真正我们需要的
这个Module模块和使用import导入的模块是一样的
回到工程
首先调用require.context导入某个文件夹的所有匹配文件,返回执行上下文的环境赋值给files变量
声明一个configRouters用来暴露给外层index.js作为vue-router的数组
调用files函数的keys方法返回modules文件夹下所有以.js结尾的文件的文件名,返回文件名组成的数组
遍历数组每一项,如果是index.js就跳过(index.js并不是路由模块),调用files函数传入遍历的元素返回一个Modules模块
因为我的路径是用export default导出的,所以在Module模块的default属性中获取到我导出的内容(即路由的结构),类似这种样子
- 将上一步返回的所有路由结构添加到configRouters数组然后暴露给外层的index.js
- 外层引入后导入到vue-router中就可以使用了
写在后面
在使用require.context自动导入路由文件时发现一个问题,路由的顺序不是你期望的样子,因为webpack是根据你文件夹中文件的位置排序的,这个时候需要定义一个标识符来给路由数组排序,这里我们给每个文件夹最上层的路由添加一个sort属性用于排序
随后在读取模块后,给外层index传入路由配置前,给路由的模块排序