feat: introduce React Renderer Middleware (#309)
* feat: introduce React Renderer Middleware * docs: update readme * chore: add changeset * use `global.d.ts` * remove global.d.ts * import types correctlypull/314/head
parent
ee12e3c08a
commit
9348fa2663
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@hono/react-renderer': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: introduce React Renderer
|
|
@ -0,0 +1,224 @@
|
||||||
|
# React Renderer Middleware
|
||||||
|
|
||||||
|
React Renderer Middleware allows for the easy creation of a renderer based on React for Hono.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```txt
|
||||||
|
npm i @hono/react-renderer react react-dom hono
|
||||||
|
npm i -D @types/react @types/react-dom
|
||||||
|
```
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
`tsconfig.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "react"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { reactRenderer } from '@hono/react-renderer'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
'*',
|
||||||
|
reactRenderer(({ children }) => {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>React + Hono</h1>
|
||||||
|
<div>{children}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
return c.render(<p>Welcome!</p>)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extending `Props`
|
||||||
|
|
||||||
|
You can define types of `Props`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
declare module '@hono/react-renderer' {
|
||||||
|
interface Props {
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can use it in the `reactRenderer()` function and pass values as a second argument to `c.render()`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
app.get(
|
||||||
|
'*',
|
||||||
|
reactRenderer(({ children, title }) => {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{title}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>{children}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
return c.render(<p>Welcome!</p>, {
|
||||||
|
title: 'Top Page',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### `useRequestContext()`
|
||||||
|
|
||||||
|
You can get an instance of `Context` in a function component:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const Component = () => {
|
||||||
|
const c = useRequestContext()
|
||||||
|
return <p>You access {c.req.url}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
return c.render(<Component />)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### `docType`
|
||||||
|
|
||||||
|
If you set it `true`, `DOCTYPE` will be added:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
app.get(
|
||||||
|
'*',
|
||||||
|
reactRenderer(
|
||||||
|
({ children }) => {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div>{children}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
docType: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The HTML is the following:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div><p>Welcome!</p></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can specify the `docType` as you like.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
app.get(
|
||||||
|
'*',
|
||||||
|
reactRenderer(
|
||||||
|
({ children }) => {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div>{children}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
docType:
|
||||||
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `stream`
|
||||||
|
|
||||||
|
It enables returning a streaming response. You can use a `Suspense` with it:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { reactRenderer } from '@hono/react-renderer'
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
'*',
|
||||||
|
reactRenderer(
|
||||||
|
({ children }) => {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div>{children}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stream: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let done = false
|
||||||
|
|
||||||
|
const Component = () => {
|
||||||
|
if (done) {
|
||||||
|
return <p>Done!</p>
|
||||||
|
}
|
||||||
|
throw new Promise((resolve) => {
|
||||||
|
done = true
|
||||||
|
setTimeout(resolve, 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
return c.render(
|
||||||
|
<Suspense fallback='loading...'>
|
||||||
|
<Component />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitation
|
||||||
|
|
||||||
|
A streaming feature is not available on Vite or Vitest.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Yusuke Wada <https://github.com/yusukebe>
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"name": "@hono/react-renderer",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "React Renderer Middleware for Hono",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "vitest --run",
|
||||||
|
"build": "tsup ./src/index.ts --external hono,react,react-dom --format esm,cjs --dts",
|
||||||
|
"publint": "publint"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "https://registry.npmjs.org",
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/honojs/middleware.git"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/honojs/middleware",
|
||||||
|
"peerDependencies": {
|
||||||
|
"hono": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18.2.17",
|
||||||
|
"hono": "^3.11.7",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"tsup": "^8.0.1",
|
||||||
|
"vitest": "^1.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
export * from './react-renderer'
|
||||||
|
|
||||||
|
export type Props = {}
|
||||||
|
|
||||||
|
declare module 'hono' {
|
||||||
|
interface ContextRenderer {
|
||||||
|
(children: React.ReactElement, props?: Props): Response | Promise<Response>
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import type { Context } from 'hono'
|
||||||
|
import type { Env, MiddlewareHandler } from 'hono/types'
|
||||||
|
import React from 'react'
|
||||||
|
import { renderToString, renderToReadableStream } from 'react-dom/server'
|
||||||
|
import type { Props } from '.'
|
||||||
|
|
||||||
|
type RendererOptions = {
|
||||||
|
docType?: boolean | string
|
||||||
|
stream?: boolean | Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseProps = {
|
||||||
|
c: Context
|
||||||
|
children: React.ReactElement
|
||||||
|
}
|
||||||
|
|
||||||
|
const RequestContext = React.createContext<Context | null>(null)
|
||||||
|
|
||||||
|
const createRenderer =
|
||||||
|
(c: Context, component?: React.FC<Props & BaseProps>, options?: RendererOptions) =>
|
||||||
|
async (children: React.ReactElement, props?: Props) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
const node = component ? component({ children, c, ...props }) : children
|
||||||
|
|
||||||
|
if (options?.stream) {
|
||||||
|
const stream = await renderToReadableStream(
|
||||||
|
React.createElement(RequestContext.Provider, { value: c }, node)
|
||||||
|
)
|
||||||
|
return c.body(stream, {
|
||||||
|
headers:
|
||||||
|
options.stream === true
|
||||||
|
? {
|
||||||
|
'Transfer-Encoding': 'chunked',
|
||||||
|
'Content-Type': 'text/html; charset=UTF-8',
|
||||||
|
}
|
||||||
|
: options.stream,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const docType =
|
||||||
|
typeof options?.docType === 'string'
|
||||||
|
? options.docType
|
||||||
|
: options?.docType === true
|
||||||
|
? '<!DOCTYPE html>'
|
||||||
|
: ''
|
||||||
|
const body =
|
||||||
|
docType + renderToString(React.createElement(RequestContext.Provider, { value: c }, node))
|
||||||
|
return c.html(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reactRenderer = (
|
||||||
|
component?: React.FC<Props & BaseProps>,
|
||||||
|
options?: RendererOptions
|
||||||
|
): MiddlewareHandler =>
|
||||||
|
function reactRenderer(c, next) {
|
||||||
|
c.setRenderer(createRenderer(c, component, options))
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const useRequestContext = <E extends Env = any>(): Context<E> => {
|
||||||
|
const c = React.useContext(RequestContext)
|
||||||
|
if (!c) {
|
||||||
|
throw new Error('RequestContext is not provided.')
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { reactRenderer, useRequestContext } from '../src/react-renderer'
|
||||||
|
|
||||||
|
const RequestUrl = () => {
|
||||||
|
const c = useRequestContext()
|
||||||
|
return <>{c.req.url}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Basic', () => {
|
||||||
|
const app = new Hono()
|
||||||
|
app.use(
|
||||||
|
// @ts-expect-error - `title` is not defined
|
||||||
|
reactRenderer(({ children, title }) => {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<head>{title}</head>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
app.get('/', (c) => {
|
||||||
|
return c.render(
|
||||||
|
<h1>
|
||||||
|
<RequestUrl />
|
||||||
|
</h1>,
|
||||||
|
{
|
||||||
|
title: 'Title',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return HTML with layout', async () => {
|
||||||
|
const res = await app.request('http://localhost/')
|
||||||
|
expect(res).not.toBeNull()
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(await res.text()).toBe(
|
||||||
|
'<html><head>Title</head><body><h1>http://localhost/</h1></body></html>'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return HTML without layout', async () => {
|
||||||
|
const app = new Hono()
|
||||||
|
app.use('*', reactRenderer())
|
||||||
|
app.get('/', (c) =>
|
||||||
|
c.render(
|
||||||
|
<h1>
|
||||||
|
<RequestUrl />
|
||||||
|
</h1>,
|
||||||
|
{ title: 'Title' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const res = await app.request('http://localhost/')
|
||||||
|
expect(res).not.toBeNull()
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(await res.text()).toBe('<h1>http://localhost/</h1>')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return a default doctype', async () => {
|
||||||
|
const app = new Hono()
|
||||||
|
app.use(
|
||||||
|
'*',
|
||||||
|
reactRenderer(
|
||||||
|
({ children }) => {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ docType: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
app.get('/', (c) => c.render(<h1>Hello</h1>, { title: 'Title' }))
|
||||||
|
const res = await app.request('/')
|
||||||
|
expect(res).not.toBeNull()
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(await res.text()).toBe('<!DOCTYPE html><html><body><h1>Hello</h1></body></html>')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should return a custom doctype', async () => {
|
||||||
|
const app = new Hono()
|
||||||
|
app.use(
|
||||||
|
'*',
|
||||||
|
reactRenderer(
|
||||||
|
({ children }) => {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
docType:
|
||||||
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
app.get('/', (c) => c.render(<h1>Hello</h1>, { title: 'Title' }))
|
||||||
|
const res = await app.request('/')
|
||||||
|
expect(res).not.toBeNull()
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(await res.text()).toBe(
|
||||||
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html><body><h1>Hello</h1></body></html>'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Streaming', () => {
|
||||||
|
it.skip('Vitest does not support Streaming')
|
||||||
|
})
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"rootDir": "./",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "react"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"test/**/*.tsx",
|
||||||
|
],
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
/// <reference types="vitest" />
|
||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
},
|
||||||
|
})
|
55
yarn.lock
55
yarn.lock
|
@ -1833,6 +1833,22 @@ __metadata:
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@hono/react-renderer@workspace:packages/react-renderer":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@hono/react-renderer@workspace:packages/react-renderer"
|
||||||
|
dependencies:
|
||||||
|
"@types/react": "npm:^18"
|
||||||
|
"@types/react-dom": "npm:^18.2.17"
|
||||||
|
hono: "npm:^3.11.7"
|
||||||
|
react: "npm:^18.2.0"
|
||||||
|
react-dom: "npm:^18.2.0"
|
||||||
|
tsup: "npm:^8.0.1"
|
||||||
|
vitest: "npm:^1.0.4"
|
||||||
|
peerDependencies:
|
||||||
|
hono: "*"
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@hono/sentry@workspace:packages/sentry":
|
"@hono/sentry@workspace:packages/sentry":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@hono/sentry@workspace:packages/sentry"
|
resolution: "@hono/sentry@workspace:packages/sentry"
|
||||||
|
@ -4287,7 +4303,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react@npm:^18":
|
"@types/react-dom@npm:^18.2.17":
|
||||||
|
version: 18.2.17
|
||||||
|
resolution: "@types/react-dom@npm:18.2.17"
|
||||||
|
dependencies:
|
||||||
|
"@types/react": "npm:*"
|
||||||
|
checksum: 33b53078ed7e9e0cfc4dc691e938f7db1cc06353bc345947b41b581c3efe2b980c9e4eb6460dbf5ddc521dd91959194c970221a2bd4bfad9d23ebce338e12938
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/react@npm:*, @types/react@npm:^18":
|
||||||
version: 18.2.45
|
version: 18.2.45
|
||||||
resolution: "@types/react@npm:18.2.45"
|
resolution: "@types/react@npm:18.2.45"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -10312,6 +10337,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"hono@npm:^3.11.7":
|
||||||
|
version: 3.11.7
|
||||||
|
resolution: "hono@npm:3.11.7"
|
||||||
|
checksum: 6665e26801cb4c4334e09dbb42453bf40e1daae9a44bf9a1ed22815f6cb701c0d9ac1a4452624234f36093996c8afff0a41d2d5b52a77f4f460178be48e022bf
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"hosted-git-info@npm:^2.1.4":
|
"hosted-git-info@npm:^2.1.4":
|
||||||
version: 2.8.9
|
version: 2.8.9
|
||||||
resolution: "hosted-git-info@npm:2.8.9"
|
resolution: "hosted-git-info@npm:2.8.9"
|
||||||
|
@ -16706,6 +16738,18 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-dom@npm:^18.2.0":
|
||||||
|
version: 18.2.0
|
||||||
|
resolution: "react-dom@npm:18.2.0"
|
||||||
|
dependencies:
|
||||||
|
loose-envify: "npm:^1.1.0"
|
||||||
|
scheduler: "npm:^0.23.0"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.2.0
|
||||||
|
checksum: 66dfc5f93e13d0674e78ef41f92ed21dfb80f9c4ac4ac25a4b51046d41d4d2186abc915b897f69d3d0ebbffe6184e7c5876f2af26bfa956f179225d921be713a
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-is@npm:^18.0.0":
|
"react-is@npm:^18.0.0":
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
resolution: "react-is@npm:18.2.0"
|
resolution: "react-is@npm:18.2.0"
|
||||||
|
@ -17441,6 +17485,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"scheduler@npm:^0.23.0":
|
||||||
|
version: 0.23.0
|
||||||
|
resolution: "scheduler@npm:0.23.0"
|
||||||
|
dependencies:
|
||||||
|
loose-envify: "npm:^1.1.0"
|
||||||
|
checksum: b777f7ca0115e6d93e126ac490dbd82642d14983b3079f58f35519d992fa46260be7d6e6cede433a92db70306310c6f5f06e144f0e40c484199e09c1f7be53dd
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"scoped-regex@npm:^2.0.0":
|
"scoped-regex@npm:^2.0.0":
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
resolution: "scoped-regex@npm:2.1.0"
|
resolution: "scoped-regex@npm:2.1.0"
|
||||||
|
|
Loading…
Reference in New Issue