Skip to content

Commit

Permalink
feat(angular): add inline stories support in the docs addon
Browse files Browse the repository at this point in the history
The configuration has to be set up in each angular project to avoid forcing dependencies to `@angular/elements` ans `@webcomponents/custom-elements`
  • Loading branch information
ThibaudAV committed Jan 28, 2021
1 parent bd9f12f commit 94230a2
Show file tree
Hide file tree
Showing 15 changed files with 173 additions and 8 deletions.
4 changes: 2 additions & 2 deletions addons/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ Storybook Docs supports all view layers that Storybook supports except for React
| Source | + | + | + | + | + | + | + | + | + | + | + |
| Notes / Info | + | + | + | + | + | + | + | + | + | + | + |
| Props table | + | + | + | + | + | | | | | | |
| Props controls | + | + | | | | | | | | | |
| Props controls | + | + | + | | | | | | | | |
| Description | + | + | + | + | + | | | | | | |
| Inline stories | + | + | | | + | + | | | | | |
| Inline stories | + | + | + | | + | + | | | | | |

**Note:** `#` = WIP support

Expand Down
24 changes: 24 additions & 0 deletions addons/docs/angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,30 @@ And for `MDX` you can modify it as an attribute on the `Story` element:
<Story name='basic' height='400px'>{...}</Story>
```

## Inline Stories

Storybook Docs renders all Angular stories inside IFrames by default. But it is possible to use an inline rendering:

To get this, you'll first need to install Angular elements:

```sh
yarn add -D @angular/elements @webcomponents/custom-elements
```

Then update `.storybook/preview.js`:

```js
import { addParameters } from '@storybook/angular';
import { prepareForInline } from '@storybook/addon-docs/angular/inline';

addParameters({
docs: {
inlineStories: true,
prepareForInline,
},
});
```

## More resources

Want to learn more? Here are some more articles on Storybook Docs:
Expand Down
1 change: 1 addition & 0 deletions addons/docs/angular/inline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../dist/esm/frameworks/angular/prepareForInline');
5 changes: 5 additions & 0 deletions addons/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"@babel/core": "^7.12.10",
"@emotion/core": "^10.1.1",
"@emotion/styled": "^10.0.27",
"@storybook/angular": "6.2.0-alpha.18",
"@storybook/react": "6.2.0-alpha.18",
"@storybook/vue": "6.2.0-alpha.18",
"@storybook/web-components": "6.2.0-alpha.18",
Expand Down Expand Up @@ -123,6 +124,7 @@
},
"peerDependencies": {
"@babel/core": "^7.11.5",
"@storybook/angular": "6.2.0-alpha.18",
"@storybook/vue": "6.2.0-alpha.18",
"babel-loader": "^8.0.0",
"react": "^16.8.0 || ^17.0.0",
Expand All @@ -132,6 +134,9 @@
"webpack": ">=4"
},
"peerDependenciesMeta": {
"@storybook/angular": {
"optional": true
},
"@storybook/vue": {
"optional": true
},
Expand Down
47 changes: 47 additions & 0 deletions addons/docs/src/frameworks/angular/prepareForInline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { IStory, StoryContext } from '@storybook/angular';
import { ElementRendererService } from '@storybook/angular/element-renderer';
import { StoryFn } from '@storybook/addons';

const customElementsVersions: Record<string, number> = {};

/**
* Uses angular element to generate on-the-fly web components and reference it with `customElements`
* then it is added into react
*/
export const prepareForInline = (storyFn: StoryFn<IStory>, { id, parameters }: StoryContext) => {
// Upgrade story version in order that the next defined component has a unique key
customElementsVersions[id] =
customElementsVersions[id] !== undefined ? customElementsVersions[id] + 1 : 0;

const customElementsName = `${id}_${customElementsVersions[id]}`;

const Story = class Story extends React.Component {
wrapperRef: React.RefObject<unknown>;

elementName: string;

constructor(props: any) {
super(props);
this.wrapperRef = React.createRef();
}

async componentDidMount() {
// eslint-disable-next-line no-undef
customElements.define(
customElementsName,
await new ElementRendererService().renderAngularElement({
storyFnAngular: storyFn(),
parameters,
})
);
}

render() {
return React.createElement(customElementsName, {
ref: this.wrapperRef,
});
}
};
return React.createElement(Story);
};
1 change: 1 addition & 0 deletions app/angular/element-renderer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/ts3.9/element-renderer.d';
1 change: 1 addition & 0 deletions app/angular/element-renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/ts3.9/element-renderer');
10 changes: 10 additions & 0 deletions app/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@
"@angular/compiler": "^11.1.0",
"@angular/compiler-cli": "^11.1.0",
"@angular/core": "^11.1.0",
"@angular/elements": "^11.1.0",
"@angular/forms": "^11.1.0",
"@angular/platform-browser": "^11.1.0",
"@angular/platform-browser-dynamic": "^11.1.0",
"@nrwl/workspace": "^11.1.5",
"@types/autoprefixer": "^9.7.2",
"@types/jest": "^25.2.3",
"@webcomponents/custom-elements": "^1.4.3",
"jest": "^26.6.3",
"jest-preset-angular": "^8.3.2",
"ts-jest": "^26.4.4"
Expand All @@ -88,18 +90,26 @@
"@angular/compiler": ">=6.0.0",
"@angular/compiler-cli": ">=6.0.0",
"@angular/core": ">=6.0.0",
"@angular/elements": ">=6.0.0",
"@angular/forms": ">=6.0.0",
"@angular/platform-browser": ">=6.0.0",
"@angular/platform-browser-dynamic": ">=6.0.0",
"@babel/core": "*",
"@nrwl/workspace": ">=11.1.0",
"@webcomponents/custom-elements": ">=1.4.3",
"rxjs": "^6.0.0",
"typescript": "^3.4.0 || >=4.0.0",
"zone.js": "^0.8.29 || ^0.9.0 || ^0.10.0 || ^0.11.0"
},
"peerDependenciesMeta": {
"@angular/elements": {
"optional": true
},
"@nrwl/workspace": {
"optional": true
},
"@webcomponents/custom-elements": {
"optional": true
}
},
"engines": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Should be added first :
// Custom Elements polyfill. Required for browsers that do not natively support Custom Elements.
import '@webcomponents/custom-elements';
// Custom Elements ES5 shim. Required when using ES5 bundles on browsers that natively support
// Custom Elements (either because the browser does not support ES2015 modules or because the app
// is explicitly configured to generate ES5 only bundles).
import '@webcomponents/custom-elements/src/native-shim';

import { Injector, NgModule, Type } from '@angular/core';
import { createCustomElement, NgElementConstructor } from '@angular/elements';

import { BehaviorSubject } from 'rxjs';
import { ICollection, StoryFnAngularReturnType } from '../types';
import { Parameters } from '../types-6-0';
import { getStorybookModuleMetadata } from './StorybookModule';
import { RendererService } from './RendererService';

/**
* Bootstrap angular application to generate a web component with angular element
*/
export class ElementRendererService {
private platform = RendererService.getInstance().platform;

/**
* Returns a custom element generated by Angular elements
*/
public async renderAngularElement({
storyFnAngular,
parameters,
}: {
storyFnAngular: StoryFnAngularReturnType;
parameters: Parameters;
}): Promise<CustomElementConstructor> {
const ngModule = getStorybookModuleMetadata(
{ storyFnAngular, parameters },
new BehaviorSubject<ICollection>(storyFnAngular.props)
);

return this.platform
.bootstrapModule(createElementsModule(ngModule))
.then((m) => m.instance.ngEl);
}
}

const createElementsModule = (ngModule: NgModule): Type<{ ngEl: CustomElementConstructor }> => {
@NgModule({ ...ngModule })
class ElementsModule {
public ngEl: NgElementConstructor<unknown>;

constructor(private injector: Injector) {
this.ngEl = createCustomElement(ngModule.bootstrap[0] as Type<unknown>, {
injector: this.injector,
});
}

ngDoBootstrap() {}
}
return ElementsModule;
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class RendererService {
return RendererService.instance;
}

private platform: PlatformRef;
public platform: PlatformRef;

private staticRoot = document.getElementById('root');

Expand All @@ -43,6 +43,7 @@ export class RendererService {
}
// platform should be set after enableProdMode()
this.platform = platformBrowserDynamic();
this.initAngularBootstrapElement();
}

/**
Expand Down Expand Up @@ -83,7 +84,7 @@ export class RendererService {
);
}

initAngularBootstrapElement() {
private initAngularBootstrapElement() {
// Adds DOM element that angular will use as bootstrap component
const storybookWrapperElement = document.createElement(
RendererService.SELECTOR_STORYBOOK_WRAPPER
Expand Down
1 change: 1 addition & 0 deletions app/angular/src/element-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ElementRendererService } from './client/preview/angular-beta/ElementRendererService';
7 changes: 4 additions & 3 deletions examples/angular-cli/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { addParameters, addDecorator } from '@storybook/angular';
import { addParameters } from '@storybook/angular';
import { setCompodocJson } from '@storybook/addon-docs/angular';
import { prepareForInline } from '@storybook/addon-docs/angular/inline';
import addCssWarning from '../src/cssWarning';

// @ts-ignore
Expand All @@ -18,7 +19,7 @@ addCssWarning();

addParameters({
docs: {
// inlineStories: true,
iframeHeight: '60px',
inlineStories: true,
prepareForInline,
},
});
2 changes: 2 additions & 0 deletions examples/angular-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@angular-devkit/core": "^11.1.0",
"@angular/cli": "^11.1.0",
"@angular/compiler-cli": "^11.1.0",
"@angular/elements": "^11.1.0",
"@compodoc/compodoc": "^1.1.11",
"@storybook/addon-a11y": "6.2.0-alpha.18",
"@storybook/addon-actions": "6.2.0-alpha.18",
Expand All @@ -54,6 +55,7 @@
"@types/jest": "^25.2.3",
"@types/node": "^14.14.20",
"@types/webpack-env": "^1.16.0",
"@webcomponents/custom-elements": "^1.4.3",
"babel-plugin-require-context-hook": "^1.0.0",
"global": "^4.4.0",
"jasmine-core": "~3.6.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exports[`Storyshots Core/Parameters passed to story 1`] = `
>
Parameters are {
"docs": {
"iframeHeight": "60px"
"inlineStories": true
},
"globalParameter": "globalParameter",
"framework": "angular",
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,13 @@
dependencies:
tslib "^2.0.0"

"@angular/elements@^11.1.0":
version "11.1.0"
resolved "https://registry.yarnpkg.com/@angular/elements/-/elements-11.1.0.tgz#fd81e8dc01d4fa0ab36ee1ad2778ff5886ff8fd1"
integrity sha512-Bj6lwVg/uILRt2fFSKW+suezYJMXEkVJx+D+2a486ZnD/jT1UN5kJ21GfGy9c4tY4XtHlWTWc2CMPXGKDAhd7A==
dependencies:
tslib "^2.0.0"

"@angular/forms@^11.1.0":
version "11.1.0"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-11.1.0.tgz#6ab2e81df80dc9a9d0898d2a57270de5d9eb30e5"
Expand Down Expand Up @@ -6482,6 +6489,11 @@
"@webassemblyjs/wast-parser" "1.9.0"
"@xtuc/long" "4.2.2"

"@webcomponents/custom-elements@^1.4.3":
version "1.4.3"
resolved "https://registry.yarnpkg.com/@webcomponents/custom-elements/-/custom-elements-1.4.3.tgz#1800d49f38bb4425ebfd160b50115e62776109d7"
integrity sha512-iD0YW46SreUQANGccywK/eC+gZELNHocZZrY2fGwrIlx/biQOTkAF9IohisibHbrmIHmA9pVCIdGwzfO+W0gig==

"@webpack-contrib/schema-utils@^1.0.0-beta.0":
version "1.0.0-beta.0"
resolved "https://registry.yarnpkg.com/@webpack-contrib/schema-utils/-/schema-utils-1.0.0-beta.0.tgz#bf9638c9464d177b48209e84209e23bee2eb4f65"
Expand Down

0 comments on commit 94230a2

Please sign in to comment.