Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add @remix-run/azure-functions package #2521

Closed
wants to merge 68 commits into from
Closed
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
bf83669
test(express): add basic test for createRemixRequest
mcansh Aug 10, 2021
010d5c4
test(architect): add tests for headers
mcansh Aug 10, 2021
f1f10ba
test(vercel): extract headers conversion function, start adding tests…
mcansh Aug 10, 2021
5e64fd3
test(vercel): shutdown server after running test
mcansh Aug 10, 2021
70bc39f
chore: remove `Object.fromEntries(responseHeaders)` spreading
mcansh Aug 10, 2021
6f8d02b
test(vercel): add more tests
mcansh Aug 10, 2021
10f1b11
test(vercel): add test for createRequestHandler
mcansh Aug 11, 2021
e19177b
test(vercel): use supertest for better header testing
mcansh Aug 12, 2021
5c94bd3
fix(express): read port from request app settings
mcansh Aug 12, 2021
cabc7ac
fix(express): req.get("host") returns the port
mcansh Aug 12, 2021
df40493
test(express): update request mocking
mcansh Aug 12, 2021
b36f60d
feat: initial azure adapter
mcansh Aug 12, 2021
b7c613d
test(azure): add test for createRemixRequest
mcansh Aug 12, 2021
22e2292
test(azure): add initial test for createRequestHandler
mcansh Aug 12, 2021
b40a51f
chore: update remix-node version
mcansh Aug 12, 2021
389719c
chore: update notes
mcansh Aug 13, 2021
7e50834
chore(azure): use return instead of `context.res = {}`
mcansh Aug 16, 2021
9206f3d
chore: input headers are just a string
mcansh Aug 16, 2021
4983217
feat: add initial azure template
mcansh Aug 17, 2021
d7406f2
chore(azure): update template readme, add tweaked github action that …
mcansh Aug 18, 2021
265521d
chore(azure): use generic secret name in github action
mcansh Aug 18, 2021
6835d4c
chore(azure): remove erroneous 'a'
mcansh Aug 18, 2021
d6d4ba4
Add space after comma
mjackson Aug 18, 2021
a39c439
feat(getDocumentHeaders): re-add support for multiple set-cookie headers
mcansh Aug 18, 2021
6b3f71b
chore(azure): update adapter to use update request/response/headers
mcansh Aug 18, 2021
505647b
chore: rename azure action
mcansh Aug 18, 2021
b18919c
chore(init/azure): update handler
mcansh Aug 18, 2021
f82b0e7
chore(init/azure): add rewrites and required index.html file
mcansh Aug 19, 2021
fca6d0a
feat(azure): add support for dev
mcansh Aug 19, 2021
fc6bcdb
Revert "feat(azure): add support for dev"
mcansh Aug 19, 2021
36070e9
chore(CLA)
aaronpowell Mar 28, 2022
0bdbe0c
chore: fix up from linting problems
aaronpowell Mar 29, 2022
369ee57
fix: better package name and sample app
aaronpowell Mar 29, 2022
e37e686
renamed the template
aaronpowell Mar 29, 2022
32bb27f
tweaking the deps and adding ts
aaronpowell Mar 29, 2022
19a1f6d
fix: forgot typescript dep
aaronpowell Mar 29, 2022
27df6b1
fix: adding function tools as a dependency
aaronpowell Mar 30, 2022
7f152ff
fix: forgot to set worker/language on init to avoid prompting
aaronpowell Mar 30, 2022
49e463c
fix: bad merge on styling doc
aaronpowell Mar 30, 2022
bc19122
Apply suggestions from code review
aaronpowell Mar 30, 2022
0bac793
fix: missing .eslintrc and wrong favicon
aaronpowell Mar 30, 2022
74d77b6
fix: linting mistakes
aaronpowell Mar 30, 2022
c9a21ed
fix: using @remix-run/node not server directly
aaronpowell Mar 30, 2022
4c150dd
chore: reverting whitespace from merge
aaronpowell Mar 30, 2022
cc6a1b8
Update packages/remix-azure-functions/package.json
aaronpowell Mar 30, 2022
97766bd
Update templates/azure-functions/.eslintrc
aaronpowell Mar 30, 2022
b5204bb
fix: using the correct packages
aaronpowell Mar 30, 2022
292e671
fix: azure template works
aaronpowell Mar 30, 2022
a81bcb1
chore: linting fixes
aaronpowell Mar 30, 2022
bf336fa
feat: Adding Azure to the list of templates
aaronpowell Mar 30, 2022
6e43ff1
Apply suggestions from code review
aaronpowell Mar 31, 2022
49f4245
chore: removing mock that's no longer needed
aaronpowell Mar 31, 2022
bbfc8f8
chore: should be a devDep
aaronpowell Mar 31, 2022
3e6103b
fix: azure-functions extension tests weren't included
aaronpowell Mar 31, 2022
4fa2072
test: fixing the azure functions tests
aaronpowell Mar 31, 2022
ee10de7
test: adding missing tests
aaronpowell Mar 31, 2022
adb6781
Apply suggestions from code review
aaronpowell Mar 31, 2022
266d4b2
chore: tidying up docs for azure functions
aaronpowell Mar 31, 2022
8373aab
chore: alphabetising the order
aaronpowell Mar 31, 2022
86edd6b
fix: missing jest setup
aaronpowell May 4, 2022
408cdea
chore: update `yarn.lock`
MichaelDeBoey Jun 8, 2022
cb539af
Apply suggestions from code review
aaronpowell May 23, 2022
58be825
chore(remix-azure-functions): run `sort-package-json`
MichaelDeBoey Jun 8, 2022
63783db
chore(remix-azure-functions): update dependencies
MichaelDeBoey Jun 8, 2022
a2ffdf0
chore(remix-azure-functions): add `engines` field to `package.json`
MichaelDeBoey Jun 8, 2022
8e94a44
Apply suggestions from code review
aaronpowell Jun 16, 2022
ecaaeef
Update templates/azure-functions/app/entry.client.tsx
aaronpowell Sep 29, 2022
f9b65ff
Apply suggestions from code review
aaronpowell Sep 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- aaronpowell
- aaronpowell96
- aaronshaf
- abereghici
Expand Down
1 change: 1 addition & 0 deletions docs/guides/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ npx create-remix@latest
Fly.io
Netlify
Vercel
Azure Static Web Apps
```

Each target has unique file structures, configuration files, cli commands that need to be run, server environment variables to be set etc. Because of this, it's important to read the README.md to deploy the app. It's got all of the steps you need to take to get your app live within minutes.
Expand Down
1 change: 1 addition & 0 deletions docs/other-api/adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ order: 2
Idiomatic Remix apps can generally be deployed anywhere because Remix adapt's the server's request/response to the [Web Fetch API][web-fetch-api]. It does this through adapters. We maintain a few adapters:

- `@remix-run/architect`
- `@remix-run/azure-functions`
- `@remix-run/cloudflare-pages`
- `@remix-run/cloudflare-workers`
- `@remix-run/express`
Expand Down
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ module.exports = {
"packages/remix-node",
"packages/remix-react",
"packages/remix-server-runtime",
"packages/remix-azure-functions",
],
watchPlugins: [
require.resolve("jest-watch-select-projects"),
require.resolve("jest-watch-typeahead/filename"),
require.resolve("jest-watch-typeahead/testname"),
],
};
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"packages/create-remix",
"packages/remix",
"packages/remix-architect",
"packages/remix-azure-functions",
"packages/remix-cloudflare",
"packages/remix-cloudflare-pages",
"packages/remix-cloudflare-workers",
Expand Down
17 changes: 17 additions & 0 deletions packages/remix-azure-functions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Welcome to Remix!

[Remix](https://remix.run) is a web framework that helps you build better websites with React.

To get started, open a new shell and run:

```sh
npx create-remix@latest
```

Then follow the prompts you see in your terminal.

For more information about Remix, [visit remix.run](https://remix.run)!

## About

This package can be used to host Remix within an [Azure Function](https://docs.microsoft.com/azure/azure-functions/) or in [Azure Static Web Apps](https://docs.microsoft.com/azure/static-web-apps) (using the built-in Azure Functions support).
269 changes: 269 additions & 0 deletions packages/remix-azure-functions/__tests__/server-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import type { Context, HttpRequest } from "@azure/functions";
import {
createRequestHandler as createRemixRequestHandler,
} from "@remix-run/node";

import {
createRemixHeaders,
createRemixRequest,
createRequestHandler,
} from "../server";

// We don't want to test that the remix server works here (that's what the
// puppetteer tests do), we just want to test the express adapter
jest.mock("@remix-run/node", () => {
let original = jest.requireActual("@remix-run/node");
return {
...original,
createRequestHandler: jest.fn(),
};
});

let mockedCreateRequestHandler =
createRemixRequestHandler as jest.MockedFunction<
typeof createRemixRequestHandler
>;

describe("azure createRequestHandler", () => {
let context: Context;

beforeEach(() => {
context = { log: jest.fn() } as unknown as Context;
});

describe("basic requests", () => {
afterEach(() => {
mockedCreateRequestHandler.mockReset();
});

afterAll(() => {
jest.restoreAllMocks();
});

it("handles requests", async () => {
mockedCreateRequestHandler.mockImplementation(() => async (req) => {
return new Response(`URL: ${new URL(req.url).pathname}`);
});

let mockedRequest: HttpRequest = {
method: "GET",
url: "/foo/bar",
rawBody: "",
headers: {
"x-ms-original-url": "http://localhost:3000/foo/bar",
},
params: {},
query: {},
body: "",
};

let res = await createRequestHandler({ build: undefined })(
context,
mockedRequest
);

expect(res.status).toBe(200);
expect(res.body).toBe("URL: /foo/bar");
});

it("handles status codes", async () => {
mockedCreateRequestHandler.mockImplementation(() => async () => {
return new Response (null, { status: 204 });
});

let mockedRequest: HttpRequest = {
method: "GET",
url: "/foo/bar",
rawBody: "",
headers: {
"x-ms-original-url": "http://localhost:3000/foo/bar",
},
params: {},
query: {},
body: "",
};

let res = await createRequestHandler({ build: undefined })(
context,
mockedRequest
);

expect(res.status).toBe(204);
});

it("sets headers", async () => {
mockedCreateRequestHandler.mockImplementation(() => async (req) => {
let headers = new Headers({ "X-Time-Of-Year": "most wonderful" });
headers.append(
"Set-Cookie",
"first=one; Expires=0; Path=/; HttpOnly; Secure; SameSite=Lax"
);
headers.append(
"Set-Cookie",
"second=two; MaxAge=1209600; Path=/; HttpOnly; Secure; SameSite=Lax"
);
headers.append(
"Set-Cookie",
"third=three; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Path=/; HttpOnly; Secure; SameSite=Lax"
);
for(let [key, value] of req.headers.entries()) {
headers.set(key, value);
}
return new Response (null, { headers });
});

let mockedRequest: HttpRequest = {
method: "GET",
url: "/foo/bar",
rawBody: "",
headers: {
"x-ms-original-url": "http://localhost:3000/foo/bar",
},
params: {},
query: {},
body: "",
};

let res = await createRequestHandler({ build: undefined })(
context,
mockedRequest
);

expect(res.headers["x-ms-original-url"]).toEqual(["http://localhost:3000/foo/bar"]);
expect(res.headers["X-Time-Of-Year"]).toEqual(["most wonderful"]);
expect(res.headers["Set-Cookie"]).toEqual([
"first=one; Expires=0; Path=/; HttpOnly; Secure; SameSite=Lax",
"second=two; MaxAge=1209600; Path=/; HttpOnly; Secure; SameSite=Lax",
"third=three; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Path=/; HttpOnly; Secure; SameSite=Lax",
]);
});
});
});

describe("azure createRemixHeaders", () => {
describe("creates fetch headers from azure headers", () => {
it("handles empty headers", () => {
expect(createRemixHeaders({})).toMatchInlineSnapshot(`
Headers {
Symbol(map): Object {},
}
`);
});

it("handles simple headers", () => {
expect(createRemixHeaders({ "x-foo": "bar" })).toMatchInlineSnapshot(`
Headers {
Symbol(map): Object {
"x-foo": Array [
"bar",
],
},
}
`);
});

it("handles multiple headers", () => {
expect(createRemixHeaders({ "x-foo": "bar", "x-bar": "baz" }))
.toMatchInlineSnapshot(`
Headers {
Symbol(map): Object {
"x-bar": Array [
"baz",
],
"x-foo": Array [
"bar",
],
},
}
`);
});

it("handles headers with multiple values", () => {
expect(createRemixHeaders({ "x-foo": "bar, baz" }))
.toMatchInlineSnapshot(`
Headers {
Symbol(map): Object {
"x-foo": Array [
"bar, baz",
],
},
}
`);
});

it("handles headers with multiple values and multiple headers", () => {
expect(createRemixHeaders({ "x-foo": "bar, baz", "x-bar": "baz" }))
.toMatchInlineSnapshot(`
Headers {
Symbol(map): Object {
"x-bar": Array [
"baz",
],
"x-foo": Array [
"bar, baz",
],
},
}
`);
});
});
});

describe("azure createRemixRequest", () => {
it("creates a request with the correct headers", async () => {
let request: HttpRequest = {
method: "GET",
url: "/foo/bar",
rawBody: "",
headers: {
"x-ms-original-url": "http://localhost:3000/foo/bar",
},
params: {},
query: {},
body: "",
};

expect(createRemixRequest(request)).toMatchInlineSnapshot(`
NodeRequest {
"abortController": undefined,
"agent": undefined,
"compress": true,
"counter": 0,
"follow": 20,
"size": 0,
"timeout": 0,
Symbol(Body internals): Object {
"body": null,
"disturbed": false,
"error": null,
},
Symbol(Request internals): Object {
"headers": Headers {
Symbol(map): Object {
"x-ms-original-url": Array [
"http://localhost:3000/foo/bar",
],
},
},
"method": "GET",
"parsedURL": Url {
"auth": null,
"hash": null,
"host": "localhost:3000",
"hostname": "localhost",
"href": "http://localhost:3000/foo/bar",
"path": "/foo/bar",
"pathname": "/foo/bar",
"port": "3000",
"protocol": "http:",
"query": null,
"search": null,
"slashes": true,
},
"redirect": "follow",
"signal": null,
},
}
`);
});
});
2 changes: 2 additions & 0 deletions packages/remix-azure-functions/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { installGlobals } from "@remix-run/node";
installGlobals();
2 changes: 2 additions & 0 deletions packages/remix-azure-functions/globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { installGlobals } from "@remix-run/node";
installGlobals();
4 changes: 4 additions & 0 deletions packages/remix-azure-functions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import "./globals";

export type { GetLoadContextFunction, RequestHandler } from "./server";
export { createRequestHandler } from "./server";
5 changes: 5 additions & 0 deletions packages/remix-azure-functions/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
...require("../../jest/jest.config.shared"),
displayName: "azure-functions",
};
9 changes: 9 additions & 0 deletions packages/remix-azure-functions/magicExports/remix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable import/no-extraneous-dependencies */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now have a new way of handling magicExports.
Please add the magic exports in getMagicExports function of the root's rollup.utils.js


// Re-export everything from this package that is available in `remix`.
aaronpowell marked this conversation as resolved.
Show resolved Hide resolved

export {
RequestHandler,
createRequestHandler,
GetLoadContextFunction,
} from "@remix-run/azure-functions";
29 changes: 29 additions & 0 deletions packages/remix-azure-functions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@remix-run/azure-functions",
"version": "1.6.8",
aaronpowell marked this conversation as resolved.
Show resolved Hide resolved
"description": "Azure Functions server request handler for Remix",
"bugs": {
"url": "https://github.com/remix-run/remix/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/remix-run/remix",
"directory": "packages/remix-azure-functions"
},
"license": "MIT",
"dependencies": {
"@remix-run/node": "1.6.8"
aaronpowell marked this conversation as resolved.
Show resolved Hide resolved
},
"devDependencies": {
"@azure/functions": "^3.2.0",
"@types/supertest": "^2.0.12",
"node-mocks-http": "^1.11.0",
"supertest": "^6.2.4"
},
"peerDependencies": {
"@azure/functions": "^3.0.0"
},
"engines": {
"node": ">=14"
}
}
6 changes: 6 additions & 0 deletions packages/remix-azure-functions/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const { getAdapterConfig } = require("../../rollup.utils");

/** @returns {import("rollup").RollupOptions[]} */
module.exports = function rollup() {
return [getAdapterConfig("azure-functions")];
};
Loading