Sapper: Firebase hosting

Created on 22 Aug 2018  Â·  24Comments  Â·  Source: sveltejs/sapper

I'm evaluating Sapper/Svelte, and it's been great so far :+1:

However setting Firebase hosting was tricky because of weird Sapper behavior:

Error: ENOENT: no such file or directory, open '/home/laurent/projects/myproject/firebase/functions/.sapper/dev/client_assets.json'
    at Object.openSync (fs.js:434:3)
    at Object.readFileSync (fs.js:339:35)
    at fs.readFileSync (/home/laurent/projects/myproject/firebase/functions/node_modules/sapper/src/middleware.ts:286:22)
    at handle_page (/home/laurent/projects/myproject/firebase/functions/node_modules/sapper/src/middleware.ts:306:53)
    at find_route (/home/laurent/projects/myproject/firebase/functions/node_modules/sapper/src/middleware.ts:540:6)
    at go (/home/laurent/projects/myproject/firebase/functions/node_modules/sapper/src/middleware.ts:557:5)
    at /home/laurent/projects/myproject/firebase/functions/node_modules/sapper/src/middleware.ts:559:6
    at find_route (/home/laurent/projects/myproject/firebase/functions/node_modules/sapper/src/middleware.ts:275:3)
    at go (/home/laurent/projects/myproject/firebase/functions/node_modules/sapper/src/middleware.ts:557:5)
    at /home/laurent/projects/myproject/firebase/functions/node_modules/sapper/src/middleware.ts:559:6

…untill setting the environment variable SAPPER_DEST to the build path.

  • I first used node firebase/functions/ssrapp to have the build folder inside my functions folder but I had an error…
ENOENT: no such file or directory, open '/home/laurent/projects/myproject/firebase/functions/app/template.html'

…untill I changed it to node firebase/functions/app and made changes accordingly. Is there an app folder hardcoded somewhere?

To sum it up here is the _simplified_ (I run "normal" Polka server if node env is 'development' to use npm run dev) code I use to host my Sapper app on Firebase:

// app/server.js
import * as functions from 'firebase-functions'
import * as express from 'express'
import sapper from 'sapper'
import { manifest } from './manifest/server.js'

process.env.SAPPER_DEST = 'app' // needed

const app = express() // Polka is not working with Firebase
  .use(
    // compression is done by Firebase Nginx servers
    // assets folder static files are served by Firebase hosting
    sapper({ manifest })
  )

export default functions.https.onRequest(app)

Hope it helps Firebase users.

All 24 comments

:wave: Do you know what was causing Polka to not work? Or at least what kind of erroiyou were getting? Thanks !

with

const app = polka().use(sapper({ manifest }))

I get:

{"stack":"TypeError: handler is not a function\n at cloudFunction (/home/laurent/projects/myproject/firebase/functions/node_modules/firebase-functions/lib/providers/https.js:57:9)\n at app.use (/usr/local/lib/node_modules/firebase-tools/node_modules/@google-cloud/functions-emulator/src/supervisor/worker.js:151:11)\n at Layer.handle [as handle_request] (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/layer.js:95:5)\n at trim_prefix (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/index.js:317:13)\n at /usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/index.js:284:7\n at Function.process_params (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/index.js:335:12)\n at next (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/index.js:275:10)\n at app.use (/usr/local/lib/node_modules/firebase-tools/node_modules/@google-cloud/functions-emulator/src/supervisor/worker.js:123:7)\n at Layer.handle [as handle_request] (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/layer.js:95:5)\n at trim_prefix (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/index.js:317:13)","message":"handler is not a function","name":"TypeError"}

Maybe because Firebase uses Express, at least for handling requests without a server instance created: https://firebase.google.com/docs/hosting/functions#create_an_http_function_to_your_hosting_site

Ah, that explains it.

Your export, with Polka, needs to look like this:

export default functions.https.onRequest(app.handler)

With Polka and

export default functions.https.onRequest(app.handler)

I get:

{"stack":"TypeError: Cannot set property path of # which has only a getter\n at Polka.handler (/home/laurent/projects/myproject/firebase/functions/node_modules/polka/index.js:73:12)\n at cloudFunction (/home/laurent/projects/myproject/firebase/functions/node_modules/firebase-functions/lib/providers/https.js:57:9)\n at app.use (/usr/local/lib/node_modules/firebase-tools/node_modules/@google-cloud/functions-emulator/src/supervisor/worker.js:151:11)\n at Layer.handle [as handle_request] (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/layer.js:95:5)\n at trim_prefix (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/index.js:317:13)\n at /usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/index.js:284:7\n at Function.process_params (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/index.js:335:12)\n at next (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/index.js:275:10)\n at app.use (/usr/local/lib/node_modules/firebase-tools/node_modules/@google-cloud/functions-emulator/src/supervisor/worker.js:123:7)\n at Layer.handle [as handle_request] (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/router/layer.js:95:5)","message":"Cannot set property path of # which has only a getter","name":"TypeError"}

Interesting, thanks~! I know that basic Sapper + Polka apps work fine on Firebase hosting, but I'll have to dig into more complex use cases to see where it's falling apart.

If you have an idea of what might be throwing this error, it'd be appreciated. Thanks!

@lukeed I will try some changes and I will let you know if there is something.

Are the Sapper + Polka + Firebase hosting apps you know on github? I'm curious to see how they did it…

This one was shared in the Discord circa last week: https://github.com/fusionstrings/firebase-functions-sapper

Live Demo

Thanks! It looks like they put a Sapper project inside a Firebase functions directory. It's a bit counter intuitive. I followed the way recommended by Firebase for Nuxt.js, i.e. putting a functions directory inside a Nuxt/Sapper project: https://youtu.be/ZYUWsjUxxUQ?t=106

Right, it is possible to adjust the settings used for firebase functions, to avoid having to move all of the files "down" a directory.

@lukeed Maybe I'm wrong but in this demo project don't they simply use request and response objects instead of using an existing instance of a server?
If I'm right then in this example Polka isn't really used by Google Cloud functions to serve content…

I didn't check it out closely tbh.

I think something you can try @laurentpayot is this:

let { handler } = app;
export default functions.https.onRequest((req, res) => {
  handler(req, res);
})

My guess is that the functions utility is passing in a third argument (eg, next). Polka _takes_ a third argument, but expects an object instead (via Next.js, for pre-parsed url info).

That sounds like it fits the error message you posted.

Lemme know~!

Thanks @lukeed yes it works, just like in the repo you mentioned.
You gave me the following idea:

export default functions.https.onRequest(sapper({ manifest }))

is working because no app is needed within Firebase functions, but simply the request handler (sapper({ manifest })) :wink:

Ah nice, bingo!

My server.js is like this:

import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
import * as sapper from '@sapper/server';
import bodyParser from 'body-parser';
import session from 'express-session';
import sessionFileStore from 'session-file-store';
import 'dotenv/config';

const { PORT, NODE_ENV, NOW, SECRET } = process.env;
const dev = NODE_ENV === 'development';
const FileStore = sessionFileStore(session);

function logger(req, res, next) {
    console.log(`~> Received ${req.method} on ${req.url}`);
    next();
}

function protect(req, res, next) {
    const allowed = [ '/credentials' ];
    const isPrivate = !allowed.includes(req.path);
    const isFile = req.path.includes('.');
    const hasToken = req.session.token;

    if (isPrivate && !isFile && !hasToken) {
        res.statusCode = 302;
        res.setHeader('Location', '/credentials');
        res.end();
    }

    next();
}

polka() // You can also use Express
    .use(bodyParser.json())
    .use(session({
        secret: SECRET,
        resave: false,
        saveUninitialized: false,
        cookie: { maxAge: 31536000 },
        store: new FileStore({ path: NOW ? `/tmp/sessions` : `.sessions` })
    }))
    .use(protect)
    .use(
        compression({ threshold: 0 }),
                sirv('static', { dev }),
        sapper.middleware({
            session: req => ({ user: req.session && req.session.user })
        })
    )
    .listen(PORT, err => {
        if (err) console.log('error', err);
    });


export { sapper }

My firebase functions/index.js is like this:

const functions = require('firebase-functions');
const express = require('express');
const { sapper } = require('./__sapper__/build/server/server');
const app = express().use(sapper.middleware());

exports.ssr = functions.https.onRequest(app);

Someone can help me? I got this errorrs:

  1. Wed, 29 May 2019 14:15:58 GMT express-session deprecated req.secret; provide secret option at __sapper__/build/server/server.js:3213:7
  1. Error: ENOENT: no such file or directory, scandir '/home/splem/svelte-projects/creditcardmanager/functions/static'

Hi all,

I solved these two errors:

  1. My SECRET variable was empty.
  2. I removed public from firebase.json and from polka(sivr('static', { dev })), I do not need it, I think.

But now when I enter in /home it not redirects to /credentials, some help please '-'.
??

This repository by @sanderhahn resumes the original way to deploy to Cloud Run and adds custom tips for the special case of Firebase
https://github.com/sanderhahn/gcloud-sapper

After running npm run build and copying __sapper__ into the functions folder I am still having problems

const functions = require('firebase-functions');
const express = require('express');
const { sapper } = require('./__sapper__/build/server/server');

const app = express().use(sapper.middleware());
exports.app = functions.https.onRequest(app);

Running firebase serve returns:

TypeError: Cannot read property 'middleware' of undefined

go up one level i.e.

const { sapper } = require('../__sapper__/build/server/server');

and btw, there's no point in using polka when you're on firebase, firebase uses express under the hood so all you're doing with polka is an indirection that's costly; I know, I was using polka too, went back to express and why not... :)

@evdama What? I have my __sapper__ in the functions folder so that will not fix it...?

you're calling __sapper__ from lib/index.js no? if so you must leave lib/ first, then into __sapper__/

No, that's only for typescript functions...

nm, turns out I needed to append export { sapper } at the end of my server.js 😫

yes, so the ssr cloud function can use it

This seems both resolved and a support issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Snugug picture Snugug  Â·  4Comments

freedmand picture freedmand  Â·  4Comments

milosdjakovic picture milosdjakovic  Â·  3Comments

matt3224 picture matt3224  Â·  4Comments

antony picture antony  Â·  3Comments