如何仅在 Sapper 的客户端上导入 Firebase?

Posted

技术标签:

【中文标题】如何仅在 Sapper 的客户端上导入 Firebase?【英文标题】:How to import Firebase only on client in Sapper? 【发布时间】:2019-10-12 10:18:23 【问题描述】:

我正在将 Firebase 导入到我的 Sapper 应用程序中,我不希望在服务器上评估导入。如何确保仅在客户端导入?

我正在使用 Sapper 运行 sapper export,它会生成静态文件。我试过了:

在自己的文件中创建 firebase 实例并导出 firebase.auth()firebase.firestore() 模块。

尝试调整 rollup.config.js 以不同方式解决依赖关系,如下面的错误消息所示。这带来了更多的麻烦。

client.js 中创建 Firebase 实例。不成功。

stores.js 中创建实例。不成功。

声明变量并在onMount() 中分配它。这导致我必须在不同的块范围内工作。而且感觉有点hacky。

应用程序的初始化,工作正常:

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>

但只要包含该导入,开发服务器就会崩溃。我不想在服务器上使用它,因为我只是生成静态文件。

有人对如何仅在客户端实现导入有一些想法吗?

【问题讨论】:

【参考方案1】:

所以我花了太多时间在这上面。没有比 onMOun​​t 更优雅的解决方案了。

但是,我确实意识到 sapper 确实应该用于它的 s-s-r 功能。我还写了一篇关于如何使用 Sapper s-s-r 和 Cloud Functions 在 Firebase 上进行设置的文章:

https://dev.to/eckhardtd/how-to-host-a-sapper-js-s-s-r-app-on-firebase-hmb

原始问题的另一个解决方案是通过 src/template.html 文件将 Firebase CDN 置于全局范围内。

<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>

【讨论】:

【参考方案2】:

我能够使用 ES6 导入 firebase。如果你使用 rollup,你需要在 commonjs 插件中配置 namedExports:

//--- 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;
  ,
;

已编辑 完整汇总配置

/* 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: 's-s-r',
        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,
  ,
;

【讨论】:

我按照您在此处提到的所有操作都做了,但我不断收到错误消息:initializeApp' is not exported by 'node_modules\firebase\dist\index.esm.js' firestore isnt export as well ;(您知道如何解决这个问题吗? 我将编辑我的答案以提供完整的汇总配置。仔细检查 firebase 导入语句(可能与文档不同)。这实际上与汇总而不是工兵有关。您可以通过以下 2 个链接了解更多信息:TorubleshootingCustom Named Exports 这非常适合我。 @TiagoNobrega 你知道如何摆脱“看起来你正在使用 Firebase JS SDK 的开发版本”。何时使用此汇总配置? 我按照上述步骤操作,现在当 npm run dev server 最初启动时会崩溃,提示警告:这是一个以浏览器为目标的 Firebase 捆绑包,但它似乎正在 Node 环境中运行。 只需将 namedExports 配置添加到汇总即可完美解决我的问题。【参考方案3】:

干净的方法是使用 Dynamic Import 如文档所述:Making a component s-s-r compatible

解决此问题的方法是从 onMount 函数 (仅在客户端调用) 为您的组件使用动态导入,这样您的导入代码就不会被调用服务器。

所以这里例如我们要导入firebase的coreauthentication包。

<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);
    
  

【讨论】:

【参考方案4】:

为了将 Firebase 与 Sapper 一起使用,您必须导入 firebase 而不是 firebase/app。您确实希望 firebase 能够在后端使用 s-s-r 正确加载,而不仅仅是前端。例如,如果您有一些元标记将存储在数据库中,您希望它们加载到后端(未测试)。

您可以只使用firebase,但随后您会收到烦人的控制台警告。还要记住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 函数需要一个额外的步骤,并且您必须启用 dynamic imports。 (未经测试)

export const functions = (process as any).browser ? async () => 
  await import("firebase/functions");
  return fb.functions()
 : fb.functions();

在编译时,我没有尝试运行 httpsCallable 或确认它将从后端的数据库加载 seo s-s-r 从数据库。让我知道它是否有效。

既然 Sapper 已经死了,我怀疑所有这些都将适用于新的 SvelteKit。

【讨论】:

以上是关于如何仅在 Sapper 的客户端上导入 Firebase?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Sapper 中使用 mysql2 库?

如何将 Google Adsense 添加到 Svelte/Sapper 网络应用程序?

如何导入依赖于 Flutter 平台的 Dart 文件或包?

Firebase 使任务仅在主线程上执行,导致 Unity 冻结

如何将 AWS Amplify 与 Sapper 一起使用?

如何将 Sapper 添加到现有的 Svelte 项目中?