在 Sapper 中,您的代码将在浏览器和 SSR 的 Node 中运行。真正的Node,甚至不是像jsdom之类的模拟浏览器环境。 (不一定是“将”,事实上,它是“可能”,因为只有浏览器请求的第一个页面才会在服务器端呈现,但您仍然需要确保所有代码都支持这两种页面)。
在 Node 中,有很多浏览器 API,比如localStorage
,不可用。
在 JS 中,你不能像这样测试变量是否存在:
if (!localStorage) ...
如果该变量不存在,您将会遇到崩溃。测试变量是否存在的安全方法如下:
if (typeof localStorage === 'undefined') ...
因此,通过重写您的商店,使您的代码与浏览器 + Node 兼容,如下所示:
const createBrowserTokenStore = () => {
const store = writable(localStorage.getItem('token'))
// Store the token in LocalStorage whenever it´s updated
store.subscribe((val) => {
if (!localStorage) return
if (!val) return localStorage.removeItem('token')
localStorage.setItem('token', val)
})
return store
}
// just enough to not crash in Node
const createNodeTokenStore = () => writable(null)
export const token = typeof localStorage === 'undefined'
? createNodeTokenStore()
: createBrowserTokenStore()
请注意,即使在 SSR 上下文(即 Node)中,浏览器代码也会运行。页面 SSR 时的准确操作顺序如下:
-
页面 HTML 在 Node 中呈现,并使用编译的组件ssr: true
编译选项——即,仅在一次传递中渲染 HTML 字符串的组件
-
HTML 被发送到浏览器
-
浏览器显示您的页面,因为它应该是这样的
-
在后台加载并执行JS
-
您的 Svelte 组件将重新运行hydratable
选项,这意味着他们将尝试回收现有的 DOM 元素,而不是无条件地创建它们
-
您的页面现在是交互式的,但在获取 HTML 和加载 JS 之间的短暂(或不那么短)间隔内它已经很好看
我想要的地方是,即使页面是由 SSR 渲染的,您的 JS 也将在浏览器中运行,并且结果将replaceSSR通证产生了什么。如果客户端 JS 组件生成与服务器完全不同的 DOM 元素,则 Svelte 将进行覆盖。
这意味着,在这种情况下,只需提供最小值以使代码不会在 Node 中崩溃就可以接受。如果您可以生成接近浏览器渲染结果的结果,那当然更好。 (另一种选择是调整代码,以便服务器在仅浏览器的情况下呈现类似“正在加载...”的内容)。