<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/feed.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>丝月の博客</title><description>用技术创造有趣的事物</description><link>https://sylunae-blog.vercel.app</link><item><title>当 AI 学会“好看”：为什么风格化界面仍然少了灵魂</title><link>https://sylunae-blog.vercel.app/post/ai-generated-stylized-design-lacks-soul</link><guid isPermaLink="false">ai-generated-stylized-design-lacks-soul</guid><description>AI 可以迅速复刻“二游感”“玻璃拟态”或赛博氛围，却常把设计压成可预测的拼贴。问题不在于能不能用 AI，而在于谁还在做判断。</description><pubDate>Thu, 03 Sep 2026 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近让 AI 生成过风格化前端的人，大概都见过这种瞬间：提示词里写满“二次元”“精致”“高级感”“游戏 UI”“玻璃质感”，几秒后得到一张乍看很抓眼的页面。它有发光描边、渐变、悬浮卡片、熟悉的装饰符号；把它缩小看，甚至很像一张可以投到作品集里的概念图。&lt;/p&gt;
&lt;p&gt;可一旦开始看细节，问题就浮出来了：为什么按钮在这里？信息为什么这样分组？装饰和功能之间有什么关系？为什么每个区块都在用力，却没有一个区块真的在说话？它“像设计”，但不太像一个被人认真做过的产品。&lt;/p&gt;
&lt;p&gt;这篇文章不是要把 AI 赶出创作流程。它非常适合帮我们检索、生成变体、搭脚手架，甚至暴露自己想得不够清楚的地方。真正值得警惕的是：当我们把审美判断、问题定义和最终决策一起外包时，得到的通常不是更自由的创作，而是一种更顺滑的同质化。&lt;/p&gt;
&lt;h2&gt;一张图里，哪些东西是“风格”，哪些东西是“设计”？&lt;a href=&quot;#一张图里哪些东西是风格哪些东西是设计&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先看一张成熟游戏界面。它当然有角色、色彩、材质和大量装饰，但这些并不是独立堆上去的。视觉语言在这里服务于层级、状态、叙事和操作：玩家一眼知道哪里能点、什么最重要、自己正处在哪个世界里。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;/img/posts/ai-frontend-generate-bad-experience/%E6%B8%B8%E6%88%8F%E7%95%8C%E9%9D%A2.webp&quot; alt=&quot;参考游戏界面：角色、任务、资源与入口共同构成清晰的视觉秩序&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;参考游戏界面：角色、任务、资源与入口共同构成清晰的视觉秩序&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;所谓“风格化”并不等于把元素变多。它更像一套持续做取舍的规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;信息层级&lt;/strong&gt;：先让人看到什么，后让人发现什么；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交互语义&lt;/strong&gt;：按钮、标签、进度和状态是否真的能被区分；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;世界观&lt;/strong&gt;：材质、图形、动效和文案是否都在指向同一种叙事；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;克制&lt;/strong&gt;：哪些地方应该留白，哪些特征只出现一次才有力量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些决定很难被一句“做成某某游戏的感觉”完整交代。因为它们往往来自具体场景：用户是谁、他现在焦虑还是兴奋、这个页面是第一次进入还是重复操作、品牌想让人留下怎样的记忆。&lt;/p&gt;
&lt;h2&gt;AI 为什么容易产出“正确但无感”的界面&lt;a href=&quot;#ai-为什么容易产出正确但无感的界面&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;生成模型擅长给出一个统计上合理的答案。对“游戏化个人站”“二游面板”这类要求而言，合理答案通常是把训练资料中高频共现的信号组合起来：大图、透明面板、发光边框、徽章、圆角、紫蓝渐变、密集的装饰性图标。&lt;/p&gt;
&lt;p&gt;这并不意味着模型只能复制某一张图，也不意味着每次生成都没有新组合。问题在于，它优化的是“看起来符合描述”的概率，不是“这个选择为什么只能属于这个产品”。在没有额外约束时，最稳妥的路线就是走向大家已经见过、也最容易被识别为好看的视觉平均值。&lt;/p&gt;
&lt;p&gt;下面这类产物就很能说明差异。它保留了深色、强对比、几何碎片和中心图腾等气氛，却把原本与操作、角色和场景相关的秩序，替换成了更泛化的“神秘科技感”。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;/img/posts/ai-frontend-generate-bad-experience/AI%E5%8F%82%E8%80%83%E6%B8%B8%E6%88%8F%E7%95%8C%E9%9D%A2%E5%90%8E%E7%9A%84%E6%88%90%E5%93%81%20(2).webp&quot; alt=&quot;AI 参考游戏界面生成后的方案：保留了气氛，但信息与交互语义变得模糊&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;AI 参考游戏界面生成后的方案：保留了气氛，但信息与交互语义变得模糊&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;当结果被评价为“缺少灵魂”，通常不是因为它不够华丽，而是因为它缺少一条可追溯的选择链：&lt;strong&gt;为什么是这个形状、这个位置、这个节奏，而不是另外一种？&lt;/strong&gt; 人能在限制里建立偏好、保留不规则、承认矛盾，也能为了一个不那么“高分”的细节牺牲整体的顺眼。那种带有经历和立场的偏差，常常正是风格的起点。&lt;/p&gt;
&lt;h2&gt;同质化不是 AI 独有的问题，但 AI 会让它更快发生&lt;a href=&quot;#同质化不是-ai-独有的问题但-ai-会让它更快发生&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;模板、组件库、热门设计趋势早就会带来相似性；AI 只是把“借用既有范式”的速度和规模推得更高。过去做一个页面，设计师需要亲手找参考、拆结构、试比例、删掉失败的方案。这个过程不总是高效，却会逼迫人形成判断：我借了什么？为什么借？哪些地方必须改掉？&lt;/p&gt;
&lt;p&gt;而现在，一个过于宽泛的提示词就能跳过这些问题，直接给出看似完成的结果。连续几轮“再高级一点”“再精致一点”的迭代，常常不是在逼近独特的方向，而是在不断向模型最熟悉的审美中心收敛。&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;/img/posts/ai-frontend-generate-bad-experience/%E9%A3%8E%E6%A0%BC%E5%8C%96%E7%9A%84%E4%B8%AA%E4%BA%BA%E7%AB%99%E7%82%B9%20(3).webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;/img/posts/ai-frontend-generate-bad-experience/%E9%A3%8E%E6%A0%BC%E5%8C%96%E7%9A%84%E4%B8%AA%E4%BA%BA%E7%AB%99%E7%82%B9%20(2).webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img src=&quot;/img/posts/ai-frontend-generate-bad-experience/%E9%A3%8E%E6%A0%BC%E5%8C%96%E7%9A%84%E4%B8%AA%E4%BA%BA%E7%AB%99%E7%82%B9%20(1).webp&quot; alt=&quot;风格化页面的参考图：真正有辨识度的体验来自完整的构图、材质与节奏，而不是单个特效&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;风格化页面的参考图：真正有辨识度的体验来自完整的构图、材质与节奏，而不是单个特效&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;这也是我认同 Reddit 讨论帖《&lt;a href=&quot;https://www.reddit.com/r/aiwars/comments/1kiv5ly/dependence_on_ai_art_will_lead_to_stylistic/&quot;&gt;Dependence on AI Art will lead to stylistic, cultural, and creative stagnation&lt;/a&gt;》中一个核心提醒的原因：如果创作环境只奖励快速取得图像，而不奖励把经验、情绪和社会处境转译为形式的过程，文化会更容易停留在对已有样式的再组合。&lt;/p&gt;
&lt;p&gt;不过，帖子的讨论也给出了重要的反面提醒：人类的创作欲并不会因为工具存在就自动消失，AI 也确实能让没有传统训练的人把想法可视化。与其断言“AI 必然毁掉创作”，不如把问题说得更准确：&lt;strong&gt;当 AI 成为默认答案时，我们是否还保留了提出不同问题、承担选择后果的能力？&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;最危险的外包，不是画图，而是判断&lt;a href=&quot;#最危险的外包不是画图而是判断&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;设计里的技术工作可以外包一部分：生成色板候选、补齐边界状态、快速做几个布局方向、把设计稿转换为基础组件。这些都很有价值。&lt;/p&gt;
&lt;p&gt;但如果我们连下面的问题也交给 AI，风险就开始变得个人化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我真正想表达的主题是什么？&lt;/li&gt;
&lt;li&gt;用户在这个页面上最需要完成什么？&lt;/li&gt;
&lt;li&gt;这个方案让我不安的地方，究竟是可用性问题还是我在回避自己的判断？&lt;/li&gt;
&lt;li&gt;当两个方案都“看起来不错”时，我愿意为哪一种价值取舍负责？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;过度依赖的代价，未必会立刻表现为“不会做”。它更可能表现为：面对空白页时越来越难起步；面对 AI 的第一个答案时越来越难反驳；对“好”的感受越来越依赖模型给出的措辞和排序。久而久之，我们保留了产出速度，却失去了形成偏好的肌肉。&lt;/p&gt;
&lt;p&gt;这不只发生在视觉设计。写作时把立场、论证和改写都交给模型；做产品时让它替我们决定目标用户与优先级；学习时只要结论、不经历推导——这些行为都在把“思考的过程”误认为可以无损删除的摩擦。可很多真正属于自己的洞见，恰恰是在那段缓慢、笨拙、暂时没有答案的摩擦里长出来的。&lt;/p&gt;
&lt;h2&gt;把 AI 放在副驾驶，而不是方向盘上&lt;a href=&quot;#把-ai-放在副驾驶而不是方向盘上&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我更愿意把 AI 视为一个反应很快、知识很广、但没有生活经验的协作者。它可以提出选项、解释模式、帮你把重复劳动压缩到很小；但它不知道你的项目为什么要存在，也不会为一个错误的选择负责。&lt;/p&gt;
&lt;p&gt;一个更健康的前端与设计工作流，可以是这样的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先写人话的设计意图&lt;/strong&gt;：不用提示词，先说明页面给谁用、要完成什么、希望对方感受到什么，以及绝不想变成什么。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把 AI 用在发散，而不是裁决&lt;/strong&gt;：一次要三个明显不同的方向，而不是追求第一个“可用成品”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用自己的标准做筛选&lt;/strong&gt;：对照信息层级、可访问性、品牌语气和真实场景逐项删改；不要让“很酷”成为唯一标准。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;亲自完成关键转译&lt;/strong&gt;：核心布局、交互状态、文案节奏和最能代表项目的视觉细节，应该保留人的手感与决定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保留失败样本&lt;/strong&gt;：记录哪些提示、构图或实现不工作。失败不是浪费，它会慢慢变成你的审美边界。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这样使用 AI，效率依然会提升，但主导权没有离开创作者。AI 帮你更快看见可能性；你负责决定什么值得被做出来。&lt;/p&gt;
&lt;h2&gt;结语：灵魂不是一个滤镜&lt;a href=&quot;#结语灵魂不是一个滤镜&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AI 生成的界面并非注定空洞，人做的设计也不天然有灵魂。区别不在于有没有使用模型，而在于创作过程中是否仍然存在真实的观察、选择、拒绝和承担。&lt;/p&gt;
&lt;p&gt;风格从来不是给页面加上一层“二游感”或“赛博感”的滤镜。它是一个人面对限制时反复留下的痕迹：他看见了什么、在乎什么、愿意舍弃什么。当 AI 替我们完成一切时，最先消失的也许不是作品，而是这些痕迹。&lt;/p&gt;
&lt;p&gt;把工具用得更快没有问题。别让它替你决定，自己究竟想做出什么。&lt;/p&gt;</content:encoded><category>category:笔记</category><category>category:前端</category><category>tag:AI</category><category>tag:UI 设计</category><category>tag:前端</category><category>tag:创作思考</category><category>tag:生成式 AI</category></item><item><title>第一次发布 npm 包：从准备到正式发布</title><link>https://sylunae-blog.vercel.app/post/npm-package-publishing-guide</link><guid isPermaLink="false">npm-package-publishing-guide</guid><description>从 npm 包的用途、常见形式和打包方法讲起，再按顺序完成命名、配置、检查、本地安装和正式发布，适合第一次发布 npm 包的开发者。</description><pubDate>Mon, 31 Aug 2026 16:33:52 GMT</pubDate><content:encoded>&lt;p&gt;如果手上只有一段代码或一个项目，第一步通常会让人困惑：怎样才算一个 npm 包？需要把源码放到哪里？&lt;code&gt;npm pack&lt;/code&gt; 和 &lt;code&gt;npm publish&lt;/code&gt; 又有什么区别？&lt;/p&gt;
&lt;p&gt;先从这些基础概念开始，再按实际操作顺序走一遍完整发布流程。文中的 &lt;code&gt;@your-name/my-tool&lt;/code&gt;、GitHub 地址和版本号都要换成自己的内容。&lt;/p&gt;
&lt;h2&gt;npm 包是什么&lt;a href=&quot;#npm-包是什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;npm 包可以理解为一个准备好给别人安装的项目目录。目录中至少要有 &lt;code&gt;package.json&lt;/code&gt;，它会告诉 npm：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包叫什么、当前是什么版本。&lt;/li&gt;
&lt;li&gt;从哪个文件开始运行或导入。&lt;/li&gt;
&lt;li&gt;安装时还需要哪些依赖。&lt;/li&gt;
&lt;li&gt;哪些文件应该交给使用者。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个常见的包目录如下：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;my-tool/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├─ package.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├─ README.md&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├─ LICENSE&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├─ src/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  └─ index.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└─ dist/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   ├─ index.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   └─ index.d.ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的 &lt;code&gt;src/&lt;/code&gt; 存放源码，&lt;code&gt;dist/&lt;/code&gt; 存放构建后交付给使用者的文件。普通 JavaScript 项目也可以直接使用源码文件；TypeScript 和需要编译的项目通常会先生成 JavaScript。&lt;/p&gt;
&lt;p&gt;安装一个 npm 包时，npm 会下载它的文件和运行依赖，并把它放进项目的 &lt;code&gt;node_modules&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; @your-name/my-tool&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;npm registry 是存放和分发这些包的服务。公开发布后，其他人可以通过包名和版本安装同一份内容。&lt;/p&gt;
&lt;h2&gt;为什么要发布成 npm 包&lt;a href=&quot;#为什么要发布成-npm-包&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;适合发布的内容通常有这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会在多个项目中重复使用的函数、组件或配置。&lt;/li&gt;
&lt;li&gt;希望提供给其他开发者使用的 SDK、插件或工具库。&lt;/li&gt;
&lt;li&gt;希望用户安装后直接在终端执行的 CLI。&lt;/li&gt;
&lt;li&gt;需要通过版本号维护和分发的公共模块。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完整的网站、桌面应用或服务端项目通常有自己的部署方式。只有其中需要复用或单独安装的部分，才需要整理成 npm 包。&lt;/p&gt;
&lt;h2&gt;npm 包有哪些形式&lt;a href=&quot;#npm-包有哪些形式&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;从使用方式看，常见形式有三种：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;形式&lt;/th&gt;&lt;th&gt;用户怎样使用&lt;/th&gt;&lt;th&gt;需要配置的入口&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;普通库&lt;/td&gt;&lt;td&gt;在代码中 &lt;code&gt;import&lt;/code&gt; 或 &lt;code&gt;require&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;exports&lt;/code&gt; 或 &lt;code&gt;main&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CLI&lt;/td&gt;&lt;td&gt;安装后在终端运行命令&lt;/td&gt;&lt;td&gt;&lt;code&gt;bin&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;同时提供两种方式&lt;/td&gt;&lt;td&gt;既能导入，也能运行命令&lt;/td&gt;&lt;td&gt;&lt;code&gt;exports&lt;/code&gt; 和 &lt;code&gt;bin&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;从发布范围看，又可以分为：&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;形式&lt;/th&gt;&lt;th&gt;示例&lt;/th&gt;&lt;th&gt;适合什么情况&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;非 scoped 公共包&lt;/td&gt;&lt;td&gt;&lt;code&gt;my-tool&lt;/code&gt;&lt;/td&gt;&lt;td&gt;名称简单，但要占用全局唯一包名&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;scoped 公共包&lt;/td&gt;&lt;td&gt;&lt;code&gt;@your-name/my-tool&lt;/code&gt;&lt;/td&gt;&lt;td&gt;归在个人或组织名下，仍可供所有人安装&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;restricted 包&lt;/td&gt;&lt;td&gt;&lt;code&gt;@your-company/my-tool&lt;/code&gt;&lt;/td&gt;&lt;td&gt;只给有权限的用户或团队使用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;本地 &lt;code&gt;.tgz&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;my-tool-0.1.0.tgz&lt;/code&gt;&lt;/td&gt;&lt;td&gt;测试、内部传递，暂时不上传 registry&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;本文主要讲公开包。第一次发布时，使用个人 scope 往往更容易管理名称。&lt;/p&gt;
&lt;h2&gt;一段代码怎样变成 npm 包&lt;a href=&quot;#一段代码怎样变成-npm-包&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先确定“包目录”。独立工具通常直接使用项目根目录；大型项目可以把要发布的部分放在 &lt;code&gt;cli/&lt;/code&gt;、&lt;code&gt;packages/my-tool/&lt;/code&gt; 或单独生成的发布目录中。&lt;code&gt;package.json&lt;/code&gt; 所在的目录就是包的根目录。&lt;/p&gt;
&lt;p&gt;如果目录里还没有 &lt;code&gt;package.json&lt;/code&gt;，可以先运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; my-tool&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后完成下面几件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;整理要提供给用户的代码。&lt;/li&gt;
&lt;li&gt;TypeScript 等源码先构建成可运行的 JavaScript。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;package.json&lt;/code&gt; 中填写包名、版本、入口和依赖。&lt;/li&gt;
&lt;li&gt;添加 README、LICENSE，并限制发布文件。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;npm pack&lt;/code&gt; 生成 &lt;code&gt;.tgz&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;安装这份 &lt;code&gt;.tgz&lt;/code&gt; 做本地测试。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;npm publish&lt;/code&gt; 上传到 registry。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最小流程可以记成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;代码&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ package.json 和入口&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ 构建产物&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ npm pack&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ 本地安装测试&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ npm publish&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;npm pack&lt;/code&gt; 只在本地生成压缩包，不会公开任何内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; pack&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出通常类似：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;your-name-my-tool-0.1.0.tgz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这份 &lt;code&gt;.tgz&lt;/code&gt; 就是等待检查和发布的 npm 包。理解这条流程后，再开始确定正式包名和发布账号。&lt;/p&gt;
&lt;h2&gt;1. 确定包名和命名空间&lt;a href=&quot;#1-确定包名和命名空间&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;npm 包有两种常见命名方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;my-tool              普通包&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@your-name/my-tool   scoped 包&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@your-name&lt;/code&gt; 叫作 scope，可以使用 npm 用户名或组织名。它相当于一层命名空间，适合管理一组相关的包，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@your-name/core&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@your-name/cli&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;@your-name/config&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;包名尽量使用小写字母和连字符，名称要能说明用途，也要避开他人的商标和相似包名。&lt;a href=&quot;https://docs.npmjs.com/package-name-guidelines/&quot;&gt;npm 的包名建议&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;可以先查一下名称是否已经存在：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; view&lt;/span&gt;&lt;span&gt; @your-name/my-tool&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;包名、GitHub 仓库名和 CLI 命令名可以不同。比如 npm 包叫 &lt;code&gt;@your-name/my-tool&lt;/code&gt;，仓库叫 &lt;code&gt;my-tool-repo&lt;/code&gt;，安装后的命令仍然可以叫 &lt;code&gt;my-tool&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;2. 准备 npm 账号和 2FA&lt;a href=&quot;#2-准备-npm-账号和-2fa&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;到 &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npmjs.com&lt;/a&gt; 创建账号，验证邮箱，然后在账号设置中启用双重认证（2FA）。恢复码要另外保存，别放进项目目录或 Git 仓库。&lt;a href=&quot;https://docs.npmjs.com/configuring-two-factor-authentication/&quot;&gt;npm 2FA 指南&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;回到终端，确认 npm 使用官方 registry：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; get&lt;/span&gt;&lt;span&gt; registry&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正常结果是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://registry.npmjs.org/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后登录并确认身份：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; login&lt;/span&gt;&lt;span&gt; --registry=https://registry.npmjs.org/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; whoami&lt;/span&gt;&lt;span&gt; --registry=https://registry.npmjs.org/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;npm whoami&lt;/code&gt; 应该输出自己的 npm 用户名。如果项目里有 &lt;code&gt;.npmrc&lt;/code&gt;，也要检查其中有没有其他 registry。不要把 token 提交到 Git。&lt;/p&gt;
&lt;h2&gt;3. 完善 package.json&lt;a href=&quot;#3-完善-packagejson&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;下面是一份适合公开 CLI 包的简化示例：&lt;/p&gt;
&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;@your-name/my-tool&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;version&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;A small command-line tool for processing files.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;module&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;bin&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;my-tool&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;./dist/cli.js&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;files&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;dist/&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;README.md&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;LICENSE&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;engines&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;node&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&amp;gt;=20&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;license&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;MIT&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;repository&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;git&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;git+https://github.com/your-name/my-tool.git&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;homepage&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://github.com/your-name/my-tool#readme&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;bugs&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://github.com/your-name/my-tool/issues&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;publishConfig&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;registry&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://registry.npmjs.org/&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;access&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;public&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;先看几个容易写错的字段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;：npm 上显示和安装时使用的名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;version&lt;/code&gt;：本次发布的版本，同一个版本不能重复发布。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;files&lt;/code&gt;：允许进入 npm 包的文件，建议使用白名单。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bin&lt;/code&gt;：CLI 的入口。普通库不需要这个字段。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;engines&lt;/code&gt;：项目实际支持的 Node.js 版本，不要直接照抄示例。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repository&lt;/code&gt;、&lt;code&gt;homepage&lt;/code&gt;、&lt;code&gt;bugs&lt;/code&gt;：源码、主页和问题反馈地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publishConfig&lt;/code&gt;：固定发布目标和访问级别，减少发错 registry 的机会。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果是供代码导入的普通库，可以去掉 &lt;code&gt;bin&lt;/code&gt;，改为配置入口：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;module&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;exports&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;./dist/index.js&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;types&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;./dist/index.d.ts&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只有生成了 &lt;code&gt;index.d.ts&lt;/code&gt;，才填写 &lt;code&gt;types&lt;/code&gt;。运行时需要的包放进 &lt;code&gt;dependencies&lt;/code&gt;，构建和测试工具放进 &lt;code&gt;devDependencies&lt;/code&gt;。&lt;a href=&quot;https://docs.npmjs.com/cli/v11/configuring-npm/package-json/&quot;&gt;npm package.json 文档&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;private: true 应该怎么用&lt;a href=&quot;#private-true-应该怎么用&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&quot;private&quot;: true&lt;/code&gt; 会直接阻止 &lt;code&gt;npm publish&lt;/code&gt;。&lt;a href=&quot;https://docs.npmjs.com/cli/v11/configuring-npm/package-json/#private&quot;&gt;npm private 字段说明&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;开发阶段可以先加上它，防止误发：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;private&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果直接从当前目录发布，最终验收前要删掉这个字段。删掉以后需要重新打包和检查，因为压缩包里的 &lt;code&gt;package.json&lt;/code&gt; 也发生了变化。&lt;/p&gt;
&lt;p&gt;有些项目会保留根目录的 &lt;code&gt;private: true&lt;/code&gt;，再把准备发布的文件复制到单独目录。发布目录使用另一份精简的 &lt;code&gt;package.json&lt;/code&gt;，其中不带 &lt;code&gt;private: true&lt;/code&gt;。后面的 &lt;code&gt;pack:cli&lt;/code&gt; 就属于这种做法。&lt;/p&gt;
&lt;h3&gt;GitHub 地址什么时候填&lt;a href=&quot;#github-地址什么时候填&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;GitHub 仓库并非发布 npm 包的前置条件。不过，开源项目最好先创建仓库并推送代码，再填写 &lt;code&gt;repository&lt;/code&gt;、&lt;code&gt;homepage&lt;/code&gt; 和 &lt;code&gt;bugs&lt;/code&gt;。这样可以直接验证链接，npm 包页面也不会出现空地址。&lt;/p&gt;
&lt;h2&gt;4. 添加 LICENSE 和 README&lt;a href=&quot;#4-添加-license-和-readme&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;开源项目要选择许可证，并在包目录放一份完整的 &lt;code&gt;LICENSE&lt;/code&gt; 文件。&lt;code&gt;package.json&lt;/code&gt; 中的 &lt;code&gt;license&lt;/code&gt; 要与它一致。MIT、Apache-2.0、GPL 等许可证的要求不同，选择前可以查看 &lt;a href=&quot;https://choosealicense.com/&quot;&gt;Choose a License&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;README 不用写得很长，至少包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这个包解决什么问题。&lt;/li&gt;
&lt;li&gt;安装命令。&lt;/li&gt;
&lt;li&gt;一个能直接复制的使用示例。&lt;/li&gt;
&lt;li&gt;常用参数或配置。&lt;/li&gt;
&lt;li&gt;支持的 Node.js 版本。&lt;/li&gt;
&lt;li&gt;问题反馈地址和许可证。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CLI 包最好加上 &lt;code&gt;--help&lt;/code&gt; 示例；普通库要写清楚怎样 &lt;code&gt;import&lt;/code&gt;。README 放在发布包的根目录，npm 才会在包页面显示它。&lt;a href=&quot;https://docs.npmjs.com/about-package-readme-files/&quot;&gt;npm README 说明&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;5. 控制要发布的文件&lt;a href=&quot;#5-控制要发布的文件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;npm 发布的是打包后的文件，不会直接照搬 Git 仓库页面。最简单的控制方式是在 &lt;code&gt;package.json&lt;/code&gt; 中使用 &lt;code&gt;files&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;files&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;dist/&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;README.md&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;LICENSE&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样比维护一长串排除规则更容易检查。&lt;code&gt;.npmignore&lt;/code&gt; 也能排除文件，但最终结果仍然要以 &lt;code&gt;npm pack&lt;/code&gt; 生成的内容为准。&lt;a href=&quot;https://docs.npmjs.com/cli/v11/using-npm/developers/&quot;&gt;npm 文件包含规则&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;重点留意这些内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.env&lt;/code&gt;、token、私钥和证书。&lt;/li&gt;
&lt;li&gt;数据库备份、日志和真实用户数据。&lt;/li&gt;
&lt;li&gt;测试文件、旧构建产物和其他 &lt;code&gt;.tgz&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;打包进 JavaScript 或 source map 的内部地址和敏感值。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. 运行测试并生成 .tgz&lt;a href=&quot;#6-运行测试并生成-tgz&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先把项目本身检查一遍。下面是常见命令，按项目实际提供的脚本执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; ci&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; lint&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; check&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; test&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; run&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; audit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 pnpm 的项目可以改成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; --frozen-lockfile&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; lint&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; check&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; test&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; audit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;项目没有 &lt;code&gt;check&lt;/code&gt; 或 &lt;code&gt;build&lt;/code&gt; 脚本时不需要硬加。至少要保证测试通过，并重新生成准备发布的 &lt;code&gt;dist/&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;接着预览 npm 会收进去的文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; pack&lt;/span&gt;&lt;span&gt; --dry-run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;确认文件列表和体积，再生成压缩包：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; pack&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行后会得到类似这样的文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;your-name-my-tool-0.1.0.tgz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.tgz&lt;/code&gt; 就是 npm 包的压缩文件。&lt;code&gt;npm publish&lt;/code&gt; 最后上传的内容也会按这套规则打包。&lt;a href=&quot;https://docs.npmjs.com/cli/v11/commands/npm-pack/&quot;&gt;npm pack 文档&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;pack&lt;div&gt;&lt;/div&gt; 是什么&lt;a href=&quot;#pack-是什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pack:cli&lt;/code&gt; 只是项目自定义的脚本名，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;scripts&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;pack:cli&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;node scripts/pack-cli.mjs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它常用于“主项目里附带一个 CLI”的情况。一个比较稳妥的流程是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;构建 CLI&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ 清空临时发布目录&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ 只复制 CLI 运行需要的文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ 生成精简的 package.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;→ 在发布目录执行 npm pack&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主项目可以继续保留 &lt;code&gt;private: true&lt;/code&gt;。脚本生成的发布目录只包含 &lt;code&gt;dist&lt;/code&gt;、README、LICENSE 和发布用的 &lt;code&gt;package.json&lt;/code&gt;，可以减少把网站源码、配置或环境文件一起发出去的机会。&lt;/p&gt;
&lt;p&gt;独立的小型 npm 包直接使用 &lt;code&gt;files&lt;/code&gt; 和 &lt;code&gt;npm pack&lt;/code&gt; 就够了。Monorepo 如果使用 pnpm 的 &lt;code&gt;workspace:&lt;/code&gt; 依赖，应通过对应 workspace 的打包流程生成包，并检查 &lt;code&gt;.tgz&lt;/code&gt; 内的依赖版本。&lt;a href=&quot;https://pnpm.io/workspaces#publishing-workspace-packages&quot;&gt;pnpm workspace 发布说明&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;7. 人工检查 .tgz&lt;a href=&quot;#7-人工检查-tgz&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;不要只看 &lt;code&gt;npm pack&lt;/code&gt; 打印的摘要。先列出压缩包中的全部文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;tar&lt;/span&gt;&lt;span&gt; -tzf&lt;/span&gt;&lt;span&gt; ./your-name-my-tool-0.1.0.tgz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再把它解压到一个空目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; npm-package-review&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;tar&lt;/span&gt;&lt;span&gt; -xzf&lt;/span&gt;&lt;span&gt; ./your-name-my-tool-0.1.0.tgz&lt;/span&gt;&lt;span&gt; -C&lt;/span&gt;&lt;span&gt; npm-package-review&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开 &lt;code&gt;npm-package-review/package/package.json&lt;/code&gt;，逐项确认：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包名和版本正确。&lt;/li&gt;
&lt;li&gt;没有 &lt;code&gt;private: true&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bin&lt;/code&gt;、&lt;code&gt;exports&lt;/code&gt;、&lt;code&gt;types&lt;/code&gt; 指向的文件都存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dependencies&lt;/code&gt; 中没有本地路径。&lt;/li&gt;
&lt;li&gt;README 和 LICENSE 已包含。&lt;/li&gt;
&lt;li&gt;文件列表中没有密钥、环境文件、日志和无关源码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;还可以检查 JSON 能否正常解析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; -e&lt;/span&gt;&lt;span&gt; &quot;JSON.parse(require(&apos;node:fs&apos;).readFileSync(&apos;./npm-package-review/package/package.json&apos;, &apos;utf8&apos;)); console.log(&apos;package.json OK&apos;)&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改了源码、构建结果或 &lt;code&gt;package.json&lt;/code&gt;，就重新生成并检查 &lt;code&gt;.tgz&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;8. 在空目录模拟安装&lt;a href=&quot;#8-在空目录模拟安装&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;到仓库外面新建一个空目录，然后安装刚才检查过的 &lt;code&gt;.tgz&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; &quot;C:/path/to/your-name-my-tool-0.1.0.tgz&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CLI 可以这样测试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;./node_modules/.bin/my-tool&lt;/span&gt;&lt;span&gt; --help&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;./node_modules/.bin/my-tool&lt;/span&gt;&lt;span&gt; --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows PowerShell 也可以运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;node_modules&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;.bin&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;my-tool.cmd&lt;/span&gt;&lt;span&gt; --&lt;/span&gt;&lt;span&gt;help&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;普通库可以测试导入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; --input-type=module&lt;/span&gt;&lt;span&gt; -e&lt;/span&gt;&lt;span&gt; &quot;import(&apos;@your-name/my-tool&apos;).then(console.log)&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后照着 README 完整跑一次主要功能。这个步骤能发现缺少运行依赖、入口写错、文件漏打包等问题。&lt;/p&gt;
&lt;h2&gt;9. 确认本地代码已经推送&lt;a href=&quot;#9-确认本地代码已经推送&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;正式发布前，确认本地没有漏提交的改动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; status&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt; origin&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; status&lt;/span&gt;&lt;span&gt; -sb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看当前提交和远程分支：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; rev-parse&lt;/span&gt;&lt;span&gt; HEAD&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; rev-parse&lt;/span&gt;&lt;span&gt; origin/main&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个提交号应该一致。如果使用其他分支，把 &lt;code&gt;origin/main&lt;/code&gt; 换成实际分支。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.tgz&lt;/code&gt; 和 &lt;code&gt;dist/&lt;/code&gt; 是否提交到 Git，要看项目约定。更重要的是保存好这次发布对应的源码提交。代码有变化时，重新执行测试、打包和空目录安装。&lt;/p&gt;
&lt;h2&gt;10. 正式发布&lt;a href=&quot;#10-正式发布&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先做最后一次确认：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; whoami&lt;/span&gt;&lt;span&gt; --registry=https://registry.npmjs.org/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; view&lt;/span&gt;&lt;span&gt; @your-name/my-tool@0.1.0&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span&gt; --registry=https://registry.npmjs.org/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首次发布时，第二条通常会返回找不到包或版本。注意区分名称不存在、网络错误和权限错误。&lt;/p&gt;
&lt;p&gt;建议直接发布已经检查和安装过的 &lt;code&gt;.tgz&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; publish&lt;/span&gt;&lt;span&gt; ./your-name-my-tool-0.1.0.tgz&lt;/span&gt;&lt;span&gt; --access&lt;/span&gt;&lt;span&gt; public&lt;/span&gt;&lt;span&gt; --registry=https://registry.npmjs.org/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;公开的 scoped 包要加 &lt;code&gt;--access public&lt;/code&gt;。普通非 scoped 包也可以保留这个参数，命令更统一。&lt;a href=&quot;https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/&quot;&gt;npm scoped 包发布指南&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;终端会要求完成 2FA 验证。发布成功后，同一个包名和版本号不能再次使用；修复内容时要更新版本号。&lt;a href=&quot;https://docs.npmjs.com/cli/v11/commands/npm-publish/&quot;&gt;npm publish 文档&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;11. 发布后再安装一次&lt;a href=&quot;#11-发布后再安装一次&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;查看线上信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; view&lt;/span&gt;&lt;span&gt; @your-name/my-tool@0.1.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在另一个空目录安装线上版本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; @your-name/my-tool@0.1.0&lt;/span&gt;&lt;span&gt; --registry=https://registry.npmjs.org/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重复运行 README 中的主要示例，并打开 npm 包页面检查 README、LICENSE 和 GitHub 链接。&lt;/p&gt;
&lt;p&gt;确认没有问题后，可以给对应的 Git 提交加版本标签：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; tag&lt;/span&gt;&lt;span&gt; v0.1.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; push&lt;/span&gt;&lt;span&gt; origin&lt;/span&gt;&lt;span&gt; v0.1.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;发布前检查清单&lt;a href=&quot;#发布前检查清单&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt; npm 账号、邮箱和 2FA 已设置好。&lt;/li&gt;
&lt;li&gt; 包名、scope、版本号和 registry 正确。&lt;/li&gt;
&lt;li&gt; &lt;code&gt;package.json&lt;/code&gt; 的入口、依赖和仓库地址正确。&lt;/li&gt;
&lt;li&gt; README 与 LICENSE 已放进包目录。&lt;/li&gt;
&lt;li&gt; 测试和构建通过。&lt;/li&gt;
&lt;li&gt; &lt;code&gt;.tgz&lt;/code&gt; 已人工解压检查。&lt;/li&gt;
&lt;li&gt; 在仓库外安装并运行过同一份 &lt;code&gt;.tgz&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt; 本地源码已推送到对应远程提交。&lt;/li&gt;
&lt;li&gt; 公开 scoped 包使用了 &lt;code&gt;--access public&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt; 发布后已从 npm 安装线上版本。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>category:笔记</category><category>category:前端</category><category>tag:npm</category><category>tag:Node.js</category><category>tag:CLI</category><category>tag:开源</category></item><item><title>用 pnpm workspace 维护项目与主题包</title><link>https://sylunae-blog.vercel.app/post/pnpm-workspace-theme-package</link><guid isPermaLink="false">pnpm-workspace-theme-package</guid><description>用一份简单的笔记理解 pnpm workspace：把主题做成独立包，让项目始终使用同一份源码。</description><pubDate>Mon, 31 Aug 2026 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;项目里有一套会长期维护的主题、设计 token 或共享 UI 组件时，把文件复制到每个业务项目里，刚开始很省事，后面往往会变成同步负担：改了一处样式，还要记得去其他地方补上。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pnpm workspace&lt;/code&gt; 提供了一个更顺手的组织方式：把项目和主题包放进同一个仓库，让它们直接共享主题源码。&lt;/p&gt;
&lt;h2&gt;先看目录&lt;a href=&quot;#先看目录&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一个常见的结构大致是这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;my-app/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├─ src/                       # 页面、路由和业务代码&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├─ packages/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  └─ my-theme/               # 主题与共享 UI 的源码&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├─ package.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├─ pnpm-workspace.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└─ pnpm-lock.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 &lt;code&gt;packages/my-theme&lt;/code&gt; 是一个正常的 npm 包，只是它和业务项目住在同一个仓库里。主题的 CSS、组件、图片和字体都只维护在这里。&lt;/p&gt;
&lt;h2&gt;workspace 做了什么&lt;a href=&quot;#workspace-做了什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在根目录创建 &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt;，告诉 pnpm 哪些目录属于同一个工作区：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;packages&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - &lt;/span&gt;&lt;span&gt;packages/*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主题包也需要自己的 &lt;code&gt;package.json&lt;/code&gt;，其中最重要的是包名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;@acme/theme&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;version&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根目录的业务项目再把它声明为依赖：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;dependencies&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;@acme/theme&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace:*&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;workspace:*&lt;/code&gt; 的意思可以理解为：使用当前仓库里同名的包。执行 &lt;code&gt;pnpm install&lt;/code&gt; 后，pnpm 会把两者连接起来，因此本地改动会立刻被业务项目使用。&lt;/p&gt;
&lt;h2&gt;日常开发时会发生什么&lt;a href=&quot;#日常开发时会发生什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;假设主题包里有这些文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;packages/my-theme/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├─ src/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  ├─ components/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│  └─ theme/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│     ├─ tokens.css&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│     └─ styles.css&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└─ package.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后修改 &lt;code&gt;tokens.css&lt;/code&gt;、主题样式或共享组件，项目读取到的就是更新后的内容，不需要再复制文件，也没有额外的同步步骤。&lt;/p&gt;
&lt;p&gt;代码可以通过包名引用组件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Button, ThemeProvider } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@acme/theme&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CSS 也可以从包中导入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;@import&lt;/span&gt;&lt;span&gt; &apos;@acme/theme/theme.css&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体入口文件叫什么，取决于主题包在 &lt;code&gt;package.json&lt;/code&gt; 中暴露了哪些 &lt;code&gt;exports&lt;/code&gt;。如果导入失败，通常先检查包名、入口文件和 &lt;code&gt;exports&lt;/code&gt; 配置。&lt;/p&gt;
&lt;h2&gt;文件该放在哪里&lt;a href=&quot;#文件该放在哪里&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;可以用一个简单的判断来划分边界：会被多个项目复用、并且希望统一维护的内容，放进主题包；只服务于当前产品的内容，留在业务项目里。&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;放进主题包&lt;/th&gt;&lt;th&gt;留在业务项目&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;设计 token、颜色、字体规则&lt;/td&gt;&lt;td&gt;页面与路由&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;通用主题 CSS&lt;/td&gt;&lt;td&gt;接口请求与业务状态&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Button、Dialog 这类共享组件&lt;/td&gt;&lt;td&gt;只在当前页面使用的组件&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;通用图标与主题资源&lt;/td&gt;&lt;td&gt;产品文案与业务逻辑&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这样改主题时，关注点会很明确；业务代码也不会被一套可复用的基础样式牵着走。&lt;/p&gt;
&lt;h2&gt;部署时需要注意什么&lt;a href=&quot;#部署时需要注意什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;workspace 并不只在本地有效。将主题包源码、&lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; 和 &lt;code&gt;pnpm-lock.yaml&lt;/code&gt; 一起提交到仓库后，Vercel 之类的平台在仓库根目录安装和构建即可：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; --frozen-lockfile&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;pnpm 会安装 workspace 内的依赖，构建工具再把被 CSS 或 JavaScript 引用到的主题资源一并处理进产物。&lt;/p&gt;
&lt;h2&gt;小结&lt;a href=&quot;#小结&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;workspace 的重点在于让多个包从一开始就指向同一份源码。对于需要长期迭代的主题或组件库，这能减少重复文件，也让更新路径更清楚。&lt;/p&gt;</content:encoded><category>category:笔记</category><category>tag:pnpm</category><category>tag:workspace</category><category>tag:Monorepo</category><category>tag:工程化</category></item><item><title>高效学习法：拆、仿、练、创，把知识变成能力</title><link>https://sylunae-blog.vercel.app/post/effective-learning-method</link><guid isPermaLink="false">effective-learning-method</guid><description>用拆、仿、练、创组织学习，结合具体反馈、主动回忆、间隔复习和迁移练习，明确每一步该做什么、如何检查进步，也讨论没有强者陪练时怎样继续成长。</description><pubDate>Mon, 31 Aug 2026 04:30:43 GMT</pubDate><content:encoded>&lt;p&gt;看完一本书，觉得自己懂了；合上书，却讲不清楚。跟着教程能完成练习，换一个题目，又不知道从哪里开始。&lt;/p&gt;
&lt;p&gt;这种落差提醒我们：&lt;strong&gt;看懂、记住和独立使用，是三件不同的事。&lt;/strong&gt; 学习计划如果只统计读了多少页、看了多少课，就很难发现能力卡在哪里。&lt;/p&gt;
&lt;p&gt;可以用“拆、仿、练、创”组织学习：先建立框架，再分析范例，接着针对薄弱点练习，最后换一个场景独立使用。反馈负责纠偏，回忆与间隔负责巩固，再把新问题带回下一轮。&lt;/p&gt;
&lt;p&gt;这是一套便于执行的组织方法，不是经过整体实验验证的万能公式。下面以“把一个观点表达清楚”为例，把每一步落到具体动作上。&lt;/p&gt;
&lt;h2&gt;拆：围绕任务，建立一张够用的地图&lt;a href=&quot;#拆围绕任务建立一张够用的地图&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;进入陌生领域，先别急着从教材第一页一路读下去。先写出自己想完成的任务，例如：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;写一段 300 字的观点说明，让没接触过这个话题的人看懂结论和理由。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;有了任务，再浏览教材目录、入门综述和优秀成品，用三个问题搭起框架：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;这个领域要解决哪些核心问题？&lt;/strong&gt; 表达涉及写给谁、说什么、如何组织、怎样让对方理解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完成任务需要哪些部分？&lt;/strong&gt; 可以先拆成观点、理由、例子、段落衔接和修改。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这些部分怎样联系？&lt;/strong&gt; 先明确读者和观点，才能判断哪些理由有用；例子要支撑理由，而不只是让文章热闹。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把答案写在一页纸上，每个部分配一个例子，再标出自己最不确定的地方。不必追求完整漂亮的思维导图，也不要花一周整理“学习路线”却迟迟不动手。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这一阶段的产出，是一张能指导行动、允许修改的地图。&lt;/strong&gt; 检查标准很简单：你是否知道下一步要做什么，以及为什么先做它？&lt;/p&gt;
&lt;h2&gt;仿：拆出范例的做法，再换材料重做&lt;a href=&quot;#仿拆出范例的做法再换材料重做&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;“多看优秀作品”还不够。看完只记得“很流畅”，并没有提取出可以复用的方法。&lt;/p&gt;
&lt;p&gt;挑两三个与目标接近、难度能理解的范例。学习短文表达，就先分析短文，不必一上来研究长篇名作。可以做一张这样的拆解表：&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;范例中的做法&lt;/th&gt;&lt;th&gt;它解决什么问题&lt;/th&gt;&lt;th&gt;自己怎样试一次&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;开头直接说明观点&lt;/td&gt;&lt;td&gt;让读者知道这一段要说什么&lt;/td&gt;&lt;td&gt;用一句话写出核心判断&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;理由后紧跟具体例子&lt;/td&gt;&lt;td&gt;把抽象判断变得可理解&lt;/td&gt;&lt;td&gt;补一个读者熟悉的场景&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;长句后用短句收束&lt;/td&gt;&lt;td&gt;突出重点、调整节奏&lt;/td&gt;&lt;td&gt;拆开一个过长的句子，比较效果&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;删除重复的修饰词&lt;/td&gt;&lt;td&gt;减少阅读负担&lt;/td&gt;&lt;td&gt;找出删掉后不影响意思的词&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;拆解之后，先遮住原文，凭记忆写出结构，再换一个主题重写。最后对照范例，看看自己遗漏了什么，哪些做法换了场景就不适用。&lt;/p&gt;
&lt;p&gt;多比较一个范例，还能避免把某位作者的个人风格误当成通用规则。比如“结论放开头”适合许多说明场景，但不必成为所有叙事的固定开场。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;模仿的重点是理解选择，而不是替换几个词。&lt;/strong&gt; 练习稿可以贴近范例，公开发表时则应使用自己的内容，并清楚标注直接引用。&lt;/p&gt;
&lt;h2&gt;练：一次解决一个短板，让反馈能指导下一次&lt;a href=&quot;#练一次解决一个短板让反馈能指导下一次&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;重复熟悉的任务能维持熟练度，却未必能解决当前的瓶颈。“表达不好”也太宽泛，无法直接练习。把它缩小成可观察的问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我能提出观点，但举出的例子经常没有支撑这个观点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;围绕这个短板，安排一轮小练习：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;写下一个观点，只为它找三个候选例子。&lt;/li&gt;
&lt;li&gt;对每个例子补一句：“它支持这个观点，因为……”&lt;/li&gt;
&lt;li&gt;找出解释最勉强的一项，判断是例子无关，还是观点本身说得过大。&lt;/li&gt;
&lt;li&gt;修改后换一个观点重做，检验是否学会了判断方法。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;反馈可以来自老师、同伴、评分标准或对照答案。关键是让反馈具体到“哪里有问题、依据是什么、下一步怎样改”。比起问“写得怎么样”，更有用的问题是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你读出的中心观点是什么？哪个例子最不能支撑它？你在哪一句开始跟不上？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;收到意见后，也要区分事实错误、目标未达成与个人偏好。不是所有修改建议都必须接受，但应该能说明自己的判断依据。&lt;/p&gt;
&lt;h3&gt;接触强者，是寻找反馈的一条路径&lt;a href=&quot;#接触强者是寻找反馈的一条路径&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;更有经验的人可能看到你没有意识到的漏洞，也能展示另一种做法。但水平差距本身不保证学习发生：如果只知道“我输了”，却看不懂关键差异，下一轮仍可能原样重复。&lt;/p&gt;
&lt;p&gt;更有用的方式，是请对方只指出当前最值得改的一处，展示修改前后的区别，再由自己独立重做。&lt;/p&gt;
&lt;p&gt;没有强者陪练，也可以通过其他渠道获得参照：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对照高质量范例&lt;/strong&gt;，比较自己与范例在同一环节的处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;与同伴互评&lt;/strong&gt;，检查方法是否能稳定奏效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向新手解释&lt;/strong&gt;，从他们的追问中发现自己跳过的前提。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;换一个受众或限制条件&lt;/strong&gt;，例如把同一个观点分别讲给同行和完全不了解的人。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个人在某项技能上经验较少，不代表他的沟通、执行或观察能力也弱。学习时不必把人排成一条高低分明的队伍。&lt;/p&gt;
&lt;p&gt;“三分之一时间与较弱者练、三分之一与同水平者练、三分之一与强者练”，可以作为搭配练习对象的经验性提醒，&lt;strong&gt;不应视为适用于所有领域的黄金比例&lt;/strong&gt;。更实际的分配依据是：现在缺基础、缺稳定性，还是缺少对更高标准的认识？&lt;/p&gt;
&lt;p&gt;与自己比较时，也尽量保留具体作品和错误记录。例如比较同类任务中，读者误解观点的次数是否减少。参照物应是可检查的进步，而不是一个标准不断上移、永远无法满足的“完美自我”。&lt;/p&gt;
&lt;h2&gt;巩固：合上资料回忆，隔一段时间再检验&lt;a href=&quot;#巩固合上资料回忆隔一段时间再检验&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;练习结束时做对了，不代表几天后还能做到。为学习留出巩固环节，可以先从两个动作开始。&lt;/p&gt;
&lt;h3&gt;主动回忆：先尝试，再核对&lt;a href=&quot;#主动回忆先尝试再核对&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;看完一小节后，合上资料，回答三个问题：它解决什么问题？关键步骤是什么？我能否举一个自己的例子？写不出来的地方，就是需要回去核对的地方。&lt;/p&gt;
&lt;p&gt;学习操作技能时，则把“复述”换成“不看教程独立做一次”。卡住后先定位缺口，再查对应内容，避免从头重看却没有再次动手。&lt;/p&gt;
&lt;p&gt;检索练习不仅用于检测，也可能促进记忆。Karpicke 与 Roediger 的词汇学习实验发现，初次学会之后继续测试，对延迟回忆有明显帮助；继续重复学习没有产生同样的效果。这支持我们加入主动回忆，但不意味着首次阅读和理解可以省略。参见&lt;a href=&quot;https://doi.org/10.1126/science.1152408&quot;&gt;检索练习研究&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;间隔复习：根据表现调整距离&lt;a href=&quot;#间隔复习根据表现调整距离&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;可以先试一个简单安排：当天结束时回忆一次，隔天再做一次，几天后换一道题，一周后检查能否独立使用。每次都先尝试，再看笔记。&lt;/p&gt;
&lt;p&gt;这只是起点，不是固定的“遗忘曲线时间表”。回忆很轻松，就拉长间隔；关键步骤反复遗漏，就缩短间隔并补练。间隔效果还与希望保留多久有关，研究并不支持一套对所有材料都最优的时间表。参见&lt;a href=&quot;https://pubmed.ncbi.nlm.nih.gov/16719566/&quot;&gt;分散练习的元分析&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;也别把睡眠当作可以随意挪用的学习时间。睡眠参与记忆巩固，熬夜增加的输入时间不能直接等同于更多学习成果；它也不能替代白天的理解与练习。参见&lt;a href=&quot;https://journals.physiology.org/doi/full/10.1152/physrev.00032.2012&quot;&gt;睡眠与记忆综述&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;创：换场景使用，检查方法的适用边界&lt;a href=&quot;#创换场景使用检查方法的适用边界&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这里的“创”，不要求凭空发明新东西。能脱离原例子，在自己的任务中重新组织和使用方法，就是一次有价值的迁移。&lt;/p&gt;
&lt;p&gt;可以沿着“换内容 → 换受众 → 换任务”的顺序逐步尝试：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学会“观点—理由—例子”之后，先换一个话题写短文。&lt;/li&gt;
&lt;li&gt;再把短文改成给新手听的两分钟说明，检查哪些术语需要解释。&lt;/li&gt;
&lt;li&gt;最后把方法用于项目提案，判断除了理由和例子，是否还需要成本、风险与下一步行动。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;迁移后问自己：&lt;strong&gt;哪些结构可以保留？哪些条件变了？哪里必须重新设计？&lt;/strong&gt; 把成功和失败各记一个例子，比只记一句“这个方法很通用”更有帮助。&lt;/p&gt;
&lt;h3&gt;穿插练习，练的是辨别何时用什么&lt;a href=&quot;#穿插练习练的是辨别何时用什么&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;掌握基本做法后，可以把几类容易混淆的任务放在一起。例如学习表达时，混合练习“解释概念”“论证观点”和“描述过程”，每次动笔前先判断该用哪种结构。&lt;/p&gt;
&lt;p&gt;这与一边写作一边刷视频的多任务切换不同。它要求你在相关任务之间辨别和选择，而不是不断打断注意力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;穿插练习并非总比集中练习好。&lt;/strong&gt; 元分析发现，效果随材料类型与相似程度变化，部分任务并未显示优势。可以先集中理解一个新方法，再用混合任务检验选择能力，而不必从第一天就把所有内容打乱。参见&lt;a href=&quot;https://pubmed.ncbi.nlm.nih.gov/31556629/&quot;&gt;穿插学习的元分析&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;教别人，让理解接受追问&lt;a href=&quot;#教别人让理解接受追问&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;找一个不了解主题的人，用自己的话讲清楚，再请对方复述或尝试完成任务。尤其留意“为什么”“什么时候不能用”“换个例子行不行”这类问题。&lt;/p&gt;
&lt;p&gt;这类做法常被称为“费曼技巧”。它的价值在于迫使你回忆、组织和解释，并暴露缺口；&lt;strong&gt;讲得顺畅不等于讲得正确，能讲清也不等于能完成所有实际任务。&lt;/strong&gt; 还要核对资料，并回到真实任务中验证。&lt;/p&gt;
&lt;p&gt;研究支持某些情境下讲授能够促进理解，但不能据此把“教别人”称为所有学习任务中最强的检验方式。参见&lt;a href=&quot;https://www.sciencedirect.com/science/article/pii/S0361476X13000209&quot;&gt;学习后讲授与准备讲授的实验比较&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;把下一次学习写成一张任务卡&lt;a href=&quot;#把下一次学习写成一张任务卡&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;不必每次都完整走一遍所有步骤。刚入门时多拆、多看范例；已经知道问题在哪里，就把时间放在练习和反馈上。每次结束，留下能接着做的记录：&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;项目&lt;/th&gt;&lt;th&gt;示例&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;本次目标&lt;/td&gt;&lt;td&gt;写一段 300 字的观点说明&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;当前短板&lt;/td&gt;&lt;td&gt;例子不能有效支撑观点&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;参考范例&lt;/td&gt;&lt;td&gt;两段结构清楚、主题相近的短文&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;本次练习&lt;/td&gt;&lt;td&gt;为一个观点选三个例子，并说明关联&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;检查方式&lt;/td&gt;&lt;td&gt;请读者复述观点，指出最无关的例子&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;修正记录&lt;/td&gt;&lt;td&gt;删除一个只增加趣味、没有支撑作用的例子&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;下次检验&lt;/td&gt;&lt;td&gt;隔天不看笔记，换一个观点重做&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;迁移任务&lt;/td&gt;&lt;td&gt;把短文改成面向新手的口头说明&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;最后问自己：“今天发现并修正了什么？下次怎样确认它已经改善？”如果能回答这两个问题，学习记录就不再只有投入的时间，也开始留下能力变化的证据。&lt;/p&gt;</content:encoded><category>category:随笔</category><category>tag:学习方法</category><category>tag:刻意练习</category><category>tag:主动回忆</category><category>tag:个人成长</category></item><item><title>网站开发技术栈，一次理清语言、框架、工具与云服务</title><link>https://sylunae-blog.vercel.app/post/web-development-stack-guide</link><guid isPermaLink="false">web-development-stack-guide</guid><description>从 HTML、CSS、JavaScript 到 React、Vite、Next.js，再到 Cloudflare、Supabase 与 Stripe，按职责理清网站开发技术栈的分类、关系与常见组合。</description><pubDate>Mon, 31 Aug 2026 03:28:33 GMT</pubDate><content:encoded>&lt;p&gt;刚接触网站开发时，经常会看到这样的介绍：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这个网站用 TypeScript、React、Next.js、Tailwind CSS、Supabase 和 Stripe 开发，部署在 Cloudflare。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这些名字放在一起，并不代表它们是六七种相互竞争的框架。它们分别负责写逻辑、组织界面、处理页面、设置样式、保存数据、收款和部署。&lt;/p&gt;
&lt;p&gt;理解技术栈，先问两个问题：&lt;strong&gt;它属于哪一类？它解决什么问题？&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;先按职责分组&lt;a href=&quot;#先按职责分组&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;类别&lt;/th&gt;&lt;th&gt;常见技术&lt;/th&gt;&lt;th&gt;主要负责什么&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;页面基础与语言&lt;/td&gt;&lt;td&gt;HTML、CSS、JavaScript、TypeScript&lt;/td&gt;&lt;td&gt;描述内容、样式和逻辑&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;界面库与框架&lt;/td&gt;&lt;td&gt;React、Vue、Angular&lt;/td&gt;&lt;td&gt;组织组件，让界面随数据变化&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;样式工具与组件库&lt;/td&gt;&lt;td&gt;Tailwind CSS、MUI、Element Plus&lt;/td&gt;&lt;td&gt;更快地完成外观和常见控件&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;开发与构建工具&lt;/td&gt;&lt;td&gt;Vite、Webpack、Turbopack&lt;/td&gt;&lt;td&gt;运行开发环境、转换和打包代码&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;网站与应用框架&lt;/td&gt;&lt;td&gt;Next.js、Nuxt、Astro&lt;/td&gt;&lt;td&gt;组织页面、路由、渲染与服务端能力&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;运行时、后端与数据&lt;/td&gt;&lt;td&gt;Node.js、Express、PostgreSQL&lt;/td&gt;&lt;td&gt;执行服务端代码、处理请求、保存数据&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;云平台与第三方服务&lt;/td&gt;&lt;td&gt;Vercel、Cloudflare、Supabase、Stripe&lt;/td&gt;&lt;td&gt;托管网站，提供数据、身份认证或支付能力&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这是一张职责地图，不是严格的继承树。框架可能内置构建工具，云平台也可能同时提供计算和数据库服务。&lt;strong&gt;同层经常需要选型，不同层通常相互配合；也并非每层都必须引入一个产品。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;HTML、CSS、JS、TS：写网页的基础&lt;a href=&quot;#htmlcssjsts写网页的基础&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;浏览器中的普通网页，主要由三种基础技术构成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTML&lt;/strong&gt; 是标记语言，描述内容的结构和语义，例如标题、段落、链接和表单。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSS&lt;/strong&gt; 是样式表语言，控制颜色、字体、布局和不同屏幕下的呈现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JavaScript（JS）&lt;/strong&gt; 是编程语言，处理交互与逻辑，例如点击按钮、筛选列表和请求数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以把一个登录表单拆开看：HTML 定义输入框，CSS 决定它的样子，JS 处理提交时的交互。这也是 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Your_first_website&quot;&gt;MDN 入门教程&lt;/a&gt;采用的基础划分。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TypeScript（TS）是在 JavaScript 基础上增加类型系统的语言。&lt;/strong&gt; 它帮助编辑器和检查工具提前发现问题，例如把字符串误当成数字。浏览器通常执行的是去掉类型、经过转换的 JavaScript，而不是直接执行 TypeScript 源码。参见 &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html&quot;&gt;TypeScript 官方入门&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;因此，TS 与 JS 不是两套互不相干的网页技术；React、Vue 等也没有取代 HTML、CSS、JS，只是让我们用更有组织的方式编写界面。&lt;/p&gt;
&lt;h2&gt;React、Vue、Angular：组织界面；UI 库：提供现成控件&lt;a href=&quot;#reactvueangular组织界面ui-库提供现成控件&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;页面复杂以后，手动修改每个元素会很繁琐。组件化工具允许我们把导航栏、商品卡片、购物车拆成可复用的组件，再根据数据更新界面。&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;技术&lt;/th&gt;&lt;th&gt;更准确的定位&lt;/th&gt;&lt;th&gt;主要特点&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://react.dev/&quot;&gt;React&lt;/a&gt;&lt;/td&gt;&lt;td&gt;构建用户界面的库&lt;/td&gt;&lt;td&gt;聚焦组件与界面更新，路由等能力通常由其他库或上层框架提供&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://vuejs.org/guide/introduction.html&quot;&gt;Vue&lt;/a&gt;&lt;/td&gt;&lt;td&gt;渐进式 JavaScript 框架&lt;/td&gt;&lt;td&gt;用模板、组件和响应式数据组织界面，可按需要引入配套工具&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://angular.dev/overview&quot;&gt;Angular&lt;/a&gt;&lt;/td&gt;&lt;td&gt;功能较完整的 Web 框架&lt;/td&gt;&lt;td&gt;以 TypeScript 为基础，官方体系提供路由、表单、依赖注入等能力&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;日常交流会把三者统称为“前端框架”，但它们的职责范围并不完全相同。理解边界比争论称呼更有用。&lt;/p&gt;
&lt;p&gt;React 中常见的 &lt;strong&gt;JSX&lt;/strong&gt;，是允许在 JavaScript 中描述界面的语法扩展；&lt;strong&gt;TSX&lt;/strong&gt; 则是在 TypeScript 中使用 JSX。它们不是另一套框架，也不等于浏览器直接读取的 HTML。&lt;/p&gt;
&lt;p&gt;UI 组件库又是另一层。React 提供构建组件的机制，而组件库提供已经做好的按钮、弹窗、表格等：&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;生态&lt;/th&gt;&lt;th&gt;常见组件库&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;React&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://mui.com/material-ui/&quot;&gt;MUI&lt;/a&gt;、&lt;a href=&quot;https://ant.design/docs/react/introduce/&quot;&gt;Ant Design&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Vue&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://element-plus.org/en-US/&quot;&gt;Element Plus&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Angular&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://material.angular.dev/&quot;&gt;Angular Material&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;选组件库时要看它面向哪个生态，不能因为外观相似就直接混用。&lt;/p&gt;
&lt;p&gt;还有两个经常被混在一起的名字：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://tailwindcss.com/docs/styling-with-utility-classes&quot;&gt;Tailwind CSS&lt;/a&gt;&lt;/strong&gt; 是以工具类为主的 CSS 框架，通过组合类名设置样式；它本身不是 React 组件库。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://ui.shadcn.com/docs&quot;&gt;shadcn/ui&lt;/a&gt;&lt;/strong&gt; 提供可引入项目、由你直接修改的组件源码和分发工具。它与通常作为依赖安装、升级的组件库，在代码所有权和维护方式上有所不同。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，&lt;code&gt;React + Tailwind CSS + shadcn/ui&lt;/code&gt; 是一种分工：React 管组件逻辑，Tailwind 管样式，shadcn/ui 提供组件起点。&lt;/p&gt;
&lt;h2&gt;Vite：把开发代码变成可运行的产物&lt;a href=&quot;#vite把开发代码变成可运行的产物&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://vite.dev/guide/&quot;&gt;Vite&lt;/a&gt; 是开发服务器与构建工具。&lt;/strong&gt; 开发时，它提供本地预览和热更新；构建时，它协调代码转换、资源处理和生产打包。&lt;/p&gt;
&lt;p&gt;它可以服务于 React、Vue，也可以用于不使用界面框架的项目。因此：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;React + Vite&lt;/code&gt;：React 负责界面，Vite 负责开发与构建。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Vue + Vite&lt;/code&gt;：Vue 负责界面，Vite 仍然做同一类工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Vite 不负责替你设计页面、保存用户数据，也不等于网站上线后的业务服务器。&lt;/strong&gt; 它能启动本地服务器，不代表构建后的站点需要继续运行 Vite。&lt;/p&gt;
&lt;p&gt;Webpack、Turbopack 也属于构建工具范畴，但“构建工具”是大类：有的更聚焦打包，有的还整合开发服务器和插件流程。项目往往通过框架间接使用它们，不必自己逐个拼装。&lt;/p&gt;
&lt;p&gt;另一个细节是：&lt;strong&gt;能转换 TypeScript，不等于已经检查类型。&lt;/strong&gt; Vite 本身只做转换，类型检查需要 &lt;code&gt;tsc&lt;/code&gt;、&lt;code&gt;vue-tsc&lt;/code&gt; 等工具或项目脚本单独承担。参见 &lt;a href=&quot;https://vite.dev/guide/features#typescript&quot;&gt;Vite 的 TypeScript 说明&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;Next.js、Nuxt、Astro：组织整个网站&lt;a href=&quot;#nextjsnuxtastro组织整个网站&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;界面组件之外，网站还需要决定“哪个网址显示哪个页面”“数据在哪里读取”“页面何时生成”。应用框架会为这些问题提供一套约定。&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;框架&lt;/th&gt;&lt;th&gt;与其他技术的关系&lt;/th&gt;&lt;th&gt;主要职责与侧重&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://nextjs.org/docs&quot;&gt;Next.js&lt;/a&gt;&lt;/td&gt;&lt;td&gt;基于 React&lt;/td&gt;&lt;td&gt;整合路由、页面渲染、数据获取和服务端功能&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://nuxt.com/docs/4.x/getting-started/introduction&quot;&gt;Nuxt&lt;/a&gt;&lt;/td&gt;&lt;td&gt;基于 Vue，默认使用 Vite 构建&lt;/td&gt;&lt;td&gt;整合路由、渲染、自动导入和服务端功能&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://docs.astro.build/en/concepts/why-astro/&quot;&gt;Astro&lt;/a&gt;&lt;/td&gt;&lt;td&gt;有自己的组件语法，可集成 React、Vue 等&lt;/td&gt;&lt;td&gt;侧重内容型网站，按需要为局部界面添加客户端交互&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Next.js 与 React、Nuxt 与 Vue，都是建立在已有界面技术上的关系。用了 Next.js，依然在写 React；用了 Nuxt，依然在写 Vue。&lt;/p&gt;
&lt;p&gt;Astro 不要求使用 React 或 Vue。它可以用自己的组件生成页面，也可以把 React、Vue 组件嵌入页面，并只为需要交互的部分加载相应的浏览器端代码。这类局部交互区域常被称为“岛屿”。&lt;/p&gt;
&lt;pre&gt;flowchart TD
  Next[Next.js] --&amp;gt;|基于| React[React]
  Nuxt[Nuxt] --&amp;gt;|基于| Vue[Vue]
  Nuxt --&amp;gt;|默认构建工具| Vite[Vite]
  Astro[Astro] -.-&amp;gt;|可选集成| React
  Astro -.-&amp;gt;|可选集成| Vue&lt;/pre&gt;
&lt;p&gt;图中实线表示基础或工具关系，虚线表示可选集成，不是执行顺序。&lt;/p&gt;
&lt;p&gt;Next.js 也自带构建体系，例如 &lt;a href=&quot;https://nextjs.org/docs/pages/api-reference/turbopack&quot;&gt;Turbopack&lt;/a&gt;，不需要因为用了 React 就再加一个 Vite。“Next.js 还是 Vite”实际上是在比较一套应用框架方案与一套更自行组装的开发方案。&lt;/p&gt;
&lt;h3&gt;顺便理清 CSR、SSR、SSG&lt;a href=&quot;#顺便理清-csrssrssg&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这三个缩写描述的是&lt;strong&gt;页面如何生成&lt;/strong&gt;，不是三种框架：&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;方式&lt;/th&gt;&lt;th&gt;主要页面内容何时、在哪里生成&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;CSR，客户端渲染&lt;/td&gt;&lt;td&gt;浏览器运行 JavaScript 后生成&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SSR，服务端渲染&lt;/td&gt;&lt;td&gt;通常在请求到来时，由服务端生成 HTML&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SSG，静态生成&lt;/td&gt;&lt;td&gt;在构建阶段提前生成 HTML，访问时直接提供&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;同一个框架可以支持多种方式，同一个网站也可以按页面混合使用。静态生成的页面仍然能通过 JS 实现搜索、评论等交互；服务端生成 HTML 后，也可以在浏览器中接上交互逻辑。&lt;/p&gt;
&lt;h2&gt;后端与云服务：代码在哪里运行，数据由谁管理&lt;a href=&quot;#后端与云服务代码在哪里运行数据由谁管理&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;前端&lt;/strong&gt;主要指用户浏览器中的界面与交互；&lt;strong&gt;后端&lt;/strong&gt;处理可信的业务逻辑，例如权限验证、订单处理和数据写入。两者通常通过 API 通信，也就是约定好的程序接口。&lt;/p&gt;
&lt;p&gt;这里要再分清三层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;运行时&lt;/strong&gt;：例如 &lt;a href=&quot;https://nodejs.org/en/about&quot;&gt;Node.js&lt;/a&gt;，让 JavaScript 在浏览器之外运行。它既能运行开发工具，也能运行后端程序，本身不是编程语言或 UI 框架。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后端框架&lt;/strong&gt;：例如 Node.js 生态的 Express、NestJS，以及 Python 的 Django、PHP 的 Laravel、Java 的 Spring Boot，用于组织请求处理和业务逻辑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据库&lt;/strong&gt;：例如 PostgreSQL、MySQL，负责持久化数据。SQL 是查询语言，数据库则是执行查询、管理数据的系统。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next.js、Nuxt 等提供服务端能力后，小型应用可以在同一个项目里写前后端；复杂系统也可以独立部署后端。&lt;strong&gt;“全栈框架”不意味着数据库、权限规则和所有业务逻辑已经自动完成。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;云平台与第三方服务，则承担其中一部分运行和维护工作。&lt;/p&gt;
&lt;h3&gt;Cloudflare：网络与应用部署平台&lt;a href=&quot;#cloudflare网络与应用部署平台&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Cloudflare 是一组产品，不是某一种前端框架。DNS 用于域名解析，CDN 用于内容分发和缓存；Workers 可以运行服务端逻辑并提供静态资源，Pages 提供网站部署能力，R2 提供对象存储，例如保存图片和上传文件。可以结合 &lt;a href=&quot;https://developers.cloudflare.com/use-cases/web-apps/&quot;&gt;Cloudflare 网站应用架构说明&lt;/a&gt;理解这些分工。&lt;/p&gt;
&lt;p&gt;因此，“用了 Cloudflare”可能只是用了 DNS，也可能把应用部署在 Workers 上，必须看具体产品。&lt;/p&gt;
&lt;p&gt;Vercel、Netlify 也是常见的网站部署平台；AWS、Google Cloud、Azure 则提供更广的云基础设施。部署平台与框架是两类选择，实际搭配时仍要检查运行时和适配器兼容性。&lt;/p&gt;
&lt;h3&gt;Supabase：以 PostgreSQL 为基础的后端服务平台&lt;a href=&quot;#supabase以-postgresql-为基础的后端服务平台&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://supabase.com/docs&quot;&gt;Supabase&lt;/a&gt; 在 PostgreSQL 之外，还提供身份认证、文件存储、实时更新和函数等能力。它可以减少自行搭建通用后端设施的工作，但不负责代写你的业务规则。&lt;/p&gt;
&lt;p&gt;“用 Supabase”与“用 PostgreSQL”不矛盾：前者是服务平台，后者是其数据库基础。Firebase 也常被归入后端服务平台，但具体数据模型与产品能力不同，不能当作完全等价的替换。&lt;/p&gt;
&lt;h3&gt;Stripe：支付与订阅服务&lt;a href=&quot;#stripe支付与订阅服务&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Stripe 负责支付相关能力：&lt;a href=&quot;https://stripe.com/payments&quot;&gt;Payments&lt;/a&gt; 处理收款，&lt;a href=&quot;https://stripe.com/payments/checkout&quot;&gt;Checkout&lt;/a&gt; 提供预构建的结账界面，&lt;a href=&quot;https://stripe.com/billing&quot;&gt;Billing&lt;/a&gt; 管理订阅与计费。&lt;/p&gt;
&lt;p&gt;它能与不同前后端技术组合。安装 Stripe 的 SDK，只是让应用更方便地调用服务，并不意味着支付能力全部运行在浏览器中。&lt;/p&gt;
&lt;p&gt;使用托管服务后，仍要配置权限。需要保密的支付密钥、数据库管理凭据不能放进浏览器代码；涉及支付状态与敏感写入的操作，也不能只相信前端传来的结果。密钥的前后端边界可参见 &lt;a href=&quot;https://docs.stripe.com/keys&quot;&gt;Stripe API 密钥说明&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;把名字放回一个具体项目&lt;a href=&quot;#把名字放回一个具体项目&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;下面只是职责组合示例，并不要求每个网站都照搬：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;网站需求&lt;/th&gt;&lt;th&gt;一种组合&lt;/th&gt;&lt;th&gt;分工&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;博客、文档站&lt;/td&gt;&lt;td&gt;Astro + CSS + Cloudflare Pages&lt;/td&gt;&lt;td&gt;生成内容页面、设置样式、托管静态产物&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;管理后台&lt;/td&gt;&lt;td&gt;Vue + TypeScript + Vite + Element Plus + 独立 API&lt;/td&gt;&lt;td&gt;组织界面、检查类型、构建、提供控件、处理业务&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;带登录和订阅的产品&lt;/td&gt;&lt;td&gt;Next.js + Tailwind CSS + Supabase + Stripe + Vercel&lt;/td&gt;&lt;td&gt;构建应用、设置样式、管理账号与数据、收款、部署&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;第三组方案中，Next.js 已经包含 React 和构建工具；Supabase 又包含 PostgreSQL。技术栈清单常省略这些间接依赖，所以清单短不代表没有使用它们。&lt;/p&gt;
&lt;p&gt;开发中还会遇到 &lt;strong&gt;npm、pnpm、Yarn&lt;/strong&gt;：它们是包管理器，负责安装依赖、管理版本和运行项目脚本。&lt;code&gt;pnpm build&lt;/code&gt; 的具体工作由项目中的 &lt;code&gt;build&lt;/code&gt; 脚本决定，不是 pnpm 自己完成了打包。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt; 记录代码版本；GitHub 等平台托管仓库、支持协作。&lt;strong&gt;Docker&lt;/strong&gt; 打包应用及其运行环境。它们属于开发与交付流程，也不应与 React、数据库或云服务混为一类。&lt;/p&gt;
&lt;p&gt;以后再看到陌生名字，可以按这条顺序辨认：&lt;strong&gt;它是用来表达代码、组织界面、加工代码、运行程序、保存数据，还是提供外部服务？&lt;/strong&gt; 把职责放对位置，就能看清哪些可以替换，哪些需要配合，以及自己的项目究竟需不需要它。&lt;/p&gt;</content:encoded><category>category:笔记</category><category>category:前端</category><category>tag:网站开发</category><category>tag:前端</category><category>tag:技术栈</category><category>tag:TypeScript</category><category>tag:React</category><category>tag:Vue</category></item></channel></rss>