本篇基于图片加载的基础来简单介绍require, require.context和import的区别!
现如今的前端项目用webpack打包已经成为了行业趋势,然而在此模式的前提下,图片的引入是我们不可避免的问题。正常的图片引入是用img标签或者元素背景图的方式,采用这种方式的图片,webpack都能正常打包并显示。但是如果直接在js文件中定义图片路径,并赋给图片元素的话不能正常显示的,这是因为webpack打包后,会将静态资源文件放在dist/static/img下,我们的网站实际上以dist目录作为根目录,并由此加载该目录下的index.html所需的css、js、img等。而当我们在js文件中动态引入图片时url-loader是无法探测到图片路径的。我们build后发现,图片根本不会打包输出到dist目录(webpack是按需打包的)。
此处介绍了三种动态引入图片的方式:
1. require
1 2 | // html <img :src="imgList[0]" /> |
1 2 3 4 5 6 7 | // xxx.js let imgList = [ require('../images/a.png'), require('../images/b.jpg') ] |
官方文档:如果你的 request 含有表达式(expressions),会创建一个上下文(context),因为在编译时(compile time)并不清楚具体是哪一个模块被导入。
原因:
- webpack本身是一个预编译路径 不能require纯变量的打包工具,无法预测未知变量路径
- require(path) ,path 至少要有三部分组成, 目录,文件名和后缀
- 目录:webpack 才知道从哪里开始查找
- 后缀 文件后缀,必须要加上,不然会报错
- 文件名:可用变量表示
1. 错误引用
上述意思即是不能通过以下这种方式加载图片,这种方式下,webpack找不到具体是哪个模块(图片)被引入,故而无法将图片hash并输出到dist文件下。
1 2 3 | let imgUrlStr = '../images/a.png'; let imgUrl = require(imgUrlStr); |
2. 正确引用
鉴于require在纯变量的情况下找不到模块,所以我们至少要在require参数中写明一个目录(如下边代码中的example 2和example 3),这样的话,虽然不知道具体的模块,但是webpack也会为我们做些分析工作:
-
- 分析目录: ‘../images’
-
- 提取正则表达式 : ‘/^.*.png$/‘
但是此种情况下,webpack生成的上下文模块(context module)。它包含目录下的所有模块的引用,是通过一个 request 解析出来的正则表达式,去匹配目录下所有符合的模块,然后都 require 进来。此 context module 包含一个 map 对象,会把 request 中所有模块翻译成对应的模块 id。这意味着 webpack 能够支持动态地 require,但会导致所有可能用到的模块都包含在 bundle 中。
1 2 3 4 5 6 7 8 9 | let imgName = 'a'; let imgAllName = 'a.png'; // example 1 let imgUrl = require('../images/a.png'); // 纯字符串 // example 2 let imgUrl = require('../images/' + imgAllName); // 目录 + 文件全名 // example 3 let imgUrl = require('../images/' + imgName + '.png'); // 目录 + 文件名 + 后缀 |
2. require.context
此方法可理解为require方法的详细实现,用require.context() 函数来创建自己的 context。可以给这个函数传入三个参数:一个要搜索的目录,一个标记表示是否还搜索其子目录, 以及一个匹配文件的正则表达式。
1 2 3 4 5 6 | // 语法 require.context(directory, useSubdirectories = false, regExp = /^.//); // example // 创建出一个 context,其中所有文件都来自父文件夹及其所有子级文件夹,request 以 `.png` 结尾。 require.context('../images', true, /.png$/); |
1. require.context返回值
一个 context module 会导出一个(require)函数,此函数可以接收一个参数:request。
此导出函数有三个属性:resolve, keys, id。
- resolve 是一个函数,它返回 request 被解析后得到的模块 id。
- keys 也是一个函数,它返回一个数组,由所有可能被此 context module 处理的请求(译者注:参考下面第二段代码中的 key)组成。
- id 是 context module 里面所包含的模块 id. 它可能在你使用 module.hot.accept 时会用到。
2. 图片预加载
动态加载文件夹下所有图片实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // example // 图片预加载, preloadAllImages () { let imgCounts = 0; // 已加载图片计数,可实现真实进度条 let imgsFun = require.context('../images', true, /.(png|jpg)$/); let imgKeys = imgsFun.keys(); imgKeys.forEach(item => { let Img = new Image(); Img.src = imgsFun(item); Img.onload = function () { imgCounts++; } Img.onerror = function () { imgCounts++; }; }); } |
3. import
require是运行时加载模块,但import命令会被javascript引擎静态分析,先于模块内其他模块执行,做不到运行时加载,因此为了实现类似于require的动态加载,就提出了实现一个import()函数方法,
1 | import(specifier); |
上面代码中,import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。
import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,也会加载指定的模块。另外,import()函数与所加载的模块没有静态连接关系,这点也是与import语句不相同。
import() 特性依赖于内置的 Promise。如果想在低版本浏览器使用 import(),记得使用像 es6-promise 或者 promise-polyfill 这样 polyfill 库,来预先填充(shim) Promise 环境。
1 2 3 4 5 6 7 | // example let imgUrl = ''; // 与require参数类似,不能通过纯参数的方式引入模块。正确的引入方式可查看以上require的引入方式 import('../assets/tree/tree.png').then(res => { imgUrl = res; }); |