webpack 动态批量加载文件的实现方法
背景
最近笔者在工作中遇到了一个小需求:
要实现一个组件来播放帧图片
这个需求本身不复杂,但是需要在组件中一次性引入十张图片,就像下面这样:
//就是这么任性,下标从0开始~ importframe0from'./assets/frame_0.png' importframe1from'./assets/frame_1.png' importframe2from'./assets/frame_2.png' //..省略n张 importframe7from'./assets/frame_8.png' importframe8from'./assets/frame_9.png' importframe9from'./assets/frame_10.png'
作为一个有代码洁癖的程序员,我是不允许这种重复性代码存在滴,于是乎就尝试有没有什么简单的方法。
方法一:绕过webpack
由于笔者用的是vue-cli3,熟悉的小伙伴都知道,将图片以固定的格式放在public文件夹下面,然后在代码中直接以绝对路径引入即可。这么做的话,就可以根据文件名构造一个url数组,简单代码如下:
constframes=[] _.times(10,v=>{ frames.push(`/images/frame_${v}.png`) }) //然后你就得到10个url的数组啦
此方法本身是vue-cli提供的一个应急手段,它有几个缺点:
- 无法利用webpack处理资源,无法产生内容哈希,不利于缓存更新
- 无法利用url-loader将资源内联成base64字符串以减少网络请求
方法二:require
由于import是静态关键字,所以如果想要批量加载文件,可以使用require,但是直接像下面这样写是不行的:
constframes=[] _.times(10,v=>{ constpath=`./assets/images/frame_${v}.png` frames.push(require(path)) }
上面的代码中的path是在程序运行时才能确定的,即属于runtime阶段,而webpack中的require是在构建阶段确定文件位置的,所以webpack没法推测出这个path在哪里。
但是却可以这样写:
constframes=[] _.times(10,v=>{ frames.push(require(`./assets/images/frame_${v}.png`)) } //frames中就得到带hash值的路径
虽然这两种写法在语法上没有差别,但是第二种写法在构建时提示了webpack,webpack会将./assets/images中的所有文件都加入到bundle中,从而在你运行时可以找到对应的文件。
在使用方法二的时候笔者尝试将批量加载的逻辑提取到其他模块用来复用:
exportfunctionloadAll(n,prefix,suffix){ constframes=[] _.times(n,v=>{ frames.push(require('./'+prefix+v+suffix)) }) returnframes }
但是显然失败了,因为提取后的代码,运行的context属于另一个模块,所以也就无法找到相对路径中的文件。
方法三:require.context
上面两种方法都不算很优雅,于是就去翻webpack的文档,终于,让我找到了这么一个方法:require.context
require.context( directory:String, includeSubdirs:Boolean/*可选的,默认值是true*/, filter:RegExp/*可选的,默认值是/^\.\/.*$/,所有文件*/, mode:String/*可选的,'sync'|'eager'|'weak'|'lazy'|'lazy-once',默认值是'sync'*/ )
指定一系列完整的依赖关系,通过一个directory路径、一个includeSubdirs选项、一个filter更细粒度的控制模块引入和一个mode定义加载方式。然后可以很容易地解析模块.
我们还是看上面的例子:
constframes=[] constcontext=require.context('./assets/images',false,/frame_\d+.png/) context.keys().forEach(k=>{ frames.push(context(k)) })
这里的代码通过require.context创建了一个require上下文。
- 第一个参数指定了需要加载的文件夹,即组件当前目录下的./assets/images文件夹
- 第二个参数指定是否需要包含子目录,由于没有子目录,所以传false
- 第三个参数指定需要包含的文件的匹配规则,我们用一个正则表示
然后使用context.keys()就能拿到该上下文的文件路径列表,而context本身也是一个方法,相当于设置过上下文的require,我们将require后的文件放入数组中,数组中的路径其实是带hash值的,如下是我项目中的图片:
["/static/img/frame_0.965ef86f.png","/static/img/frame_1.c7465967.png","/static/img/frame_2.41e82904.png","/static/img/frame_3.faef7de9.png","/static/img/frame_4.27ebbe45.png","/static/img/frame_5.d98cbebe.png","/static/img/frame_6.c10859bc.png","/static/img/frame_7.5e9cbdf0.png","/static/img/frame_8.b3b92c71.png","/static/img/frame_9.36660295.png"]
而且如果设置过内联图片的话,数组中可能还有图片的base64串。
重构一下
方法三已经解决了我们的问题,而且可以批量require某个文件夹中的文件。但是forEach那块的逻辑明显是重复的,所以我们当然提取出来啦,以后多个组件调用的时候只需要引入即可:
公共模块:
/** *批量加载帧图片 *@param{Function}context-require.context创建的函数 *@returns{Array}返回的所有图片 */ functionloadFrames(context){ constframes=[] context.keys().forEach(k=>{ frames.push(context(k)) }) returnframes }
组件中:
constcontext=require.context('./assets/images',false,/frame_\d+.png/) constframes=loadFrames(context)
大功告成!感兴趣的小伙伴可以点击文末链接查看详细文档~
参考链接
require.context
webpackdynamicrequire
到此这篇关于webpack动态批量加载文件的实现方法的文章就介绍到这了,更多相关webpack动态批量加载文件内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!