模板可扩展性
模板扩展性是在PWA Kit v3中引入的一项功能。此功能的目标是让您能够通过自定义模板更轻松地构建 PWA Kit 项目。您可以自定义现有基本模板,例如 Retail React App,也可以创建自己的。我们鼓励 PWA Kit 社区成员相互分享基本模板。
模板可扩展性可帮助您修改所选模板,而无需复制其中的每个文件。
模板可扩展性是一项可选功能。但是,2023 年 6 月 15 日之后生成的新项目会自动配置为使用模板扩展性。
在 PWA Kit v3 中,项目可以有基本模板和覆盖目录,这两个目录都可以在项目的 package.json
文件中进行配置。要使用模板扩展性,您必须使用 package.json
的 ccExtensibility.extends
声明基本模板,并且必须使用 ccExtensibility.overridesDir
声明覆盖目录。(配置详细信息将在下一节中介绍。)
基本模板是 npm 模块,其中包含已预先配置的功能齐全的 PWA Kit 项目,以便另一个 PWA Kit 项目可以覆盖其某些文件。有时,基本模板也称为“可扩展应用程序”。"
覆盖目录是项目内的一个目录,您可以在其中存储覆盖基本模板中相应文件的文件。
假设您已经定义了覆盖目录并将 Retail React App 定义为您的基本模板。假设您想要覆盖 Retail React App 的主页组件。在 Retail React App 中,主页组件的代码在 @salesforce/retail-react-app
包内的 app/pages/home/index.jsx
中。要覆盖该文件,您必须在 package.json
通过 ccExtensibility.overridesDir
声明的目录中重新创建相同的文件。因此,新文件的路径为 <ccExtensibility.overridesDir>/app/pages/home/index.jsx
。
现在,每当 index.jsx
被导入到 @salesforce/retail-react-app
包中的任何文件时,都会加载覆盖目录中的文件,而不是包中的文件。只需要文件名的基础相同即可实现覆盖。文件扩展名可以不同。
构建应用程序时,可以逐渐将文件从基本模板添加到覆盖目录中。
覆盖的文件越多,跟上基本模板中更改所需的工作就越多。
要查看实际的模板可扩展性,请通过运行“npx @salesforce/pwa-kit-create-app@latest --outputDir <path/to/new/local/project>”生成新的 PWA Kit v3 项目。
生成的项目使用 Retail React App 基本模板,该模板提供的应用程序与 https://pwa-kit.mobify-storefront.com/ 中的应用程序基本相同。
本部分中的说明假定您要为现有项目启用模板扩展性,而该项目在生成时并未启用该功能。
要定义基本模板,请将 @salesforce/retail-react-app
(或不同的模板)添加为项目 package.json
文件中的 npm 依赖项。
然后使用以下键和值将 ccExtensibility
添加至 package.json
:
如果您使用的是 Retail React App 以外的基本模板,请不要忘记将 @salesforce/retail-react-app
替换为其他模板的包标识符。
您可以将 overridesDir
的值替换为自定义目录名称,但为了项目(以及我们的示例代码)之间的一致性,我们建议使用 overrides
。
主项目和基本模板中不能同时存在相同的依赖项。@salesforce/pwa-kit-dev
的两个 PWA Kit 应用程序必须不同,以区分主项目和基本模板。(主项目必须定义基本模板,不能从自身继承。)
您的项目不得与 package.json
文件中列出的基本模板的 npm 依赖项存在版本冲突。如果底层模板使用 @chakra-ui
,您的项目也必须将其作为依赖项,而且必须是相同的版本。这种预防措施可以防止由于同一包的版本冲突而导致捆绑包意外膨胀(更不用说功能损坏)。此规则有一个例外:当您为导入给定依赖项的基本模板中的所有文件添加覆盖时。在这种情况下,基本模板的依赖项版本永远不会导入到您的项目中,因为您已经删除了通过覆盖将其导入的所有文件。
添加底层模板中未使用的其他 npm 依赖项即可。 👌
部署到 Managed Runtime 的每个 PWA Kit 项目都必须具有以下文件:
<overridesDir>/app/main.jsx
<overridesDir>/app/ssr.js
<overridesDir>/app/routes.jsx
<overridesDir>/app/request-processor.js
config/default.js
(例外:如果您使用package.json
的mobify
密钥来存储站点配置数据)
要自定义项目中的行为,通常会覆盖 @salesforce/retail-react-app
中的以下文件:
app/pages/home/index.jsx
:从主页删除默认的 Salesforce 营销内容是几乎所有项目的第一步。app/static/*
:这些文件默认提供桌面和移动浏览器使用的图标来为您的网站打造品牌。将其更新以匹配您的品牌。app/constants.js
app/assets/svg/brand-logo.svg
:该文件在生成器项目中被覆盖。按照此示例覆盖项目中的其他图标。
这种“文件系统作为 API”设置的目的是帮助您找到并复制底层文件,然后自定义行为和逻辑。将代码分解为编排许多子组件的较小文件,可以更轻松地仅针对您想要覆盖的行为。
尽管如此,有时您必须将文件复制到覆盖中才能仅更改该文件的部分行为。尽最大可能导入底层模板导出并将其重新导出,以便更改模板版本不需要在扩展项目中手动更新代码。
例如,在生成项目中的 routes.jsx
文件中,请注意默认路由是通过 import {routes as _routes} from '@salesforce/retail-react-app/app/routes'
导入的,其中 _routes
旨在作为约定,表明这些默认路由由外部依赖项管理。就像文件 @salesforce/retail-react-app/app/routes.jsx
一样,我们自定义执行 routes.jsx
导出路由;它添加了 /
(主页)路线。新路由优先于默认路由,这是因为 React Router 在路由数组中查找给定路径名的第一项。
对于大多数使用模板可扩展性的项目来说,相对导入是最佳方法。基本模板文件的导入行为(例如,@salesforce/retail-react-app
)具有特殊逻辑,该逻辑优先考虑覆盖目录中的文件。此导入逻辑不适用于覆盖目录中的文件。因此,我们建议对于大多数执行使用相对导入。
从“心智模型”的角度考虑模板可扩展性可能很复杂,但可能会出现意外的“陷阱”,即意外导入两个不同的冲突文件。PWA Kit 项目通过 @salesforce/pwa-kit-dev
构建,在内部使用了 webpack。@salesforce/pwa-kit-dev
有一个插件可以启用模板可扩展性的“覆盖”功能。该插件检查所有 webpack 文件请求并询问两个问题:
- 此文件请求是否源自基本模板?
- 此文件是否存在于覆盖目录中?
如果两个问题的答案都是“是”,则文件请求将在构建时重写,从而指向覆盖目录中的文件。
对于源自覆盖目录的 webpack 文件请求,此逻辑不起作用。因此,可以从覆盖目录和基本模板导入相同的文件。如果出现这种情况,例如 @saleforce/retail-react-app/my-file
和 <overrides directory>/my-file
被导入到同一个捆绑包中,则 webpack 会抛出类似 can’t find <export name> from <filename>
这样的混淆性错误。Webpack 抛出此错误是因为捆绑包不知道哪个导入是捆绑包内含物的预期目标。
如果覆盖中存在等效文件,一定要避免从基本模板导入文件,但有一个重要例外。正如前面特殊文件部分的代码示例所述,以及 constants.js
所演示,最好导入基本模板并重新导出其所有导出,根据需要覆盖或附加尽可能少的导出。
当需要覆盖 app/components/_app/index.jsx
组件时,有很多全局组件,例如Header
、Footer
和 DrawerMenu
。覆盖 _app/index.jsx
到位后,基本模板导入此组件的任何尝试都会将文件请求重新路由到 <ccExtensibility.overridesDir>/app/components/_app/index.jsx
。但有一个难点!因为 Header
和 Footer
由 app/components/_app/index.jsx
导入,所以导入也必须在 _app/index.jsx
更新!否则,_app/index.jsx
从基本模板导入 Header
和 Footer
。
源自 ccExtensibility.extends
的 ECMAScript 导入很“神奇”,因为 @salesforce/pwa-kit-dev
在多个位置检查该名称文件,且 ccExtensibility.overridesDir
优先。
另一方面,源自 <ccExtensibility.overridesDir>/*
的导入并不神奇,因此您必须针对您想要的文件,这样 <ccExtensibility.overridesDir>/app/components/header/index.jsx
必须像这样在<ccExtensibility.overridesDir>/app/components/_app/index.jsx
中显式导入:
不要像这样从包中导入,因为它会绕过覆盖目录中的标头:
请注意,routes.jsx
是一个特殊文件。在 webpack 术语中,它构成了整个应用程序的“入口点”。如果您在 routes.jsx
中设置了任何不正确的内容,则会导致整个应用程序在编译阶段失败,因为 pwa-kit-dev
中基于路由的分块无法正确解析。出于该原因,我们提供了一个示例,说明如何通过在 overridesDir
中混合来自默认 @salesforce/retail-react-app/app/routes.jsx
和本地 routes.jsx
的导入来正确扩展路由。
因为此处的导入位于“树形结构顶部”,所以如果您覆盖这些文件,然后覆盖 footer.jsx
,则必须返回到 _app/index.jsx
并修改该导入以指向相对的模板导入。
模板可扩展性系统的已知限制是,例如通过神奇的方式拉入 retail-react-app/constants
,每当基本模板 (@salesforce/retail-react-app
) 导入此文件时,它都应导出相同的值。
覆盖中的文件无法导出与基本模板中等效项相同的 ECMAScript 导出,这可能导致意外错误,如下所示。
像这样将 constants.js
文件添加到覆盖目录会导致错误:
错误消息示例:
此错误的原因是 CAT_MENU_DEFAULT_ROOT_CATEGORY
是 @salesforce/retail-react-app/components/_app/index.jsx
中的预期导出,并且在使用上面的唯一导出 CUSTOM_MESSAGE
覆盖它的过程中,您的覆盖破坏了由 constants.js
导出的基础“API 契约”(文件依赖于给定值的能力,在本例 CAT_MENU_DEFAULT_ROOT_CATEGORY
)
正确的方法如下。这种方法避免了遗漏作为必需和预期导出的 CAT_MENU_DEFAULT_ROOT_CATEGORY
。这样我们就可以保持导出 API 的一致性。
此方法仅适用于附加更改。在以下示例中,我们添加不在基础 CUSTOM_MESSAGE
文件导出中的 retail-react-app/constants.js
导出。
此方法适用于以下场景,其中 DEFAULT_LOCALE
是由 retail-react-app/constants
导出,我们正在改变该值:
模板挂钩是一种与 Retail React App 模板交互的新方式,以往可通过命令npx pwa-kit-create-app
使用(现已重命名为 npx @salesforce/pwa-kit-create-app
)。
作为模板可扩展性功能的一部分,@salesforce/retail-react-app
添加了新的“模板挂钩”,支持全局添加组件的小子集,如下列表中所列举。默认情况下,每个组件都会返回 null
并故意为空,以避免给已完成的 PWA Kit 实现项目添加不必要的膨胀。基础 @salesforce/retail-react-app
从未向这些组件添加任何功能。模板挂钩始终返回 null
,以确保客户执行可以在这些位置“挂钩”到模板中以自定义项目,而不必覆盖不必要的文件。
自 @salesforce/retail-react-app@1.0.0
开始,以下模板挂钩可用:
app/components/_app/partials/above-header.jsx
app/pages/product-list/partials/above-page-header.jsx
在 PWA Kit v3 发布时,只有一个公开提供的可扩展基本模板:@salesforce/retail-react-app
(预计稍后会有更多)。
要用作基本模板,PWA Kit 项目必须发布到 npm,并且其所有 ECMAScript 导入都必须以与包名称匹配的值作为前缀。例如,@salesforce/retail-react-app
在 npm 上发布,其导入以 @salesforce/retail-react-app
为前缀。此前缀是对包根目录的引用,这是它作为基本模板正常工作的必要条件。
要将 PWA Kit 项目用作可扩展的基本模板,它必须对其所有导入使用“@salesforce/retail-react-app
在 package.json
中添加 ccExtensibility
,这可确保本地 IDE 引用(例如,@salesforce/retail-react-app/app/components/_app
)正确解析。