Как импортировать Firebase только на клиенте в Sapper?
Я импортирую Firebase в свое приложение Sapper, я не хочу, чтобы импорт оценивался на сервере. Как мне убедиться, что импорт осуществляется только на стороне клиента?
Я использую Сапер для запуска sapper export
который генерирует статические файлы. Я пытался:
Создание экземпляра Firebase в своем собственном файле и экспорт
firebase.auth()
а такжеfirebase.firestore()
модули.Попытка отрегулировать rollup.config.js, чтобы разрешить зависимости по-разному, как предлагается из сообщения об ошибке ниже. Это приносит больше головной боли.
Создание экземпляра Firebase в
client.js
, Неудачный.Создание экземпляра в
stores.js
, Неудачный.Объявление переменной и назначение ее в
onMount()
, Это заставляет меня работать в разных сферах. И чувствует себя немного хакером.
Инициализация приложения, работает нормально:
import firebase from 'firebase/app'
const config = {...}
firebase.initializeApp(config);
Я также обнаружил, что если я изменю импорт просто import firebase from 'firebase'
Я не получаю эту ошибку сервера:
@firebase/app:
Warning: This is a browser-targeted Firebase bundle but it appears it is being run in a Node environment. If running in a Node environment, make sure you are using the bundle specified by the "main" field in package.json.
If you are using Webpack, you can specify "main" as the first item in
"resolve.mainFields": https://webpack.js.org/configuration/resolve/#resolvemainfields
If using Rollup, use the rollup-plugin-node-resolve plugin and set "module" to false and "main" to true: https://github.com/rollup/rollup-plugin-node-resolve
Я ожидал просто экспортировать эти функциональные возможности Firebase из файла и импортировать их в мои компоненты, такие как:
<script>
import { auth } from "../firebase";
</script>
Но как только этот импорт включается, сервер dev падает. Я не хочу использовать его на сервере, так как я просто генерирую статические файлы.
У кого-нибудь есть идеи, как добиться импорта только на стороне клиента?
4 ответа
Так что я потратил слишком много времени на это. На самом деле нет более элегантного решения, чем onMOunt.
Тем не менее, я понял, что сапер действительно должен использоваться для его возможностей SSR. И я написал статью о том, как настроить Firebase с помощью Sapper SSR и облачных функций:
https://dev.to/eckhardtd/how-to-host-a-sapper-js-ssr-app-on-firebase-hmb
Другое решение первоначального вопроса - поместить CDN Firebase в глобальную сферу посредством src/template.html
файл.
<body>
<!-- The application will be rendered inside this element,
because `app/client.js` references it -->
<div id='sapper'>%sapper.html%</div>
<!-- Sapper creates a <script> tag containing `app/client.js`
and anything else it needs to hydrate the app and
initialise the router -->
%sapper.scripts%
<!-- Insert these scripts at the bottom of the HTML, but before you use any Firebase services -->
<!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/6.0.4/firebase-app.js"></script>
<!-- Add Firebase products that you want to use -->
<script src="https://www.gstatic.com/firebasejs/6.0.4/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.0.4/firebase-firestore.js"></script>
</body>
</html>
и в компоненте:
<script>
import { onMount } from 'svelte';
let database, authentication;
onMount(() => {
database = firebase.firestore();
authentication = firebase.auth();
});
const authHandler = () => {
if (process.browser) {
authentication
.createUserWithEmailAndPassword()
.catch(e => console.error(e));
}
}
</script>
<button on:click={authHandler}>Sign up</button>
Мне удалось импортировать firebase с помощью ES6. Если вы используете накопительный пакет, вам необходимо настроить namedExports в плагине commonjs:
//--- rollup.config.js ---
...
commonjs({
namedExports: {
// left-hand side can be an absolute path, a path
// relative to the current directory, or the name
// of a module in node_modules
'node_modules/idb/build/idb.js': ['openDb'],
'node_modules/firebase/dist/index.cjs.js': ['initializeApp', 'firestore'],
},
}),
Вы можете использовать это так:
//--- db.js ---
import * as firebase from 'firebase';
import 'firebase/database';
import { firebaseConfig } from '../config'; //<-- Firebase initialization config json
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
export { firebase };
// Initialize db
export const db = firebase.firestore();
и, возможно, использовать его в такой службе:
// --- userService.js ----
import { db } from './common';
const usersCol = db.collection('users');
export default {
async login(username, password) {
const userDoc = await usersCol.doc(username).get();
const user = userDoc.data();
if (user && user.password === password) {
return user;
}
return null;
},
};
EDITED Полная конфигурация накопительного пакета
/* eslint-disable global-require */
import resolve from 'rollup-plugin-node-resolve';
import replace from 'rollup-plugin-replace';
import commonjs from 'rollup-plugin-commonjs';
import svelte from 'rollup-plugin-svelte';
import babel from 'rollup-plugin-babel';
import { terser } from 'rollup-plugin-terser';
import config from 'sapper/config/rollup';
import { sass } from 'svelte-preprocess-sass';
import pkg from './package.json';
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
// eslint-disable-next-line no-shadow
const onwarn = (warning, onwarn) =>
(warning.code === 'CIRCULAR_DEPENDENCY' && warning.message.includes('/@sapper/')) || onwarn(warning);
export default {
client: {
input: config.client.input(),
output: config.client.output(),
plugins: [
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode),
}),
svelte({
dev,
hydratable: true,
emitCss: true,
preprocess: {
style: sass(),
},
}),
resolve({
browser: true,
}),
commonjs({
namedExports: {
// left-hand side can be an absolute path, a path
// relative to the current directory, or the name
// of a module in node_modules
'node_modules/idb/build/idb.js': ['openDb'],
'node_modules/firebase/dist/index.cjs.js': ['initializeApp', 'firestore'],
},
}),
legacy &&
babel({
extensions: ['.js', '.mjs', '.html', '.svelte'],
runtimeHelpers: true,
exclude: ['node_modules/@babel/**'],
presets: [
[
'@babel/preset-env',
{
targets: '> 0.25%, not dead',
},
],
],
plugins: [
'@babel/plugin-syntax-dynamic-import',
[
'@babel/plugin-transform-runtime',
{
useESModules: true,
},
],
],
}),
!dev &&
terser({
module: true,
}),
],
onwarn,
},
server: {
input: config.server.input(),
output: config.server.output(),
plugins: [
replace({
'process.browser': false,
'process.env.NODE_ENV': JSON.stringify(mode),
}),
svelte({
generate: 'ssr',
dev,
}),
resolve(),
commonjs(),
],
external: Object.keys(pkg.dependencies).concat(require('module').builtinModules || Object.keys(process.binding('natives'))),
onwarn,
},
serviceworker: {
input: config.serviceworker.input(),
output: config.serviceworker.output(),
plugins: [
resolve(),
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode),
}),
commonjs(),
!dev && terser(),
],
onwarn,
},
};
Чистый способ - использовать динамический импорт, как сказано в документации: Обеспечение совместимости компонента SSR
Способ обойти это - использовать динамический импорт для вашего компонента из функции onMount (которая вызывается только на клиенте), так что ваш код импорта никогда не вызывается на сервере.
Итак, например, мы хотим импортировать ядро firebase и пакет аутентификации.
<script>
let firebase;
onMount(async () => {
const module = await import("firebase/app");
await import("firebase/auth");
firebase = module.default;
firebase.initializeApp(firebaseConfig);
});
<script>
И теперь вы можете использовать объект firebase как можете, например, мы хотим войти в систему с адресом электронной почты и паролем:
let email;
let password;
async function login() {
try {
let result = await firebase.auth().signInWithEmailAndPassword(
email,
password
);
console.log(result.user);
} catch (error) {
console.log(error.code, error.message);
}
}
Чтобы использовать Firebase с Sapper, вам не нужно импортировать. Вы действительно хотите, чтобы firebase могла правильно загружаться с SSR на бэкэнде, а не только во внешнем интерфейсе. Если у вас есть, например, некоторые метатеги, которые будут храниться в базе данных, вы хотите, чтобы они загружались на бэкэнд (НЕПРОВЕРЕНО).
Вы можете просто использовать, но тогда вы получите раздражающее предупреждение консоли. Помните также
firebase
загружает ВСЕ зависимости firebase, пока
firebase/app
нет, поэтому вы не хотите использовать его во внешнем интерфейсе. Вероятно, есть способ с
admin-firebase
, но мы хотим иметь меньше зависимостей.
Ни в коем случае не используйте rxfire . Тебе это не нужно. Это вызывает ошибки с Sapper. Просто Firebase.
firebase.ts
import firebase from 'firebase/app';
import "firebase/auth";
import "firebase/firestore";
import * as config from "./config.json";
const fb = (process as any).browser ? firebase : require('firebase');
fb.initializeApp(config);
export const auth = fb.auth();
export const googleProvider = new fb.auth.GoogleAuthProvider();
export const db = fb.firestore();
Функции Firebase требуют дополнительного шага, и вы должны включить динамический импорт . (НЕПРОВЕРЕНО)
export const functions = (process as any).browser ? async () => {
await import("firebase/functions");
return fb.functions()
} : fb.functions();
Пока он компилируется, я не пробовал запускать httpsCallable и не подтверждал, что он будет загружаться из базы данных на бэкэнде для seo ssr из базы данных. Сообщите мне, если это сработает.
Я подозреваю, что все это будет работать с новым SvelteKit теперь, когда Sapper мертв.