diff --git a/app/riot/package.json b/app/riot/package.json new file mode 100644 index 000000000000..133dfab77419 --- /dev/null +++ b/app/riot/package.json @@ -0,0 +1,88 @@ +{ + "name": "@storybook/riot", + "version": "6.5.0-rc.1", + "description": "Storybook for Riot.js: Develop Riot Component in isolation with Hot Reloading.", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/main/app/riot", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "app/riot" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "license": "MIT", + "main": "dist/cjs/client/index.js", + "module": "dist/esm/client/index.js", + "types": "dist/ts3.9/client/index.d.ts", + "typesVersions": { + "<3.8": { + "dist/ts3.9/*": [ + "dist/ts3.4/*" + ] + } + }, + "bin": { + "build-storybook": "./bin/build.js", + "start-storybook": "./bin/index.js", + "storybook-server": "./bin/index.js" + }, + "files": [ + "bin/**/*", + "dist/**/*", + "templates/**/*", + "README.md", + "*.js", + "*.d.ts" + ], + "scripts": { + "prepare": "node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/addons": "6.5.0-rc.1", + "@storybook/client-logger": "6.5.0-rc.1", + "@storybook/core": "6.5.0-rc.1", + "@storybook/core-common": "6.5.0-rc.1", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/docs-tools": "6.5.0-rc.1", + "@storybook/node-logger": "6.5.0-rc.1", + "@storybook/store": "6.5.0-rc.1", + "core-js": "^3.8.2", + "global": "^4.4.0", + "loader-utils": "^2.0.0", + "react": "16.14.0", + "react-dom": "16.14.0", + "read-pkg-up": "^7.0.1", + "regenerator-runtime": "^0.13.7", + "ts-dedent": "^2.0.0", + "webpack": ">=4.0.0 <6.0.0" + }, + "devDependencies": { + "@types/loader-utils": "^2.0.0", + "@types/webpack-env": "^1.16.0", + "riot": "^6.1.2", + "@riotjs/compiler": "^6.3.0", + "webpack": "4" + }, + "peerDependencies": { + "@babel/core": "*", + "riot": "^6.1.2", + "@riotjs/compiler": "^6.3.0", + "@riotjs/webpack-loader": "^6.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "publishConfig": { + "access": "public" + }, + "gitHead": "3f09d4e6b0c655a092dc812488ef2c7ed3808401", + "sbmodern": "dist/modern/client/index.js" +} diff --git a/app/riot/preset.js b/app/riot/preset.js new file mode 100644 index 000000000000..f09c6698e046 --- /dev/null +++ b/app/riot/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/cjs/server/preset'); diff --git a/app/riot/src/client/index.ts b/app/riot/src/client/index.ts new file mode 100644 index 000000000000..de13a1bc49ff --- /dev/null +++ b/app/riot/src/client/index.ts @@ -0,0 +1,18 @@ +export { + storiesOf, + setAddon, + addDecorator, + addParameters, + configure, + getStorybook, + forceReRender, + raw, + app, + activeStoryComponent, +} from './preview'; + +export * from './preview/types-6-0'; + +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} diff --git a/app/riot/src/client/preview/config.ts b/app/riot/src/client/preview/config.ts new file mode 100644 index 000000000000..cbb8e5f1fc6a --- /dev/null +++ b/app/riot/src/client/preview/config.ts @@ -0,0 +1,4 @@ +export { render, renderToDOM } from './render'; +export { decorateStory as applyDecorators } from './decorateStory'; + +export const parameters = { framework: 'riot' }; diff --git a/app/riot/src/client/preview/decorateStory.ts b/app/riot/src/client/preview/decorateStory.ts new file mode 100644 index 000000000000..94d7553e924c --- /dev/null +++ b/app/riot/src/client/preview/decorateStory.ts @@ -0,0 +1,58 @@ +import type {DecoratorFunction, StoryContext, LegacyStoryFn} from '@storybook/csf'; +import {RiotComponent} from 'riot' +import {sanitizeStoryContextUpdate} from '@storybook/store'; + +import type {RiotFramework} from './types-6-0'; +import {StoryFnRiotReturnType} from './types' + +function prepare( + rawStory: RiotFramework['storyResult'], + innerStory?: StoryFnRiotReturnType +): StoryFnRiotReturnType | null { + const story = rawStory as Partial; + + if (story == null) { + return null; + } + + if (innerStory) { + return { + components: { + ...(story.components || {}), + story: innerStory + }, + template: story.template + }; + } + + return { + template: story + }; +} + +export function decorateStory( + storyFn: LegacyStoryFn, + decorators: DecoratorFunction[] +): LegacyStoryFn { + return decorators.reduce( + (decorated: LegacyStoryFn, decorator) => (context: StoryContext) => { + let story: RiotFramework['storyResult']; + + const decoratedStory: RiotFramework['storyResult'] = decorator((update) => { + story = decorated({...context, ...sanitizeStoryContextUpdate(update)}); + return story; + }, context); + + if (!story) { + story = decorated(context); + } + + if (decoratedStory === story) { + return story; + } + + return prepare(decoratedStory, story) as RiotFramework['storyResult']; + }, + (context) => prepare(storyFn(context)) as LegacyStoryFn + ); +} diff --git a/app/riot/src/client/preview/globals.ts b/app/riot/src/client/preview/globals.ts new file mode 100644 index 000000000000..67bee5a51108 --- /dev/null +++ b/app/riot/src/client/preview/globals.ts @@ -0,0 +1,6 @@ +import global from 'global'; + +const { window: globalWindow } = global; + +globalWindow.STORYBOOK_REACT_CLASSES = {}; +globalWindow.STORYBOOK_ENV = 'riot'; diff --git a/app/riot/src/client/preview/index.ts b/app/riot/src/client/preview/index.ts new file mode 100644 index 000000000000..2bb88aa42660 --- /dev/null +++ b/app/riot/src/client/preview/index.ts @@ -0,0 +1,38 @@ +/* eslint-disable prefer-destructuring */ +import { start } from '@storybook/core'; +import type { ClientStoryApi, Loadable } from '@storybook/addons'; + +import './globals'; +import { renderToDOM } from './render'; +import type { IStorybookSection } from './types'; +import type { RiotComponent } from './types-6-0'; + +export interface ClientApi extends ClientStoryApi { + setAddon(addon: any): void; + configure(loader: Loadable, module: NodeModule): void; + getStorybook(): IStorybookSection[]; + clearDecorators(): void; + forceReRender(): void; + raw: () => any; // todo add type + load: (...args: any[]) => void; +} + +const framework = 'riot'; +const api = start(renderToDOM); + +export const storiesOf: ClientApi['storiesOf'] = (kind, m) => { + return (api.clientApi.storiesOf(kind, m) as ReturnType).addParameters({ + framework, + }); +}; + +export const configure: ClientApi['configure'] = (...args) => api.configure(framework, ...args); +export const addDecorator: ClientApi['addDecorator'] = api.clientApi + .addDecorator as ClientApi['addDecorator']; +export const addParameters: ClientApi['addParameters'] = api.clientApi + .addParameters as ClientApi['addParameters']; +export const clearDecorators: ClientApi['clearDecorators'] = api.clientApi.clearDecorators; +export const setAddon: ClientApi['setAddon'] = api.clientApi.setAddon; +export const forceReRender: ClientApi['forceReRender'] = api.forceReRender; +export const getStorybook: ClientApi['getStorybook'] = api.clientApi.getStorybook; +export const raw: ClientApi['raw'] = api.clientApi.raw; diff --git a/app/riot/src/client/preview/render.ts b/app/riot/src/client/preview/render.ts new file mode 100644 index 000000000000..061d271da12d --- /dev/null +++ b/app/riot/src/client/preview/render.ts @@ -0,0 +1,38 @@ +import dedent from 'ts-dedent'; +import {component} from 'riot'; +import {generateTemplateFunctionFromString} from '@riotjs/compiler' +import type {RenderContext} from '@storybook/store'; + +import {StoryFnRiotReturnType} from './types'; +import {RiotFramework} from './types-6-0'; + +let currentComponent + +export function renderToDOM( + {title, name, storyFn, showMain, showError, showException}: RenderContext, + domElement: HTMLElement +) { + const element: StoryFnRiotReturnType = storyFn(); + + if (!element) { + showError({ + title: `Expecting a Riot component from the story: "${name}" of "${title}".`, + description: dedent` + Did you forget to return the Riot component from the story? + Use "() => ({ template: '' })" or "() => ({ components: {MyComp}, template: '' })" when defining the story. + `, + }); + return; + } + + showMain(); + + if (currentComponent) { + currentComponent.unmount() + } + + currentComponent = component({ + exports: element, + template: Function(generateTemplateFunctionFromString(element.template))() + })(domElement) +} diff --git a/app/riot/src/client/preview/types-6-0.ts b/app/riot/src/client/preview/types-6-0.ts new file mode 100644 index 000000000000..113a14d7c131 --- /dev/null +++ b/app/riot/src/client/preview/types-6-0.ts @@ -0,0 +1,46 @@ +import { RiotComponent } from 'riot'; +import type { + Args, + ComponentAnnotations, + StoryAnnotations, + AnnotatedStoryFn, +} from '@storybook/csf'; +import type { StoryFnRiotReturnType } from './types'; + +export type { Args, ArgTypes, Parameters, StoryContext } from '@storybook/csf'; + +export type RiotFramework = { + component: RiotComponent; + storyResult: StoryFnRiotReturnType; +}; + +/** + * Metadata to configure the stories for a component. + * + * @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export) + */ +export type Meta = ComponentAnnotations; + +/** + * Story function that represents a CSFv2 component example. + * + * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) + */ +export type StoryFn = AnnotatedStoryFn; + +/** + * Story function that represents a CSFv3 component example. + * + * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) + */ +export type StoryObj = StoryAnnotations; + +/** + * Story function that represents a CSFv2 component example. + * + * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) + * + * NOTE that in Storybook 7.0, this type will be renamed to `StoryFn` and replaced by the current `StoryObj` type. + * + */ +export type Story = StoryFn; diff --git a/app/riot/src/client/preview/types-7-0.ts b/app/riot/src/client/preview/types-7-0.ts new file mode 100644 index 000000000000..d897ad97a81a --- /dev/null +++ b/app/riot/src/client/preview/types-7-0.ts @@ -0,0 +1,14 @@ +import type { Args } from '@storybook/csf'; + +import type { StoryObj } from './types-6-0'; + +export type { StoryFn, StoryObj, Meta } from './types-6-0'; + +// NOTE these types are reversed from the way they are in types-6-0 and types-6-3 + +/** + * Story function that represents a CSFv3 component example. + * + * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) + */ +export type Story = StoryObj; diff --git a/app/riot/src/client/preview/types.ts b/app/riot/src/client/preview/types.ts new file mode 100644 index 000000000000..9561ac0df3c2 --- /dev/null +++ b/app/riot/src/client/preview/types.ts @@ -0,0 +1,22 @@ +import { RiotComponent } from 'riot'; + +export type { RenderContext } from '@storybook/core'; + +export interface ShowErrorArgs { + title: string; + description: string; +} + +export type StoryFnRiotReturnType = Partial & { + template?: string +} + +export interface IStorybookStory { + name: string; + render: (context: any) => any; +} + +export interface IStorybookSection { + kind: string; + stories: IStorybookStory[]; +} diff --git a/app/riot/src/server/build.ts b/app/riot/src/server/build.ts new file mode 100755 index 000000000000..d8abf06a4396 --- /dev/null +++ b/app/riot/src/server/build.ts @@ -0,0 +1,4 @@ +import { buildStatic } from '@storybook/core/server'; +import options from './options'; + +buildStatic(options); diff --git a/app/riot/src/server/framework-preset-riot.ts b/app/riot/src/server/framework-preset-riot.ts new file mode 100644 index 000000000000..be9f4afc8900 --- /dev/null +++ b/app/riot/src/server/framework-preset-riot.ts @@ -0,0 +1,61 @@ +import { Configuration } from 'webpack'; +import { findDistEsm } from '@storybook/core-common'; +import type { StorybookConfig } from '@storybook/core-common'; + +export function webpack(config: Configuration): Configuration { + return { + ...config, + plugins: [ + ...config.plugins, + ], + module: { + ...config.module, + rules: [ + ...config.module.rules, + { + test: /\.riot$/, + loader: require.resolve('@riotjs/webpack-loader'), + options: {}, + }, + { + test: /\.ts$/, + use: [ + { + loader: require.resolve('ts-loader'), + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.riot$/], + }, + }, + ], + }, + { + test: /\.tsx$/, + use: [ + { + loader: require.resolve('ts-loader'), + options: { + transpileOnly: true, + // Note this is different from the `appendTsSuffixTo` above! + appendTsxSuffixTo: [/\.riot$/], + }, + }, + ], + }, + ], + }, + resolve: { + ...config.resolve, + extensions: [...config.resolve.extensions, '.riot'], + alias: { + ...config.resolve.alias, + riot$: require.resolve('riot/riot.esm.js'), + '@riotjs/compiler': require.resolve('@riotjs/compiler/dist/compiler.essential.js') + }, + }, + }; +} + +export const previewAnnotations: StorybookConfig['previewAnnotations'] = (entry = []) => { + return [...entry, findDistEsm(__dirname, 'client/preview/config')]; +}; diff --git a/app/riot/src/server/index.ts b/app/riot/src/server/index.ts new file mode 100755 index 000000000000..774d96025a84 --- /dev/null +++ b/app/riot/src/server/index.ts @@ -0,0 +1,4 @@ +import { buildDev } from '@storybook/core/server'; +import options from './options'; + +buildDev(options); diff --git a/app/riot/src/server/options.ts b/app/riot/src/server/options.ts new file mode 100644 index 000000000000..5c17d3624523 --- /dev/null +++ b/app/riot/src/server/options.ts @@ -0,0 +1,8 @@ +import { sync } from 'read-pkg-up'; +import type { LoadOptions } from '@storybook/core-common'; + +export default { + packageJson: sync({ cwd: __dirname }).packageJson, + framework: 'riot', + frameworkPresets: [require.resolve('./preset')], +} as LoadOptions; diff --git a/app/riot/src/server/preset.ts b/app/riot/src/server/preset.ts new file mode 100644 index 000000000000..46f196de65bf --- /dev/null +++ b/app/riot/src/server/preset.ts @@ -0,0 +1,5 @@ +import type { StorybookConfig } from '@storybook/core-common'; + +export const addons: StorybookConfig['addons'] = [ + require.resolve('./framework-preset-riot'), +]; diff --git a/app/riot/src/typings.d.ts b/app/riot/src/typings.d.ts new file mode 100644 index 000000000000..cbe9b934d170 --- /dev/null +++ b/app/riot/src/typings.d.ts @@ -0,0 +1,5 @@ +declare module 'global'; + +declare module '@riotjs/loader' { + export const RiotLoaderPlugin: any +} diff --git a/app/riot/standalone.js b/app/riot/standalone.js new file mode 100644 index 000000000000..d11a82f79957 --- /dev/null +++ b/app/riot/standalone.js @@ -0,0 +1,8 @@ +const build = require('@storybook/core/standalone'); +const frameworkOptions = require('./dist/cjs/server/options').default; + +async function buildStandalone(options) { + return build(options, frameworkOptions); +} + +module.exports = buildStandalone; diff --git a/app/riot/tsconfig.json b/app/riot/tsconfig.json new file mode 100644 index 000000000000..ea47a78eb476 --- /dev/null +++ b/app/riot/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": [ + "webpack-env", + "node" + ], + "resolveJsonModule": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "src/**/*.test.*" + ] +} \ No newline at end of file diff --git a/app/riot/types-6-0.d.ts b/app/riot/types-6-0.d.ts new file mode 100644 index 000000000000..b5946b39a8d8 --- /dev/null +++ b/app/riot/types-6-0.d.ts @@ -0,0 +1 @@ +export * from './dist/ts3.9/client/preview/types-6-0.d'; diff --git a/app/riot/types-7-0.d.ts b/app/riot/types-7-0.d.ts new file mode 100644 index 000000000000..a6080e072100 --- /dev/null +++ b/app/riot/types-7-0.d.ts @@ -0,0 +1 @@ +export * from './dist/ts3.9/client/preview/types-7-0.d';