在 Sapper 中,您的代码将在浏览器和 Node for SSR 中运行。真正的 Node,甚至不是像 jsdom 之类的模拟浏览器环境。 (不一定是“会”,实际上是“可能”,因为只有浏览器请求的第一个页面会在服务器端呈现,但仍然需要确保所有代码都支持两者)。
在 Node 中,许多浏览器 API,如 localStorage,不可用。
在 JS 中,你不能像这样测试变量的存在:
if (!localStorage) ...
如果该变量不存在,您会因此而崩溃。测试变量是否存在的安全方法如下:
if (typeof localStorage === 'undefined') ...
因此,通过像这样重写您的商店,使您的代码浏览器 + 节点兼容:
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 之间的短暂(或不那么短)间隔内已经很好看
我想说明的是,您的 JS 将在浏览器中运行,即使该页面是由 SSR 呈现的,并且结果将替换 SSR 通行证产生的内容。如果客户端 JS 组件产生的 DOM 元素与服务器完全不同,那么 Svelte 将覆盖。
这意味着,在这种情况下,只提供最低限度让代码不会在 Node 中崩溃是可以接受的。如果您可以生成接近浏览器也将呈现的结果,那当然更好。 (另一种选择是调整您的代码,以便服务器在仅浏览器的情况下呈现类似“正在加载...”的内容)。