Skip to content

核心实现与部署上线 ​

本页对应 核心实现与部署上线,共 5 段视频,约 32 分钟。

学习目标 ​

  1. 能够口述 mini 微前端“注册子应用配置、覆写路由、匹配激活路由、加载并渲染子应用”四个核心步骤及其职责边界。
  2. 能够独立实现 registerMicroApp 与 getAppConfig,将子应用配置存入模块变量,并通过控制台验证配置可被正确读取。
  3. 能够通过监听 popstate 并覆写 history.pushState/replaceState,在前进、后退及编程式导航时插入自定义加载逻辑,并验证回调生效。
  4. 能够基于 location.pathname 与 activeRule 完成激活路由匹配,仅在匹配成功时触发子应用加载,并通过浏览器操作验证匹配结果。
  5. 能够使用 import-html-entry 加载子应用 entry 资源,将 template 渲染到 container 节点并执行 scripts,验证子应用样式、结构与交互均生效。
  6. 能够定义和使用环境变量,按环境切换子应用加载地址,正确配置子应用 base,并说明客户端环境变量的注意事项及常见端口/路径错误排查方法。

知识点 ​

先厘清本节要解决的问题 ​

本节笔记只给出一个核心概念和一个小场景,没有给出具体框架代码。先说明一点:你提供的转写稿实际在讲微前端 mini-qiankun 的注册、路由复写、子应用加载与渲染,并不是“环境变量”这一节的内容。为保证不编造,下面不把 registerMicroApp、import-html-entry 等微前端实现硬塞进环境变量教程;只吸收其中可迁移的调试意识:地址类配置必须和实际运行环境一致,否则就会出现请求错地址、加载失败等问题。

本节要解决的问题是:项目开发阶段接口基地址是:

text
https://api-hmzs.itheima.net/v1

项目上线后接口基地址变成:

text
https://api-hmzs.itheima.net/api

如果业务代码里到处写死 /v1,上线前就要全局搜索、逐个替换,既麻烦又容易漏。环境变量的价值,就是把这种“随环境变化的值”集中管理:变量名不变,变量的值根据当前环境自动切换。

开发环境与生产环境接口地址切换示意

环境变量到底“变”的是什么 ​

为什么需要?
因为同一个项目会运行在不同环境里,例如开发环境、测试环境、生产环境。不同环境可能需要不同的接口地址、资源地址或开关配置。若把这些值写死在代码里,代码就失去了环境适应性。

怎么想?
不要把“环境变量”理解成某个具体工具的专属语法,而要先抓住本质:业务代码只认一个变量名,例如“接口基地址”;开发环境给这个变量配置 v1 地址,上线环境给同一个变量配置 api 地址。这样业务代码不需要改,变的是环境配置。

怎么做?
第一步,找出项目中真正会随环境变化的值,本节就是接口基地址。第二步,给它定义一个稳定的变量名,并在请求封装处统一读取这个变量,而不是在每個接口里手写域名。第三步,在开发环境和生产环境分别提供对应的值。具体写在哪个文件、用什么读取语法,取决于项目脚手架;本节笔记没有展开,所以不要照搬其他项目的写法。第四步,分别用开发启动和生产构建验证请求地址是否正确。

易错点。
变量名要统一,不能请求封装里叫一个名字、环境配置里又叫另一个名字;业务代码里不能残留硬编码的旧地址;切换环境后要重新启动或重新构建,不能只看源码里“看起来改了”。

在项目里落地时的推导顺序 ​

为什么需要先找变化点?
环境变量不是越多越好。只有真正随环境变化的值才需要抽出来。接口基地址是典型变化点;而组件结构、业务逻辑通常不随环境变化。

怎么想?
可以把接口地址拆成“固定域名 + 环境路径”来理解:域名相同,路径从 /v1 变为 /api。因此请求封装应该只拼接路径,不应把完整地址散落在各处。

怎么做?
可以用下面的对照关系来检查配置是否写对:

text
开发环境:API_BASE_URL = https://api-hmzs.itheima.net/v1
生产环境:API_BASE_URL = https://api-hmzs.itheima.net/api

请求时代码只使用 API_BASE_URL 这个变量。上线前检查生产环境最终取到的是 /api,开发时取到的是 /v1。

易错点。
一是只改了请求封装,却还有页面直接写死旧地址;二是开发环境验证通过就直接上线,没有用生产构建再验证;三是把地址末尾斜杠处理混乱,导致出现 //api 或路径拼接错误。调试时最直接的方法是打印当前环境实际使用的基地址,再发起一次请求看 Network 面板里的完整 URL。

用调试思路避免“配置写了但没生效” ​

为什么需要调试?
环境变量涉及“配置注入”和“代码读取”两个环节,任何一环出错都可能表现为“还是旧地址”。

怎么想?
先确认当前运行的是哪个环境,再确认变量是否被正确读取,最后确认请求是否真的使用了该变量。

怎么做?
按顺序检查:当前启动命令对应开发还是生产;配置中同一个变量名是否都有值;请求封装读取后打印出的值是否为目标地址;浏览器 Network 中请求 URL 是否与预期一致。若项目使用构建产物部署,还要确认部署的是最新构建结果。

易错点。
转写稿中曾出现“端口不一致导致加载失败”的调试案例,它提醒我们:地址配置必须和实际服务一致。环境变量同理,配置里的地址、端口、路径只要有一处与实际环境不符,就会请求失败。另一个常见问题是把“环境变量”误当成运行时自动魔法:很多项目其实是在启动或构建时注入值,改完配置不重启、不重新构建,就不会生效。

小结:判断是否真正掌握 ​

你应该能用自己的话说清:环境变量解决的是“同一个变量在不同环境取不同值”的问题;本节场景中,开发环境使用 /v1,上线后使用 /api;落地时要把接口基地址集中到请求封装,业务代码只引用变量;验证时要分别检查开发环境和生产构建后的实际请求地址。只要还残留硬编码地址,或者只改配置不验证构建结果,就还没有真正完成环境变量适配。

本节范围与笔记内容 ​

本节笔记的主题是“环境变量的定义和使用”,核心只有两点:一是环境变量文件放在哪里、对应哪条运行命令;二是环境变量用什么语法定义。需要先说明:提供的转写稿讲的是 mini 乾坤、子应用注册、路由匹配、加载与渲染等内容,和本节“环境变量”不是同一主题。转写稿中“自用/子用”应纠正为“子应用”,“秘你乾坤”应理解为“mini 乾坤”,“复写路由”应理解为“覆写路由”;但这些纠错不能直接套到环境变量上。为避免编造,本节教程只依据笔记中的表格、语法和运行机制展开,不额外虚构变量名或读取代码。

笔记给出的定义位置和运行机制如下:

对应环境文件说明
开发环境.env.development当运行vue-cli-service serve 的时候会以此文件为配置文件,这个文件中可以定义针对开发环境的环境变量
生产环境.env.production当运行vue-cli-service build 的时候会以此文件为配置文件,这个文件中可以定义针对生产环境的环境变量

为什么需要区分开发环境和生产环境 ​

为什么需要:同一个项目在开发阶段和生产阶段往往需要不同的配置。笔记把开发环境对应到 .env.development,把生产环境对应到 .env.production,目的就是让不同命令读取不同文件,避免把开发用配置和生产用配置混在一起。

怎么想:不要先问“变量写在哪里”,而要先问“当前执行的是哪条命令”。运行 vue-cli-service serve 时,笔记明确说会以 .env.development 为配置文件;运行 vue-cli-service build 时,会以 .env.production 为配置文件。也就是说,文件选择不是手动指定的,而是由运行命令触发的。

怎么做:开发相关变量写入 .env.development,生产相关变量写入 .env.production。两个文件分别服务于对应命令。

易错点:不要把开发环境变量写到生产文件里,也不要把生产环境变量写到开发文件里;不要以为写了两个文件就会同时生效,笔记强调的是“运行哪条命令,就以对应文件为配置文件”。

怎么想:先确定命令,再确定文件 ​

为什么需要:环境变量是否生效,首先取决于运行机制。笔记没有给出额外的读取 API,而是把“文件”和“命令”绑定起来。因此理解顺序应该是:命令决定文件,文件提供变量。

怎么想:看到 serve,就想到开发环境与 .env.development;看到 build,就想到生产环境与 .env.production。这是本节最直接的推导。

怎么做:在实际操作前先确认当前要执行的是开发启动还是生产构建,然后只修改对应文件。不要把“文件存在”误认为“文件一定被读取”,关键仍是运行命令。

易错点:只改了文件但没有通过对应命令运行,可能会误判变量没有生效;反过来,执行了某条命令,却去检查另一个环境文件,也会得到错误结论。排查时应按“命令 → 文件”的顺序核对。

怎么做:使用 key = value 定义环境变量 ​

笔记明确说:定义环境变量采用 key = value 的语法进行定义,其中 key 表示环境变量名称,value 表示环境变量值。笔记中的代码块只给出了语法占位,保留如下:

json
key = value

这里的 json 只是笔记里的代码块标记,并不表示环境变量文件要写成 JSON。实际应按笔记文字理解为“名称 = 值”的形式,而不是写成 JSON 对象、数组或使用冒号分隔。

为什么需要:统一语法可以让文件内容清晰表达“变量名”和“变量值”的对应关系。

怎么想:先写变量名称,再写等号,再写变量值。key 是名称,value 是值,两者一一对应。

怎么做:在 .env.development 或 .env.production 中按 key = value 的形式书写。具体变量名和值应由项目实际需要决定;笔记没有给出具体示例,因此不编造具体名称。

易错点:不要把 key = value 写成 JSON 的 "key": "value";不要漏掉等号;不要把变量名和变量值写反;不要把两个环境的文件内容随意互换。

使用与调试:按笔记机制逐项核对 ​

为什么需要:环境变量没有按预期生效时,最容易犯的错误是直接怀疑语法,而忽略运行命令和文件名。

怎么想:按笔记能确认的三件事排查:第一,当前运行的是 vue-cli-service serve 还是 vue-cli-service build;第二,对应文件是否是 .env.development 或 .env.production;第三,文件内容是否采用 key = value 语法。

怎么做:先核对命令,再核对文件名,最后核对定义语法。这个顺序与笔记的“定义位置和运行机制”一致,不需要引入笔记之外的结论。

易错点:不要凭感觉判断“应该读到了”;不要只检查一个文件;不要把代码块标记 json 当成真实文件格式。若排查后仍不生效,应回到项目实际命令和文件内容继续核对,而不是编造额外机制。

小结 ​

本节要记住三句话:开发环境用 .env.development,对应 vue-cli-service serve;生产环境用 .env.production,对应 vue-cli-service build;环境变量按 key = value 定义。转写稿中的微前端内容不属于本节主题,不能把“注册子应用、路由匹配、加载渲染”等步骤硬套到环境变量上。按笔记范围理解和使用,才不会把文件位置、运行命令和定义语法混为一谈。

先明确“客户端代码”的范围 ​

为什么需要:环境变量不是所有地方都能随便读。Vue CLI 项目里,只有被 webpack 注入到客户端 bundle 的变量,才能在浏览器端通过 process.env.XXX 访问。笔记里说“客户端代码其实就是我们的 src 目录”,意思是:你在 src 下写的组件、路由、store、工具函数等,最终会进入浏览器执行,因此受客户端环境变量白名单限制。

怎么想:不要把“项目里能读到环境变量”和“浏览器里能读到环境变量”混为一谈。vue.config.js、Node 脚本、构建配置属于非客户端代码,它们运行在 Node 环境,能读取更多环境变量;而 src 目录下的代码会被打包进前端资源,必须经过 Vue CLI 的筛选。

怎么做:在 src 目录中只使用被允许的变量:NODE_ENV、BASE_URL,以及以 VUE_APP_ 开头的变量。其它变量即使写在 .env 文件里,也不会自动出现在客户端代码的 process.env 中。

易错点:不要把服务端密钥、数据库密码等写进 VUE_APP_ 变量,因为它们会被打包进客户端资源,等于公开。也不要误以为在 .env 里随便定义一个变量,src 里就能直接读到。

客户端环境变量识别范围示意图

NODE_ENV 与 BASE_URL 的特殊规则 ​

为什么需要:NODE_ENV 用来区分开发、生产等模式,影响构建和运行行为;BASE_URL 与项目部署的基础路径有关,Vue CLI 内部会用它处理公共资源路径。它们属于特殊变量,所以客户端代码可以识别。

怎么想:特殊变量不等于可以随意改写。笔记明确指出:NODE_ENV 可以自定义覆盖,BASE_URL 覆盖不了。也就是说,NODE_ENV 虽然特殊,但在环境变量配置中仍可按项目需要覆盖;而 BASE_URL 不应通过 .env 去强行覆盖,它应由 Vue CLI 的公共路径配置体系统一决定。

怎么做:需要改变模式相关行为时,按项目规范配置 NODE_ENV;不要试图在 .env 文件中自定义 BASE_URL 来替代公共路径配置。客户端代码中读取 process.env.BASE_URL 时,应接受框架计算出的值。

易错点:把“可识别”误解为“可任意覆盖”。BASE_URL 覆盖不了是硬规则;NODE_ENV 可覆盖也要谨慎,避免开发/生产判断混乱。修改 .env 后要重启开发服务器,否则可能看不到变化。

自定义变量为什么推荐统一 VUE_APP_ 前缀 ​

为什么需要:客户端环境变量有白名单,如果今天用 VUE_APP_,明天用别的前缀,记忆负担大,也容易把变量写到 src 后却读不到。

怎么想:笔记建议“为避免记忆负担,在业务中自定义环境变量的时候,推荐采用严格的 VUE_APP 变量前缀打头”。这不是说所有变量都必须这样,而是业务自定义变量统一走 VUE_APP_,能减少“为什么这个变量在客户端拿不到”的排查成本。

怎么做:业务变量统一命名成 VUE_APP_XXX,例如 VUE_APP_API_BASE、VUE_APP_TITLE;在 src 中通过 process.env.VUE_APP_XXX 读取。非客户端代码不受此限制,但为了团队一致,也建议按用途区分命名。

易错点:前缀大小写要严格一致;变量名不要拼错;不要把普通变量误写成 VUE_APP_ 后又放入敏感信息。还要注意,只有 src 下的客户端代码受白名单限制,非客户端代码没有这个要求,但也不能因此把敏感信息带到前端。

调试思路与转写稿说明 ​

为什么需要:环境变量问题往往不是代码逻辑错,而是“变量没被注入”或“注入范围不对”。调试时要先确认代码位置,再确认变量名,最后确认构建是否重启。

怎么想:先判断读取变量的文件是否在 src 目录;再检查变量是否属于 NODE_ENV、BASE_URL 或 VUE_APP_ 前缀;最后检查 .env 修改后是否重启。不要一上来就怀疑框架。

怎么做:在客户端代码中临时打印 process.env 的相关字段,确认目标变量是否存在;若不存在,回到变量命名和文件位置排查。非客户端代码中读取正常、客户端读不到,通常就是白名单问题。

易错点:把非客户端代码的读取结果当成客户端结果;把 BASE_URL 当成可随意覆盖的普通变量;把敏感信息放进 VUE_APP_。另外,你提供的转写稿主要讲 registerMicroApp、路由复写、子应用加载渲染等微前端内容,与本节“客户端环境变量注意事项”主题不一致,因此这里没有把微前端推导过程硬套进环境变量章节,以免混淆知识点。

本节目标与前置说明 ​

本节要完成的是“服务器环境准备”:在本地使用 Node 准备好两台服务器,分别承载后台管理项目和前台可视化项目。需要先说明一点:你提供的转写稿主要讲的是微前端核心实现步骤,例如注册子应用配置、覆写路由、匹配激活路由、加载并渲染子应用等,与“服务器环境准备”这一节笔记并不是同一主题。为避免编造,下面不把转写稿中不相关的推导硬塞进本节,而是严格依据本节笔记扩写服务器准备与部署流程。

服务器A与服务器B示意

为什么需要先准备两台服务器 ​

微前端或前后台分离项目通常不是只有一个入口。后台管理项目和前台可视化项目需要各自有独立的部署位置和访问地址。如果两个项目混在同一个服务、同一个端口下,后续部署、访问和排查都会变得混乱:到底哪个地址对应后台管理,哪个地址对应大屏可视化,静态资源又应该放到哪里,都不清晰。

因此,本节先准备两台服务器:服务器 A 对应后台管理项目,服务器 B 对应前台可视化项目。这样做的核心目的,是让“项目源码—打包产物—静态服务目录—访问地址”形成一一对应关系。后面无论部署还是联调,都能明确知道应该访问哪个地址、检查哪个目录。

怎么想:先确定服务与地址的对应关系 ​

笔记中已经给出明确对应关系:

text
serverforadmin     服务器A,后台管理项目部署位置
服务地址:http://localhost:8086

serverforbigscreen 服务器B,前台可视化项目部署位置
服务地址:http://localhost:8089

这里的思路不是“先把代码随便放进去”,而是先确定两个服务的职责:

  1. serverforadmin 只服务后台管理项目,访问地址为 http://localhost:8086;
  2. serverforbigscreen 只服务前台可视化项目,访问地址为 http://localhost:8089;
  3. 每个服务中都有一个 public 文件夹,它是静态服务文件夹;
  4. 打包后的代码要放到对应服务的 public 文件夹中。

也就是说,public 是最终对外提供静态资源的目录,而不是源码目录。我们不是把项目源码直接扔进 public,而是把项目打包后生成的 dist 目录下的文件放进去。

怎么做:打包并放入对应 public ​

整体流程可以概括为四步。

第一步,确认两台 Node 服务器已经准备好,并明确各自端口:A 为 8086,B 为 8089。访问地址分别是:

text
http://localhost:8086
http://localhost:8089

第二步,检查每个服务中是否都有 public 文件夹。目录关系可以理解为:

text
serverforadmin/
  public/

serverforbigscreen/
  public/

第三步,对两个项目分别进行打包。打包完成后,会生成 dist 目录。注意笔记中的表述是“把生成的 dist 目录下的文件放到对应的服务器的 public 文件夹中”,也就是放 dist 里面的内容,而不是把整个 dist 文件夹再套一层放进去。

对应关系如下:

text
后台管理项目 dist 下的文件  ->  serverforadmin/public/
前台可视化项目 dist 下的文件 ->  serverforbigscreen/public/

第四步,部署完成后,分别访问两个地址进行验证:后台管理项目访问 http://localhost:8086,前台可视化项目访问 http://localhost:8089。如果页面能正常加载,说明静态目录和访问地址基本对应正确。

易错点 ​

第一,端口不要混。后台管理是 8086,前台可视化是 8089,不要把两个项目的地址写反或混用。

第二,public 是静态服务文件夹,不是源码文件夹。不要把手写源码直接放进去,而应放打包后的产物。

第三,注意“dist 目录下的文件”这一层。如果多套了一层 dist,访问路径可能多出一级,导致静态资源找不到。

第四,两个项目的产物要放到各自对应的服务器中。后台管理项目的产物不要放到 serverforbigscreen/public,前台可视化项目也不要放到 serverforadmin/public。

第五,localhost 表示本机访问。当前阶段是本地环境准备,地址写 localhost 没有问题;如果换到其他机器访问,就不能简单照搬这个地址。

本节的重点不是写业务代码,而是把两台服务器、两个端口、两个 public 静态目录以及两个项目的打包产物对应关系准备好。后续部署和联调都建立在这个清晰的环境基础之上。

先看这段配置在做什么 ​

微前端主应用要把子应用"接进来",靠的是 registerMicroApps 这份配置。笔记里的代码虽然只有十几行,但每一个字段都承担着一件事:

  • name:子应用的名字,用来标识这份子应用配置;
  • entry:子应用启动后的服务地址,主应用就是从这个地址去把子应用的资源拉过来;
  • container:子应用被拉过来以后渲染到哪个节点上;
  • activeRule:路由匹配规则,当浏览器地址命中这个路径时,才触发加载子应用。

转写稿里对这三件事的定位说得很清楚:先注册子应用配置,然后靠 activeRule 做路由匹配,匹配上了才去加载子应用,最后把资源渲染到 container 对应的节点上。本节只处理其中一个字段:entry。

vue
import { registerMicroApps, start } from 'qiankun'

const isDev = process.env.NODE_ENV === 'development'

registerMicroApps([
  {
    name: 'hmzs-big-screen',
    entry: `${isDev ? '//localhost:5173' : '//localhost:8089'}`,
    container: '#container',
    activeRule: '/big-screen'
  }
])

start()

为什么 entry 要区分环境 ​

为什么需要:子应用大屏项目在开发阶段是自己单独跑起来的,服务地址是 //localhost:5173;上线之后它被打包部署到另一个位置,端口变成了 8089。如果 entry 写死成开发地址,上线后主应用就会去请求一个根本不存在的地址,子应用自然加载不出来。

怎么想:entry 必须是"当前这个环境下真实可达的地址"。开发时子应用和主应用是两套本地服务,上线后子应用换了部署位置,所以这个值不能写死,得根据当前所处的环境算出来。

怎么做:用一个变量 isDev 记录当前是不是开发环境,再在 entry 里用三元表达式切换:

js
const isDev = process.env.NODE_ENV === 'development'

entry: `${isDev ? '//localhost:5173' : '//localhost:8089'}`

顺带一提,地址写成 //localhost:5173 这种不带协议的写法,是为了让浏览器沿用当前页面的协议,主应用是 http 还是 https 都不用再改一遍。

易错点:笔记正文里前后两处地址都写成了 http://localhost:5173/big-screen,这明显是笔误,实际以上面代码为准——开发环境 5173,生产环境 8089。写代码时以代码里的端口为准,别照抄正文那句话。另外 entry 指向的是子应用的服务根地址,具体走哪个路径由 activeRule(这里是 /big-screen)去匹配,不要把路径后缀重复拼进 entry。

环境值从哪来 ​

为什么需要:不能靠人工在打包前手动改地址,也不能用 location.hostname === 'localhost' 这种运行时判断,因为子应用部署在哪个域名、哪个端口,和主应用是不是本地跑没有必然关系。

怎么想:环境信息应该在构建阶段就确定下来,process.env.NODE_ENV 就是干这个的——它能拿到当前运行的环境,是生产还是开发,开发环境的值是 development。

怎么做:直接比较字符串,得到布尔值 isDev,后续所有和环境相关的分支都用它。

易错点:值只有 development、production 这类,别写成 'dev'、'develop',判断一旦写错,isDev 永远是 false,本地开发时也会去请求生产地址,现象是"本地怎么都加载不出来",而报错地址是 8089,很容易被误判成子应用没启动。

地址配错时怎么排查 ​

转写稿里有一段很典型的调试过程,正好对应 entry 最容易出的问题。当时加载子应用直接报错,控制台里请求的地址是 127.0.0.1:5500,而子应用实际跑在 62345 端口上——两边端口不一致,请求当然失败。

排查的顺序是:

  1. 看控制台报错里请求的是哪个地址,这是主应用真实去拉的地址;
  2. 回到子应用那边确认它当前实际监听的是哪个端口;
  3. 对比两者,改 entry,改完重新触发一次加载验证。

当时把端口从 5500 改成 62345 之后,再点加载子应用,资源就成功拉回来了,template 拿到了完整的模板,exactScripts 也拿到了执行脚本的方法。这个"地址必须和子应用实际运行地址一致"的结论,和环境切换是同一件事的两面:开发环境对 5173,生产环境对 8089,任何一边对不上,表现都是加载失败。

小结 ​

这一段改动很小,但把 entry 从"写死"变成"按环境计算",解决了开发和生产部署位置不同的问题。记住三件事:entry 必须是当前环境下真实可达的地址;环境判断靠 process.env.NODE_ENV === 'development',字符串别写错;加载失败时第一件事是对比报错地址和子应用实际端口,而不是怀疑 activeRule 或 container。

先明确:base 解决的是静态资源从哪取 ​

为什么需要:子应用大屏项目上线后,服务器地址变成了 http://localhost:8089。如果 Vite 仍按默认方式生成静态资源地址,主应用把子应用加载过来后,页面里的 js、css 等资源就可能指向错误位置,导致样式丢失、脚本不执行或请求 404。转写稿里反复强调“加载子应用”本质上是先拿到子应用的 HTML 资源,再解析模板和脚本;资源地址不对,后续渲染就会失败。

怎么想:vite.config.js 里的 base 决定项目启动时静态资源(如 js、css)从哪个服务器获取。它不是 vue-router 的路由 base,而是资源公共路径。既然子应用服务地址变成 http://localhost:8089,就应把 base 指向它。

怎么做:在 defineConfig 中设置 base: 'http://localhost:8089/',并保留 qiankun 插件、开发服务器 origin 和别名配置。

易错点:base 建议保留末尾斜杠,避免资源路径拼接歧义;不要把 base 和路由的 base 混为一谈;不要只改主应用配置而漏掉子应用资源地址。

修改子应用的 Vite 配置 ​

本节笔记中的代码原样保留如下:

javascript
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'

export default defineConfig({
  base: 'http://localhost:8089/',
  plugins: [vue(), qiankun('hmzs-big-screen', {
    useDevMode: true
  })],
  server: {
    // 解决开发环境下的静态资源访问问题
    origin: '//127.0.0.1:5173'
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    }
  }
})

为什么需要:这段配置同时承担了资源路径、qiankun 子应用注册和开发服务器访问地址三件事。只改 base 而不理解其他项,容易在调试时误判问题来源。

怎么想:base 管资源从哪取;qiankun('hmzs-big-screen', { useDevMode: true }) 是子应用接入 qiankun 的插件配置;server.origin 的注释明确写着“解决开发环境下的静态资源访问问题”。转写稿里提到加载子应用时要拿到 HTML、解析 template 和 execScripts,因此资源路径和入口地址必须一致。

怎么做:按笔记设置 base 后,启动子应用,让主应用加载子应用,观察 Network 中 js、css 请求是否指向 http://localhost:8089 相关地址;页面样式和交互应正常。

易错点:useDevMode: true 是笔记中的既有配置,不要随意删除;server.origin 也不要漏掉,否则开发环境静态资源访问可能异常。

用微前端加载流程理解这次修改 ​

为什么需要:转写稿用 mini qiankun 讲了四步:注册子应用配置、路由匹配、加载子应用、渲染子应用。加载时不是“凭空显示”,而是通过网络请求获取子应用 HTML,再解析模板和脚本并执行。base 改错,资源请求就会落到错误服务器。

怎么想:可以把 base 理解成子应用资源的地基。主应用负责把子应用 HTML 拉过来,但 HTML 里引用的 js、css 仍要按 base 去取。转写稿里强调“模板渲染到 container 对应节点”“脚本执行后交互才生效”,这正说明资源路径必须正确。

怎么做:先保证子应用在 http://localhost:8089 可访问,再让主应用加载;加载后检查 container 节点内是否出现子应用模板,点击子应用按钮是否有交互。

易错点:转写稿里出现过一个典型调试过程:子应用实际端口是 62345,但入口路径写成了 127.0.0.1:5500,加载立即报错;把端口改回实际端口后才拿到 template 和 execScripts。这说明 base、entry、server.origin 等地址必须和实际服务一致,不能凭印象写。转写稿中的“自用、词应用、幼儿、录由”等同音表述,按正确术语应统一理解为“子应用、URL、路由”。

调试与验证要点 ​

为什么需要:地址类配置最容易“看起来改了但没生效”,必须通过请求和控制台验证。

怎么想:先确认子应用自身能启动并访问,再确认主应用加载时请求的是 8089 资源,最后确认模板和脚本都生效。

怎么做:启动子应用和主应用;打开开发者工具 Network,筛选 js、css,检查请求域名是否为 http://localhost:8089;查看 Console 是否有 404 或脚本错误;检查 container 节点是否渲染出子应用内容,并测试按钮交互。

易错点:只看到 HTML 渲染出来不代表脚本生效,转写稿中模板渲染后按钮无交互,执行脚本后才恢复交互;因此要同时验证样式、脚本和交互。base 末尾斜杠、端口、入口地址三者要一致,避免把“资源路径问题”误判成 qiankun 注册问题。

代码示例 ​

示例一:环境变量定义 + 主应用根据环境切换子应用地址 ​

ini
# .env.development —— 开发环境(vue-cli-service serve 时生效)
NODE_ENV = development
VUE_APP_BASE_API = https://api-hmzs.itheima.net/v1
ini
# .env.production —— 生产环境(vue-cli-service build 时生效)
NODE_ENV = production
VUE_APP_BASE_API = https://api-hmzs.itheima.net/api
js
// 主应用 main.js
import { registerMicroApps, start } from 'qiankun'

const isDev = process.env.NODE_ENV === 'development'

registerMicroApps([
  {
    name: 'hmzs-big-screen',
    entry: `${isDev ? '//localhost:5173' : '//localhost:8089'}`,
    container: '#container',
    activeRule: '/big-screen'
  }
])

start()

要点解释:

  • 环境变量用 key = value 语法定义,.env.development 对应开发环境,.env.production 对应生产环境,框架会自动按运行命令挑选对应文件。
  • 客户端代码(即 src 目录)只识别 NODE_ENV、BASE_URL 和以 VUE_APP_ 打头的变量,其它前缀在业务里读到的是 undefined,所以自定义变量统一加 VUE_APP_ 前缀最省心。
  • NODE_ENV 可以被自定义覆盖,BASE_URL 覆盖不了。
  • 易错点:改了 .env 文件必须重启 dev server 才生效;entry 写成 //localhost:5173 这种协议相对地址,可避免 http/https 混用被拦截。
  • 易错点:生产环境端口写成开发端口(或反之),子应用会 404,必须与实际部署服务的地址一致(此处 8086 是后台管理、8089 是大屏)。

示例二:子应用 vite.config.js 配置 base ​

js
// 子应用 vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'

export default defineConfig({
  base: 'http://localhost:8089/',
  plugins: [vue(), qiankun('hmzs-big-screen', {
    useDevMode: true
  })],
  server: {
    // 解决开发环境下的静态资源访问问题
    origin: '//127.0.0.1:5173'
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    }
  }
})

要点解释:

  • base 决定打包后 js、css 等静态资源从哪个服务器去取。上线后子应用部署在 http://localhost:8089,所以 base 要指向该地址,否则资源会 404。
  • qiankun('hmzs-big-screen', { useDevMode: true }) 里的名字必须与主应用 registerMicroApps 中的 name 完全一致,否则子应用生命周期对接不上。
  • server.origin 解决开发环境下静态资源地址被解析成相对路径的问题。
  • 易错点:base 末尾的 / 不能漏;base 与主应用 entry 中配置的地址必须是同一个服务。

示例三:mini-qiankun 核心实现(注册 → 路由匹配 → 加载渲染) ​

js
// mini-qiankun.js
import { importEntry } from 'import-html-entry'

// 1. 存配置
let appConfig = []

export function registerMicroApps(config) {
  appConfig = config
}

export function getAppConfig() {
  return appConfig
}

// 2. 启动:让路由复写逻辑生效
export function start() {
  rewriteRouter()
}

// 3. 复写路由:拿到路由切换的时机
function rewriteRouter() {
  // 前进 / 后退(go、back)走 popstate
  window.addEventListener('popstate', () => {
    matchRouter()
  })

  // push / replace 走 pushState,需要暂存原方法再覆盖
  const rawPushState = window.history.pushState
  window.history.pushState = function (...args) {
    rawPushState.apply(window.history, args) // 先执行原有逻辑
    matchRouter()                            // 再插入自己的逻辑
  }
}

// 4. 匹配激活路由
function matchRouter() {
  const path = window.location.pathname          // 拿到当前 URL 路径
  const app = getAppConfig().find(item => item.activeRule === path) // 拿到匹配项
  if (app) {
    loadMicroApp(app)                            // 匹配上才加载
  }
}

// 5. 加载 + 渲染子应用
async function loadMicroApp(app) {
  const res = await importEntry(app.entry)       // 加载子应用的 HTML 资源
  const container = document.querySelector(app.container)
  container.innerHTML = res.template             // 渲染模板
  res.execScripts()                              // 执行脚本,交互才生效
}

要点解释:

  • registerMicroApps 只负责把配置存进模块内的全局变量,真正的启动动作在 start 里做,这与 qiankun 的 API 用法保持一致。
  • 路由复写是核心:popstate 处理浏览器的前进后退,pushState 处理编程式导航(如 router.push)。覆盖时必须先暂存 rawPushState,在自定义方法里用 apply(window.history, args) 先执行原逻辑,再插入自己的逻辑——直接覆盖不执行原方法会导致路由跳转失效。
  • 匹配用 window.location.pathname 与 activeRule 做相等判断,find 匹配不到返回 undefined,必须判空,否则任何路径都会去加载子应用。
  • 加载渲染分两步:res.template 通过 innerHTML 塞进 container,res.execScripts() 执行脚本。只渲染模板不执行脚本,页面能显示但点击无反应,这是最常见的现象。
  • 易错点:app.entry 的端口写错(如写成 5500 而实际是 62345)会直接报请求失败,务必以子应用实际启动地址为准。

常见错误 ​

1. 环境变量在 src 里读不到

  • 现象:process.env.API_BASE 在组件里打印为 undefined,接口地址变成 undefined/xxx。
  • 原因:客户端代码(src 目录)只注入 NODE_ENV、BASE_URL 和 VUE_APP_ 前缀的变量,自定义 key 没加前缀被过滤掉了。
  • 修法:改名为 VUE_APP_API_BASE,改完必须重启 dev server(.env 文件不参与 HMR)。

2. 打包后仍请求开发环境地址

  • 现象:本地 serve 一切正常,build 部署后接口 404,Network 里还是 http://localhost:xxxx。
  • 原因:把地址硬编码在 axios 里,或 .env.production 压根没建 / key 名与 .env.development 不一致。
  • 修法:两份 .env.* 保持完全相同的 key 集合,只改 value;代码里只引用 key,绝不写死地址。用 vue-cli-service build 后本地 serve 一下 dist 验证。

3. 微前端 entry 环境判断写错

  • 现象:开发时子应用加载正常,上线后主应用报 Failed to fetch / 子应用白屏;或反过来。
  • 原因:process.env.NODE_ENV 判断反了,或者把 entry 写成了子应用的页面路由(//localhost:8089/big-screen)而不是服务根地址。
  • 修法:entry 只写到端口(//localhost:8089),路径交给 activeRule;判断写成 const isDev = process.env.NODE_ENV === 'development',打包后 console 打印确认走的是生产分支。

4. 子应用 base 与部署服务器不一致

  • 现象:主应用把 HTML 拉过来了,但子应用的 JS/CSS 404,页面样式全丢、交互失效。
  • 原因:vite.config.js 的 base 还停留在默认 / 或开发端口,打包产物的资源路径指向了错误服务器。
  • 修法:生产构建时 base 必须等于子应用部署服务器地址(如 http://localhost:8089/);同时确认 dist 已放进该服务器的 public 目录。

5. 手动实现加载时端口照抄示例

  • 现象:import-html-entry 请求 5500 端口,实际子应用跑在 62345,控制台一片红。
  • 原因:直接复制示例代码里的端口,没对照自己 vite 启动输出的真实端口。
  • 修法:以终端实际输出为准;把 entry 提成变量或环境变量,避免多处硬编码。

6. 模板渲染了但点击无反应

  • 现象:子应用界面显示出来了,按钮点了没效果,控制台无报错。
  • 原因:只做了 innerHTML = template,漏了 execScripts()——脚本没执行,事件监听自然没绑上。
  • 修法:渲染模板后紧跟执行脚本,顺序不能颠倒(脚本可能依赖已存在的 DOM)。

练习 ​

1. 环境变量最小验证 新建 .env.development 与 .env.production,各定义 VUE_APP_API_BASE,值分别为 /api-dev 与 /api-prod;在 main.js 里 console.log 输出。要求:npm run serve 看到 dev 值,npm run build + 本地起静态服务打开 dist 看到 prod 值。再额外定义一个不带前缀的 API_BASE,观察它为何是 undefined。

2. 环境变量改造 axios 把项目中硬编码的基地址改为读环境变量,并封装成 src/utils/request.js 统一导出。验收标准:全项目搜索不到任何 http:// 开头的接口地址字面量。

3. 微前端 entry 双环境适配 主应用 registerMicroApps 中,让 entry 在开发时指向 //localhost:5173、生产时指向 //localhost:8089。要求用 process.env.NODE_ENV 判断,并在 start() 之前打印一行当前环境标识,打包后确认输出正确。

4. 子应用 base 配置与产物验证 把子应用 vite.config.js 的 base 改为生产服务器地址并打包,用浏览器 DevTools 的 Network 面板确认所有 js/css 请求的域名端口都指向 8089;再把 dist 拷入对应服务器 public 目录,验证主应用能正常加载且样式完整。

5. (进阶)补全 mini-qiankun 参照笔记中“注册 → 路由匹配 → 加载 → 渲染”四步,在 matchRoute 里打印匹配结果,要求:点击“加载子应用”和浏览器前进/后退时都能触发;匹配失败时打印 undefined 且不发起任何网络请求。思考题:popstate 与 pushState 两条路径为什么必须都挂上?

原视频对照 ​

#视频时长
1核心实现步骤分析04:13
2实现registerMicroApp04:19
3实现覆写路由06:59
4实现激活路由匹配06:16
5实现子应用的加载和渲染10:44

小结 ​

  1. 环境变量的本质是同一 key、多份文件、按运行命令自动选文件,serve 读 .env.development,build 读 .env.production。
  2. 客户端(src)变量有三条硬规则:只认 NODE_ENV、BASE_URL、VUE_APP_*;NODE_ENV 可覆盖、BASE_URL 不可覆盖;改 .env 必须重启。
  3. 判断“当前环境”只信 process.env.NODE_ENV,不要自己造 flag,否则打包后容易走错分支。
  4. 微前端场景下环境变量解决两件事:主应用用哪个 entry 去拉子应用、子应用从哪个服务器取静态资源(base)。
  5. entry 写服务根地址、activeRule 写路由路径,两者职责不同,混写是白屏高发点。
  6. mini-qiankun 的四步骨架是“注册配置 → 覆写路由拿时机 → 匹配 activeRule → 拉取渲染执行脚本”,乾坤的 API 只是把这四步封装好了。
  7. 加载 ≠ 渲染:拉 HTML 只是第一步,template 塞进 container、execScripts 执行脚本缺一不可,否则“看得见点不动”。
  8. 覆写 pushState/replaceState 时,务必先暂存原方法、内部 call 回原对象再插入自己的逻辑——这是所有“无侵入拦截”的通用套路。