Vue3 + Vite批量导入模块 / 资源

7,678 阅读5分钟

在Vue项目开发中,我们常常会导入一些外部的模块,或者是自己写模块导入。

但是模块多了,一个个地导入很显然不是一个好办法,因此我们可以批量导入。

1,前置基础知识 - JavaScript模块化编程

在以往开发普通网页程序时,我们直接使用script标签引入了js文件即可调用其函数,但是在Vue中你发现就不行了,因为Vue也是使用了模块化编程标准。

通常我们可以把一个封装了很多要复用的函数和变量的js文件称作模块,模块必须暴露(导出)其中的变量、函数才能被外部导入并调用其中的函数。

现在常见的模块标准有两个:CommonJSES6

在这里我们着重讲解ES6模块。

(1) export语句 - 暴露函数和变量使外部调用

现在我们自己创建一个js文件表示我们自己的模块,里面封装一些常常复用的函数、变量等等,就需要使用export暴露出来。

// 导出常量a
export const a = 'test';

// 导出函数myPrint
export function myPrint(msg) {
	console.log('[]' + msg);
}

可见在定义变量/函数时在前面加上export即可。

除此之外,还可以批量导出:

const a = 'test';

function myPrint(msg) {
	console.log('[]' + msg);
}

// 导出常量a和函数myPrint
export {
	a,
	myPrint
};

(2) import语句 - 导入变量/函数并使用

上面封装好了函数、变量等等并导出了,但是还是不能直接使用的。需要在要使用的地方进行导入操作:

// 导入函数myPrint和常量a
import {
	a,
	myPrint
} from './mymodule.js';

// 使用导入的变量和函数
console.log(a);
myPrint('msg');

这样就可以使用了。

可见import语法如下:

import { 变量1/函数1, 变量2/函数2, 变量3/函数3, ... } from 'js文件路径';

需要注意,导入的变量和函数一次可以有多个,用大括号括起来,并且导入的变量/函数名必须和模块中导出的变量/函数名一致!

(3) import * as xxx语句 - 全部导入

对于需要导入很多的模块,我们import后面需要写一长串的导入的变量和函数,因此我们还可以一次性全部导入。

对于上面的mymodule里面导出了一个变量和一个函数,在此我们可以一次性全部导入:

// 一次性全部导入该模块的内容并命名为my
import * as my from './mymodules.js';

// 调用模块属性a
console.log(my.a);
// 调用模块函数myPrint
my.myPrint('hhh');

可见,用import * as 名字 from '模块路径'的方式不仅可以方便地导入一个模块js文件的所有内容,还可以自己定义一个名字以方便调用。

(4) export default - 默认导出

上述的导出方式其实是命名式导出,其它文件导入时的变量/函数名必须和模块中导出的变量/函数名一致。

但是有时候不知道我们要导入的js文件中的函数/变量名怎么办呢?

可以使用默认导出,即export default语句,例如默认导出一个函数:

// 默认导出myPrint函数
export default function myPrint(msg) {
	console.log('[]' + msg);
}

默认导出后,导入时可以自行命名:

// 导入文件中默认导出的函数/变量并命名为p
import p from './mymodule.js';
//使用
p('msg');

可见这里没有使用大括号,并且导入时可以自行命名,不需要和原模块中函数名一样。

注意,一个js文件只能有一个默认导出!不能多次默认导出!

因此,一个有很多变量和函数的复用模块,我们可以这么写:

export default {
	// 变量a
	a: 'a',
	// 函数myPrint
	myPrint() {
		console.log('aa');
	}
}

调用时:

import m from './mymodule.js';

//使用
console.log(m.a);
m.myPrint();

很显然,这里是直接默认导出了一个大的匿名JavaScript对象,并把变量、函数写在这个对象里面。然后再导入,调用其中变量/函数即可。

其实在Vue开发中,我们用到的很多外部JavaScript的库,都是这么做的。

事实上,我们的Vue单文件组件原理不也是这样的?

2,在Vite工程中批量导入JavaScript模块

在之前的Vue-cli开发中,我们可以使用require.context()来实现批量导入。但是由于Vite是完全基于ES6模块的,因此就不能使用这种方式了。

当然,Vite官方也提供了实现批量导入的方式,即Glob导入方式,它能够实现批量导入资源,无论是JavaScript文件还是图片等等静态资源,并且提供了懒加载和立即加载的方式。

例如我现在工程目录下src/assets/js下有三个js文件:

image.png

每个模块中,都导出了属性和函数形式如下:

image.png

下面,我们来探讨一下懒加载立即加载这两种方式,大家按照需要选择其一即可。

(1) 懒加载

懒加载是glob函数导入的默认方式,通过动态导入实现,我们直接来看例子。

现在要在根组件App.vue批量导入它们,则在<script>部分开头写:

const lazyImportModules = import.meta.glob('./assets/js/*.js');

这样就导入了./assets/js下所有js模块文件。

当然这种方式也可以匹配多级目录

const lazyImportModules = import.meta.glob('./assets/js/**/*.js');

这样就导入了./assets/js下所有js文件及其子目录下的所有js文件。

在这里我们将其导入为一个变量importModules,这个变量里面到底是啥呢?我们在mounted函数里面打印一下看看:

console.log(lazyImportModules);

image.png

可见导入的是一个大对象,这个对象中的每个属性即为模块路径,而对应的值即为通过import函数导入的模块,模块的函数变量等等都在里面。

也就是说,导入后,lazyImportModules的真面目是这样的:

// Vite生成的代码
const lazyImportModules = {
	'./assets/js/one.js': () => import('/src/assets/js/one.js'),
	'./assets/js/two.js': () => import('/src/assets/js/two.js'),
	'./assets/js/three.js': () => import('/src/assets/js/three.js')
}

因此要使用模块时,可以调用对应属性的函数进行导入。

举个例子,现在要调用模块one中的print函数:

// 调用懒加载的one模块import函数进行导入
const lazyOneModule = await lazyImportModules['./assets/js/one.js']();
// 调用导入后one模块进行打印
lazyOneModule.default.print('这是one模块!');

可见上述有以下要点:

  • 从导入对象lazyImportModules中获取的指定模块导入,实质上是一个import函数,这个函数是异步的,最好是使用await关键字等待结果
  • 该函数执行返回后得到的对象的default属性,即为导入的模块本身,调用这个属性中的函数、属性即可访问到你导入的模块函数、属性

如果是第一次学习,大家可能有点绕,建议多使用console.log打印一下,看看从模块导入、执行导入的import函数以及导入后的模块是怎么组成的。

(2) 立即加载

懒加载虽然性能比起立即加载要好一点,不过也显而易见,使用起来很麻烦。

所以我们还可以使用立即加载的方式直接导入所有模块,而不是导入每个模块的import函数

通过立即加载的方式导入全部JavaScript模块:

const eagerImportModules = import.meta.glob('./assets/js/*.js', { eager: true });

可见,只要在glob函数加上第二个参数,指定eager选项为true即可。

我们打印导入结果eagerImportModules试试:

console.log(eagerImportModules);

image.png

可见比起懒加载,立即加载好像是帮我们执行了import函数,将其结果存放在结果中,只不过现在其大对象属性仍然是模块路径,但是其值不再是import函数,我们可以直接调用其值的default属性获取模块本身并调用模块。

可见,立即加载的导入结果eagerImportModules其真面目是这样的:

// Vite生成的代码
import * as one from './assets/js/one.js';
import * as two from './assets/js/two.js';
import * as three from './assets/js/three.js';

const eagerImportModules = {
	'./assets/js/one.js': { default: one },
	'./assets/js/two.js': { default: two },
	'./assets/js/three.js': { default: three }
}

那么现在调用起来也就很简单了,直接通过模块路径取得对象,调用其default属性即可调用模块,例如现在要调用模块twoprint函数:

// 调用立即加载的two模块的print函数
eagerImportModules['./assets/js/two.js'].default.print('这是two模块!');

可见这里就很方便了!不需要先执行懒加载中导入的import函数。

大家可以对比一下两种加载方式导入后的真面目是什么样的。

(3) 简化路径属性

到这相信大家又发现问题了:每次调用批量导入的模块,就要用模块的完整路径去取,还需要带上default属性,及其不方便。

我们能不能实现就用模块名(js文件名)去取出对应的模块呢?当然可以,事实上方法很多,我来讲一下我的思路。

以立即加载的方式为例,我们将上述存放导入模块的变量eagerImportModules中的每个属性名都给用字符串裁剪的方式替换成模块名,并将每个属性对应的值直接替换为它的default值不就行了吗?我们来试一下:

// 对批量导入存放模块的对象进行处理
// 先获取其全部属性
const keys = Object.keys(eagerImportModules);
// 执行批量替换操作
for (let path of keys) {
	// 裁剪字符串方式得到路径中的文件名(无扩展名)
	let name = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.js'));
	// 对原对象执行添加新的属性并删除旧属性达到处理效果
	eagerImportModules[name] = eagerImportModules[path].default;
	delete eagerImportModules[path];
}

现在,我们就可以很方便地进行调用了!

// 调用模块one的print
eagerImportModules.one.print('hello');
// 打印模块two的name属性
console.log(eagerImportModules.two.name);

如果是使用懒加载,可以在循环裁剪时,先执行每个模块的import函数得到结果,获取其default属性再存入结果。

3,在Vite工程中批量导入图片/音频等静态资源

在Vite中如果是想要动态绑定图片音频视频等等,也是要用import语句的,同样地图片多了,不想一个个地import应当怎么做呢?

事实上,我们还可以用import.meta.glob批量导入静态资源例如图片音频等等。

假设现在在src/assets/image下有很多图片。

image.png

我们仍然可以用上面的方式批量导入,这里以立即加载为例:

const importImages = import.meta.glob('./assets/images/*', { eager: true });

方式和上面一模一样,只不过这次导入的是静态资源,所以说上述importImagesdefault变成了对应资源的路径。

打印看一看:

image.png

同样地,我们可以把上述导入的每个default部分存起来再使用v-for批量呈现,试一下子,整个Vue文件代码如下:

<template>
	<div id="app">
		<!-- 然后就可以批量呈现了 -->
		<img v-for="item in images" :src="item" height="150" alt="无法显示"/>
	</div>
</template>

<script setup>
import { onMounted, reactive } from 'vue';

// 立即加载方式导入图片
const importImages = import.meta.glob('./assets/images/*', { eager: true });
// 存放批量导入的图片
const images = reactive([]);

onMounted(async () => {
	// 打印立即加载的图片
	console.log(importImages);
	// 把导入的图片对象中每个default属性(对应实际导入的图片)取出来放到data中的变量images中去
	for (let path in importImages) {
		images.push(importImages[path].default);
	}
});
</script>

效果:

image.png

可见Vite中的批量导入非常方便,无论是模块还是资源都可以。对于静态资源,我们是不是还可以用这个批量导入机制,结合我之前写的判断各个资源加载完成的方法的文章,更加简单地实现网站资源加载检测并制作进度条呢?

参考文档:

示例仓库地址:传送门