├── .funcignore
├── .github
└── workflows
│ └── azure-static-web-apps-kind-wave-0f8b93b1e.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── README.orig.md
├── credentials.js
├── fs
├── index.js
├── package.json
└── promises.js
├── function_notes_delete
└── function.json
├── function_notes_post
└── function.json
├── function_notes_put
└── function.json
├── function_react
├── function.json
└── index.server.js
├── funcutil
├── auth.js
├── babelregister.server.js
└── react-utils.server.js
├── host.json
├── local.settings.json
├── notes
└── .gitkeep
├── package-lock.json
├── package.json
├── public
├── checkmark.svg
├── chevron-down.svg
├── chevron-up.svg
├── cross.svg
├── favicon.ico
├── index.html
├── logo.svg
└── style.css
├── scripts
├── build.js
├── init_db.sh
└── seed.js
└── src
├── App.server.js
├── Cache.client.js
├── EditButton.client.js
├── LocationContext.client.js
├── Note.server.js
├── NoteEditor.client.js
├── NoteList.server.js
├── NoteListSkeleton.js
├── NotePreview.js
├── NoteSkeleton.js
├── Root.client.js
├── SearchField.client.js
├── SidebarNote.client.js
├── SidebarNote.js
├── Spinner.js
├── TextWithMarkdown.js
├── config.js
├── db.server.js
└── index.client.js
/.funcignore:
--------------------------------------------------------------------------------
1 | *.js.map
2 | *.ts
3 | .git*
4 | .vscode
5 | local.settings.json
6 | test
7 | tsconfig.json
--------------------------------------------------------------------------------
/.github/workflows/azure-static-web-apps-kind-wave-0f8b93b1e.yml:
--------------------------------------------------------------------------------
1 | name: Azure Static Web Apps CI/CD
2 |
3 | on:
4 | push:
5 | branches:
6 | - azure-static-web-apps
7 | pull_request:
8 | types: [opened, synchronize, reopened, closed]
9 | branches:
10 | - azure-static-web-apps
11 |
12 | jobs:
13 | build_and_deploy_job:
14 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
15 | runs-on: ubuntu-latest
16 | name: Build and Deploy Job
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | submodules: true
21 | - name: Build app and API
22 | run: | # build and then remove package.json (so deploy step doesn't reinstall modules)
23 | npm install
24 | npm run build
25 | npm prune --production
26 | rm package.json package-lock.json
27 | ls -la build
28 | - name: Deploy
29 | id: builddeploy
30 | uses: Azure/static-web-apps-deploy@v0.0.1-preview
31 | with:
32 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_KIND_WAVE_0F8B93B1E }}
33 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
34 | action: "upload"
35 | ###### Repository/Build Configurations - These values can be configured to match you app requirements. ######
36 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
37 | app_location: "build" # App source code path
38 | api_location: "/" # Api source code path - optional
39 | output_location: "" # Built app content directory - optional
40 | ###### End of Repository/Build Configurations ######
41 |
42 | close_pull_request_job:
43 | if: github.event_name == 'pull_request' && github.event.action == 'closed'
44 | runs-on: ubuntu-latest
45 | name: Close Pull Request Job
46 | steps:
47 | - name: Close Pull Request
48 | id: closepullrequest
49 | uses: Azure/static-web-apps-deploy@v0.0.1-preview
50 | with:
51 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_KIND_WAVE_0F8B93B1E }}
52 | action: "close"
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /dist
14 |
15 | # notes
16 | notes/*.md
17 |
18 | # misc
19 | .DS_Store
20 | .eslintcache
21 | .env
22 | .env.local
23 | .env.development.local
24 | .env.test.local
25 | .env.production.local
26 |
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 |
31 | # vscode
32 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | /.pnp
4 | .pnp.js
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 | /dist
12 |
13 | # misc
14 | .DS_Store
15 | .eslintcache
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | *.html
26 | *.json
27 | *.md
28 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | */
8 |
9 | 'use strict';
10 |
11 | module.exports = {
12 | arrowParens: 'always',
13 | bracketSpacing: false,
14 | singleQuote: true,
15 | jsxBracketSameLine: true,
16 | trailingComma: 'es5',
17 | printWidth: 80,
18 | };
19 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-azuretools.vscode-azurefunctions"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Attach to Node Functions",
6 | "type": "node",
7 | "request": "attach",
8 | "port": 9229,
9 | "preLaunchTask": "func: host start"
10 | }
11 | ]
12 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "azureFunctions.deploySubpath": ".",
3 | "azureFunctions.postDeployTask": "npm install",
4 | "azureFunctions.projectLanguage": "JavaScript",
5 | "azureFunctions.projectRuntime": "~3",
6 | "debug.internalConsoleOptions": "neverOpen",
7 | "azureFunctions.preDeployTask": "npm prune"
8 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "func",
6 | "command": "host start",
7 | "problemMatcher": "$func-node-watch",
8 | "isBackground": true,
9 | "dependsOn": "npm install"
10 | },
11 | {
12 | "type": "shell",
13 | "label": "npm install",
14 | "command": "npm install"
15 | },
16 | {
17 | "type": "shell",
18 | "label": "npm prune",
19 | "command": "npm prune --production",
20 | "problemMatcher": []
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to make participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies within all project spaces, and it also applies when
49 | an individual is representing the project or its community in public spaces.
50 | Examples of representing a project or community include using an official
51 | project e-mail address, posting via an official social media account, or acting
52 | as an appointed representative at an online or offline event. Representation of
53 | a project may be further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at . All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Facebook, Inc. and its affiliates.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Server Components Demo - Azure Static Web Apps
2 |
3 | This is a port of the [React Server Components "React Notes"](https://github.com/reactjs/server-components-demo) demo for Azure Static Web Apps. React Server Components is currently an experimental project, and the code here to make it work in Azure is equally experimental. For demonstration purposes only!
4 |
5 | **Live demo: https://react-notes.anthonychu.com/**
6 |
7 | Azure services used:
8 | - [Azure Static Web Apps](https://docs.microsoft.com/en-us/azure/static-web-apps/overview) (and Azure Functions)
9 | - Azure Database for PostgreSQL - Flexible Server
10 |
11 | See [original README](README.orig.md) for license and other info.
12 |
13 | ## Run locally
14 |
15 | 1. Fork and clone this repo.
16 |
17 | 1. Start an instance of Postgres locally with the demo's default credentials. Docker works great:
18 | ```bash
19 | docker run --name react-notes -p 5432:5432 -e POSTGRES_USER=notesadmin -e POSTGRES_PASSWORD=password -d postgres
20 | ```
21 |
22 | 1. Install Azure Functions Core Tools.
23 | ```bash
24 | npm i -g azure-functions-core-tools@3 --unsafe-perm true
25 | ```
26 |
27 | 1. Update `src/config.js` to use local Azure Functions URL:
28 | ```js
29 | module.exports = {
30 | apiBaseUrl: '/api'
31 | };
32 | ```
33 |
34 | 1. Build the app.
35 | ```bash
36 | npm install
37 | npm run build
38 | ```
39 |
40 | 1. Start the Azure Functions app.
41 | ```bash
42 | func start
43 | ```
44 |
45 | 1. Serve the frontend with a web server. Using Python here but anything works.
46 | ```bash
47 | python -m http.server
48 | ```
49 |
50 | ## Deploy to Azure
51 |
52 | 1. Create a Postgres Database in Azure
53 | - [Azure Database for PostgreSQL - Flexible Server](https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/quickstart-create-server-portal) recommended
54 | - Cheapest one works great
55 |
56 | 1. Seed database
57 | - Set `DB_HOST`, `DB_USER`, and `DB_PASSWORD` environment variables to match how you configured your Azure Postgres instance
58 | - Run `npm run seed`
59 |
60 | 1. Create an Azure Static Web App
61 | - App location: `build`
62 | - API location: `/`
63 | - Artifact (output) location: (leave blank)
64 |
65 | 1. The workflow needs to be modified to build the app properly. Add an Action to the generated workflow:
66 | ```yaml
67 | - name: Build app and API
68 | run: | # build and then remove package.json (so deploy step doesn't reinstall modules)
69 | npm install
70 | npm run build
71 | npm prune --production
72 | rm package.json package-lock.json
73 | ls -la build
74 | ```
75 | Save and push the file to trigger another deployment. See [this file](.github/workflows/azure-static-web-apps-kind-wave-0f8b93b1e.yaml) for an example.
76 |
77 | 1. In the Azure portal, go to the Static Web App and open *Configuration*. Enter the following settings:
78 | | name | value |
79 | | --- | --- |
80 | | `BABEL_DISABLE_CACHE` | `1` |
81 | | `DB_HOST` | `.postgres.database.azure.com` |
82 | | `DB_USER` | your database username |
83 | | `DB_PASSWORD` | your database password |
84 | | `DB_SSL` | `1` |
85 | | `languageWorkers__node__arguments` | `--conditions=react-server` |
86 | | `NODE_ENV` | `production` |
87 |
88 | 1. Save the settings. It may take a few seconds to take effect. If all goes well, go to the app's URL and you should see the app.
89 |
90 | ## How does this work?
91 |
92 | 🚨 While it is fully functional, this is entirely experimental and for demonstration purposes only. Do not use for anything resembling production.
93 |
94 | A few changes were made to the demo app to work better in Azure.
95 |
96 | - Some React Server Components originally called a local HTTP endpoint. When it's running in Azure Functions, it's not advisible to make HTTP calls to itself. Those calls were converted to direct Postgres queries.
97 | - Some changes to the WebPack fonfig `scripts/build.js` to combine the contents of `build` and `publish` folders.
98 | - Changed Postgres config to enable SSL when calling an Azure database.
99 |
100 | ### Changes for Azure Functions / Static Web Apps
101 |
102 | - An HTTP function was created for every endpoint in the original demo. The functions themselves are all in `function_react/index.server.js`.
103 | - The function filename must end in `.server.js` to satisfy React Server Components conventions.
104 | - While Azure Functions supports Node.js 14, the version of Node.js currently available in Static Web Apps is 12. The demo app uses `fs/promises`, which is only in Node.js 14. Added a shim at `fs/promises.js` to get around this.
105 | - The demo requires the `--conditions` flag to be set in the Node.js process. This flag is set with `languageWorkers__node__arguments` app setting. Because Azure Functions starts the Node worker process before your app is loaded, you typically need to set an extra app setting (`WEBSITE_USE_PLACEHOLDER=0`) to delay the start of the worker process. However, function apps in Static Web Apps are not allowed to configure app settings starting with `WEBSITE_`. To get around this, if the `conditions` flag isn't set, there is code in `funcutil/babelregister.server.js` to cause a restart in the Node process. This is a huge hack and should never be used in a production app!
106 | - The `pipeToNodeWritable` function in React Server Components requires writing to a stream. Like some other serverless platforms, Azure Functions is unable to stream responses. We use a `memory-stream` for this.
107 | - `pipeToNodeWritable` looks up client components in a generated manifest. Because the manifest contains full paths from the build machine that are different than the paths in the Azure Functions environment, we use a proxy to select the file with the nearest matching name. See `funcutil/react-utils.server.js`.
108 | - CORS - When running locally and the frontend is served from a different port than the Azure Functions app, CORS is required. CORS is enabled on Azure Functions, but because the demo relies on an `X-Location` header, an additional `'Access-Control-Expose-Headers': 'X-Location'` must be added to responses.
109 | - Authentication was added to the app to allow only logged in users to view and modify their own notes.
--------------------------------------------------------------------------------
/README.orig.md:
--------------------------------------------------------------------------------
1 | # React Server Components Demo
2 |
3 | * [What is this?](#what-is-this)
4 | * [When will I be able to use this?](#when-will-i-be-able-to-use-this)
5 | * [Setup](#setup)
6 | * [DB Setup](#db-setup)
7 | + [Step 1. Create the Database](#step-1-create-the-database)
8 | + [Step 2. Connect to the Database](#step-2-connect-to-the-database)
9 | + [Step 3. Run the seed script](#step-3-run-the-seed-script)
10 | * [Notes about this app](#notes-about-this-app)
11 | + [Interesting things to try](#interesting-things-to-try)
12 | * [Built by (A-Z)](#built-by-a-z)
13 | * [Code of Conduct](#code-of-conduct)
14 | * [License](#license)
15 |
16 | ## What is this?
17 |
18 | This is a demo app built with Server Components, an experimental React feature. **We strongly recommend [watching our talk introducing Server Components](https://reactjs.org/server-components) before exploring this demo.** The talk includes a walkthrough of the demo code and highlights key points of how Server Components work and what features they provide.
19 |
20 | ## When will I be able to use this?
21 |
22 | Server Components are an experimental feature and **are not ready for adoption**. For now, we recommend experimenting with Server Components via this demo app. **Use this in your projects at your own risk.**
23 |
24 | ## Setup
25 |
26 | You will need to have nodejs >=14.9.0 in order to run this demo. [Node 14 LTS](https://nodejs.org/en/about/releases/) is a good choice!
27 |
28 | ```
29 | npm install
30 | npm start
31 | ```
32 |
33 | (Or `npm run start:prod` for a production build.)
34 |
35 | Then open http://localhost:4000.
36 |
37 | The app won't work until you set up the database, as described below.
38 |
39 |
40 | Setup with Docker
41 |
You can also start dev build of the app by using docker-compose.
42 |
Make sure you have docker and docker-compose installed then run:
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/NoteListSkeleton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | */
8 |
9 | export default function NoteListSkeleton() {
10 | return (
11 |
12 |
13 |
14 |
18 |
19 |
20 |
24 |
25 |
26 |
30 |
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/NotePreview.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | */
8 |
9 | import TextWithMarkdown from './TextWithMarkdown';
10 |
11 | export default function NotePreview({body}) {
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/src/NoteSkeleton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | *
7 | */
8 |
9 | function NoteEditorSkeleton() {
10 | return (
11 |