uni-app填坑指南——解决处理处理静态资源的问题

2024-09-07 10:52

本文主要是介绍uni-app填坑指南——解决处理处理静态资源的问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说实在话,这个标题其实有点夸大了。 uni-app并没有不解决,而是解决的不够充分不够彻底。这里我们来讨论一下uni-app在处理静态资源的问题上的一些不足之处。

1. 问题描述

uni-app中,我们可以将静态资源放在static目录下,然后通过相对路径的方式引用。比如我们有一个图片资源logo.png,我们可以通过<img src="@/static/logo.png" />的方式引用。

目前引用包括三个方式:

  • @/static/:引用static目录下的资源;

  • 通过~@/static/引用static目录下的资源。

  • 如果需要引入其他目录的静态资源,那就只能通过变量的形式,如:

<template><view><image src="/static/logo.png"></image><image :src="imgUrl"></image></view>
</template><script>
import imgUrl from './a.png'
export default {data() {return {imgUrl}}
}</script>

我们接下来看,它最终的编译产物是什么呢?

编译之后的形成的文件夹结构如下:

mp-weixin
|── assets
│   ├── a.xxxxxxx.png
├── static
│   ├── logo.png

可以看到,static目录下的资源是直接拷贝到了mp-weixin目录下,而其他目录下的资源则是被编译成了assets目录下的文件。

最终生成的WXML文件中,引用的路径是:

<image src="/static/logo.png"></image>
<image src="{{imgUrl}}"></image>

JS文件中引用的路径是:

const imgUrl = __webpack_require__('./a.png')

同时在runtime.js中,有./a.png路径的定义

  './a.png': function(module, exports, __webpack_require__) {module.exports = '/assets/a.xxxxxxx.png';}

以上代码均为伪代码,实际情况可能有所不同。

看起来,对于static目录以及其他目录的静态资源都处理了, 最终编译产物也是符合预期的。但是,这里有一个问题,staticassets目录的资源都位于mp-weixin目录下,这将导致小程序整体包大小的增大。

熟悉小程序开发的同学都知道,小程序的包大小是有限制的,根据官方介绍,目前限制如下:

  • 整个小程序所有分包大小不超过 30M(服务商代开发的小程序不超过 20M)
  • 单个分包/主包大小不能超过 2M

很明显,我们不能过多的将资源放在编译后的mp-weixin目录下。因此如何对该部分资源进行优化变成了一个问题。

2. 解决方案

描述到上述问题之后,我们如何解决这个问题呢?我们有必要描述一下我们的目标:

  • 开发环境,能够将assets目录移出mp-weixin文件夹,并提供文件服务器,以便于开发时能够访问到资源;

  • 生产环境,能够将assets目录移出mp-weixin文件夹,删除assets文件,并将资源放在CDN上,以便于小程序能够访问到资源。

那么具体怎么做呢 ?

仔细研究webpack的构建过程,我们发现webpack的complier的hooks包含了几个特殊的阶段:

  • emit:资源输出阶段,这个阶段是在资源输出之前的一个阶段,我们可以在这个阶段对资源进行处理。

  • afterEmit:资源输出后阶段,这个阶段是在资源输出之后的一个阶段,我们可以在这个阶段对资源进行处理。

  • done:构建完成阶段,这个阶段是在构建完成之后的一个阶段,我们可以在这个阶段对资源进行处理。

不涉及的hooks我们就不在赘述了,有兴趣的同学可以查看webpack官方文档

从上面的描述我们可以看出,我们可以在afterEmitdone阶段对资源进行处理,将assets目录移出mp-weixin文件夹,同时开启一个本地的服务器,以便于开发时能够访问到资源。

代码解释

我们新建一个build目录,然后在build目录下新建一个webpack-plugin-file.js文件,用于定义一个webpack plugin:

module.exports = class WebpackPluginFile {constructor(options) {this.options = options;}apply(compiler) {this.logger = compiler.getInfrastructureLogger('WebpackPluginFile');compiler.hooks.done.tap('WebpackPluginFile', () => {this.copyAssets(compiler).then(() => {this.startServer(compiler);});});}
}

接下来我们要实现copyAssets

const fs = require('fs');
const fsPromise = require('fs/promises');
const path = require('path');module.exports = class WebpackPluginFile {async copyAssets(compiler) {const outputPath = compiler.options.output.path;const assetsPath = path.resolve(outputPath, 'assets');const targetPath = path.resolve(outputPath, '..', 'assets');return this.copyDir(assetsPath, targetPath);}async copyDir(src, dist) {const stats = await fsPromise.stat(src).catch(() => null);if (!stats) {// 判断是否存在,如果不存在则返回return null;}if (stats.isFile()) {// 如果是文件,则直接拷贝return this.copyFile(src, dist);}// 如果是目录,则遍历目录if (stats.isDirectory()) {const paths = await fsPromise.readdir(src);for (let i = 0; i < paths.length; i++) {const path = paths[i];await this.copyDir(path.resolve(src, path),path.resolve(dist, path));}}}async copyFile(src, dist) {// 确保目标目录存在await this.guaranteeDir(path.dirname(dist));// 拷贝文件await fsPromise.copyFile(src, dist);}
}

看起来完美解决了问题。但是,这种拷贝是全量拷贝,如果资源很多,那么拷贝的时间将会很长。因此我们可以在copyDir方法中加入一些判断,只拷贝有变化的文件。

module.exports = class WebpackPluginFile {cache = {};isWatch = false;async copyDir(src, dist) {const stats = await fsPromise.stat(src).catch(() => null);if (!stats) {// 判断是否存在,如果不存在则返回return null;}if (stats.isFile()) {// 如果是开发环境,则记录文件的复制属性if (isWatch) {const cache = this.cache[src];if (cache) {if (cache.mtime >= stats.mtime && cache.size === stats.size) {return;}this.cache[src] = {mtime: stats.mtime,size: stats.size}this.copyFile(src, dist);}} else {this.copyFile(src, dist);}// 省略代码}}apply(compiler) {const { watch } = compiler.options;this.isWatch = !!watch;}
}

对于 watch 模式下,复制完成后更新cache缓存,包含文件的修改时间和文件大小,下次复制时,如果文件的修改时间和文件大小没有变化,则不再复制。这样能提高复制的效率。

但是,对于生产环境,我们并不需要进行缓存,对应watch = false。即直接进行拷贝。 同时,我们需要在done阶段启动一个本地服务器,以便于开发时能够访问到资源。

module.exports = class WebpackPluginFile {port = 8888;app = null;constructor(options) {this.options = options;if (options && options.port) {this.port = options.port;}}async startServer(compiler) {if (this.isWatch) {return;}const outputPath = compiler.options.output.path;const targetPath = path.resolve(outputPath, '..', 'assets');const express = require('express');const app = express();this.server = app;app.use('/assets', express.static(targetPath));app.listen(this.port, () => {console.log(`Server is running at http://localhost:${this.port}`);});}
}

按照上述代码,首先初始化时从参数中获取port,然后在done阶段调用startServer,其内部使用express框架启动一个server,并将assets目录作为静态资源目录。

需要指出的是,对于生产环境,我们通过isWatch变量判断,如果是生产环境,则直接返回,不启动server。

最后,我们增加stopServer方法,用于在done阶段关闭server。

module.exports = class WebpackPluginFile {// 省略代码async stopServer() {if (this.server) {this.server.close();}}apply(compiler) {// 省略代码compiler.hooks.done.tap('WebpackPluginFile', () => {this.copyAssets(compiler).then(() => {this.startServer(compiler);});});compiler.hooks.shutdown.tap('WebpackPluginFile', () => {this.stopServer();});}
}

小结

通过上述代码,我们实现了一个webpack插件,用于在done阶段将assets目录移出mp-weixin文件夹,并启动一个本地服务器,以便于开发时能够访问到资源。同时,我们区分了生产环境和开发环境,对于开发环境,我们通过watch变量判断,对资源进行缓存,提高资源拷贝的效率,并启动一个本地服务器,以便于访问资源。而对于生产环境,我们只需要将资源移出mp-weixin文件夹即可。

如何使用这个插件

由于uni-app有两个版本基于vue-cli和基于vite,其内核一样。我们采用的vue-cli的版本,因此我们可以在vue.config.js中引入这个插件。

const WebpackPluginFile = require('./build/webpack-plugin-file');
module.exports = {publicPath: isWatch ? 'http://127.0.0.1:8888/' : 'https://cdn.xxx.com/',configureWebpack: {plugins: [new WebpackPluginFile({port: 8888})]}
}

WebpackPluginFile需要与publicPath配合使用。对于开发环境,我们将publicPath设置为http://127.0.0.1:8888/,以便于访问到资源。对于生产环境,我们将publicPath设置为https://cdn.xxx.com/,以便于小程序能够访问到资源。

更多思考

上述基本解决了我们一开始提出的问题,但是也衍生出一些新的问题,有兴趣的同学可以去深挖一下:

  1. 多个环境如何配置,这里面讲到了开发环境、生产环境,没有提到测试环境。引入测试环境后,publicPath如何配置?

  2. 我们知道webpack 5对于resource资源默认情况有两种处理方式,assetinline,某些场景会触发inline模式,这种情况将导致图片不被复制,该情况下是否符合预期。

  3. 图片引入的改进。当前图片是需要手动引入的,并且通过变量传递给image元素,能否写成诸如<image src="@/assets/logo.png" />的形式呢?

参考资料

  • uni-app官方文档
  • 小程序分包加载

这篇关于uni-app填坑指南——解决处理处理静态资源的问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1144865

相关文章

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多