Initial Commit

This commit is contained in:
2026-09-16 17:22:14 +09:00
commit 858ee9e9da
335 changed files with 123898 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
# docs-lib
이 모듈(2_frontend)이 쓰는 라이브러리 참조 문서 모음. 구현 전에 여기 먼저 보고 감 — 기억으로 API 쓰지 말 것. 파일이 크면 통째로 읽지 말고 Grep 으로 필요한 부분만 꺼내 쓰기.
## 카탈로그
| 파일 | 라이브러리 | 출처 | 갱신법 |
| ------------------- | ----------------------- | ------------------------------------------------------------------------------- | ------------------------------------ |
| `react-markdown.md` | react-markdown v10 | https://raw.githubusercontent.com/remarkjs/react-markdown/main/readme.md | curl 로 다시 받기 |
| `tauri-api.md` | @tauri-apps/api v2.11.1 | **패키지 실물** `node_modules/@tauri-apps/api/*.d.ts`·`*.js` 에서 직접 확인 | 버전 올라가면 `.d.ts` 다시 보고 갱신 |
| `tanstack-query.md` | TanStack Query v5 | https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries | 공식 문서에서 필요한 부분 갱신 |
| `axios.md` | Axios v1 | https://axios-http.com/docs/interceptors | 공식 문서에서 필요한 부분 갱신 |
| `react-router.md` | React Router | https://reactrouter.com/api/hooks/useRoutes | 공식 문서에서 필요한 부분 갱신 |
## 갱신법
출처 URL 을 curl 로 다시 받아 덮어쓰면 됨. 새 라이브러리 문서 추가하면 위 표에 한 줄 등록.
+17
View File
@@ -0,0 +1,17 @@
# Axios v1 인터셉터 참조
출처: https://axios-http.com/docs/interceptors
확인일: 2026-09-11
## 응답 인터셉터
2xx 밖 응답은 response interceptor의 reject handler로 들어옴. 오류를 처리하지 않을 때는 reject/throw로 다음 호출자에게 전달해야 함.
```ts
instance.interceptors.response.use(
(response) => response,
(error) => Promise.reject(error)
)
```
여러 response interceptor는 등록 순서(FIFO)로 실행됨.
+879
View File
@@ -0,0 +1,879 @@
<!--
Notes for maintaining this document:
* update the version of the link for `commonmark-html` once in a while
-->
# react-markdown
[![Build][badge-build-image]][badge-build-url]
[![Coverage][badge-coverage-image]][badge-coverage-url]
[![Downloads][badge-downloads-image]][badge-downloads-url]
[![Size][badge-size-image]][badge-size-url]
React component to render markdown.
## Feature highlights
- [x] **[safe][section-security] by default**
(no `dangerouslySetInnerHTML` or XSS attacks)
- [x] **[components][section-components]**
(pass your own component to use instead of `<h2>` for `## hi`)
- [x] **[plugins][section-plugins]**
(many plugins you can pick and choose from)
- [x] **[compliant][section-syntax]**
(100% to CommonMark, 100% to GFM with a plugin)
## Contents
- [What is this?](#what-is-this)
- [When should I use this?](#when-should-i-use-this)
- [Install](#install)
- [Use](#use)
- [API](#api)
- [`Markdown`](#markdown)
- [`MarkdownAsync`](#markdownasync)
- [`MarkdownHooks`](#markdownhooks)
- [`defaultUrlTransform(url)`](#defaulturltransformurl)
- [`AllowElement`](#allowelement)
- [`Components`](#components)
- [`ExtraProps`](#extraprops)
- [`HooksOptions`](#hooksoptions)
- [`Options`](#options)
- [`UrlTransform`](#urltransform)
- [Examples](#examples)
- [Use a plugin](#use-a-plugin)
- [Use a plugin with options](#use-a-plugin-with-options)
- [Use custom components (syntax highlight)](#use-custom-components-syntax-highlight)
- [Use remark and rehype plugins (math)](#use-remark-and-rehype-plugins-math)
- [Plugins](#plugins)
- [Syntax](#syntax)
- [Compatibility](#compatibility)
- [Architecture](#architecture)
- [Appendix A: HTML in markdown](#appendix-a-html-in-markdown)
- [Appendix B: Components](#appendix-b-components)
- [Appendix C: line endings in markdown (and JSX)](#appendix-c-line-endings-in-markdown-and-jsx)
- [Security](#security)
- [Related](#related)
- [Contribute](#contribute)
- [License](#license)
## What is this?
This package is a [React][] component that can be given a string of markdown
that itll safely render to React elements.
You can pass plugins to change how markdown is transformed and pass components
that will be used instead of normal HTML elements.
- to learn markdown, see this [cheatsheet and tutorial][commonmark-help]
- to try out `react-markdown`, see [our demo][github-io-react-markdown]
## When should I use this?
There are other ways to use markdown in React out there so why use this one?
The three main reasons are that they often rely on `dangerouslySetInnerHTML`,
have bugs with how they handle markdown, or dont let you swap elements for
components.
`react-markdown` builds a virtual DOM, so React only replaces what changed,
from a syntax tree.
Thats supported because we use [unified][github-unified],
specifically [remark][github-remark] for markdown and [rehype][github-rehype]
for HTML,
which are popular tools to transform content with plugins.
This package focusses on making it easy for beginners to safely use markdown in
React.
When youre familiar with unified, you can use a modern hooks based alternative
[`react-remark`][github-react-remark] or [`rehype-react`][github-rehype-react]
manually.
If you instead want to use JavaScript and JSX _inside_ markdown files, use
[MDX][github-mdx].
## Install
This package is [ESM only][esm].
In Node.js (version 16+), install with [npm][npm-install]:
```sh
npm install react-markdown
```
In Deno with [`esm.sh`][esmsh]:
```js
import Markdown from "https://esm.sh/react-markdown@10"
```
In browsers with [`esm.sh`][esmsh]:
```html
<script type="module">
import Markdown from "https://esm.sh/react-markdown@10?bundle"
</script>
```
## Use
A basic hello world:
```js
import React from "react"
import { createRoot } from "react-dom/client"
import Markdown from "react-markdown"
const markdown = "# Hi, *Pluto*!"
createRoot(document.body).render(<Markdown>{markdown}</Markdown>)
```
<details>
<summary>Show equivalent JSX</summary>
```js
<h1>
Hi, <em>Pluto</em>!
</h1>
```
</details>
Here is an example that shows how to use a plugin
([`remark-gfm`][github-remark-gfm],
which adds support for footnotes, strikethrough, tables, tasklists and
URLs directly):
```js
import React from "react"
import { createRoot } from "react-dom/client"
import Markdown from "react-markdown"
import remarkGfm from "remark-gfm"
const markdown = `Just a link: www.nasa.gov.`
createRoot(document.body).render(<Markdown remarkPlugins={[remarkGfm]}>{markdown}</Markdown>)
```
<details>
<summary>Show equivalent JSX</summary>
```js
<p>
Just a link: <a href="http://www.nasa.gov">www.nasa.gov</a>.
</p>
```
</details>
## API
This package exports the identifiers
[`MarkdownAsync`][api-markdown-async],
[`MarkdownHooks`][api-markdown-hooks],
and
[`defaultUrlTransform`][api-default-url-transform].
The default export is [`Markdown`][api-markdown].
It also exports the additional [TypeScript][] types
[`AllowElement`][api-allow-element],
[`Components`][api-components],
[`ExtraProps`][api-extra-props],
[`HooksOptions`][api-hooks-options],
[`Options`][api-options],
and
[`UrlTransform`][api-url-transform].
### `Markdown`
Component to render markdown.
This is a synchronous component.
When using async plugins,
see [`MarkdownAsync`][api-markdown-async] or
[`MarkdownHooks`][api-markdown-hooks].
###### Parameters
- `options` ([`Options`][api-options])
— props
###### Returns
React element (`ReactElement`).
### `MarkdownAsync`
Component to render markdown with support for async plugins
through async/await.
Components returning promises are supported on the server.
For async support on the client,
see [`MarkdownHooks`][api-markdown-hooks].
###### Parameters
- `options` ([`Options`][api-options])
— props
###### Returns
Promise to a React element (`Promise<ReactElement>`).
### `MarkdownHooks`
Component to render markdown with support for async plugins through hooks.
This uses `useEffect` and `useState` hooks.
Hooks run on the client and do not immediately render something.
For async support on the server,
see [`MarkdownAsync`][api-markdown-async].
###### Parameters
- `options` ([`Options`][api-options])
— props
###### Returns
React node (`ReactNode`).
### `defaultUrlTransform(url)`
Make a URL safe.
This follows how GitHub works.
It allows the protocols `http`, `https`, `irc`, `ircs`, `mailto`, and `xmpp`,
and URLs relative to the current protocol (such as `/something`).
###### Parameters
- `url` (`string`)
— URL
###### Returns
Safe URL (`string`).
### `AllowElement`
Filter elements (TypeScript type).
###### Parameters
- `node` ([`Element` from `hast`][github-hast-element])
— element to check
- `index` (`number | undefined`)
— index of `element` in `parent`
- `parent` ([`Node` from `hast`][github-hast-nodes])
— parent of `element`
###### Returns
Whether to allow `element` (`boolean`, optional).
### `Components`
Map tag names to components (TypeScript type).
###### Type
```ts
import type { ExtraProps } from "react-markdown"
import type { ComponentProps, ElementType } from "react"
type Components = {
[Key in Extract<ElementType, string>]?: ElementType<ComponentProps<Key> & ExtraProps>
}
```
### `ExtraProps`
Extra fields we pass to components (TypeScript type).
###### Fields
- `node` ([`Element` from `hast`][github-hast-element], optional)
— original node
### `HooksOptions`
Configuration for [`MarkdownHooks`][api-markdown-hooks] (TypeScript type);
extends the regular [`Options`][api-options] with a `fallback` prop.
###### Extends
[`Options`][api-options].
###### Fields
- `fallback` (`ReactNode`, optional)
— content to render while the processor processing the markdown
### `Options`
Configuration (TypeScript type).
###### Fields
- `allowElement` ([`AllowElement`][api-allow-element], optional)
— filter elements;
`allowedElements` / `disallowedElements` is used first
- `allowedElements` (`Array<string>`, default: all tag names)
— tag names to allow;
cannot combine w/ `disallowedElements`
- `children` (`string`, optional)
— markdown
- `components` ([`Components`][api-components], optional)
— map tag names to components
- `disallowedElements` (`Array<string>`, default: `[]`)
— tag names to disallow;
cannot combine w/ `allowedElements`
- `rehypePlugins` (`Array<Plugin>`, optional)
— list of [rehype plugins][github-rehype-plugins] to use
- `remarkPlugins` (`Array<Plugin>`, optional)
— list of [remark plugins][github-remark-plugins] to use
- `remarkRehypeOptions`
([`Options` from `remark-rehype`][github-remark-rehype-options],
optional)
— options to pass through to `remark-rehype`
- `skipHtml` (`boolean`, default: `false`)
— ignore HTML in markdown completely
- `unwrapDisallowed` (`boolean`, default: `false`)
— extract (unwrap) whats in disallowed elements;
normally when say `strong` is not allowed, it and its children are dropped,
with `unwrapDisallowed` the element itself is replaced by its children
- `urlTransform` ([`UrlTransform`][api-url-transform], default:
[`defaultUrlTransform`][api-default-url-transform])
— change URLs
### `UrlTransform`
Transform URLs (TypeScript type).
###### Parameters
- `url` (`string`)
— URL
- `key` (`string`, example: `'href'`)
— property name
- `node` ([`Element` from `hast`][github-hast-element])
— element to check
###### Returns
Transformed URL (`string`, optional).
## Examples
### Use a plugin
This example shows how to use a remark plugin.
In this case, [`remark-gfm`][github-remark-gfm],
which adds support for strikethrough, tables, tasklists and URLs directly:
```js
import React from "react"
import { createRoot } from "react-dom/client"
import Markdown from "react-markdown"
import remarkGfm from "remark-gfm"
const markdown = `A paragraph with *emphasis* and **strong importance**.
> A block quote with ~strikethrough~ and a URL: https://reactjs.org.
* Lists
* [ ] todo
* [x] done
A table:
| a | b |
| - | - |
`
createRoot(document.body).render(<Markdown remarkPlugins={[remarkGfm]}>{markdown}</Markdown>)
```
<details>
<summary>Show equivalent JSX</summary>
```js
<>
<p>
A paragraph with <em>emphasis</em> and <strong>strong importance</strong>.
</p>
<blockquote>
<p>
A block quote with <del>strikethrough</del> and a URL:{" "}
<a href="https://reactjs.org">https://reactjs.org</a>.
</p>
</blockquote>
<ul className="contains-task-list">
<li>Lists</li>
<li className="task-list-item">
<input type="checkbox" disabled /> todo
</li>
<li className="task-list-item">
<input type="checkbox" disabled checked /> done
</li>
</ul>
<p>A table:</p>
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
</table>
</>
```
</details>
### Use a plugin with options
This example shows how to use a plugin and give it options.
To do that, use an array with the plugin at the first place, and the options
second.
[`remark-gfm`][github-remark-gfm] has an option to allow only double tildes for
strikethrough:
```js
import React from "react"
import { createRoot } from "react-dom/client"
import Markdown from "react-markdown"
import remarkGfm from "remark-gfm"
const markdown = "This ~is not~ strikethrough, but ~~this is~~!"
createRoot(document.body).render(
<Markdown remarkPlugins={[[remarkGfm, { singleTilde: false }]]}>{markdown}</Markdown>
)
```
<details>
<summary>Show equivalent JSX</summary>
```js
<p>
This ~is not~ strikethrough, but <del>this is</del>!
</p>
```
</details>
### Use custom components (syntax highlight)
This example shows how you can overwrite the normal handling of an element by
passing a component.
In this case, we apply syntax highlighting with the seriously super amazing
[`react-syntax-highlighter`][github-react-syntax-highlighter] by
[**@conorhastings**][github-conorhastings]:
<!-- To do: currently broken on actual ESM; lets find an alternative? -->
```js
import React from "react"
import { createRoot } from "react-dom/client"
import Markdown from "react-markdown"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { dark } from "react-syntax-highlighter/dist/esm/styles/prism"
// Did you know you can use tildes instead of backticks for code in markdown? ✨
const markdown = `Here is some JavaScript code:
~~~js
console.log('It works!')
~~~
`
createRoot(document.body).render(
<Markdown
children={markdown}
components={{
code(props) {
const { children, className, node, ...rest } = props
const match = /language-(\w+)/.exec(className || "")
return match ? (
<SyntaxHighlighter
{...rest}
PreTag="div"
children={String(children).replace(/\n$/, "")}
language={match[1]}
style={dark}
/>
) : (
<code {...rest} className={className}>
{children}
</code>
)
},
}}
/>
)
```
<details>
<summary>Show equivalent JSX</summary>
```js
<>
<p>Here is some JavaScript code:</p>
<pre>
<SyntaxHighlighter
language="js"
style={dark}
PreTag="div"
children="console.log('It works!')"
/>
</pre>
</>
```
</details>
### Use remark and rehype plugins (math)
This example shows how a syntax extension
(through [`remark-math`][github-remark-math])
is used to support math in markdown, and a transform plugin
([`rehype-katex`][github-rehype-katex]) to render that math.
```js
import React from "react"
import { createRoot } from "react-dom/client"
import Markdown from "react-markdown"
import rehypeKatex from "rehype-katex"
import remarkMath from "remark-math"
import "katex/dist/katex.min.css" // `rehype-katex` does not import the CSS for you
const markdown = `The lift coefficient ($C_L$) is a dimensionless coefficient.`
createRoot(document.body).render(
<Markdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
{markdown}
</Markdown>
)
```
<details>
<summary>Show equivalent JSX</summary>
```js
<p>
The lift coefficient (
<span className="katex">
<span className="katex-mathml">
<math xmlns="http://www.w3.org/1998/Math/MathML">{/* … */}</math>
</span>
<span className="katex-html" aria-hidden="true">
{/* … */}
</span>
</span>
) is a dimensionless coefficient.
</p>
```
</details>
## Plugins
We use [unified][github-unified],
specifically [remark][github-remark] for markdown and
[rehype][github-rehype] for HTML,
which are tools to transform content with plugins.
Here are three good ways to find plugins:
- [`awesome-remark`][github-awesome-remark] and
[`awesome-rehype`][github-awesome-rehype]
— selection of the most awesome projects
- [List of remark plugins][github-remark-plugins] and
[list of rehype plugins][github-rehype-plugins]
— list of all plugins
- [`remark-plugin`][github-topic-remark-plugin] and
[`rehype-plugin`][github-topic-rehype-plugin] topics
— any tagged repo on GitHub
## Syntax
`react-markdown` follows CommonMark, which standardizes the differences between
markdown implementations, by default.
Some syntax extensions are supported through plugins.
We use [`micromark`][github-micromark] under the hood for our parsing.
See its documentation for more information on markdown, CommonMark, and
extensions.
## Compatibility
Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
When we cut a new major release, we drop support for unmaintained versions of
Node.
This means we try to keep the current release line, `react-markdown@10`,
compatible with Node.js 16.
They work in all modern browsers (essentially: everything not IE 11).
You can use a bundler (such as esbuild, webpack, or Rollup) to use this package
in your project, and use its options (or plugins) to add support for legacy
browsers.
## Architecture
<pre><code> react-markdown
+----------------------------------------------------------------------------------------------------------------+
| |
| +----------+ +----------------+ +---------------+ +----------------+ +------------+ |
| | | | | | | | | | | |
<a href="https://commonmark.org">markdown</a>-+->+ <a href="https://github.com/remarkjs/remark">remark</a> +-<a href="https://github.com/syntax-tree/mdast">mdast</a>->+ <a href="https://github.com/remarkjs/remark/blob/main/doc/plugins.md">remark plugins</a> +-<a href="https://github.com/syntax-tree/mdast">mdast</a>->+ <a href="https://github.com/remarkjs/remark-rehype">remark-rehype</a> +-<a href="https://github.com/syntax-tree/hast">hast</a>->+ <a href="https://github.com/rehypejs/rehype/blob/main/doc/plugins.md">rehype plugins</a> +-<a href="https://github.com/syntax-tree/hast">hast</a>->+ <a href="#appendix-b-components">components</a> +-+->react elements
| | | | | | | | | | | |
| +----------+ +----------------+ +---------------+ +----------------+ +------------+ |
| |
+----------------------------------------------------------------------------------------------------------------+
</code></pre>
To understand what this project does, its important to first understand what
unified does: please read through the [`unifiedjs/unified`][github-unified]
readme
(the part until you hit the API section is required reading).
`react-markdown` is a unified pipeline — wrapped so that most folks dont need
to directly interact with unified.
The processor goes through these steps:
- parse markdown to mdast (markdown syntax tree)
- transform through remark (markdown ecosystem)
- transform mdast to hast (HTML syntax tree)
- transform through rehype (HTML ecosystem)
- render hast to React with components
## Appendix A: HTML in markdown
`react-markdown` typically escapes HTML (or ignores it, with `skipHtml`)
because it is dangerous and defeats the purpose of this library.
However, if you are in a trusted environment (you trust the markdown), and
can spare the bundle size (±60kb minzipped), then you can use
[`rehype-raw`][github-rehype-raw]:
```js
import React from "react"
import { createRoot } from "react-dom/client"
import Markdown from "react-markdown"
import rehypeRaw from "rehype-raw"
const markdown = `<div class="note">
Some *emphasis* and <strong>strong</strong>!
</div>`
createRoot(document.body).render(<Markdown rehypePlugins={[rehypeRaw]}>{markdown}</Markdown>)
```
<details>
<summary>Show equivalent JSX</summary>
```js
<div className="note">
<p>
Some <em>emphasis</em> and <strong>strong</strong>!
</p>
</div>
```
</details>
**Note**: HTML in markdown is still bound by how [HTML works in
CommonMark][commonmark-html].
Make sure to use blank lines around block-level HTML that again contains
markdown!
## Appendix B: Components
You can also change the things that come from markdown:
```js
<Markdown
components={{
// Map `h1` (`# heading`) to use `h2`s.
h1: "h2",
// Rewrite `em`s (`*like so*`) to `i` with a red foreground color.
em(props) {
const { node, ...rest } = props
return <i style={{ color: "red" }} {...rest} />
},
}}
/>
```
The keys in components are HTML equivalents for the things you write with
markdown (such as `h1` for `# heading`).
Normally, in markdown, those are: `a`, `blockquote`, `br`, `code`, `em`, `h1`,
`h2`, `h3`, `h4`, `h5`, `h6`, `hr`, `img`, `li`, `ol`, `p`, `pre`, `strong`, and
`ul`.
With [`remark-gfm`][github-remark-gfm],
you can also use `del`, `input`, `table`, `tbody`, `td`, `th`, `thead`, and `tr`.
Other remark or rehype plugins that add support for new constructs will also
work with `react-markdown`.
The props that are passed are what you probably would expect: an `a` (link) will
get `href` (and `title`) props, and `img` (image) an `src`, `alt` and `title`,
etc.
Every component will receive a `node`.
This is the original [`Element` from `hast`][github-hast-element] element being
turned into a React element.
## Appendix C: line endings in markdown (and JSX)
You might have trouble with how line endings work in markdown and JSX.
We recommend the following, which solves all line ending problems:
```js
// If you write actual markdown in your code, put your markdown in a variable;
// **do not indent markdown**:
const markdown = `
# This is perfect!
`
// Pass the value as an expression as an only child:
const result = <Markdown>{markdown}</Markdown>
```
👆 That works.
Read on for what doesnt and why that is.
You might try to write markdown directly in your JSX and find that it **does
not** work:
```js
<Markdown># Hi This is **not** a paragraph.</Markdown>
```
The is because in JSX the whitespace (including line endings) is collapsed to
a single space.
So the above example is equivalent to:
```js
<Markdown> # Hi This is **not** a paragraph. </Markdown>
```
Instead, to pass markdown to `Markdown`, you can use an expression:
with a template literal:
```js
<Markdown>{`
# Hi
This is a paragraph.
`}</Markdown>
```
Template literals have another potential problem, because they keep whitespace
(including indentation) inside them.
That means that the following **does not** turn into a heading:
```js
<Markdown>{`
# This is **not** a heading, its an indented code block
`}</Markdown>
```
## Security
Use of `react-markdown` is secure by default.
Overwriting `urlTransform` to something insecure will open you up to XSS
vectors.
Furthermore, the `remarkPlugins`, `rehypePlugins`, and `components` you use may
be insecure.
To make sure the content is completely safe, even after what plugins do,
use [`rehype-sanitize`][github-rehype-sanitize].
It lets you define your own schema of what is and isnt allowed.
## Related
- [`MDX`][github-mdx]
— JSX _in_ markdown
- [`remark-gfm`][github-remark-gfm]
— add support for GitHub flavored markdown support
- [`react-remark`][github-react-remark]
— hook based alternative
- [`rehype-react`][github-rehype-react]
— turn HTML into React elements
## Contribute
See [`contributing.md`][health-contributing] in [`remarkjs/.github`][health]
for ways to get started.
See [`support.md`][health-support] for ways to get help.
This project has a [code of conduct][health-coc].
By interacting with this repository, organization, or community you agree to
abide by its terms.
## License
[MIT][file-license] © [Espen Hovlandsdal][author]
[api-allow-element]: #allowelement
[api-components]: #components
[api-default-url-transform]: #defaulturltransformurl
[api-extra-props]: #extraprops
[api-hooks-options]: #hooksoptions
[api-markdown]: #markdown
[api-markdown-async]: #markdownasync
[api-markdown-hooks]: #markdownhooks
[api-options]: #options
[api-url-transform]: #urltransform
[author]: https://espen.codes/
[badge-build-image]: https://github.com/remarkjs/react-markdown/workflows/main/badge.svg
[badge-build-url]: https://github.com/remarkjs/react-markdown/actions
[badge-coverage-image]: https://img.shields.io/codecov/c/github/remarkjs/react-markdown.svg
[badge-coverage-url]: https://codecov.io/github/remarkjs/react-markdown
[badge-downloads-image]: https://img.shields.io/npm/dm/react-markdown.svg
[badge-downloads-url]: https://www.npmjs.com/package/react-markdown
[badge-size-image]: https://img.shields.io/bundlejs/size/react-markdown
[badge-size-url]: https://bundlejs.com/?q=react-markdown
[commonmark-help]: https://commonmark.org/help/
[commonmark-html]: https://spec.commonmark.org/0.31.2/#html-blocks
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
[esmsh]: https://esm.sh
[file-license]: license
[github-awesome-rehype]: https://github.com/rehypejs/awesome-rehype
[github-awesome-remark]: https://github.com/remarkjs/awesome-remark
[github-conorhastings]: https://github.com/conorhastings
[github-hast-element]: https://github.com/syntax-tree/hast#element
[github-hast-nodes]: https://github.com/syntax-tree/hast#nodes
[github-io-react-markdown]: https://remarkjs.github.io/react-markdown/
[github-mdx]: https://github.com/mdx-js/mdx/
[github-micromark]: https://github.com/micromark/micromark
[github-react-remark]: https://github.com/remarkjs/react-remark
[github-react-syntax-highlighter]: https://github.com/react-syntax-highlighter/react-syntax-highlighter
[github-rehype]: https://github.com/rehypejs/rehype
[github-rehype-katex]: https://github.com/remarkjs/remark-math/tree/main/packages/rehype-katex
[github-rehype-plugins]: https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins
[github-rehype-raw]: https://github.com/rehypejs/rehype-raw
[github-rehype-react]: https://github.com/rehypejs/rehype-react
[github-rehype-sanitize]: https://github.com/rehypejs/rehype-sanitize
[github-remark]: https://github.com/remarkjs/remark
[github-remark-gfm]: https://github.com/remarkjs/remark-gfm
[github-remark-math]: https://github.com/remarkjs/remark-math
[github-remark-plugins]: https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins
[github-remark-rehype-options]: https://github.com/remarkjs/remark-rehype#options
[github-topic-rehype-plugin]: https://github.com/topics/rehype-plugin
[github-topic-remark-plugin]: https://github.com/topics/remark-plugin
[github-unified]: https://github.com/unifiedjs/unified
[health]: https://github.com/remarkjs/.github
[health-coc]: https://github.com/remarkjs/.github/blob/main/code-of-conduct.md
[health-contributing]: https://github.com/remarkjs/.github/blob/main/contributing.md
[health-support]: https://github.com/remarkjs/.github/blob/main/support.md
[npm-install]: https://docs.npmjs.com/cli/install
[react]: http://reactjs.org
[section-components]: #appendix-b-components
[section-plugins]: #plugins
[section-security]: #security
[section-syntax]: #syntax
[typescript]: https://www.typescriptlang.org
+10
View File
@@ -0,0 +1,10 @@
# React Router route 정리 참고
출처: https://reactrouter.com/api/hooks/useRoutes
- `useRoutes(routes)``RouteObject[]`로 route 트리를 만들고 현재 URL과 맞는 element를 반환함.
- 부모 route의 `children`은 중첩 UI를 구성하며 부모 element의 `Outlet` 위치에 렌더됨.
- 선언한 route와 맞지 않는 URL은 마지막 `path: "*"` route에서 `Navigate`로 기본 화면에 보낼 수 있음.
- `Navigate``replace`는 잘못된 URL을 브라우저 방문 기록에 남기지 않을 때 사용함.
이 프로젝트에서는 `routes.tsx`가 유일한 route 트리이며, `PATHS`가 주소 문자열의 진실원천임.
+18
View File
@@ -0,0 +1,18 @@
# TanStack Query v5 참조
출처: https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries
확인일: 2026-09-11
## enabled와 초기 로딩
- `enabled: false`이고 캐시가 없으면 `status === "pending"`, `fetchStatus === "idle"`임.
- `isLoading``isPending && isFetching`이라 실제 첫 fetch 중일 때만 참임.
- 조건이 충족된 뒤 자동 fetch해야 하면 `enabled`에 조건을 넘김.
```tsx
const query = useQuery({
queryKey: ["todos", filter],
queryFn: () => fetchTodos(filter),
enabled: Boolean(filter),
})
```
+192
View File
@@ -0,0 +1,192 @@
# @tauri-apps/api v2 — 프론트가 쓸 표면 박제
**작성**: 2026-08-15 | **근거**: `specs/004-tauri-shell/research.md` R3
**여기 적힌 건 전부 `node_modules/@tauri-apps/api/` 의 실제 `.d.ts`·`.js` 에서 뽑은 것이다.** 공식 가이드 예제는 옛 버전이 섞여 있어 시그니처 근거로 안 쓴다.
**박제 기준 버전: 2.11.1** (`package.json``^2.11.1`)
우리가 쓰는 건 딱 두 개 — `invoke`(JS→Rust)와 `listen`(Rust→JS). 나머지 모듈(`window`, `menu`, `tray`, `path` …)은 **안 쓴다.** 창·트레이·핫키는 전부 Rust 쪽에서 하고, 프론트는 우리가 만든 `#[tauri::command]` 만 부른다.
---
## 1. `invoke` — JS → Rust
```ts
// node_modules/@tauri-apps/api/core.d.ts:127
declare function invoke<T>(cmd: string, args?: InvokeArgs, options?: InvokeOptions): Promise<T>
// core.d.ts:105
type InvokeArgs = Record<string, unknown> | number[] | ArrayBuffer | Uint8Array
```
```ts
import { invoke } from "@tauri-apps/api/core"
await invoke<Snippet[]>("snippets_list")
await invoke("paste_code", { text })
```
- **`Promise` 를 돌려준다** → 요청↔응답 짝맞춤이 공짜. `snippetBridge.ts``reqId`·`pending` Map 이 새 껍데기 경로에선 통째로 필요 없어지는 근거 (research R6)
- Rust 커맨드가 `Err(String)` 을 주면 **그 문자열로 reject** 된다 → 한글 오류 메시지가 그대로 `Error` 로 올라와 sonner 토스트까지 흐름 (`contracts/transport-mapping.md` §3)
- 인자 이름은 Rust 커맨드의 파라미터 이름과 맞아야 한다 (`paste_code(text: String)``{ text }`)
### 속을 보면
```js
// core.js:201
async function invoke(cmd, args = {}, options) {
return window.__TAURI_INTERNALS__.invoke(cmd, args, options)
}
```
**`window.__TAURI_INTERNALS__.invoke` 를 그대로 부른다.** 이게 아래 호스트 판별에서 중요해진다.
---
## 2. `listen` — Rust → JS
```ts
// event.d.ts:87
declare function listen<T>(
event: EventName,
handler: EventCallback<T>,
options?: Options
): Promise<UnlistenFn>
// event.d.ts:25,33,34
interface Event<T> {
event: string
id: number
payload: T
}
type EventCallback<T> = (event: Event<T>) => void
type UnlistenFn = () => void
```
```ts
import { listen } from "@tauri-apps/api/event"
const unlisten = await listen<BridgeMessage>("bridge", (e) => {
handle(e.payload) // ← 페이로드는 e.payload 안에 있다
})
```
### ⚠️ 함정 2개
**(1) 페이로드가 한 겹 더 들어있다.** WebView2 는 `event.data` 가 곧 메시지인데, Tauri 는 `Event<T>` 로 감싸서 `event.payload` 안에 있다. `bridgeNavigate.ts` 의 분기 본문을 그대로 재사용하려면 **transport 층에서 `e.payload` 를 벗겨서** 넘겨야 한다 (research R5 가 노린 "분기 로직 무변경"이 이 한 줄에 달림).
**(2) `listen` 은 async 다.** `Promise<UnlistenFn>` 을 돌려준다.
지금 `bridgeNavigate.ts``initBridgeNavigate()` 는 **동기 함수**고, WebView2 의 `addEventListener` 는 즉시 붙는다. Tauri 경로는 그렇지 않아서:
- `initBridgeNavigate()` 를 async 로 바꾸면 **호출부(앱 루트)가 바뀐다** → SC-001("통로 파일 바깥 변경 0줄") 위반
- 그래서 **transport 안에서 Promise 를 삼키고**(fire-and-forget) 밖에는 동기 시그니처를 유지하는 쪽이 맞다
- 대신 **리스너 붙기 전에 도착한 푸시는 놓친다.** 다행히 이 코드에는 이미 대비가 있다 — `lastPasteTarget` 스냅샷과 `pendingCaptureImage` 의 read-once 소비가 마운트 레이스를 막으려고 들어간 것이라, 같은 장치가 여기서도 먹는다
관련: `once`(1회), `emit`/`emitTo`(JS→Rust 이벤트) 도 있지만 **우리는 안 쓴다.** 계약상 JS→Rust 는 전부 `invoke` (transport-mapping §1).
---
## 3. 호스트 판별 — `isTauri()` 를 쓰지 말 것
패키지가 공식 함수를 하나 주긴 한다:
```js
// core.js:278
function isTauri() {
return !!(globalThis || window).isTauri
}
```
**이건 `window.isTauri` 라는 별개 플래그를 볼 뿐, `invoke` 가 실제로 쓰는 `__TAURI_INTERNALS__` 와 다르다.**
research R4 가 정한 `__TAURI_INTERNALS__` 기준이 맞다 — `invoke` 가 바로 그걸 부르므로, **"invoke 를 부를 수 있는가"를 직접 재는 셈**이라 한 단계 더 정확하다.
### 그런데 `"__TAURI_INTERNALS__" in window` 도 부족하다 ⚠️
테스트용 `clearMocks()` 를 뜯어보면:
```js
// mocks.js:4 — mockIPC 가 부르는 것
function mockInternals() {
window.__TAURI_INTERNALS__ = window.__TAURI_INTERNALS__ ?? {}
window.__TAURI_EVENT_PLUGIN_INTERNALS__ = window.__TAURI_EVENT_PLUGIN_INTERNALS__ ?? {}
}
// mocks.js:267 — clearMocks
function clearMocks() {
if (typeof window.__TAURI_INTERNALS__ !== "object") return
delete window.__TAURI_INTERNALS__.invoke
delete window.__TAURI_INTERNALS__.transformCallback
// ... 속성만 지운다. window.__TAURI_INTERNALS__ 객체 자체는 안 지움
}
```
**`clearMocks()` 는 속성만 지우고 빈 객체를 남긴다.** 그래서:
| 판별식 | mockIPC 후 | clearMocks 후 | 판정 |
| ---------------------------------------------------------- | ---------- | ---------------- | ----------------------------------------- |
| `"__TAURI_INTERNALS__" in window` | true | **true (틀림)** | ❌ 브라우저 모드 테스트가 tauri 로 오판됨 |
| `typeof window.__TAURI_INTERNALS__?.invoke === "function"` | true | **false (맞음)** | ✅ |
`in` 으로 재면 "브라우저에서는 조용히 no-op / 즉시 reject"(FR-003) 테스트가 **tauri 경로를 타서 `__TAURI_INTERNALS__.invoke is not a function` 으로 터진다.** 우리가 의도한 한글 "데스크톱 전용" reject 가 아니라 엉뚱한 TypeError.
**결론: `.invoke` 가 함수인지로 판별한다.** 실제 앱에서도 Tauri 가 앱 스크립트보다 먼저 내부 객체를 통째로 주입하므로 안전하고, 의미도 더 정확하다("통로가 있나"가 아니라 "통로가 작동하나").
---
## 4. 테스트 — 공식 mock 이 있다
```ts
// mocks.d.ts
export declare function mockIPC(
cb: (cmd: string, payload?: InvokeArgs) => unknown,
options?: MockIPCOptions
): void
export declare function clearMocks(): void
export declare function mockWindows(current: string, ..._additionalWindows: string[]): void
export declare function mockConvertFileSrc(osName: string): void
export interface MockIPCOptions {
shouldMockEvents?: boolean
} // 2.7.0+
```
```ts
import { mockIPC, clearMocks } from "@tauri-apps/api/mocks"
afterEach(() => clearMocks())
it("snippets_list 를 부른다", async () => {
mockIPC((cmd) => (cmd === "snippets_list" ? [] : undefined))
await expect(snippetsApi.list()).resolves.toEqual([])
})
```
**`shouldMockEvents: true` 를 주면 `listen`/`emit` 도 mock 된다** — Rust 푸시(`emit("bridge", ...)`)를 흉내내 `bridgeNavigate` 분기를 테스트할 수 있다.
```ts
mockIPC(() => {}, { shouldMockEvents: true })
// 이제 emit('bridge', {...}) 하면 listen 핸들러가 불림
```
> ⚠️ 주의: `plugin:event|` 로 시작하는 invoke 를 **전부 가로챈다**(mocks.js:89). `shouldMockEvents` 를 켠 테스트에서는 이벤트 관련 invoke 가 우리 mock 콜백에 안 온다.
### 세 호스트 테스트할 때 (T012)
`mockIPC`**테스트 본문에서 런타임에** `window.__TAURI_INTERNALS__` 를 심는다. 기존 브릿지 테스트가 `window.chrome` 을 런타임에 심는 것과 똑같은 방식이다.
**transport 가 모듈 로드 시점에 호스트를 상수로 굳히면 이 mock 들이 전부 안 먹는다.** research R4 의 "한 번 판별하고 굳힌다"를 문자 그대로 구현하면 테스트 3개 + 신규 테스트가 다 깨진다. 판별 시점을 **최초 사용 시 1회**로 하거나, 굳히되 테스트용 재판별 훅을 같이 내야 한다 (`specs/004-tauri-shell/tasks.md` T015 가 이 결정을 먼저 하라고 박아둔 이유).
`clearMocks()` 를 쓸 거면 위 §3 의 `.invoke` 판별식이 **필수**다.
---
## 5. 안 쓰는 것
패키지에 이만큼 더 있지만 이 기능에서는 **안 쓴다** — 창·트레이·핫키·경로는 전부 Rust 담당이고, 프론트는 우리 커맨드만 부른다 (`contracts/transport-mapping.md`).
`window`, `webviewWindow`, `webview`, `menu`, `tray`, `path`, `app`, `dpi`, `image`
혹시 쓰게 되면 **`capabilities/default.json` 에 권한을 추가해야 한다.** 지금은 `core:default` 하나뿐이라 그것들은 **런타임에 조용히 거부**된다 (컴파일 에러 안 남). 자세한 건 `4_rust_tauri/docs-lib/tauri-v2.md` §9.