Browse Source

[Beta] Fix header IDs (#5128)

* Fix heading scripts

* [Beta] Fix header ID generation

* Fix headers

* Remove the Convention block breaking TOC

* Fix message
main
dan 2 years ago
committed by GitHub
parent
commit
54e6276f2f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 58
      beta/plugins/remark-header-custom-ids.js
  2. 2
      beta/scripts/headingIDHelpers/generateHeadingIDs.js
  3. 6
      beta/scripts/headingIDHelpers/validateHeadingIDs.js
  4. 25
      beta/src/components/MDX/Convention.tsx
  5. 2
      beta/src/components/MDX/MDXComponents.tsx
  6. 12
      beta/src/content/apis/react-dom/client/createRoot.md
  7. 12
      beta/src/content/apis/react-dom/client/hydrateRoot.md
  8. 2
      beta/src/content/apis/react/forwardRef.md
  9. 2
      beta/src/content/apis/react/memo.md
  10. 2
      beta/src/content/blog/2016/04/07/react-v15.md
  11. 8
      beta/src/content/blog/2019/08/08/react-v16.9.0.md
  12. 6
      beta/src/content/blog/2020/02/26/react-v16.13.0.md
  13. 16
      beta/src/content/learn/extracting-state-logic-into-a-reducer.md
  14. 4
      beta/src/content/learn/start-a-new-react-project.md
  15. 2
      beta/src/pages/[[...markdownPath]].js

58
beta/plugins/remark-header-custom-ids.js

@ -11,7 +11,7 @@
const toString = require('mdast-util-to-string');
const visit = require('unist-util-visit');
const slugs = require('github-slugger')();
const toSlug = require('github-slugger').slug;
function patch(context, key, value) {
if (!context[key]) {
@ -22,30 +22,50 @@ function patch(context, key, value) {
const svgIcon = `<svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>`;
module.exports = ({
icon = svgIcon,
className = `anchor`,
maintainCase = false,
} = {}) => {
slugs.reset();
module.exports = ({icon = svgIcon, className = `anchor`} = {}) => {
return function transformer(tree) {
const ids = new Set();
visit(tree, 'heading', (node) => {
const children = node.children;
let tail = children[children.length - 1];
// Generate slugs on the fly (even if not specified in markdown)
// so that it's possible to copy anchor links in newly written content.
let id = slugs.slug(toString(node), maintainCase);
// However, for committed docs, we'll extract slug from the headers.
if (tail && tail.type === 'text' && tail.value === '/}') {
tail = children[children.length - 2];
if (tail && tail.type === 'emphasis') {
// Use custom ID instead.
id = toString(tail);
let children = [...node.children];
let id;
if (children[children.length - 1].type === 'mdxTextExpression') {
// # My header {/*my-header*/}
id = children.pop().value;
const isValidCustomId = id.startsWith('/*') && id.endsWith('*/');
if (!isValidCustomId) {
throw Error(
'Expected header ID to be like: {/*some-header*/}. ' +
'Instead, received: ' +
id
);
}
id = id.slice(2, id.length - 2);
if (id !== toSlug(id)) {
throw Error(
'Expected header ID to be a valid slug. You specified: {/*' +
id +
'*/}. Replace it with: {/*' +
toSlug(id) +
'*/}'
);
}
} else {
// # My header
id = toSlug(toString(node));
}
const data = patch(node, 'data', {});
if (ids.has(id)) {
throw Error(
'Cannot have a duplicate header with id "' +
id +
'" on the page. ' +
'Rename the section or give it an explicit unique ID. ' +
'For example: #### Arguments {/*setstate-arguments*/}'
);
}
ids.add(id);
const data = patch(node, 'data', {});
patch(data, 'id', id);
patch(data, 'htmlAttributes', {});
patch(data, 'hProperties', {});

2
beta/scripts/headingIDHelpers/generateHeadingIDs.js

@ -82,7 +82,7 @@ function addHeaderIDs(lines) {
}
async function main(paths) {
paths = paths.length === 0 ? ['src/pages'] : paths;
paths = paths.length === 0 ? ['src/content'] : paths;
const [unifiedMod, remarkParseMod, remarkSlugMod] = await Promise.all([
import('unified'),

6
beta/scripts/headingIDHelpers/validateHeadingIDs.js

@ -17,9 +17,7 @@ function validateHeaderId(line) {
const match = /\{\/\*(.*?)\*\/}/.exec(line);
const id = match;
if (!id) {
console.error(
'Run yarn fix-headings to generate headings.'
);
console.error('Run yarn fix-headings to generate headings.');
process.exit(1);
}
}
@ -51,7 +49,7 @@ function validateHeaderIds(lines) {
* @param {Array<string>} paths
*/
async function main(paths) {
paths = paths.length === 0 ? ['src/pages'] : paths;
paths = paths.length === 0 ? ['src/content'] : paths;
const files = paths.map((path) => [...walk(path)]).flat();
files.forEach((file) => {

25
beta/src/components/MDX/Convention.tsx

@ -1,25 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import {H3} from './Heading';
interface ConventionProps {
children: React.ReactNode;
name: string;
}
function Convention({children, name}: ConventionProps) {
const id = name ? `${name}-conventions` : 'conventions';
return (
<section className="my-12">
<H3 id={id} className="text-2xl leading-9">
Conventions
</H3>
{children}
</section>
);
}
export default Convention;

2
beta/src/components/MDX/MDXComponents.tsx

@ -9,7 +9,6 @@ import cn from 'classnames';
import CodeBlock from './CodeBlock';
import {CodeDiagram} from './CodeDiagram';
import ConsoleBlock from './ConsoleBlock';
import Convention from './Convention';
import ExpandableCallout from './ExpandableCallout';
import ExpandableExample from './ExpandableExample';
import {H1, H2, H3, H4} from './Heading';
@ -356,7 +355,6 @@ export const MDXComponents = {
pre: CodeBlock,
CodeDiagram,
ConsoleBlock,
Convention,
DeepDive: (props: {
children: React.ReactNode;
title: string;

12
beta/src/content/apis/react-dom/client/createRoot.md

@ -284,16 +284,16 @@ React will display `<App />` in the `root`, and take over managing the DOM insid
[See examples above.](#usage)
#### Parameters {/*parameters*/}
#### Parameters {/*root-render-parameters*/}
* `reactNode`: A *React node* that you want to display. This will usually be a piece of JSX like `<App />`, but you can also pass a React element constructed with [`createElement()`](/apis/react/createElement), a string, a number, `null`, or `undefined`.
#### Returns {/*returns*/}
#### Returns {/*root-render-returns*/}
`root.render` returns `undefined`.
#### Caveats {/*caveats*/}
#### Caveats {/*root-render-caveats*/}
* The first time you call `root.render`, React will clear all the existing HTML content inside the React root before rendering the React component into it.
@ -318,16 +318,16 @@ This is mostly useful if your React root's DOM node (or any of its ancestors) ma
Calling `root.unmount` will unmount all the components in the root and "detach" React from the root DOM node, including removing any event handlers or state in the tree.
#### Parameters {/*parameters*/}
#### Parameters {/*root-unmount-parameters*/}
`root.unmount` does not accept any parameters.
#### Returns {/*returns*/}
#### Returns {/*root-unmount-returns*/}
`root.unmount` returns `undefined`.
#### Caveats {/*caveats*/}
#### Caveats {/*root-unmount-caveats*/}
* Calling `root.unmount` will unmount all the components in the tree and "detach" React from the root DOM node.

12
beta/src/content/apis/react-dom/client/hydrateRoot.md

@ -196,16 +196,16 @@ React will update `<App />` in the hydrated `root`.
[See examples above.](#usage)
#### Parameters {/*parameters*/}
#### Parameters {/*root-render-parameters*/}
* `reactNode`: A "React node" that you want to update. This will usually be a piece of JSX like `<App />`, but you can also pass a React element constructed with [`createElement()`](/apis/react/createElement), a string, a number, `null`, or `undefined`.
#### Returns {/*returns*/}
#### Returns {/*root-render-returns*/}
`root.render` returns `undefined`.
#### Caveats {/*caveats*/}
#### Caveats {/*root-render-caveats*/}
* If you call `root.render` before the root has finished hydrating, React will clear the existing server-rendered HTML content and switch the entire root to client rendering.
@ -226,16 +226,16 @@ This is mostly useful if your React root's DOM node (or any of its ancestors) ma
Calling `root.unmount` will unmount all the components in the root and "detach" React from the root DOM node, including removing any event handlers or state in the tree.
#### Parameters {/*parameters*/}
#### Parameters {/*root-unmount-parameters*/}
`root.unmount` does not accept any parameters.
#### Returns {/*returns*/}
#### Returns {/*root-unmount-returns*/}
`render` returns `null`.
#### Caveats {/*caveats*/}
#### Caveats {/*root-unmount-caveats*/}
* Calling `root.unmount` will unmount all the components in the tree and "detach" React from the root DOM node.

2
beta/src/content/apis/react/forwardRef.md

@ -579,7 +579,7 @@ const MyInput = forwardRef(function MyInput(props, ref) {
* `ref`: The `ref` attribute passed by the parent component. The `ref` can be an object or a function. If the parent component has not passed a ref, it will be `null`. You should either pass the `ref` you receive to another component, or pass it to [`useImperativeHandle`.](/apis/react/useImperativeHandle)
#### Returns {/*returns*/}
#### Returns {/*render-returns*/}
`forwardRef` returns a React component that you can render in JSX. Unlike React components defined as plain functions, the component returned by `forwardRef` is able to take a `ref` prop.

2
beta/src/content/apis/react/memo.md

@ -177,7 +177,7 @@ If you set a state variable to its current value, React will skip re-rendering y
---
### Updating a memoized component using a context {/*updating-a-memoized-component-using-state*/}
### Updating a memoized component using a context {/*updating-a-memoized-component-using-a-context*/}
Even when a component is memoized, it will still re-render when a context that it's using changes. Memoization only has to do with props that are passed to the component from its parent.

2
beta/src/content/blog/2016/04/07/react-v15.md

@ -85,7 +85,7 @@ If you can’t use `npm` yet, we provide pre-built browser builds for your conve
### Breaking changes {/*breaking-changes*/}
- #### No more extra `<span>`s
- #### No extra `<span>`s
It’s worth calling out the DOM structure changes above again, in particular the change from `<span>`s. In the course of updating the Facebook codebase, we found a very small amount of code that was depending on the markup that React generated. Some of these cases were integration tests like WebDriver which were doing very specific XPath queries to target nodes. Others were simply tests using `ReactDOM.renderToStaticMarkup` and comparing markup. Again, there were a very small number of changes that had to be made, but we don’t want anybody to be blindsided. We encourage everybody to run their test suites when upgrading and consider alternative approaches when possible. One approach that will work for some cases is to explicitly use `<span>`s in your `render` method.

8
beta/src/content/blog/2019/08/08/react-v16.9.0.md

@ -216,12 +216,12 @@ Refer to the documentation for [detailed installation instructions](/docs/instal
## Changelog {/*changelog*/}
### React {/*react*/}
### React {/*changelog-react*/}
- Add `<React.Profiler>` API for gathering performance measurements programmatically. ([@bvaughn](https://github.com/bvaughn) in [#15172](https://github.com/facebook/react/pull/15172))
- Remove `unstable_ConcurrentMode` in favor of `unstable_createRoot`. ([@acdlite](https://github.com/acdlite) in [#15532](https://github.com/facebook/react/pull/15532))
### React DOM {/*react-dom*/}
### React DOM {/*changelog-react-dom*/}
- Deprecate old names for the `UNSAFE_*` lifecycle methods. ([@bvaughn](https://github.com/bvaughn) in [#15186](https://github.com/facebook/react/pull/15186) and [@threepointone](https://github.com/threepointone) in [#16103](https://github.com/facebook/react/pull/16103))
- Deprecate `javascript:` URLs as a common attack surface. ([@sebmarkbage](https://github.com/sebmarkbage) in [#15047](https://github.com/facebook/react/pull/15047))
@ -238,11 +238,11 @@ Refer to the documentation for [detailed installation instructions](/docs/instal
- Fix hiding Suspense fallback nodes when there is an `!important` style. ([@acdlite](https://github.com/acdlite) in [#15861](https://github.com/facebook/react/pull/15861) and [#15882](https://github.com/facebook/react/pull/15882))
- Slightly improve hydration performance. ([@bmeurer](https://github.com/bmeurer) in [#15998](https://github.com/facebook/react/pull/15998))
### React DOM Server {/*react-dom-server*/}
### React DOM Server {/*changelog-react-dom-server*/}
- Fix incorrect output for camelCase custom CSS property names. ([@bedakb](https://github.com/bedakb) in [#16167](https://github.com/facebook/react/pull/16167))
### React Test Utilities and Test Renderer {/*react-test-utilities-and-test-renderer*/}
### React Test Utilities and Test Renderer {/*changelog-react-test-utilities-and-test-renderer*/}
- Add `act(async () => ...)` for testing asynchronous state updates. ([@threepointone](https://github.com/threepointone) in [#14853](https://github.com/facebook/react/pull/14853))
- Add support for nesting `act` from different renderers. ([@threepointone](https://github.com/threepointone) in [#16039](https://github.com/facebook/react/pull/16039) and [#16042](https://github.com/facebook/react/pull/16042))

6
beta/src/content/blog/2020/02/26/react-v16.13.0.md

@ -186,12 +186,12 @@ Refer to the documentation for [detailed installation instructions](/docs/instal
## Changelog {/*changelog*/}
### React {/*react*/}
### React {/*changelog-react*/}
- Warn when a string ref is used in a manner that's not amenable to a future codemod ([@lunaruan](https://github.com/lunaruan) in [#17864](https://github.com/facebook/react/pull/17864))
- Deprecate `React.createFactory()` ([@trueadm](https://github.com/trueadm) in [#17878](https://github.com/facebook/react/pull/17878))
### React DOM {/*react-dom*/}
### React DOM {/*changelog-react-dom*/}
- Warn when changes in `style` may cause an unexpected collision ([@sophiebits](https://github.com/sophiebits) in [#14181](https://github.com/facebook/react/pull/14181), [#18002](https://github.com/facebook/react/pull/18002))
- Warn when a function component is updated during another component's render phase ([@acdlite](<(https://github.com/acdlite)>) in [#17099](https://github.com/facebook/react/pull/17099))
@ -202,7 +202,7 @@ Refer to the documentation for [detailed installation instructions](/docs/instal
- Don't call `toString()` of `dangerouslySetInnerHTML` ([@sebmarkbage](https://github.com/sebmarkbage) in [#17773](https://github.com/facebook/react/pull/17773))
- Show component stacks in more warnings ([@gaearon](https://github.com/gaearon) in [#17922](https://github.com/facebook/react/pull/17922), [#17586](https://github.com/facebook/react/pull/17586))
### Concurrent Mode (Experimental) {/*concurrent-mode-experimental*/}
### Concurrent Mode (Experimental) {/*changelog-concurrent-mode-experimental*/}
- Warn for problematic usages of `ReactDOM.createRoot()` ([@trueadm](https://github.com/trueadm) in [#17937](https://github.com/facebook/react/pull/17937))
- Remove `ReactDOM.createRoot()` callback params and added warnings on usage ([@bvaughn](https://github.com/bvaughn) in [#17916](https://github.com/facebook/react/pull/17916))

16
beta/src/content/learn/extracting-state-logic-into-a-reducer.md

@ -268,9 +268,11 @@ function handleDeleteTask(taskId) {
It is a regular JavaScript object. You decide what to put in it, but generally it should contain the minimal information about _what happened_. (You will add the `dispatch` function itself in a later step.)
<Convention conventionFor="action objects">
<Note>
An action object can have any shape. By convention, it is common to give it a string `type` that describes what happened, and pass any additional information in other fields. The `type` is specific to a component, so in this example either `'added'` or `'added_task'` would be fine. Choose a name that says what happened!
An action object can have any shape.
By convention, it is common to give it a string `type` that describes what happened, and pass any additional information in other fields. The `type` is specific to a component, so in this example either `'added'` or `'added_task'` would be fine. Choose a name that says what happened!
```js
dispatch({
@ -280,7 +282,7 @@ dispatch({
});
```
</Convention>
</Note>
### Step 2: Write a reducer function {/*step-2-write-a-reducer-function*/}
@ -331,9 +333,11 @@ function tasksReducer(tasks, action) {
> Because the reducer function takes state (`tasks`) as an argument, you can **declare it outside of your component.** This decreases the indentation level and can make your code easier to read.
<Convention conventionFor="reducer functions">
<Note>
The code above uses if/else statements, but it's a convention to use [switch statements](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/switch) inside reducers. The result is the same, but it can be easier to read switch statements at a glance.
The code above uses if/else statements, but it's a convention to use [switch statements](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/switch) inside reducers. The result is the same, but it can be easier to read switch statements at a glance. We'll be using them throughout the rest of this documentation like so:
We'll be using them throughout the rest of this documentation like so:
```js
function tasksReducer(tasks, action) {
@ -371,7 +375,7 @@ We recommend to wrap each `case` block into the `{` and `}` curly braces so that
If you're not yet comfortable with switch statements, using if/else is completely fine.
</Convention>
</Note>
<DeepDive title="Why are reducers called this way?">

4
beta/src/content/learn/start-a-new-react-project.md

@ -50,7 +50,7 @@ For more information, [check out the official guide.](https://create-react-app.d
> Create React App doesn't handle backend logic or databases. You can use it with any backend. When you build a project, you'll get a folder with static HTML, CSS and JS. Because Create React App can't take advantage of the server, it doesn't provide the best performance. If you're looking for faster loading times and built-in features like routing and server-side logic, we recommend using a framework instead.
### Popular alternatives {/*popular-alternatives*/}
### Popular alternatives {/*toolkit-popular-alternatives*/}
* [Vite](https://vitejs.dev/guide/)
* [Parcel](https://parceljs.org/getting-started/webapp/)
@ -61,7 +61,7 @@ If you're looking to **start a production-ready project,** [Next.js](https://nex
The [Next.js Foundations](https://nextjs.org/learn/foundations/about-nextjs) tutorial is a great introduction to building with React and Next.js.
### Popular alternatives {/*popular-alternatives*/}
### Popular alternatives {/*framework-popular-alternatives*/}
* [Gatsby](https://www.gatsbyjs.org/)
* [Remix](https://remix.run/)

2
beta/src/pages/[[...markdownPath]].js

@ -55,7 +55,7 @@ function reviveNodeOnClient(key, val) {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~ IMPORTANT: BUMP THIS IF YOU CHANGE ANY CODE BELOW ~~~
const DISK_CACHE_BREAKER = 4;
const DISK_CACHE_BREAKER = 5;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Put MDX output into JSON for client.

Loading…
Cancel
Save