Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
39 changes: 37 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
{
"extends": ["eslint:recommended", "next"],
"root": true,
"overrides": [
{
"files": ["**/*.{mjs,js,jsx,ts,tsx}"],
"extends": ["plugin:prettier/recommended"],
"env": { "node": true, "es6": true }
"plugins": ["import"],
"env": { "node": true, "es6": true },
"rules": {
"import/order": [
"warn",
{
"groups": [
"builtin",
"external",
"internal",
"sibling",
"parent",
"index",
"type"
]
}
]
}
},
{
"files": ["**/**.test.{js,jsx,ts,tsx}"],
Expand All @@ -13,7 +31,12 @@
},
{
"files": ["**/*.{ts,tsx}"],
"globals": { "globalThis": false }
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"globals": { "globalThis": false },
"rules": {
"@typescript-eslint/consistent-type-imports": "error"
}
},
{
"files": ["**/*.tsx"],
Expand All @@ -24,6 +47,7 @@
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"consistent-return": "off",
"react/destructuring-assignment": ["warn", "never"],
Comment thread
ovflowd marked this conversation as resolved.
Outdated
"react/function-component-definition": [
"error",
{
Expand All @@ -34,6 +58,17 @@
"react/jsx-filename-extension": [
2,
{ "extensions": [".js", ".jsx", ".ts", ".tsx"] }
],
"no-restricted-syntax": [
"error",
{
"selector": "ImportDeclaration[source.value='react'][specifiers.0.type='ImportDefaultSpecifier']",
"message": "Default React import not allowed since we use the TypeScript jsx-transform. If you need a global type that collides with a React named export (such as `MouseEvent`), try using `globalThis.MouseHandler`"
},
{
"selector": "ImportDeclaration[source.value='react'] :matches(ImportNamespaceSpecifier)",
"message": "Named * React import is not allowed. Please import what you need from React with Named Imports"
}
]
}
},
Expand Down
5 changes: 4 additions & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Default rules
* @nodejs/website

- @nodejs/website

# Node.js Release Blog Posts

/pages/en/blog/release @nodejs/releasers
/pages/en/blog/announcements @nodejs/releasers
128 changes: 124 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Thank you for your interest in contributing to the Node.js Website. Before you p
- [Code of Conduct](https://github.com/nodejs/node/blob/HEAD/CODE_OF_CONDUCT.md)
- [Getting started](#getting-started)
- [Vocabulary](#vocabulary)
- [Creating Components](#creating-components)
- [Commit message guidelines](#commit-guidelines)
- [Unit Tests and Storybooks](#unit-tests-and-storybooks)
- [Pull Request Policy](#pull-request-policy)
- [Before merging](#before-merging)
- [When merging](#when-merging)
Expand Down Expand Up @@ -101,6 +103,63 @@ We also offer other commands that offer you assistance during your local develop
- `npm run storybook` starts Storybook's local server
- `npm run build-storybook` builds Storybook as a static web application for publishing

## Creating Components

The Node.js Website uses **React.js** as a Frontend Framework for the development of the Website. React allows us to create user interfaces with a modern take on Web Development.
Comment thread
ovflowd marked this conversation as resolved.
Outdated

If you're unfamiliar with React or Web Development in general, we encourage a read before taking on complex issues and tasks as this repository is **not for educational purposes** and we expect you to have a basic understanding of the technologies used.

We also recommend getting familiar with technologies such as [Next.js][], [MDX][], [SCSS][] and "concepts" such as "CSS Modules" and "CSS-in-JS".

### Best Practices when creating a Component

- All React Components should be placed within the `components` folder.
- Each Component should be placed whenever possible within a sub-folder, which we call the "Domain" of the Component
- The domain is the representation of where these Components belong to or where will be used.
- For example, Components used within Article Pages or that are part of the structure of an Article or the Article Layouts, should be placed within `components/Article`
- Each component should have its own folder with the name of the Component
- The structure of each component folder follows the following template:
```text
- ComponentName
- index.tsx // the component itself
- index.module.css // all styles of the component are placed there
Comment thread
ovflowd marked this conversation as resolved.
Outdated
- index.stories.tsx // component Storybook stories
- __tests__ // component tests (such as unit tests, etc)
- index.test.tsx
```
- React Hooks belonging to a single Component should be placed within the Component's folder
- If the Hook as a wider usability or can be used by other Components, then it should be placed at the root `hooks` folder.
- If the Component has "sub-components" they should follow the same philosophy as the Component itself.
- For example, if the Component `ComponentName` has a sub-component called `SubComponentName`, then it should be placed within `ComponentName/SubComponentName`

#### How a new Component should look like when freshly created

```tsx
import styles from './index.module.scss';
import type { FC } from 'react';

type MyComponentProps = {}; // The types of the Props of your Component

const MyComponent: FC<MyComponentProps> = props => (
// Actual code of my Component
);

export default MyComponent;
```

### Best practices for Component development in general

- Avoid spreading props `{ ... }` on the definition of the Component
- Avoid importing `React`, only import the modules from React that you need
- When importing types use `import type { NameOfImport } from 'module'`
- When defining a Component use the `FC` type from React to define the type of the Component
- When using `children` as a prop, use the `FC<PropsWithChildren<MyComponentProps>>` type instead
- Alterenatively you can define your type as `type MyComponentProps = PropsWithChildren<{ my other props}>`
- Each Props type should be prefixed by the name of the Component
- Components should always be the `default` export of a React Component file
- Avoid using DOM/Web APIs/`document`/`window` API access within a React Component. Use utilities or Hooks when you need a Reactive state
- Avoid making your Component too big. Desconstruct i into smaller Components/Hooks whenever possible
Comment thread
ovflowd marked this conversation as resolved.
Outdated

## Commit Guidelines

This project follows the [Conventional Commits][] specification.
Expand All @@ -114,6 +173,58 @@ Commits should be signed. You can read more about [Commit Signing][] here.
- Commit messages **must** start with a capital letter
- Commit messages **must not** end with a period `.`

## Unit Tests and Storybooks

Each new feature or bug fix should be accompanied by a unit test (when deemed valuable). We use [Jest][] as our test runner and [React Testing Library][] for our React unit tests.

We also use [Storybook][] to document our components. Each component should have a storybook story that documents the component's usage.

### General Guidelines for Unit Tests

Unit Tests are fundamental to ensure that code changes do not disrupt the functionalities of the Node.js Website:

- We recommend that unit tests are added for content covering `util`, `scripts` and `components` whenever possible.
Comment thread
ovflowd marked this conversation as resolved.
Outdated
- Unit Tests should cover that the functionality of a given change is working as expected.
- When creating unit tests for React components, we recommend that the tests cover all the possible states of the component.
- We also recommend mocking external dependencies, if unsure about how to mock a certain dependency, raise the question on your Pull Request.
- We recommend using [Jest's Mock Functions](https://jestjs.io/docs/en/mock-functions) for mocking dependencies.
- We recommend using [Jest's Mock Modules](https://jestjs.io/docs/en/manual-mocks) for mocking dependencies that are not available on the Node.js runtime.
- Common Providers and Contexts from the lifecycle of our App, such as [`react-intl`][] should not be mocked but given an empty or fake context whenever possible.
- We recommend reading previous unit tests from the codebase for inspiration and code guidelines.

### General Guidelines for Storybooks

Storybooks are an essential part of our development process. They help us to document our components and to ensure that the components are working as expected.

They also allow Developers to preview Components and be able to test them manually/individually to the smallest unit of the Application. (The individual Component itself).

**Storybooks should be fully typed and follow the following template:**

```tsx
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
import NameOfComponent from './index';

type Story = StoryObj<typeof NameOfComponent>;
type Meta = MetaObj<typeof NameOfComponent>;

export const Default: Story = {
// If the component has any props that are interactable, they should be passed here
// We recommend reading Storybook docs for args: https://storybook.js.org/docs/react/writing-stories/args
args: {},
Comment thread
ovflowd marked this conversation as resolved.
Outdated
};

// If the Component has more than one State/Layout/Variant, there should be one Story for each variant
export const AnotherStory: Story = {
args: {},
};

export default { component: NameOfComponent } as Meta;
```

- Stories should have `args` whenever possible, we want to be able to test the different aspects of a Component
- Please follow the template above to keep the Storybooks as consistent as possible
- We recommend reading previous Storybooks from the codebase for inspiration and code guidelines.

## Pull Request Policy

### Before merging
Expand Down Expand Up @@ -159,21 +270,23 @@ More details about Collaboration can be found in the [COLLABORATOR_GUIDE.md](./C
## Developer's Certificate of Origin 1.1

```

By contributing to this project, I certify that:

* (a) The contribution was created in whole or in part by me and I have the right to
- (a) The contribution was created in whole or in part by me and I have the right to
submit it under the open source license indicated in the file; or
* (b) The contribution is based upon previous work that, to the best of my knowledge,
- (b) The contribution is based upon previous work that, to the best of my knowledge,
is covered under an appropriate open source license and I have the right under that
license to submit that work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am permitted to submit under a
different license), as indicated in the file; or
* (c) The contribution was provided directly to me by some other person who certified
- (c) The contribution was provided directly to me by some other person who certified
(a), (b) or (c) and I have not modified it.
* (d) I understand and agree that this project and the contribution are public and that
- (d) I understand and agree that this project and the contribution are public and that
a record of the contribution (including all personal information I submit with it,
including my sign-off) is maintained indefinitely and may be redistributed consistent
with this project or the open source license(s) involved.

```

## Remarks
Expand All @@ -183,3 +296,10 @@ If something is missing here, or you feel something is not well described, feel
[`squash`]: https://help.github.com/en/articles/about-pull-request-merges#squash-and-merge-your-pull-request-commits
[Conventional Commits]: https://www.conventionalcommits.org/
[Commit Signing]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits
[Jest]: https://jestjs.io/
[React Testing Library]: https://testing-library.com/docs/react-testing-library/intro/
[Storybook]: https://storybook.js.org/
[`react-intl`]: https://formatjs.io/docs/react-intl/
[Next.js]: https://nextjs.org/
[MDX]: https://mdxjs.com/
[SCSS]: https://sass-lang.com/
9 changes: 5 additions & 4 deletions components/AnchoredHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ type AnchorHeadingProps = PropsWithChildren<{
// so we can just use '-- --' to quote the anchor name inside it.
const COMMENT_FOR_HEADANCHOR = /--\x20?([\w\x20-]+)\x20?--/;

const AnchoredHeading = ({ children, level, id }: AnchorHeadingProps) => {
const HeadingLevelTag = `h${level}` as any;
const AnchoredHeading = (props: AnchorHeadingProps) => {
const HeadingLevelTag = `h${props.level}` as any;

let sanitizedId =
id ?? children?.toLocaleString().toLocaleLowerCase().replace(/\x20/g, '-');
props.id ??
props.children?.toLocaleString().toLocaleLowerCase().replace(/\x20/g, '-');

if (sanitizedId) {
const foundAnchorAndTitle = COMMENT_FOR_HEADANCHOR.exec(sanitizedId);
Expand All @@ -46,7 +47,7 @@ const AnchoredHeading = ({ children, level, id }: AnchorHeadingProps) => {

return (
<HeadingLevelTag id={sanitizedId}>
{children}
{props.children}
<a
id={`header-${sanitizedId}`}
className="anchor"
Expand Down
8 changes: 6 additions & 2 deletions components/Article/Alert/index.stories.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import Alert from './index';
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

export default { component: Alert };
type Story = StoryObj<typeof Alert>;
type Meta = MetaObj<typeof Alert>;

export const Default = {
export const Default: Story = {
args: {
children: 'This is an alert',
},
};

export default { component: Alert } as Meta;
6 changes: 3 additions & 3 deletions components/Article/Alert/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import styles from './index.module.scss';
import type { FC, PropsWithChildren } from 'react';

const Alert = ({ children }: React.PropsWithChildren) => (
<div className={styles.alert}>{children}</div>
const Alert: FC<PropsWithChildren> = props => (
<div className={styles.alert}>{props.children}</div>
);

export default Alert;
10 changes: 7 additions & 3 deletions components/Article/BlockQuote/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import BlockQuote from './index';
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

export default { component: BlockQuote };
type Story = StoryObj<typeof BlockQuote>;
type Meta = MetaObj<typeof BlockQuote>;

export const Default = {
export const Default: Story = {
args: {
children: 'This is a block quote',
},
};

export const MultipleParagraph = {
export const MultipleParagraph: Story = {
args: {
children: [
<p key={1}>This is a block quote 1</p>,
<p key={2}>This is a block quote 2</p>,
],
},
};

export default { component: BlockQuote } as Meta;
6 changes: 3 additions & 3 deletions components/Article/BlockQuote/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { PropsWithChildren } from 'react';
import styles from './index.module.scss';
import type { FC, PropsWithChildren } from 'react';

const BlockQuote = ({ children }: PropsWithChildren) => (
<div className={styles.blockQuote}>{children}</div>
const BlockQuote: FC<PropsWithChildren> = props => (
<div className={styles.blockQuote}>{props.children}</div>
);

export default BlockQuote;
4 changes: 1 addition & 3 deletions components/Article/DataTag/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { render } from '@testing-library/react';
import React from 'react';

import DataTag from '..';
import DataTag from '../index';

describe('Data Tag component', () => {
it(`renders with red background color when tag is 'E'`, () => {
Expand Down
9 changes: 5 additions & 4 deletions components/Article/DataTag/index.stories.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { StoryObj } from '@storybook/react';
import DataTag from '.';

export default { component: DataTag };
import DataTag from './index';
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

type Story = StoryObj<typeof DataTag>;
type Meta = MetaObj<typeof DataTag>;

export const Red: Story = {
args: {
Expand All @@ -22,3 +21,5 @@ export const Blue: Story = {
tag: 'M',
},
};

export default { component: DataTag } as Meta;
12 changes: 5 additions & 7 deletions components/Article/DataTag/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import React from 'react';
import styles from './index.module.scss';
import type { FC } from 'react';

interface Props {
tag: 'E' | 'C' | 'M';
}
type DataTagProps = { tag: 'E' | 'C' | 'M' };

const DataTag = ({ tag }: Props) => (
<span className={styles.dataTag} data-tag={tag}>
{tag}
const DataTag: FC<DataTagProps> = props => (
<span className={styles.dataTag} data-tag={props.tag}>
{props.tag}
</span>
);

Expand Down
Loading