UNPKG

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