评论

关于微信小程序加载本地资源文件的一种解决方案

微信小程序读取资源文件,比如一些变更不频繁的配置或小型资源文件。

一、需求背景

有些时候在代码包里存在一些稳定的或者说保底的配置或资源文件,不希望通过http从服务端获取。此时,可能想到的办法就是利用本地来存储这样的一些文件或资源。

二、存在的问题

但是上面的想法,存在一个问题:如何在js代码逻辑中读取到这些文件内容呢?

官方api提供了 readFile 或 readFileSync 来读取文件内容。但是在代码包里的文件在打包后不一定还是原来的样子。而 readFile 或 readFileSync 是可以读取本地文件的。但是这里说的本地,不是代码包的这个本地,而是微信封装好的隔离性的目录。所以,代码包编译上传,readFile 或 readFileSync 将无法读取到这些文件了。要么提示no such file or directory 要么提示 permission denied 等错误反正就是读取失败

三、解决方案

出现上面的问题的根本原因是:当代码build和上传,最后被微信下载渲染成小程序后。代码已经不是源码的结构了,有些文件也不会被打包进去。此外整个目录结构不一样了;还有所谓的本地路径也不一样了。

所以要解决以上的几个问题,就可以最终实现读取“本地”资源文件了。

  • 问题1:如何让某个代码中的目录或文件一定被打包进build后的代码中:详见packOptions
  • 问题2:所谓的本地路径以及本地用户路径是什么?本地路径不是源代码中的路径,而是 详见本地路径 详见本地用户文件
  • 问题3:哪些文件(白名单后缀)可以被访问?详见允许上传的文件也就是说,不是所有的后缀名都可以被存放到 本地路径或本地用户路径中。也就是说如果资源文件中的 白名单后缀的文件,无法被上传。那如何解决这个问题呢?
  • 问题4:如何让我们的文件被访问到呢?以下是我实践下来的解决方案和思路。

因为问题2,所以需要将代码中的文件,通过某种方式放到 本地用户文件 中,然后还需要避免问题3 的后缀问题。还需要将资源文件加到 packOptions 中include列表,解决问题1;

综上,解决思路如下:

  1. 源代码中 添加资源文件所在的目录比如 asserts 到 packOptions.include
  2. asserts 通过预编译逻辑压缩为 zip包,且将后缀更改为问题3中的任意后缀,比如json,asserts.json
  3. 利用 copyFile copyFileSync 将 asserts.json 复制到 ${wx.env.USER_DATA_PATH}/asserts/asserts.json,此次主要 需要通过 mkdir 创建 asserts目录
  4. 利用 unzip 解压 ${wx.env.USER_DATA_PATH}/asserts/asserts.json,即可解压出 源代码中 asserts中的所有文件到 ${wx.env.USER_DATA_PATH}/asserts/ 中
  5. 通过 readFilereadFileSync 读取 asserts/xxx.json 需要封装一下 读取 ${wx.env.USER_DATA_PATH}/asserts/xxx.json 即可
  6. 完成整个流程的读取。

其中有个前提需要处理,上面的 第2步需要一个 预编译,由于我是用的uniapp实现的开发,在vue.config.js 中的webpackplugin中实现了一个zip plugin,实现了资源文件zip。如果原生小程序,需要自行处理。

具体代码如下:

// 执行文件复制操作
const fs = wx.getFileSystemManager();
// let src = `asserts/3.json`
// let dst = `${wx.env.USER_DATA_PATH}/asserts/3.json`
// fs.copyFile({
//  srcPath: src,
//  destPath: dst,
//  success: res => {
//     console.log(`文件复制成功 ${src} -> ${dst}`, res);
//  },
//  fail: err => {
//     console.error(`文件复制失败 ${src} -> ${dst}`, err);
//  }
// });
// fs.readdir({
//  dirPath: `asserts/`,
//  success(res) {
//     console.log(res.files)
//      会卡死
//     for (let i = 0; i < res.files.length; i++) {
//        let path = res.files[i]
//        let src = `asserts/${path}`
//        let dst = `${wx.env.USER_DATA_PATH}/${path}`
//        fs.copyFileSync(src, dst);
//        // fs.copyFile({
//        //     srcPath: src,
//        //     destPath: dst,
//        //     success: res => {
//        //        console.log(`文件复制成功 ${src} -> ${dst}`, res);
//        //     },
//        //     fail: err => {
//        //        console.error(`文件复制失败 ${src} -> ${dst}`, err);
//        //     }
//        // });
//     }
//  }, fail(res) {
//     console.error(res)
//  }
// })
try {
    const files = fs.readdirSync(`asserts/`)
    console.log(files)
    // 清空原来的数据
    fs.rmdirSync(`${wx.env.USER_DATA_PATH}/asserts`, true)
    fs.mkdir({ // 创建目录
       dirPath: `${wx.env.USER_DATA_PATH}/asserts`,
       recursive: false,
       complete() {
          fs.unzip({
             zipFilePath: `asserts/asserts.json`,
             targetPath: `${wx.env.USER_DATA_PATH}/asserts`,
             success(res) {
                console.log(res)
                // 此次可以抽象为一个独立函数
                fs.readFile({
                   filePath: `${wx.env.USER_DATA_PATH}/asserts/xxx.json`,
                   encoding: 'utf8',
                   position: 0,
                   success(res) {
                      console.log(`readFile成功`, res.data);
                   },
                   fail(err) {
                      console.log(`readFile失败`, err);
                   }
                })
                fs.readdir({
                   dirPath: `${wx.env.USER_DATA_PATH}/asserts`,
                   success(res) {
                      console.log(res.files)
                   }, fail(res) {
                      console.error(res)
                   }
                })
             },
             fail(res) {
                console.error(res)
             }
          })
       }
    })
} catch (e) {
    console.error(e)
}


上面的代码中存在一个测试出来的问题,我在 readdir 出的 文件,我本打断逐个copyFile到 ${wx.env.USER_DATA_PATH} 结果测试下来,开发工具直接卡死了。这个应该是 开发工具的bug。

后面仔细想想,如果asserts的文件很多,循环 copyFile 性能也不够。所以改成 copyFile -> unzip 的方式。应该性能上有所提高。

最后,如果是uniapp项目,对应的vue.config.js 文件可以添加 如下插件,实现编译时,自动资源目录zip的功能

new (class ZipPlugin {
    apply(compiler) {
       compiler.hooks.done.tap('ZipPlugin', async () => {
          let assertOutputPath = getPackageDir(staticDir)
          deleteDirectory(assertOutputPath);
          const outputPath = path.join(compiler.options.output.path, `${staticDir}/zip.json`);
          const output = fs.createWriteStream(outputPath);
          const archive = archiver('zip', {zlib: {level: 9}});
          output.on('close', function () {
             console.log(archive.pointer() + ' total bytes');
             console.log('close archiver has been finalized and the output file descriptor has closed.');
          });
          output.on('end', function () {
             console.log('end Data has been drained');
          });
          archive.on('warning', function (err) {
             console.error('warning: ', res);
          });
          archive.on('error', function (err) {
             console.error('error: ', err);
          });
          archive.pipe(output);
          // add directory
          const sourceDir = path.join(__dirname, `${staticDir}/`);
          archive.directory(sourceDir, false);
          await archive.finalize()
          console.log(`ZipPlugin sourceDir :${sourceDir} ->\n\toutputPath: ${outputPath}`)
       });
    }
})()


完结

最后一次编辑于  06-20  
点赞 1
收藏
评论
登录 后发表内容