├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── LICENSE ├── README.md ├── deploy.tf ├── img └── workers-react.png ├── package.json ├── scripts ├── deploy.js └── open-preview.js ├── src └── index.js ├── webpack.config.js └── yarn.lock /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Cloudflare, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Cloudflare GraphQL Gateway](/img/workers-react.png) 2 | 3 | Workers React Example 4 | ==== 5 | Combining the power of Cloudflare Workers and React will allow you to render the 6 | same React code you would on the browser on Cloudflare Workers. 7 | 8 | ### Dependencies 9 | - [npm](https://www.npmjs.com/get-npm) or [yarn](https://yarnpkg.com/en/docs/install#debian-stable) 10 | 11 | ### Instructions 12 | 13 | - `yarn preview` 14 | 15 | #### Static hosting 16 | In order for this application to work, you'll have to be able to serve `/worker.js` from your own origin. 17 | This example uses terraform to upload the worker script to Google Cloud Storage, but you are welcome to 18 | use any other origin. 19 | - https://cloud.google.com/storage/docs/hosting-static-website 20 | 21 | #### Terraform 22 | If you'd like to use terraform to upload your worker scripts, you'll need a 23 | vars file with the following variables 24 | 25 | ```hcl 26 | # Cloudflare variables 27 | variable "cloudflare_email" { 28 | default = "dmr@bell-labs.com" 29 | } 30 | 31 | variable "cloudflare_token" { 32 | default = "00000000000000000000000000" 33 | } 34 | 35 | # GCP exmaple variables 36 | variable "project" { 37 | default = "my-project" 38 | } 39 | 40 | variable "zone" { 41 | default = "buzzwords.app" 42 | } 43 | 44 | variable "bucket" { 45 | default = "buzzwords" 46 | } 47 | ``` 48 | after adding this file as `vars.tf` (terraform will pickup any `.tf` extension file) do 49 | `terraform init` and `terraform apply` 50 | 51 | 52 | ### About 53 | [Cloudflare Workers](http://developers.cloudflare.com/workers/) allow you to write JavaScript which runs on all of Cloudflare's 54 | 150+ global data centers. 55 | 56 | [React](https://reactjs.org) makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. 57 | -------------------------------------------------------------------------------- /deploy.tf: -------------------------------------------------------------------------------- 1 | provider "cloudflare" { 2 | email = "${var.cloudflare_email}" 3 | token = "${var.cloudflare_token}" 4 | } 5 | 6 | provider "google" { 7 | credentials = "${file("storage.json")}" 8 | project = "${var.project}" 9 | } 10 | 11 | resource "cloudflare_worker_script" "buzzwords" { 12 | zone = "${var.zone}" 13 | content = "${file("dist/worker.js")}" 14 | } 15 | 16 | resource "google_storage_bucket_object" "worker" { 17 | name = "worker.js" 18 | content_encoding = "application/javascript" 19 | content = "${file("dist/worker.js")}" 20 | bucket = "${var.bucket}" 21 | } 22 | -------------------------------------------------------------------------------- /img/workers-react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/workers-react-pwa-example/5a0f61864d80945f2d8a667e403208a4a9403589/img/workers-react.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workers-react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build": "webpack -p", 7 | "terraform": "yarn build && terraform apply -auto-approve", 8 | "deploy": "yarn build && node ./scripts/deploy.js", 9 | "preview": "yarn build && node ./scripts/open-preview.js" 10 | }, 11 | "main": "dist/worker.js", 12 | "author": "@sevki", 13 | "license": "BSD", 14 | "devDependencies": { 15 | "node-fetch": "^2.6.1", 16 | "opn": "^5.3.0" 17 | }, 18 | "dependencies": { 19 | "@babel/core": "^7.1.2", 20 | "@babel/preset-env": "^7.1.0", 21 | "@babel/preset-react": "^7.0.0", 22 | "babel-core": "^6.26.3", 23 | "babel-loader": "^8.0.4", 24 | "babel-preset-env": "^1.7.0", 25 | "react": "^16.6.1", 26 | "react-dom": "^16.6.1", 27 | "react-dom-server": "^0.0.5", 28 | "webpack": "^4.10.2", 29 | "webpack-cli": "^2.1.4" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const util = require("util"); 3 | const fetch = require("node-fetch"); 4 | const readFile = util.promisify(fs.readFile); 5 | const opn = require("opn"); 6 | 7 | async function deploy(script) { 8 | let resp = await fetch( 9 | "https://api.cloudflare.com/client/v4/accounts" + 10 | node.env.CLOUDFLARE_ZONE + 11 | "/workers/scripts/" + 12 | node.env.CLOUDFLARE_SCRIPT, 13 | { 14 | method: "PUT", 15 | headers: { 16 | "cache-control": "no-cache", 17 | "content-type": "text/javascript", 18 | "X-Auth-Email": node.env.CLOUDFLARE_EMAIL, 19 | "X-Auth-Key": node.env.CLOUDFLARE_KEY 20 | }, 21 | body: script 22 | } 23 | ); 24 | 25 | let data = await resp.json(); 26 | return data; 27 | } 28 | 29 | readFile("dist/worker.js", "utf8").then(data => { 30 | deploy(data).then(d => { 31 | console.log(d.errors); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /scripts/open-preview.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const util = require("util"); 3 | const fetch = require("node-fetch"); 4 | const readFile = util.promisify(fs.readFile); 5 | const opn = require("opn"); 6 | 7 | async function newWorker(script) { 8 | let resp = await fetch("https://cloudflareworkers.com/script", { 9 | method: "POST", 10 | headers: { 11 | "cache-control": "no-cache", 12 | "content-type": "text/javascript" 13 | }, 14 | body: script 15 | }); 16 | 17 | let data = await resp.json(); 18 | 19 | return data.id; 20 | } 21 | 22 | readFile("dist/worker.js", "utf8").then(data => { 23 | newWorker(data).then(id => 24 | opn( 25 | "https://cloudflareworkers.com/#" + id + ":https://reactjs.org", 26 | { app: "chromium" } 27 | ) 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Cloudflare, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import React from "react"; 11 | import ReactDOMServer from "react-dom/server"; 12 | 13 | class HelloMessage extends React.Component { 14 | render() { 15 | return ( 16 |
17 |
18 | 19 | 20 | 21 | {"+"} 22 | 28 | 29 | 56 | 60 | 61 | 65 | 66 | 67 |
68 |
69 |

70 | Edit src/index.js and save, yarn preview{" "} 71 | to reload. 72 |

73 | 79 | Learn about Cloudflare Workers 80 | 81 |
82 |
83 | ); 84 | } 85 | } 86 | const header = ` 87 | 88 | Cloudflare Workers React PWA Example 89 | 90 | 129 | 130 |
`; 131 | 132 | const footer = `
133 |