深度解析:uniapp 如何攻克 Tree Shaking 导致的动态资源丢失难题

3 阅读2分钟

深度解析:如何攻克 Tree Shaking 导致的动态资源丢失难题

在现代前端构建工具(如 Vite/Rollup)中,Tree Shaking 是一项核心优化技术,旨在移除未使用的代码以减小包体积。然而,在小程序开发等特定场景下,这一机制往往会“误伤”那些通过动态路径引用隐式依赖的资源,导致运行时报错。

本文将深入分析这一问题,并展示本项目如何通过 Glob 扫描 + 动态入口注入 的方案,优雅地解决了这一工程难题。


一、 问题背景:消失的文件

在 UniApp 或小程序开发中,我们经常遇到以下两类特殊资源:

  1. 分包国际化文件:为了减小主包体积,我们将语言包分散在各个分包中(如 src/subPackageA/i18n/index.ts),并在运行时根据路由动态 require
  2. 异步工具库:某些庞大的工具库(如 src/asyncUtils/),我们希望只在特定条件下异步加载,而不是打包进主包。

痛点复现

当我们写下如下代码时:

// 运行时动态加载
const loadI18n = (pkgName) => require(`../../${pkgName}/i18n/index`);

Vite/Rollup 的反应是

"我看不到任何显式的 import ... from ... 引用这个文件,所以它一定是无用的代码(Dead Code),我要把它丢弃!"

结果就是:本地开发(Dev)一切正常(因为本地服务器不做 Tree Shaking),但打包上线(Build)后,程序运行到这一行时直接报错 Module not found,文件在构建产物中根本不存在。


二、 解决方案:主动出击,强制保留

既然构建工具无法静态分析出这些依赖,我们就需要在构建配置中显式地告诉它:“这些文件很有用,请把它们作为入口打包进去!”

本项目在 vite.config.ts 中实现了一套自动化的扫描与注入机制。

1. 核心工具:fast-glob

我们引入了 fast-glob 库,它支持强大的文件模式匹配,能快速扫描出符合特定规则的文件列表。

2. 实现步骤详解

第一步:定义扫描规则

vite.config.ts 中,我们定义了需要“保护”的文件模式:

// vite.config.ts
const extraFiles = glob.sync([
  'src/**/i18n/index.ts',           // 所有分包下的国际化入口
  'src/asyncUtils/**/*.{js,ts,vue}' // 所有异步工具库文件
]);
第二步:生成入口映射

Rollup 的 input 选项支持对象格式 { alias: path }。我们需要将扫描到的文件路径转换为合理的输出路径,以保持目录结构一致。

// 遍历扫描到的文件
extraFiles.forEach(file => {
  // 生成 Entry Name,保持输出目录结构
  // 例如: src/asyncUtils/utils/index.js -> asyncUtils/utils/index
  const name = file.replace(/^src\//, '').replace(/\.(ts|js|vue)$/, '');
  
  // 建立映射关系
  extraInputs[name] = file;
});
第三步:注入构建配置

最后,将生成的 extraInputs 合并到 Vite 的 build.rollupOptions.input 中。

export default defineConfig(() => {
  return {
    build: {
      rollupOptions: {
        input: {
          // 这里的 ... 是关键,它将动态扫描到的文件加入到了构建入口中
          ...(isMp ? extraInputs : {})
        }
      }
    }
  };
});

三、 方案优势与成效

1. 自动化与零维护

开发者只需按照约定(如将文件放在 asyncUtils 目录下),无需手动在配置文件中注册每个新文件。新增一个工具类或分包语言包,构建系统会自动发现并打包

2. 目录结构完美保留

通过 file.replace(/^src\//, '') 的路径处理,构建后的产物目录结构与源码目录完全一致。这意味着运行时的 require('../asyncUtils/xxx') 路径依然有效,无需修改业务代码。

3. 彻底解决 Tree Shaking 误删

因为这些文件被提升为了构建入口(Entry Point),Rollup 会认为它们是“一级公民”,绝对不会对其进行 Tree Shaking 剔除,同时也保留了文件内部的依赖树。


四、 总结

对比维度传统方式本方案 (Glob + Input)
引用方式必须显式 import支持动态 require / 隐式调用
维护成本每新增文件需手动注册完全自动,零配置
稳定性容易因疏忽导致线上 404100% 可靠,构建产物可控
灵活性目录结构受限保持源码目录结构

通过这段不到 20 行的配置代码,我们成功弥补了静态构建工具在动态场景下的短板,为项目的模块化拆分和按需加载奠定了坚实的工程基础。