SvelteKit 最新中文文档教程(18)—— 浅层路由和 Packaging

news2025/4/17 16:40:44

前言

Svelte,一个语法简洁、入门容易,面向未来的前端框架。

从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1

image.png

Svelte 以其独特的编译时优化机制著称,具有轻量级高性能易上手等特性,非常适合构建轻量级 Web 项目

为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。

如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。

浅层路由

当您在 SvelteKit 应用中导航时,您会创建历史记录条目。点击后退和前进按钮会遍历这个条目列表,重新运行所有 load 函数,并在必要时替换页面组件。

有时,在不导航的情况下创建历史条目是有用的。例如,您可能想要显示一个模态对话框,用户可以通过返回导航来关闭它。这在移动设备上特别有价值,因为滑动手势通常比直接与 UI 交互更自然。在这些情况下,没有关联历史记录条目的模态可能会令人沮丧,因为用户可能会尝试向后滑动来关闭它,却发现自己到了错误的页面。

SvelteKit 通过 pushStatereplaceState 函数使这成为可能,这些函数允许您在不进行导航的情况下将状态与历史记录条目关联。例如,要实现一个由历史驱动的模态:

<!--- file: +page.svelte --->
<script>
  import { pushState } from '$app/navigation';
  import { page } from '$app/state';
  import Modal from './Modal.svelte';

  function showModal() {
    pushState('', {
      showModal: true
    });
  }
</script>

{#if page.state.showModal}
  <Modal close={() => history.back()} />
{/if}

模态框可以通过返回导航(取消设置 page.state.showModal)或通过交互触发 close 回调运行来关闭。

API

pushState 的第一个参数是相对于当前 URL 的 URL。要保持在当前 URL,使用 ''

第二个参数是新的页面状态,可以通过 page 对象 作为 page.state 访问。您可以通过声明 App.PageState 接口(通常在 src/app.d.ts 中)来使页面状态类型安全。

要设置页面状态而不创建新的历史记录条目,请使用 replaceState 而不是 pushState

[!旧版说明] > $app/state 中的 page.state 是在 SvelteKit 2.12 中添加的。如果您使用的是较早版本或正在使用 Svelte 4,请使用 $app/stores 中的 $page.state

为路由加载数据

在进行浅层路由时,您可能想在当前页面内渲染另一个 +page.svelte。例如,点击照片缩略图可以弹出详细视图,而不需要导航到照片页面。

为此,您需要加载 +page.svelte 所需的数据。一个便捷的方法是在 <a> 元素的 click 处理程序中使用 preloadData。如果元素(或其父元素)使用 data-sveltekit-preload-data,数据将已经被请求,preloadData 将复用该请求。

<!--- file: src/routes/photos/+page.svelte --->
<script>
  import { preloadData, pushState, goto } from '$app/navigation';
  import { page } from '$app/state';
  import Modal from './Modal.svelte';
  import PhotoPage from './[id]/+page.svelte';

  let { data } = $props();
</script>

{#each data.thumbnails as thumbnail}
  <a
    href="/photos/{thumbnail.id}"
    onclick={async (e) => {
      if (innerWidth < 640        // 如果屏幕太小则退出
        || e.shiftKey             // 或链接在新窗口中打开
        || e.metaKey || e.ctrlKey // 或新标签页中打开 (mac: metaKey, win/linux: ctrlKey)
        // 也应考虑鼠标滚轮点击
      ) return;

      // 阻止导航
      e.preventDefault();

      const { href } = e.currentTarget;

      // 运行 `load` 函数(或者说,获取由于 `data-sveltekit-preload-data`
      // 而已经在运行的 `load` 函数的结果)
      const result = await preloadData(href);

      if (result.type === 'loaded' && result.status === 200) {
        pushState(href, { selected: result.data });
      } else {
        // 出现问题!尝试导航
        goto(href);
      }
    }}
  >
    <img alt={thumbnail.alt} src={thumbnail.src} />
  </a>
{/each}

{#if page.state.selected}
  <Modal onclose={() => history.back()}>
    <!-- 将页面数据传递给 +page.svelte 组件,
         就像 SvelteKit 在导航时那样 -->
    <PhotoPage data={page.state.selected} />
  </Modal>
{/if}

注意事项

在服务端渲染期间,page.state 始终是一个空对象。对于用户首次访问的页面也是如此 — 如果用户重新加载页面(或从另一个文档返回),状态将不会应用,直到他们进行导航。

浅层路由是一个需要 JavaScript 才能工作的功能。在使用它时要谨慎,并尝试考虑在 JavaScript 不可用时的合理后备行为。

Packaging

您可以使用 SvelteKit 来构建应用程序和组件库,使用 @sveltejs/package 包(npx sv create 提供了设置此功能的选项)。

在创建应用程序时,src/routes 的内容是对外公开的部分;src/lib 包含应用程序的内部库。

组件库的结构与 SvelteKit 应用程序完全相同,区别在于 src/lib 是对外公开的部分,而根目录下的 package.json 用于发布包。src/routes 可能是随库附带的文档或演示站点,也可能只是开发时使用的沙箱。

运行 @sveltejs/package 提供的 svelte-package 命令会将 src/lib 的内容生成到一个 dist 目录中(可以配置),其中包括以下内容:

  • src/lib 中的所有文件。Svelte 组件会被预处理,TypeScript 文件会被转译为 JavaScript。
  • 为 Svelte、JavaScript 和 TypeScript 文件生成类型定义(d.ts 文件)。您需要安装 typescript >= 4.0.0 来支持此功能。类型定义文件会被放置在实现文件旁边,手动编写的 d.ts 文件将原样复制。您可以禁用生成,但我们强烈建议不要这样做 —— 使用您库的用户可能会需要这些文件来支持 TypeScript。

[!注意] @sveltejs/package 的第 1 版会生成一个 package.json。现在不再如此,它会使用项目中的 package.json 并验证其正确性。如果您仍然使用第 1 版,请查看此 PR 获取迁移说明。

package.json 的结构

因为您现在正在为公共使用构建一个库,因此 package.json 的内容变得更为重要。通过它,您可以配置包的入口点、发布到 npm 的文件以及库的依赖。我们将逐一介绍最重要的字段。

name

这是您包的名称,其他人可以使用该名称安装您的包,并可在 https://npmjs.com/package/<name> 网站上看到它。

{
	"name": "your-library"
}

在此处阅读关于它的更多内容。

license

每个包都应有一个 license 字段,以告知人们如何使用它。目前非常流行的一种许可证是 MIT,它在分发和复用方面非常宽松且无需担保。

{
	"license": "MIT"
}

在此处阅读关于它的更多内容。请注意,应在包中包含一个 LICENSE 文件。

files

该字段告诉 npm 哪些文件将被打包并上传到 npm。它应包含输出文件夹(默认为 dist)。您的 package.jsonREADMELICENSE 文件会始终被包括在内,因此您不需要指定它们。

{
	"files": ["dist"]
}

要排除不必要的文件(如单元测试,或者仅从 src/routes 导入的模块等)可以将它们添加到 .npmignore 文件中。这将导致包更小,安装速度更快。

在此处阅读关于它的更多内容。

exports

"exports" 字段包含包的入口点。如果您通过 npx sv create 设置了一个新的库项目,它会设置为单一出口,即包的根目录:

{
	"exports": {
		".": {
			"types": "./dist/index.d.ts",
			"svelte": "./dist/index.js"
		}
	}
}

这告诉打包工具和工具链,您的包只有一个入口点,即根目录,所有内容应通过以下方式导入:

// @errors: 2307
import { Something } from 'your-library';

typessvelte 键是导出条件,它们告诉工具在查找 your-library 导入时应引入哪个文件:

  • TypeScript 看到 types 条件,会查找类型定义文件。如果您不发布类型定义,请忽略此条件。
  • 支持 Svelte 的工具会看到 svelte 条件,知道这是一个 Svelte 组件库。如果您发布的库不导出任何 Svelte 组件,并且也可以在非 Svelte 项目中使用(如 Svelte store 库),您可以将此条件替换为 default

[!注意] 早期版本的 @sveltejs/package 还添加了一个 package.json 导出。这不再是模板的一部分,因为所有工具都可以处理没有明确导出的 package.json

您可以根据需要调整 exports 并提供更多入口点。例如,如果您想直接暴露 src/lib/Foo.svelte 组件而不是通过 src/lib/index.js 文件重新导出组件,您可以创建以下导出映射……

{
	"exports": {
		"./Foo.svelte": {
			"types": "./dist/Foo.svelte.d.ts",
			"svelte": "./dist/Foo.svelte"
		}
	}
}

……然后您的库的使用者可以用如下方式导入该组件:

// @filename: ambient.d.ts
declare module 'your-library/Foo.svelte';

// @filename: index.js
// ---cut---
import Foo from 'your-library/Foo.svelte';

[!注意] 请注意,如果您提供类型定义,采用此方式可能需要额外处理。在此处阅读关于此问题的更多详细信息。

通常,exports 映射的每个键都是用户从您的包中导入某些内容的路径。而值则是将被导入的文件的路径或包含这些文件路径的导出条件映射。

在此处阅读关于 exports 的更多内容。

svelte

这是一个遗留字段,用于让工具识别 Svelte 组件库。如果使用 svelte 导出条件,它已不再必要,但为了向尚未了解导出条件的过时工具提供兼容性,建议保留它。它应指向您的根入口点。

{
	"svelte": "./dist/index.js"
}
sideEffects

package.json 中的 sideEffects 字段用于让打包工具判断模块是否可能包含副作用。如果模块在被导入时对其他脚本可见的行为产生变化(例如修改全局变量或内置 JavaScript 对象的原型),则视为有副作用。由于副作用可能会影响应用程序的其他部分,这些文件/模块无论其导出是否在应用程序中使用,都会被包括在最终的打包文件中。

sideEffects 字段中指定的模块会帮助打包工具更积极地从最终的打包文件中剔除未使用的导出(即 tree-shaking),从而生成更小更高效的打包文件。不同的打包工具以不同的方式处理 sideEffects。尽管 Vite 不需要此配置,但建议为库声明所有 CSS 文件具有副作用,以保持与 webpack 兼容。新创建的项目中的默认配置如下:

/// file: package.json
{
	"sideEffects": ["**/*.css"]
}

如果您的库中的脚本存在副作用,请确保更新 sideEffects 字段。在新创建的项目中,所有脚本默认标记为无副作用。如果错误地将包含副作用的文件标记为没有副作用,可能会导致功能异常。

如果您的包中有副作用的文件,可以通过数组指定这些文件:

/// file: package.json
{
	"sideEffects": ["**/*.css", "./dist/sideEffectfulFile.js"]
}

这样只会将指定的文件视为有副作用的文件。

TypeScript

即使您自己不使用 TypeScript,也应为您的库提供类型定义,这样使用您库的人可以获得正确的智能提示。@sveltejs/package 让生成类型的过程对您来说基本上是透明的。默认情况下,在打包您的库时,会为 JavaScript、TypeScript 和 Svelte 文件自动生成类型定义。您只需要确保 exports 映射中的 types 条件指向正确的文件。当通过 npx sv create 初始化库项目时,会自动设置为根导出。

然而,如果您除了根导出还有其他内容,例如提供 your-library/foo 导入,您需要额外注意提供类型定义。不幸的是,默认情况下 TypeScript 不会 为这种导出解析 types 条件,比如 { "./foo": { "types": "./dist/foo.d.ts", ... }}。相反,它会从库的根目录(即 your-library/foo.d.ts 而不是 your-library/dist/foo.d.ts)查找 foo.d.ts 文件。为了解决这个问题,您有两种选择:

第一种选择是要求使用您库的人在其 tsconfig.json(或 jsconfig.json)中将 moduleResolution 选项设置为 bundler(从 TypeScript 5 开始可用,未来是最佳推荐选项)、node16nodenext。这会使 TypeScript 实际查看 exports 映射并正确解析这些类型。

第二种选择是滥用 TypeScript 的 typesVersions 特性连接类型。typesVersionspackage.json 中的一个字段,TypeScript 根据 TypeScript 版本检查不同类型定义,同时也包含路径映射功能。我们利用该路径映射功能来满足需求。对于上面提到的 foo 导出,相应的 typesVersions 定义如下:

{
	"exports": {
		"./foo": {
			"types": "./dist/foo.d.ts",
			"svelte": "./dist/foo.js"
		}
	},
	"typesVersions": {
		">4.0": {
			"foo": ["./dist/foo.d.ts"]
		}
	}
}

>4.0 表示如果使用的 TypeScript 版本大于 4,则 TypeScript 会检查内部映射。内部映射告诉 TypeScript your-library/foo 的类型定义在 ./dist/foo.d.ts 中,这实际上是对 exports 条件的复制。您还可以使用 * 通配符一次性提供多个类型定义而无需重复。如果选择使用 typesVersions,您需要通过它声明所有类型导入,包括根导入(定义为 "index.d.ts": [..])。

您可以在此处 阅读有关该功能的更多信息。

最佳实践

除非您计划将包仅供其他 SvelteKit 项目使用,否则应避免在包中使用 SvelteKit 特定模块(如 $app/environment)。例如,与其使用 import { browser } from '$app/environment',不如使用 import { BROWSER } from 'esm-env'(参见 esm-env 文档)。您可能还希望将当前 URL 或导航操作作为 prop 传入,而不是直接依赖 $app/state$app/navigation 等。这种更通用的编写方式还会使测试、UI 演示等工具的设置变得更加容易。

svelte.config.js(而非 vite.config.jstsconfig.json)中通过 aliases 添加别名,以便它们被 svelte-package 处理。

应仔细考虑对包的更改是错误修复、新功能还是重大更改,并相应地更新包版本。注意,如果从现有库中移除任何 exports 路径或其内的任何 export 条件,应将其视为重大更改。

{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
// 将 `svelte` 更改为 `default` 是重大更改:
---			"svelte": "./dist/index.js"---
+++			"default": "./dist/index.js"+++
    },
// 移除此项是重大更改:
---		"./foo": {
      "types": "./dist/foo.d.ts",
      "svelte": "./dist/foo.js",
      "default": "./dist/foo.js"
    },---
// 添加此项是可以的:
+++		"./bar": {
      "types": "./dist/bar.d.ts",
      "svelte": "./dist/bar.js",
      "default": "./dist/bar.js"
    }+++
  }
}

选项

svelte-package 接受以下选项:

  • -w/--watch — 监听 src/lib 的文件更改并重新构建包
  • -i/--input — 包含包所有文件的输入目录。默认为 src/lib
  • -o/--output — 处理后的文件写入的输出目录。您的 package.jsonexports 应指向该文件夹内的文件,files 数组也应包含该文件夹。默认为 dist
  • -t/--types — 是否创建类型定义(d.ts 文件)。我们强烈建议这样做,因为它有助于提升生态系统库的质量。默认为 true
  • --tsconfig — tsconfig 或 jsconfig 的路径。如果未提供,则会在工作区路径中搜索最近的 tsconfig/jsconfig。

发布

要发布生成的包:

npm publish

限制

所有的相对文件导入需要完全指定路径,遵守 Node 的 ESM 算法。这意味着对于像 src/lib/something/index.js 这样的文件,必须包括文件名和扩展名:

// @errors: 2307
import { something } from './something+++/index.js+++';

如果您使用 TypeScript,您需要以同样的方式导入 .ts 文件,但使用 .js 文件后缀而不是 .ts 文件后缀。(这是一个 TypeScript 的设计决策,超出我们的控制范围。)在您的 tsconfig.jsonjsconfig.json 中设置 "moduleResolution": "NodeNext" 将有助于解决这个问题。

除 Svelte 文件(预处理)和 TypeScript 文件(转换为 JavaScript)外,所有文件都按原样复制。

Svelte 中文文档

点击查看中文文档:

  1. SvelteKit 浅层路由
  2. SvelteKit Packaging

系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2331023.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

集成nacos2.2.1出现的错误汇总

总结 1.jdk问题 jdk要一致 2.idea使用问题 idea启动nacos要配置&#xff0c;idea启动类要启动两次&#xff0c;并配置两次vm参数 3.项目依赖问题 依赖要正确添加&#xff0c;有的模块就是不能用公共模块的pom配置&#xff0c;需要独立配置&#xff0c;先后启动顺序也要注意…

LabVIEW 开发如何降本增效

在 LabVIEW 开发领域&#xff0c;如何在确保项目质量的同时降低开发成本&#xff0c;是众多企业和开发者共同关注的焦点。这不仅关乎资源的高效利用&#xff0c;更影响项目的投资回报率和市场竞争力。下面&#xff0c;我们将从多个维度深入剖析降本策略&#xff0c;并结合具体案…

Tomcat 负载均衡

目录 二、Tomcat Web Server 2.1 Tomcat 部署 2.1.1 Tomcat 介绍 2.1.2 Tomcat 安装 2.2 Tomcat 服务管理 2.2.1 Tomcat 启停 2.2.2 目录说明 2.2.3编辑主页 2.3 Tomcat管理控制台 2.3.1开启远程管理 2.3.2 配置远程管理密码 三、负载均衡 3.1 重新编译Nginx 3.1.1 确…

4月8日日记

今天抖音刷到一个视频 记了一下笔记 想做自媒体&#xff0c;直播&#xff0c;抖音是最大的平台&#xff0c;但是我的号之前因为跟人互喷被封号了 今天想把实名认证转移到新号上&#xff0c;试了一下竟然这次成功了&#xff0c;本以为能开直播了但是 还是因为之前的号有违规记…

【JavaScript】十六、事件捕获和事件冒泡

文章目录 1、事件流2、事件捕获3、事件捕获4、阻止冒泡5、解绑事件6、鼠标经过事件的区别7、两种事件注册语法的区别 1、事件流 先举个形象的例子&#xff1a;你去西安大雁塔旅游 出发找目的地时&#xff1a;先从你家出发&#xff0c;到陕西省西安市&#xff0c;再到雁塔区&a…

基于 Spring Boot 瑞吉外卖系统开发(一)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;一&#xff09; 系统概述 系统功能 技术选型 初始项目和数据准备 初始项目和SQL文件下载 创建数据库并导入数据 打开reggie项目 运行效果 主函数启动项目&#xff0c;访问URL&#xff1a; http://127.0.0.1:8080/backend/pag…

WordPress超简洁的主题:果果CMS主题

果果CMS是基于WordPress开发的超精简的一款主题&#xff0c;它在原有的特性上添加了许多新特性&#xff0c;例如&#xff1a;随机文章、随机标签、随机分类、广告、友情链接等。 新版特性&#xff1a; 小&#xff1a;主题安装包文件大小只有140.48KB。少&#xff1a;主题最小…

leetcode13.罗马数字转整数

遍历&#xff0c;下一个值不大于当前值就加上当前值&#xff0c;否则就减去当前值 class Solution {public int romanToInt(String s) {Map<Character, Integer> map Map.of(I, 1,V, 5,X, 10,L, 50,C, 100,D, 500,M, 1000);int sum 0;for (int i 0; i < s.length(…

线程安全问题的原因与解决方案总结

目录 一 什么是线程安全&#xff1f; 二 线程安全问题的实例 三 线程安全问题的原因 1.多个线程修改共享数据 2.抢占式执行 3.修改操作不是原子的 4.内存可见性问题 5.指令重排序 四 解决方案 1.同步代码块 2.同步方法 3.加锁lock解决问题 一 什么是线程安全&…

Tunable laser激光器的前向和后向锁波长方案

----转载自秦岭农民的文章 Tunable laser可调激光器的锁波长方案 激光器锁波长技术是指通过各种手段将激光器的输出波长稳定在某一特定值或范围内&#xff0c;以满足高精度应用的需求。这些技术包括Etalon、波长计/光谱仪反馈、波长参考源、温度控制、电流控制、锁相环&#…

蓝桥杯:日期统计

文章目录 问题描述解法一递归解法二&#xff1a;暴力破解 问题描述 首先我们要了解什么是子序列&#xff0c;就是一个序列之中可以忽略元素但是不能改变顺序之后获得的序列就叫做子序列。 如"123"就是"11234"的子序列而不是"11324"的子序列 解法…

IQ解调原理#通信原理系列

IQ解调原理&#xff1a;接收端收到s(t)信号后&#xff0c;分为两路&#xff1a; 一路信号乘以cosω₀t再积分&#xff0c;就可以得到a&#xff1a; 另一路乘以 -sinω₀t再积分&#xff0c;就可以得到b&#xff1a;

C++蓝桥杯实训篇(三)

片头 嗨&#xff01;小伙伴们&#xff0c;大家好~ 今天我们来学习前缀和与差分相关知识&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 一、一维前缀和 以上&#xff0c;是我们用数学知识求解区间和&#xff0c;现在我们使用前缀和来求解&#xff1a; 我们知道&am…

【数据挖掘】岭回归(Ridge Regression)和线性回归(Linear Regression)对比实验

这是一个非常实用的 岭回归&#xff08;Ridge Regression&#xff09;和线性回归&#xff08;Linear Regression&#xff09;对比实验&#xff0c;使用了 scikit-learn 中的 California Housing 数据集 来预测房价。 &#x1f4e6; 第一步&#xff1a;导入必要的库 import num…

CExercise_07_1指针和数组_1编写函数交换数组中两个下标的元素

题目&#xff1a; 要求编写函数将数组作为参数传递来实现&#xff1a; 1.编写函数交换数组中两个下标的元素。函数声明如下&#xff1a;void swap(int *arr, int i, int j) 。要求不使用[]运算符&#xff0c;将[]还原成解引用运算符和指针加法来完成。 关键点 通过指针交换数组…

塔能科技:智能路灯物联运维产业发展现状与趋势分析

随着智慧城市建设的推进&#xff0c;智能路灯物联运维产业正经历快速发展&#xff0c;市场规模持续扩大。文章探讨了智能路灯物联运维的技术体系、市场机遇和挑战&#xff0c;并预测了未来发展趋势&#xff0c;为行业发展提供参考。 关键词 智能路灯&#xff1b;物联运维&#…

ZW3D二次开发_普通对话框_设置对话框弹出位置

ZW3D的普通对话框可以在UI设计时静态地设置对话框弹出的位置&#xff0c;方法如下&#xff1a; 选中对话框的最顶级对象&#xff0c;即ZsCc::Form对象&#xff0c;在属性管理器中添加一个动态属性“form_pos”&#xff0c;类型为“StringList”&#xff0c;如下图所示 不同属性…

低代码开发「JNPF」应用场景

政务系统快速搭建 在数字化政务转型的浪潮下&#xff0c;JNPF 快速开发平台扮演着关键角色&#xff0c;为政府部门提供了高效且便捷的审批流程自动化解决方案。 以 “一网通办” 为例&#xff0c;通过平台的可视化拖拽式配置功能&#xff0c;政府工作人员能够将原本复杂繁琐的…

欧拉函数模板

1.欧拉函数模板 - 蓝桥云课 问题描述 这是一道模板题。 首先给出欧拉函数的定义&#xff1a;即 Φ(n) 表示的是小于等于 n 的数中和 n 互质的数的个数。 比如说 Φ(6)2&#xff0c;当 n 是质数的时候&#xff0c;显然有 Φ(n)n−1。 题目大意&#xff1a; 给定 n 个正整数…

屏幕空间反射SSR-笔记

屏幕空间反射SSR 相关文章&#xff1a; [OpenGL] 屏幕空间反射效果 Games202-RealTime GI in Screen Space github上的例子&#xff0c;使用visual studio2019 github例子对应的文章 使用OpenGL和C实现发光柱子的SSR倒影 下面是一个使用OpenGL和C实现屏幕空间反射(SSR)来创建…