UNPKG

8.84 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23 return new (P || (P = Promise))(function (resolve, reject) {
24 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27 step((generator = generator.apply(thisArg, _arguments || [])).next());
28 });
29};
30Object.defineProperty(exports, "__esModule", { value: true });
31exports.AbstractRenderer = void 0;
32/* eslint-disable no-undef */
33const core_1 = require("@angular/core");
34const platform_browser_dynamic_1 = require("@angular/platform-browser-dynamic");
35const rxjs_1 = require("rxjs");
36const telejson_1 = require("telejson");
37const StorybookModule_1 = require("./StorybookModule");
38// platform must be init only if render is called at least once
39let platformRef;
40function getPlatform(newPlatform) {
41 if (!platformRef || newPlatform) {
42 platformRef = platform_browser_dynamic_1.platformBrowserDynamic();
43 }
44 return platformRef;
45}
46class AbstractRenderer {
47 constructor(storyId) {
48 this.storyId = storyId;
49 if (typeof NODE_ENV === 'string' && NODE_ENV !== 'development') {
50 try {
51 // platform should be set after enableProdMode()
52 core_1.enableProdMode();
53 }
54 catch (e) {
55 // eslint-disable-next-line no-console
56 console.debug(e);
57 }
58 }
59 }
60 /**
61 * Wait and destroy the platform
62 */
63 static resetPlatformBrowserDynamic() {
64 return new Promise((resolve) => {
65 if (platformRef && !platformRef.destroyed) {
66 platformRef.onDestroy(() => __awaiter(this, void 0, void 0, function* () {
67 resolve();
68 }));
69 // Destroys the current Angular platform and all Angular applications on the page.
70 // So call each angular ngOnDestroy and avoid memory leaks
71 platformRef.destroy();
72 return;
73 }
74 resolve();
75 }).then(() => {
76 getPlatform(true);
77 });
78 }
79 /**
80 * Bootstrap main angular module with main component or send only new `props` with storyProps$
81 *
82 * @param storyFnAngular {StoryFnAngularReturnType}
83 * @param forced {boolean} If :
84 * - true render will only use the StoryFn `props' in storyProps observable that will update sotry's component/template properties. Improves performance without reloading the whole module&component if props changes
85 * - false fully recharges or initializes angular module & component
86 * @param component {Component}
87 * @param parameters {Parameters}
88 */
89 render({ storyFnAngular, forced, parameters, component, targetDOMNode, }) {
90 var _a;
91 return __awaiter(this, void 0, void 0, function* () {
92 const targetSelector = `${this.generateTargetSelectorFromStoryId()}`;
93 const newStoryProps$ = new rxjs_1.BehaviorSubject(storyFnAngular.props);
94 const moduleMetadata = StorybookModule_1.getStorybookModuleMetadata({ storyFnAngular, component, targetSelector }, newStoryProps$);
95 if (!this.fullRendererRequired({
96 storyFnAngular,
97 moduleMetadata,
98 forced,
99 })) {
100 this.storyProps$.next(storyFnAngular.props);
101 return;
102 }
103 yield this.beforeFullRender();
104 // Complete last BehaviorSubject and set a new one for the current module
105 if (this.storyProps$) {
106 this.storyProps$.complete();
107 }
108 this.storyProps$ = newStoryProps$;
109 this.initAngularRootElement(targetDOMNode, targetSelector);
110 yield getPlatform().bootstrapModule(StorybookModule_1.createStorybookModule(moduleMetadata), (_a = parameters.bootstrapModuleOptions) !== null && _a !== void 0 ? _a : undefined);
111 yield this.afterFullRender();
112 });
113 }
114 /**
115 * Only ASCII alphanumerics can be used as HTML tag name.
116 * https://html.spec.whatwg.org/#elements-2
117 *
118 * Therefore, stories break when non-ASCII alphanumerics are included in target selector.
119 * https://github.com/storybookjs/storybook/issues/15147
120 *
121 * This method returns storyId when it doesn't contain any non-ASCII alphanumerics.
122 * Otherwise, it generates a valid HTML tag name from storyId by removing non-ASCII alphanumerics from storyId, prefixing "sb-", and suffixing "-component"
123 * @protected
124 * @memberof AbstractRenderer
125 */
126 generateTargetSelectorFromStoryId() {
127 const invalidHtmlTag = /[^A-Za-z0-9-]/g;
128 const storyIdIsInvalidHtmlTagName = invalidHtmlTag.test(this.storyId);
129 return storyIdIsInvalidHtmlTagName
130 ? `sb-${this.storyId.replace(invalidHtmlTag, '')}-component`
131 : this.storyId;
132 }
133 initAngularRootElement(targetDOMNode, targetSelector) {
134 // Adds DOM element that angular will use as bootstrap component
135 // eslint-disable-next-line no-param-reassign
136 targetDOMNode.innerHTML = '';
137 targetDOMNode.appendChild(document.createElement(targetSelector));
138 }
139 fullRendererRequired({ storyFnAngular, moduleMetadata, forced, }) {
140 var _a;
141 const { previousStoryRenderInfo } = this;
142 const currentStoryRender = {
143 storyFnAngular,
144 moduleMetadataSnapshot: telejson_1.stringify(moduleMetadata),
145 };
146 this.previousStoryRenderInfo = currentStoryRender;
147 if (
148 // check `forceRender` of story RenderContext
149 !forced ||
150 // if it's the first rendering and storyProps$ is not init
151 !this.storyProps$) {
152 return true;
153 }
154 // force the rendering if the template has changed
155 const hasChangedTemplate = !!(storyFnAngular === null || storyFnAngular === void 0 ? void 0 : storyFnAngular.template) &&
156 ((_a = previousStoryRenderInfo === null || previousStoryRenderInfo === void 0 ? void 0 : previousStoryRenderInfo.storyFnAngular) === null || _a === void 0 ? void 0 : _a.template) !== storyFnAngular.template;
157 if (hasChangedTemplate) {
158 return true;
159 }
160 // force the rendering if the metadata structure has changed
161 const hasChangedModuleMetadata = currentStoryRender.moduleMetadataSnapshot !== (previousStoryRenderInfo === null || previousStoryRenderInfo === void 0 ? void 0 : previousStoryRenderInfo.moduleMetadataSnapshot);
162 return hasChangedModuleMetadata;
163 }
164}
165exports.AbstractRenderer = AbstractRenderer;
166/**
167 * Reset compiled components because we often want to compile the same component with
168 * more than one NgModule.
169 */
170AbstractRenderer.resetCompiledComponents = () => __awaiter(void 0, void 0, void 0, function* () {
171 try {
172 // Clear global Angular component cache in order to be able to re-render the same component across multiple stories
173 //
174 // References:
175 // https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/build_angular/src/webpack/plugins/hmr/hmr-accept.ts#L50
176 // https://github.com/angular/angular/blob/2ebe2bcb2fe19bf672316b05f15241fd7fd40803/packages/core/src/render3/jit/module.ts#L377-L384
177 const { ɵresetCompiledComponents } = yield Promise.resolve().then(() => __importStar(require('@angular/core')));
178 ɵresetCompiledComponents();
179 }
180 catch (e) {
181 /**
182 * noop catch
183 * This means angular removed or modified ɵresetCompiledComponents
184 */
185 }
186});