diff --git a/README.md b/README.md index 100af3089298..d818ddf25b45 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ For additional help, join us [in our Discord](https://discord.gg/sMFvFsG) or [Sl | [Vue](app/vue) | [v5.1.0](https://storybooks-vue.netlify.com/) | [![Vue](https://img.shields.io/npm/dm/@storybook/vue.svg)](app/vue) | | [Angular](app/angular) | [v5.1.0](https://storybooks-angular.netlify.com/) | [![Angular](https://img.shields.io/npm/dm/@storybook/angular.svg)](app/angular) | | [Polymer](app/polymer) | [v5.1.0](https://storybooks-polymer.netlify.com/) | [![Polymer](https://img.shields.io/npm/dm/@storybook/polymer.svg)](app/polymer) | +| [Marionette.js](app/marionette) | - | [![Marionette.js](https://img.shields.io/npm/dm/@storybook/marionette.svg)](app/marionette) | | [Mithril](app/mithril) | [v5.1.0](https://storybooks-mithril.netlify.com/) | [![Mithril](https://img.shields.io/npm/dm/@storybook/mithril.svg)](app/mithril) | | [Marko](app/marko) | [v5.1.0](https://storybooks-marko.netlify.com/) | [![Marko](https://img.shields.io/npm/dm/@storybook/marko.svg)](app/marko) | | [HTML](app/html) | [v5.1.0](https://storybooks-html.netlify.com/) | [![HTML](https://img.shields.io/npm/dm/@storybook/html.svg)](app/html) | diff --git a/app/marionette/README.md b/app/marionette/README.md new file mode 100644 index 000000000000..fffc49bd35d5 --- /dev/null +++ b/app/marionette/README.md @@ -0,0 +1,25 @@ +# Storybook for Marionette.js + +--- + +Storybook for Marionette.js is a UI development environment for your Marionette.js components. +With it, you can visualize different states of your UI components and develop them interactively. + +![Storybook Screenshot](https://github.com/storybookjs/storybook/blob/master/media/storybook-intro.gif) + +Storybook runs outside of your app. +So you can develop UI components in isolation without worrying about app specific dependencies and requirements. + +## Getting Started + +```sh +cd my-app +npx -p @storybook/cli sb init +``` + +For more information visit: [storybook.js.org](https://storybook.js.org) + +--- + +Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. \ No newline at end of file diff --git a/app/marionette/bin/build.js b/app/marionette/bin/build.js new file mode 100755 index 000000000000..26142ec0af29 --- /dev/null +++ b/app/marionette/bin/build.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +process.env.NODE_ENV = process.env.NODE_ENV || 'production'; +require('../dist/server/build'); diff --git a/app/marionette/bin/index.js b/app/marionette/bin/index.js new file mode 100755 index 000000000000..2e96258ce63d --- /dev/null +++ b/app/marionette/bin/index.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../dist/server'); diff --git a/app/marionette/package.json b/app/marionette/package.json new file mode 100644 index 000000000000..db748fa82dc0 --- /dev/null +++ b/app/marionette/package.json @@ -0,0 +1,48 @@ +{ + "name": "@storybook/marionette", + "version": "5.3.7", + "description": "Storybook for Marionette: Develop Marionette.js component in isolation with Hot Reloading.", + "keywords": [ + "storybook" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/master/app/marionette", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "app/marionette" + }, + "license": "MIT", + "main": "dist/client/index.js", + "bin": { + "build-storybook": "./bin/build.js", + "start-storybook": "./bin/index.js", + "storybook-server": "./bin/index.js" + }, + "scripts": { + "prepare": "node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/core": "5.3.7", + "common-tags": "^1.8.0", + "core-js": "^3.0.1", + "global": "^4.3.2", + "html-loader": "^0.5.5", + "regenerator-runtime": "^0.12.1" + }, + "devDependencies": { + "backbone.marionette": "*" + }, + "peerDependencies": { + "babel-loader": "^7.0.0 || ^8.0.0", + "backbone.marionette": "*" + }, + "engines": { + "node": ">=8.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/app/marionette/src/client/index.js b/app/marionette/src/client/index.js new file mode 100644 index 000000000000..c3f87077b852 --- /dev/null +++ b/app/marionette/src/client/index.js @@ -0,0 +1,15 @@ +export { + storiesOf, + setAddon, + addDecorator, + addParameters, + configure, + getStorybook, + forceReRender, + raw, + load, +} from './preview'; + +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} diff --git a/app/marionette/src/client/preview/element_check.js b/app/marionette/src/client/preview/element_check.js new file mode 100644 index 000000000000..1661295377cf --- /dev/null +++ b/app/marionette/src/client/preview/element_check.js @@ -0,0 +1,18 @@ +import Marionette from 'backbone.marionette'; + +const allMarionetteViewConstructors = [ + 'View', + 'CompositeView', + 'CollectionView', + 'NextCollectionView', +]; +const viewConstructorsSupportedByMarionette = allMarionetteViewConstructors + .filter(constructorName => constructorName in Marionette) + .map(constructorName => Marionette[constructorName]); + +// accepts an element and return true if renderable else return false +const isMarionetteRenderable = element => { + return viewConstructorsSupportedByMarionette.find(Constructor => element instanceof Constructor); +}; + +export default isMarionetteRenderable; diff --git a/app/marionette/src/client/preview/globals.js b/app/marionette/src/client/preview/globals.js new file mode 100644 index 000000000000..168d2f7c49ac --- /dev/null +++ b/app/marionette/src/client/preview/globals.js @@ -0,0 +1,3 @@ +import { window } from 'global'; + +window.STORYBOOK_ENV = 'marionette'; diff --git a/app/marionette/src/client/preview/index.js b/app/marionette/src/client/preview/index.js new file mode 100644 index 000000000000..0128a027102c --- /dev/null +++ b/app/marionette/src/client/preview/index.js @@ -0,0 +1,22 @@ +import { start } from '@storybook/core/client'; + +import './globals'; +import render from './render'; + +const { load: coreLoad, clientApi, configApi, forceReRender } = start(render); + +export const { + setAddon, + addDecorator, + addParameters, + clearDecorators, + getStorybook, + raw, +} = clientApi; + +const framework = 'marionette'; +export const storiesOf = (...args) => clientApi.storiesOf(...args).addParameters({ framework }); +export const load = (...args) => coreLoad(...args, framework); + +export const { configure } = configApi; +export { forceReRender }; diff --git a/app/marionette/src/client/preview/render.js b/app/marionette/src/client/preview/render.js new file mode 100644 index 000000000000..cab2762d2034 --- /dev/null +++ b/app/marionette/src/client/preview/render.js @@ -0,0 +1,40 @@ +import { document } from 'global'; +import { stripIndents } from 'common-tags'; +import Marionette from 'backbone.marionette'; +import isMarionetteRenderable from './element_check'; + +const rootEl = document.getElementById('root'); +const rootRegion = new Marionette.Region({ el: rootEl }); + +function render(view) { + rootRegion.show(view); +} + +export default function renderMain({ storyFn, selectedKind, selectedStory, showMain, showError }) { + const element = storyFn(); + + if (!element) { + showError({ + title: `Expecting a Marionette View from the story: "${selectedStory}" of "${selectedKind}".`, + description: stripIndents` + Did you forget to return the React element from the story? + Use "() => ()" or "() => { return ; }" when defining the story. + `, + }); + return; + } + + if (!isMarionetteRenderable(element)) { + showError({ + title: `Expecting a valid Marionette View from the story: "${selectedStory}" of "${selectedKind}".`, + description: stripIndents` + Seems like you are not returning a correct Marionette View from the story. + Could you double check that? + `, + }); + return; + } + + render(element); + showMain(); +} diff --git a/app/marionette/src/server/build.js b/app/marionette/src/server/build.js new file mode 100644 index 000000000000..d8abf06a4396 --- /dev/null +++ b/app/marionette/src/server/build.js @@ -0,0 +1,4 @@ +import { buildStatic } from '@storybook/core/server'; +import options from './options'; + +buildStatic(options); diff --git a/app/marionette/src/server/framework-preset-marionette.js b/app/marionette/src/server/framework-preset-marionette.js new file mode 100644 index 000000000000..df8a5ec614e1 --- /dev/null +++ b/app/marionette/src/server/framework-preset-marionette.js @@ -0,0 +1,3 @@ +export function webpack(config) { + return config; +} diff --git a/app/marionette/src/server/index.js b/app/marionette/src/server/index.js new file mode 100644 index 000000000000..774d96025a84 --- /dev/null +++ b/app/marionette/src/server/index.js @@ -0,0 +1,4 @@ +import { buildDev } from '@storybook/core/server'; +import options from './options'; + +buildDev(options); diff --git a/app/marionette/src/server/options.js b/app/marionette/src/server/options.js new file mode 100644 index 000000000000..a1ea7a38f6d8 --- /dev/null +++ b/app/marionette/src/server/options.js @@ -0,0 +1,6 @@ +import packageJson from '../../package.json'; + +export default { + packageJson, + frameworkPresets: [require.resolve('./framework-preset-marionette.js')], +}; diff --git a/app/marionette/standalone.js b/app/marionette/standalone.js new file mode 100644 index 000000000000..1b1febe0d3bb --- /dev/null +++ b/app/marionette/standalone.js @@ -0,0 +1,8 @@ +const build = require('@storybook/core/standalone'); +const frameworkOptions = require('./dist/server/options').default; + +async function buildStandalone(options) { + return build(options, frameworkOptions); +} + +module.exports = buildStandalone; diff --git a/lib/cli/generators/MARIONETTE/index.js b/lib/cli/generators/MARIONETTE/index.js new file mode 100644 index 000000000000..a91e8895a519 --- /dev/null +++ b/lib/cli/generators/MARIONETTE/index.js @@ -0,0 +1,37 @@ +import fse from 'fs-extra'; +import path from 'path'; +import npmInit from '../../lib/npm_init'; +import { + getVersion, + getPackageJson, + writePackageJson, + getBabelDependencies, + installDependencies, +} from '../../lib/helpers'; + +export default async npmOptions => { + const storybookVersion = await getVersion(npmOptions, '@storybook/marionette'); + fse.copySync(path.resolve(__dirname, 'template/'), '.', { overwrite: true }); + + let packageJson = getPackageJson(); + if (!packageJson) { + await npmInit(); + packageJson = getPackageJson(); + } + + packageJson.dependencies = packageJson.dependencies || {}; + packageJson.devDependencies = packageJson.devDependencies || {}; + + packageJson.scripts = packageJson.scripts || {}; + packageJson.scripts.storybook = 'start-storybook -p 6006'; + packageJson.scripts['build-storybook'] = 'build-storybook'; + + writePackageJson(packageJson); + + const babelDependencies = await getBabelDependencies(npmOptions, packageJson); + + installDependencies(npmOptions, [ + `@storybook/marionette@${storybookVersion}`, + ...babelDependencies, + ]); +}; diff --git a/lib/cli/generators/MARIONETTE/template/.storybook/config.js b/lib/cli/generators/MARIONETTE/template/.storybook/config.js new file mode 100644 index 000000000000..088f3204615f --- /dev/null +++ b/lib/cli/generators/MARIONETTE/template/.storybook/config.js @@ -0,0 +1,9 @@ +import { configure } from '@storybook/marionette'; + +// automatically import all files ending in *.stories.js +const req = require.context('../stories', true, /\.stories\.js$/); +function loadStories() { + req.keys().forEach(filename => req(filename)); +} + +configure(loadStories, module); diff --git a/lib/cli/generators/MARIONETTE/template/stories/index.stories.js b/lib/cli/generators/MARIONETTE/template/stories/index.stories.js new file mode 100644 index 000000000000..32f924b93608 --- /dev/null +++ b/lib/cli/generators/MARIONETTE/template/stories/index.stories.js @@ -0,0 +1,20 @@ +import Marionette from 'backbone.marionette'; + +import { storiesOf } from '@storybook/marionette'; + +storiesOf('Demo', module).add( + 'button', + () => + new Marionette.View({ + template: () => '', + ui: { + button: '#my_button', + }, + triggers: { + 'click @ui.button': 'click', + }, + onClick() { + console.log('button clicked'); + }, + }) +); diff --git a/lib/cli/lib/detect.js b/lib/cli/lib/detect.js index bb8c5ad95355..26af70e4e86a 100644 --- a/lib/cli/lib/detect.js +++ b/lib/cli/lib/detect.js @@ -96,6 +96,12 @@ function detectFramework(dependencies) { ) { return types.MITHRIL; } + if ( + (dependencies.dependencies && dependencies.dependencies['backbone.marionette']) || + (dependencies.devDependencies && dependencies.devDependencies['backbone.marionette']) + ) { + return types.MARIONETTE; + } if ( (dependencies.dependencies && dependencies.dependencies.marko) || diff --git a/lib/cli/lib/initiate.js b/lib/cli/lib/initiate.js index a2ba31d55e8f..451bf0d9c02f 100644 --- a/lib/cli/lib/initiate.js +++ b/lib/cli/lib/initiate.js @@ -23,6 +23,7 @@ import vueGenerator from '../generators/VUE'; import polymerGenerator from '../generators/POLYMER'; import webpackReactGenerator from '../generators/WEBPACK_REACT'; import mithrilGenerator from '../generators/MITHRIL'; +import marionetteGenerator from '../generators/MARIONETTE'; import markoGenerator from '../generators/MARKO'; import htmlGenerator from '../generators/HTML'; import webComponentsGenerator from '../generators/WEB-COMPONENTS'; @@ -163,6 +164,11 @@ const installStorybook = (projectType, options) => { .then(commandLog('Adding storybook support to your "Mithril" app')) .then(end); + case types.MARIONETTE: + return marionetteGenerator(npmOptions) + .then(commandLog('Adding storybook support to your "Marionette.js" app')) + .then(end); + case types.MARKO: return markoGenerator(npmOptions, generatorOptions) .then(commandLog('Adding storybook support to your "Marko" app')) diff --git a/lib/cli/lib/project_types.js b/lib/cli/lib/project_types.js index 639e2620f6b7..14c19f09bda4 100644 --- a/lib/cli/lib/project_types.js +++ b/lib/cli/lib/project_types.js @@ -15,6 +15,7 @@ const projectTypes = { POLYMER: 'POLYMER', WEB_COMPONENTS: 'WEB_COMPONENTS', MITHRIL: 'MITHRIL', + MARIONETTE: 'MARIONETTE', MARKO: 'MARKO', HTML: 'HTML', RIOT: 'RIOT', @@ -34,6 +35,7 @@ export const supportedFrameworks = [ 'mithril', 'riot', 'ember', + 'marionette', 'marko', 'meteor', 'preact', diff --git a/lib/cli/package.json b/lib/cli/package.json index 5c046b3afae1..d688ba8fdca7 100644 --- a/lib/cli/package.json +++ b/lib/cli/package.json @@ -74,6 +74,7 @@ "@storybook/channels": "5.3.7", "@storybook/ember": "5.3.7", "@storybook/html": "5.3.7", + "@storybook/marionette": "5.3.7", "@storybook/marko": "5.3.7", "@storybook/mithril": "5.3.7", "@storybook/polymer": "5.3.7", diff --git a/yarn.lock b/yarn.lock index ff6edda46d67..ae925bda4d28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7459,6 +7459,18 @@ babylon@^6.18.0: resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== +backbone.marionette@*: + version "4.1.2" + resolved "https://registry.yarnpkg.com/backbone.marionette/-/backbone.marionette-4.1.2.tgz#55de74363219f6d5c343dab5bff6aeb20fc44419" + integrity sha512-T8wWxZZnuYjylONTnWZsGsgXtdx2ZrE38pZWJI9LmPqzYK5j0T8uduapFO7OEpsW5rtdbBgwof30xhzAkbb5eQ== + dependencies: + backbone.radio "^2.0.0" + +backbone.radio@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/backbone.radio/-/backbone.radio-2.0.0.tgz#bbe8672b373e313f99f36d2fbcf583fe77d04f42" + integrity sha1-u+hnKzc+MT+Z820vvPWD/nfQT0I= + backbone@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" @@ -27385,6 +27397,11 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== +regenerator-runtime@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== + regenerator-transform@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"