一、需求背景
有些时候在代码包里存在一些稳定的或者说保底的配置或资源文件,不希望通过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;
综上,解决思路如下:
- 源代码中 添加资源文件所在的目录比如 asserts 到 packOptions.include
- 将 asserts 通过预编译逻辑压缩为 zip包,且将后缀更改为问题3中的任意后缀,比如json,asserts.json
- 利用 copyFile 或 copyFileSync 将 asserts.json 复制到 ${wx.env.USER_DATA_PATH}/asserts/asserts.json,此次主要 需要通过 mkdir 创建 asserts目录
- 利用 unzip 解压 ${wx.env.USER_DATA_PATH}/asserts/asserts.json,即可解压出 源代码中 asserts中的所有文件到 ${wx.env.USER_DATA_PATH}/asserts/ 中
- 通过 readFile 或 readFileSync 读取 asserts/xxx.json 需要封装一下 读取 ${wx.env.USER_DATA_PATH}/asserts/xxx.json 即可
- 完成整个流程的读取。
其中有个前提需要处理,上面的 第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}`)
});
}
})()
完结