vite-plugin-cloudflare icon indicating copy to clipboard operation
vite-plugin-cloudflare copied to clipboard

Cloudflare Pages!

Open Aslemammad opened this issue 4 years ago • 2 comments

We better discuss this with you all! please give me your suggestions!

Aslemammad avatar Dec 28 '21 21:12 Aslemammad

After struggling with multiple different bundlers, I have managed to find a way to do this 🙃

  • Repository: https://github.com/thetre97/vite-plugin-ssr-cloudflare-pages
  • Preview: https://vite-plugin-ssr-cloudflare-pages.pages.dev

I have used esbuild to bundle the worker, and it's then just a case of configuring the pages site correctly.

Dev Packages

esbuild
@esbuild-plugins/node-modules-polyfill
wrangler@beta # Used for local previews
@cloudflare/workers-types # Optional

Worker

worker/index.ts

import { createPageRenderer } from 'vite-plugin-ssr'
import '../dist/server/importBuild.js'

const renderPage = createPageRenderer({ isProduction: true })

interface EnvironmentVariables {
  EXAMPLE_SECRET: string
}

type FetchFunction = EventContext<EnvironmentVariables, '', unknown>

export default {
  async fetch (request: FetchFunction['request'], env: FetchFunction['env']) {
    // Keep browser requests happy during testing
    if (request.url.includes('favicon')) return new Response('', { status: 200 })

    if (request.url.includes('/api/')) {
      // TODO: Add your custom /api/* logic here.
      return new Response('Ok')
    }

    // Handle Asset requests
    if (request.url.includes('/assets')) return env.ASSETS.fetch(request)

    // Otherwise pass to SSR handler
    const pageContextInit = {
      url: request.url,
      fetch: (...args: [RequestInfo, RequestInit]) => fetch(...args)
    }
    const pageContext = await renderPage(pageContextInit)
    const { httpResponse } = pageContext
    if (httpResponse) {
      const { body, statusCode, contentType } = httpResponse
      return new Response(body, {
        headers: { 'content-type': contentType },
        status: statusCode
      })
    }
  }
}

Build Script

build.js

const esbuild = require('esbuild')
const { default: nodeModulesPolyfills } = require('@esbuild-plugins/node-modules-polyfill')

esbuild.build({
  entryPoints: ['./worker/index.js'],
  sourcemap: false,
  outfile: './dist/client/_worker.js',
  minify: true,
  logLevel: 'info',
  platform: 'browser',
  plugins: [nodeModulesPolyfills()],
  format: 'esm',
  target: 'es2020',
  bundle: true
}).then(() => {
  console.log(`Successfully built worker.`)
}).catch(error => {
  console.error(`There was an error whilst building this worker:`)
  console.error(error)
})

Pages

Make sure to set the Build output directory to /dist/client, as otherwise asset requests will fail because the browser is requesting /assets/some.js, whereas the actual stored asset path would be /client/assets/some.js.

You will then need to add a build script in package.json to first build the Vite app, run vite-plugin-ssr, and then run the build.js script.

"scripts": {
    "build": "npm run build:site && npm run build:worker",
    "build:site": "vite build && vite build --ssr",
    "build:worker": "node build.js",
    "serve": "wrangler pages dev ./dist/client"
  }

You can also preview the site locally, by running the build script and then the serve script.

Potential Issues

The esbuild --minify option is pretty decent, but even with this basic project the built _worker.js file comes out around 600kB - the Worker limit being 1MB. So any larger projects, with a number of pages/packages etc. will probably go over this limit quite quickly. There is talk of this limit being raised however, and there is a form to request an increase (although I don't know if this is just for the Workers platform, or whether it includes Pages Functions (which is what the above uses).

travis-r6s avatar Jan 07 '22 13:01 travis-r6s

Thank you @thetre97, I appreciate this!

I'll read it precisely in the next days, so I can improve this package! I'll let you know if I needed anything!

Aslemammad avatar Jan 07 '22 14:01 Aslemammad