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"