forked from sergiodxa/remix-utils
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit dcffa3a
Showing
10 changed files
with
9,174 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
name: CI | ||
on: [push] | ||
jobs: | ||
build: | ||
name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} | ||
|
||
runs-on: ${{ matrix.os }} | ||
strategy: | ||
matrix: | ||
node: ['10.x', '12.x', '14.x'] | ||
os: [ubuntu-latest, windows-latest, macOS-latest] | ||
|
||
steps: | ||
- name: Checkout repo | ||
uses: actions/checkout@v2 | ||
|
||
- name: Use Node ${{ matrix.node }} | ||
uses: actions/setup-node@v1 | ||
with: | ||
node-version: ${{ matrix.node }} | ||
|
||
- name: Install deps and build (with cache) | ||
uses: bahmutov/npm-install@v1 | ||
|
||
- name: Lint | ||
run: yarn lint | ||
|
||
- name: Test | ||
run: yarn test --ci --coverage --maxWorkers=2 | ||
|
||
- name: Build | ||
run: yarn build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
name: size | ||
on: [pull_request] | ||
jobs: | ||
size: | ||
runs-on: ubuntu-latest | ||
env: | ||
CI_JOB_NUMBER: 1 | ||
steps: | ||
- uses: actions/checkout@v1 | ||
- uses: andresz1/size-limit-action@v1 | ||
with: | ||
github_token: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.log | ||
.DS_Store | ||
node_modules | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 Sergio Xalambrí | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
# Remix Utils | ||
|
||
This package contains simple utility functions and types to use together with [Remix.run](https://remix.run). | ||
|
||
**To install it you need to have a valid Remix license** | ||
|
||
## Installation | ||
|
||
```bash | ||
yarn add remix-utils | ||
npm install remix-utils | ||
``` | ||
|
||
Remember you need to also install `remix`, `@remix-run/node`, `@remix-run/react` and `react`. For the first three you need a paid Remix license. | ||
|
||
## Imports | ||
|
||
```ts | ||
import { redirectBack, parseBody, json } from "remix-utils"; | ||
import type { | ||
LoaderArgs, | ||
LoaderReturn, | ||
ActionArgs, | ||
ActionReturn, | ||
LinksArgs, | ||
LinksReturn, | ||
MetaArgs, | ||
MetaReturn, | ||
HeadersArgs, | ||
HeadersReturn, | ||
} from "remix-utils"; | ||
``` | ||
|
||
## API | ||
|
||
### `redirectBack` | ||
|
||
This function is a wrapper of the `redirect` helper from Remix, contrarian to Remix's version this one receives the whole request object as first value and an object with the response init and a fallback URL. | ||
|
||
The response created with this function will have the `Location` header pointing to the `Referer` header from the request, or if not available the fallback URL provided in the second argument. | ||
|
||
```ts | ||
import { redirectBack } from "remix-utils"; | ||
import type { ActionArgs, ActionReturn } from "remix-utils"; | ||
|
||
export function action({ request }: ActionArgs): ActionReturn { | ||
return redirectBack(request, { fallback: "/" }); | ||
} | ||
``` | ||
|
||
This helper is more useful when used in an action so you can send the user to the same URL it was before. | ||
|
||
### `parseBody` | ||
|
||
This function receives the whole request and returns a promise with an instance of `URLSearchParams`, and the body of the request already parsed. | ||
|
||
```ts | ||
import { parseBody, redirectBack } from "remix-utils"; | ||
import type { ActionArgs, ActionReturn } from "remix-utils"; | ||
|
||
import { updateUser } from "../services/users"; | ||
|
||
export function action({ request, params }: ActionArgs): ActionReturn { | ||
const body = await parseBody(request); | ||
await updateUser(params.id, { username: body.get("username") }); | ||
return redirectBack(request, { fallback: "/" }); | ||
} | ||
``` | ||
|
||
This is a simple wrapper over doing `new URLSearchParams(await request.text());`. | ||
|
||
### `json` | ||
|
||
This function is a typed version of the `json` helper provided by Remix, it accepts a generic (defaults to `unknown`) and ensure at the compiler lever that the data you are sending from your loader matches the provided type. It's more useful when you create a type or interface for your whole route so you can share it between `json` and `useRouteData` to ensure you are not missing or adding extra parameters to the response. | ||
|
||
```tsx | ||
import { useRouteData } from "remix"; | ||
import { json } from "remix-utils"; | ||
import type { LoaderArgs, LoaderReturn } from "remix-utils"; | ||
|
||
import { getUser } from "../services/users"; | ||
import type { User } from "../types"; | ||
|
||
interface RouteData { | ||
user: User; | ||
} | ||
|
||
export async function loader({ request }: LoaderArgs): LoaderReturn { | ||
const user = await getUser(request); | ||
return json<RouteData>({ user }); | ||
} | ||
|
||
export default function View() { | ||
const { user } = useRouteData<RouteData>(); | ||
return <h1>Hello, {user.name}</h1>; | ||
} | ||
``` | ||
|
||
### Types | ||
|
||
This package exports a list of useful types together with the utility functions, you can import them with | ||
|
||
```ts | ||
import type { | ||
LoaderArgs, | ||
LoaderReturn, | ||
ActionArgs, | ||
ActionReturn, | ||
LinksArgs, | ||
LinksReturn, | ||
MetaArgs, | ||
MetaReturn, | ||
HeadersArgs, | ||
HeadersReturn, | ||
} from "remix-utils"; | ||
``` | ||
|
||
This types are generated from the `LoaderFunction`, `ActionFunction`, `LinksFunction`, `MetaFunction` and `HeadersFunction` exported by Remix itself, this ensure they will be up to date with your Remix version. | ||
|
||
All the `*Args` types are the first argument of the equivalent `*Funtion` type. | ||
All the `*Return` types are the return type of the equivalent `*Funtion` type. | ||
|
||
They are exported in case you don't want to use arrow functions (like me) and still want the types so instead of doing: | ||
|
||
```ts | ||
export const loader: LoaderFunction = async (args) => {}; | ||
``` | ||
|
||
You can do: | ||
|
||
```ts | ||
export async function loader(args: LoaderArgs): LoaderReturn {} | ||
``` | ||
|
||
Since all of them are TypeScript types they will not impact your bundle size in case you prefer to use the normal `*Function` types from Remix. | ||
|
||
## Author | ||
|
||
- [Sergio Xalambrí](https://sergiodxa.com) | ||
|
||
## License | ||
|
||
- MIT License | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{ | ||
"name": "remix-utils", | ||
"version": "0.1.0", | ||
"license": "MIT", | ||
"main": "dist/index.js", | ||
"typings": "dist/index.d.ts", | ||
"files": [ | ||
"dist", | ||
"src" | ||
], | ||
"engines": { | ||
"node": ">=14" | ||
}, | ||
"scripts": { | ||
"start": "tsdx watch", | ||
"build": "tsdx build", | ||
"test": "tsdx test", | ||
"lint": "tsdx lint", | ||
"prepare": "tsdx build", | ||
"size": "size-limit", | ||
"analyze": "size-limit --why" | ||
}, | ||
"peerDependencies": { | ||
"@remix-run/node": "^0.17.0", | ||
"@remix-run/react": "^0.17.0", | ||
"react": "^17.0.2", | ||
"remix": "^0.17.0" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "tsdx lint" | ||
} | ||
}, | ||
"prettier": { | ||
"printWidth": 80, | ||
"semi": true, | ||
"singleQuote": true, | ||
"trailingComma": "es5" | ||
}, | ||
"author": "Sergio Xalambrí", | ||
"module": "dist/remix-utils.esm.js", | ||
"size-limit": [ | ||
{ | ||
"path": "dist/remix-utils.cjs.production.min.js", | ||
"limit": "10 KB" | ||
}, | ||
{ | ||
"path": "dist/remix-utils.esm.js", | ||
"limit": "10 KB" | ||
} | ||
], | ||
"devDependencies": { | ||
"@size-limit/preset-small-lib": "^4.10.2", | ||
"husky": "^6.0.0", | ||
"size-limit": "^4.10.2", | ||
"tsdx": "^0.14.1", | ||
"tslib": "^2.2.0", | ||
"typescript": "^4.2.4", | ||
"@remix-run/node": "^0.17.0", | ||
"@remix-run/react": "^0.17.0", | ||
"react": "^17.0.2", | ||
"remix": "^0.17.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import type { | ||
ActionFunction, | ||
HeadersFunction, | ||
LinksFunction, | ||
LoaderFunction, | ||
MetaFunction, | ||
Request, | ||
ResponseInit, | ||
Response, | ||
} from "remix"; | ||
import { json as remixJson, redirect } from "remix"; | ||
|
||
export function redirectBack( | ||
request: Request, | ||
{ fallback, ...init }: ResponseInit & { fallback: string } | ||
): Response { | ||
return redirect(request.headers.get("Referer") ?? fallback, init); | ||
} | ||
|
||
export function parseBody(request: Request): Promise<URLSearchParams> { | ||
return request.text().then(body => new URLSearchParams(body)); | ||
} | ||
|
||
export function json<Data = unknown>(data: Data, init?: number | ResponseInit) { | ||
return remixJson(data, init); | ||
} | ||
|
||
export type LoaderArgs = Parameters<LoaderFunction>[0]; | ||
export type ActionArgs = Parameters<ActionFunction>[0]; | ||
export type LinksArgs = Parameters<LinksFunction>[0]; | ||
export type MetaArgs = Parameters<MetaFunction>[0]; | ||
export type HeadersArgs = Parameters<HeadersFunction>[0]; | ||
|
||
export type LoaderReturn = ReturnType<LoaderFunction>; | ||
export type ActionReturn = ReturnType<ActionFunction>; | ||
export type LinksReturn = ReturnType<LinksFunction>; | ||
export type MetaReturn = ReturnType<MetaFunction>; | ||
export type HeadersReturn = ReturnType<HeadersFunction>; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Request } from 'remix'; | ||
import { redirectBack, parseBody, json } from '../src'; | ||
|
||
describe('redirectBack', () => { | ||
it('uses the referer if available', () => { | ||
const request = new Request('/', { | ||
headers: { Referer: '/referer' }, | ||
}); | ||
const response = redirectBack(request, { fallback: '/fallback' }); | ||
expect(response.headers.get('Location')).toBe('/referer'); | ||
}); | ||
|
||
it('uses the fallback if referer is not available', () => { | ||
const request = new Request('/'); | ||
const response = redirectBack(request, { fallback: '/fallback' }); | ||
expect(response.headers.get('Location')).toBe('/fallback'); | ||
}); | ||
}); | ||
|
||
describe('parseBody', () => { | ||
it('reads the body as a URLSearchParams instance', async () => { | ||
const request = new Request('/', { | ||
method: 'POST', | ||
body: new URLSearchParams({ framework: 'Remix' }).toString(), | ||
}); | ||
const body = await parseBody(request); | ||
expect(body).toBeInstanceOf(URLSearchParams); | ||
expect(body.get('framework')).toBe('Remix'); | ||
}); | ||
}); | ||
|
||
describe('json', () => { | ||
it('returns a response with the JSON data', async () => { | ||
interface RouteData { | ||
framework: 'Remix'; | ||
} | ||
const response = json<RouteData>({ framework: 'Remix' }); | ||
const body = await response.json(); | ||
expect(body.framework).toBe('Remix'); | ||
}); | ||
}); |
Oops, something went wrong.