Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next.js: Add @storybook/experimental-nextjs-vite package #28800

Merged
merged 22 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2528064
Add @storybook/nextjs-vite package
valentinpalkovic Aug 3, 2024
c2d9ae3
Formatting
valentinpalkovic Aug 3, 2024
ed2586d
Add 'nextjs-vite' as a supported framework in helpers.ts
valentinpalkovic Aug 4, 2024
b10f944
Fix check step
valentinpalkovic Aug 4, 2024
33999ee
Cleanup
valentinpalkovic Aug 5, 2024
93b2e3c
Upgrade vite-plugin-storybook-nextjs
valentinpalkovic Aug 5, 2024
7bd22d4
Edit Next.js documentation
valentinpalkovic Aug 5, 2024
a25f79a
Cleanup
valentinpalkovic Aug 5, 2024
a983514
Docs updates
kylegach Aug 5, 2024
d57bba5
Rename nextjs-vite to experimental-nextjs-vite
valentinpalkovic Aug 6, 2024
e70b42f
Merge remote-tracking branch 'origin/next' into valentin/nextjs-vite
valentinpalkovic Aug 6, 2024
70193c9
Merge remote-tracking branch 'origin/next' into valentin/nextjs-vite
valentinpalkovic Aug 7, 2024
9f289c1
Bump version of @storybook/experimental-nextjs-vite
valentinpalkovic Aug 7, 2024
9fd8a14
Merge remote-tracking branch 'origin/next' into valentin/nextjs-vite
valentinpalkovic Aug 8, 2024
3257177
Fix test
valentinpalkovic Aug 8, 2024
23448ff
Use jiti instead of esbuild-register
valentinpalkovic Aug 8, 2024
3a3a480
Add webpackFinal -> viteFinal callout
kylegach Aug 8, 2024
ea34099
Merge remote-tracking branch 'origin/next' into valentin/nextjs-vite
valentinpalkovic Aug 9, 2024
f6ad2e9
Add experimental-nextjs-vite/default-ts to daily set of sandboxes
valentinpalkovic Aug 9, 2024
783f27c
Merge branch 'next' into valentin/nextjs-vite
valentinpalkovic Aug 12, 2024
0d222ab
Lint fixes
valentinpalkovic Aug 12, 2024
d1ed7ba
Fix nextjs/prerelease sandbox
valentinpalkovic Aug 12, 2024
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
1 change: 1 addition & 0 deletions code/core/src/common/utils/framework-to-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const frameworkToRenderer: Record<
'html-vite': 'html',
'html-webpack5': 'html',
nextjs: 'react',
'nextjs-vite': 'react',
'preact-vite': 'preact',
'preact-webpack5': 'preact',
qwik: 'qwik',
Expand Down
1 change: 1 addition & 0 deletions code/core/src/common/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default {
'@storybook/html-vite': '8.3.0-alpha.3',
'@storybook/html-webpack5': '8.3.0-alpha.3',
'@storybook/nextjs': '8.3.0-alpha.3',
'@storybook/nextjs-vite': '8.3.0-alpha.3',
'@storybook/preact-vite': '8.3.0-alpha.3',
'@storybook/preact-webpack5': '8.3.0-alpha.3',
'@storybook/react-vite': '8.3.0-alpha.3',
Expand Down
1 change: 1 addition & 0 deletions code/core/src/types/modules/frameworks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type SupportedFrameworks =
| 'html-vite'
| 'html-webpack5'
| 'nextjs'
| 'nextjs-vite'
| 'preact-vite'
| 'preact-webpack5'
| 'react-vite'
Expand Down
23 changes: 23 additions & 0 deletions code/frameworks/nextjs-vite/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"rules": {
"global-require": "off",
"no-param-reassign": "off",
"import/no-dynamic-require": "off",
"import/no-unresolved": "off"
},
"overrides": [
{
"files": ["**/*.stories.@(jsx|tsx)"],
"rules": {
"react/no-unknown-property": "off",
"jsx-a11y/anchor-is-valid": "off"
}
},
{
"files": ["**/*.compat.@(tsx|ts)"],
"rules": {
"local-rules/no-uncategorized-errors": "off"
}
}
]
}
10 changes: 10 additions & 0 deletions code/frameworks/nextjs-vite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Storybook for Next.js with Vite Builder

See [documentation](https://storybook.js.org/docs/get-started/frameworks/nextjs?renderer=react) for installation instructions, usage examples, APIs, and more.

## Acknowledgements

This framework borrows heavily from these Storybook addons:

- [storybook-addon-next](https://github.com/RyanClementsHax/storybook-addon-next) by [RyanClementsHax](https://github.com/RyanClementsHax/)
- [storybook-addon-next-router](https://github.com/lifeiscontent/storybook-addon-next-router) by [lifeiscontent](https://github.com/lifeiscontent)
143 changes: 143 additions & 0 deletions code/frameworks/nextjs-vite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"name": "@storybook/nextjs-vite",
"version": "8.3.0-alpha.3",
"description": "Storybook for Next.js and Vite",
"keywords": [
"storybook",
"nextjs",
"vite"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/nextjs-vite",
"bugs": {
"url": "https://github.com/storybookjs/storybook/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/storybookjs/storybook.git",
"directory": "code/frameworks/nextjs"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/storybook"
},
"license": "MIT",
"exports": {
".": {
"types": "./dist/index.d.ts",
"node": "./dist/index.js",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./preset": {
"types": "./dist/preset.d.ts",
"require": "./dist/preset.js"
},
"./dist/preview.mjs": "./dist/preview.mjs",
"./cache.mock": {
"types": "./dist/export-mocks/cache/index.d.ts",
"import": "./dist/export-mocks/cache/index.mjs",
"require": "./dist/export-mocks/cache/index.js"
},
"./headers.mock": {
"types": "./dist/export-mocks/headers/index.d.ts",
"import": "./dist/export-mocks/headers/index.mjs",
"require": "./dist/export-mocks/headers/index.js"
},
"./navigation.mock": {
"types": "./dist/export-mocks/navigation/index.d.ts",
"import": "./dist/export-mocks/navigation/index.mjs",
"require": "./dist/export-mocks/navigation/index.js"
},
"./router.mock": {
"types": "./dist/export-mocks/router/index.d.ts",
"import": "./dist/export-mocks/router/index.mjs",
"require": "./dist/export-mocks/router/index.js"
},
"./package.json": "./package.json"
},
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"typesVersions": {
"*": {
"*": [
"dist/index.d.ts"
],
"cache.mock": [
"dist/export-mocks/cache/index.d.ts"
],
"headers.mock": [
"dist/export-mocks/headers/index.d.ts"
],
"router.mock": [
"dist/export-mocks/router/index.d.ts"
],
"navigation.mock": [
"dist/export-mocks/navigation/index.d.ts"
]
}
},
"files": [
"dist/**/*",
"template/cli/**/*",
"README.md",
"*.js",
"*.d.ts",
"!src/**/*"
],
"scripts": {
"check": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/check.ts",
"prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/bundle.ts"
},
"dependencies": {
"@storybook/builder-vite": "workspace:*",
"@storybook/react": "workspace:*",
"@storybook/test": "workspace:*",
"styled-jsx": "5.1.6"
},
"devDependencies": {
"@types/node": "^18.0.0",
"next": "^14.2.5",
"typescript": "^5.3.2",
"vite-plugin-storybook-nextjs": "^1.0.0"
},
"peerDependencies": {
"next": "^14.2.5",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"storybook": "workspace:^",
"vite": "^5.0.0",
"vite-plugin-storybook-nextjs": "^1.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"optionalDependencies": {
"sharp": "^0.33.3"
},
"engines": {
"node": ">=18.0.0"
},
"publishConfig": {
"access": "public"
},
"bundler": {
"entries": [
"./src/index.ts",
"./src/preset.ts",
"./src/preview.tsx",
"./src/export-mocks/cache/index.ts",
"./src/export-mocks/headers/index.ts",
"./src/export-mocks/router/index.ts",
"./src/export-mocks/navigation/index.ts",
"./src/images/decorator.tsx"
],
"externals": [
"sb-original/image-context"
],
"platform": "node"
},
"gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16"
}
1 change: 1 addition & 0 deletions code/frameworks/nextjs-vite/preset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/preset');
8 changes: 8 additions & 0 deletions code/frameworks/nextjs-vite/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "nextjs-vite",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"targets": {
"build": {}
}
}
4 changes: 4 additions & 0 deletions code/frameworks/nextjs-vite/src/config/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { setConfig } from 'next/config';

// eslint-disable-next-line no-underscore-dangle
setConfig(process.env.__NEXT_RUNTIME_CONFIG);
23 changes: 23 additions & 0 deletions code/frameworks/nextjs-vite/src/export-mocks/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { fn } from '@storybook/test';

// biome-ignore lint/suspicious/noExplicitAny: <explanation>
type Callback = (...args: any[]) => Promise<any>;

// mock utilities/overrides (as of Next v14.2.0)
const revalidatePath = fn().mockName('next/cache::revalidatePath');
const revalidateTag = fn().mockName('next/cache::revalidateTag');
const unstable_cache = fn()
.mockName('next/cache::unstable_cache')
.mockImplementation((cb: Callback) => cb);
const unstable_noStore = fn().mockName('next/cache::unstable_noStore');

const cacheExports = {
unstable_cache,
revalidateTag,
revalidatePath,
unstable_noStore,
};

export default cacheExports;
export { unstable_cache, revalidateTag, revalidatePath, unstable_noStore };
38 changes: 38 additions & 0 deletions code/frameworks/nextjs-vite/src/export-mocks/headers/cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { fn } from '@storybook/test';
import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
// We need this import to be a singleton, and because it's used in multiple entrypoints
// both in ESM and CJS, importing it via the package name instead of having a local import
// is the only way to achieve it actually being a singleton
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore we must ignore types here as during compilation they are not generated yet
import { headers } from '@storybook/nextjs/headers.mock';

class RequestCookiesMock extends RequestCookies {
get = fn(super.get.bind(this)).mockName('next/headers::cookies().get');

getAll = fn(super.getAll.bind(this)).mockName('next/headers::cookies().getAll');

has = fn(super.has.bind(this)).mockName('next/headers::cookies().has');

set = fn(super.set.bind(this)).mockName('next/headers::cookies().set');

delete = fn(super.delete.bind(this)).mockName('next/headers::cookies().delete');
}

let requestCookiesMock: RequestCookiesMock;

export const cookies = fn(() => {
if (!requestCookiesMock) {
requestCookiesMock = new RequestCookiesMock(headers());
}
return requestCookiesMock;
}).mockName('next/headers::cookies()');

const originalRestore = cookies.mockRestore.bind(null);

// will be called automatically by the test loader
cookies.mockRestore = () => {
originalRestore();
headers.mockRestore();
requestCookiesMock = new RequestCookiesMock(headers());
};
39 changes: 39 additions & 0 deletions code/frameworks/nextjs-vite/src/export-mocks/headers/headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { fn } from '@storybook/test';

import { HeadersAdapter } from 'next/dist/server/web/spec-extension/adapters/headers';

class HeadersAdapterMock extends HeadersAdapter {
constructor() {
super({});
}

append = fn(super.append.bind(this)).mockName('next/headers::headers().append');

delete = fn(super.delete.bind(this)).mockName('next/headers::headers().delete');

get = fn(super.get.bind(this)).mockName('next/headers::headers().get');

has = fn(super.has.bind(this)).mockName('next/headers::headers().has');

set = fn(super.set.bind(this)).mockName('next/headers::headers().set');

forEach = fn(super.forEach.bind(this)).mockName('next/headers::headers().forEach');

entries = fn(super.entries.bind(this)).mockName('next/headers::headers().entries');

keys = fn(super.keys.bind(this)).mockName('next/headers::headers().keys');

values = fn(super.values.bind(this)).mockName('next/headers::headers().values');
}

let headersAdapterMock: HeadersAdapterMock;

export const headers = () => {
if (!headersAdapterMock) headersAdapterMock = new HeadersAdapterMock();
return headersAdapterMock;
};

// This fn is called by ./cookies to restore the headers in the right order
headers.mockRestore = () => {
headersAdapterMock = new HeadersAdapterMock();
};
13 changes: 13 additions & 0 deletions code/frameworks/nextjs-vite/src/export-mocks/headers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { fn } from '@storybook/test';
import * as originalHeaders from 'next/dist/client/components/headers';

// re-exports of the actual module
export * from 'next/dist/client/components/headers';

// mock utilities/overrides (as of Next v14.2.0)
export { headers } from './headers';
export { cookies } from './cookies';

// passthrough mocks - keep original implementation but allow for spying
const draftMode = fn(originalHeaders.draftMode).mockName('draftMode');
export { draftMode };
Loading