Skip to content

Commit

Permalink
Merge pull request #28800 from storybookjs/valentin/nextjs-vite
Browse files Browse the repository at this point in the history
Next.js: Add @storybook/nextjs-vite package
  • Loading branch information
valentinpalkovic committed Aug 12, 2024
2 parents 737acd2 + d1ed7ba commit 7780067
Show file tree
Hide file tree
Showing 84 changed files with 5,104 additions and 6 deletions.
1 change: 1 addition & 0 deletions code/core/src/cli/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const frameworkToDefaultBuilder: Record<SupportedFrameworks, CoreBuilder>
'html-vite': CoreBuilder.Vite,
'html-webpack5': CoreBuilder.Webpack5,
nextjs: CoreBuilder.Webpack5,
'experimental-nextjs-vite': CoreBuilder.Vite,
'preact-vite': CoreBuilder.Vite,
'preact-webpack5': CoreBuilder.Webpack5,
qwik: CoreBuilder.Vite,
Expand Down
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',
'experimental-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 @@ -41,6 +41,7 @@ export default {
'@storybook/types': '8.3.0-alpha.4',
'@storybook/angular': '8.3.0-alpha.4',
'@storybook/ember': '8.3.0-alpha.4',
'@storybook/experimental-nextjs-vite': '8.3.0-alpha.4',
'@storybook/html-vite': '8.3.0-alpha.4',
'@storybook/html-webpack5': '8.3.0-alpha.4',
'@storybook/nextjs': '8.3.0-alpha.4',
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 @@ -2,6 +2,7 @@
export type SupportedFrameworks =
| 'angular'
| 'ember'
| 'experimental-nextjs-vite'
| 'html-vite'
| 'html-webpack5'
| 'nextjs'
Expand Down
23 changes: 23 additions & 0 deletions code/frameworks/experimental-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/experimental-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/experimental-nextjs-vite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"name": "@storybook/experimental-nextjs-vite",
"version": "8.3.0-alpha.4",
"description": "Storybook for Next.js and Vite",
"keywords": [
"storybook",
"nextjs",
"vite"
],
"homepage": "https://github.com/storybookjs/storybook/tree/next/code/frameworks/experimental-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": "jiti ../../../scripts/prepare/check.ts",
"prep": "jiti ../../../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/experimental-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/experimental-nextjs-vite/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "experimental-nextjs-vite",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"targets": {
"build": {}
}
}
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);
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 };
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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';
import { fn } from '@storybook/test';

import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';

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());
};
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();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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

0 comments on commit 7780067

Please sign in to comment.