# ember-cli-storybook **Repository Path**: mirrors_hashicorp/ember-cli-storybook ## Basic Information - **Project Name**: ember-cli-storybook - **Description**: 📒 Ember storybook adapter - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-03-26 - **Last Updated**: 2026-05-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ember-cli-storybook > 📒 Ember storybook adapter # Compatibility * Ember.js v3.16 or above * Ember CLI v2.13 or above * Node.js v16 or above # Installation ```shell ember install @storybook/ember-cli-storybook ``` # Usage This will be triggered automatically as a post build action when running `ember build` > package.json options (defaults) ```json "storybook": { "ignoreTestFiles": true, "config": {} } ``` > ember-cli-build options (defaults) ```json "ember-cli-storybook": { "componentFilePathPatterns": [], "enableAddonDocsIntegration": true, // enables yuidoc generation } ``` ## Config The config object represents anything that could be parsed from an `index.html` file. This must be in the format as below: ```json { "meta": [{ "attributes": ["name", "content"] }, { "attributes": ["name", "content"] }, { "attributes": ["name", "content", "id"] }], "link": [{ "selector": "link", "attributes": ["rel", "href"] }], "script": [{ "selector": "script", "attributes": ["src"] }] } ``` So in order to add a script tag to the generated `preview-head.html` a potential config would look like: ```json "storybook": { "config": { "script": { "src": "./assets/foo.js" } } } ``` > It is important to note that storybook will by default serve any files out of the `public` folder. If you have custom files you would like to serve they need to exist there. # Troubleshooting ### Components that need routing for query parameters The Storybook integration for Ember [renders stories into a custom component](https://github.com/storybookjs/storybook/blob/27210146d605e5a8fc630c0a70b7743be61d8f3f/app/ember/src/client/preview/render.js#L31-L34) in a router-less environment. This works for many situations but is insufficient when you have a story that requires query parameters, like a component that persists sorting or pagination information to the URL. There’s no official way to accomplish this as of yet, but you can work around it by dynamically adding a route, visiting it using the [private `startRouting` API](https://api.emberjs.com/ember/3.14/classes/EmberRouter/methods/startRouting?anchor=startRouting&show=inherited%2Cprivate), and injecting a pseudo-controller, such as in this utility function: ```javascript function injectRoutedController(controllerClass) { return on('init', function() { let container = getOwner(this); container.register('controller:storybook', controllerClass); let routerFactory = container.factoryFor('router:main'); routerFactory.class.map(function() { this.route('storybook'); }); let router = container.lookup('router:main'); router.initialURL = 'storybook'; router.startRouting(true); this.set('controller', container.lookup('controller:storybook')); }); } ``` Then you can use it in a story like this: ```javascript export let SortableColumns = () => { return { template: hbs` Name Language {{row.model.name}} {{row.model.lang}} `, context: { injectRoutedController: injectRoutedController( Controller.extend({ queryParams: ['sortProperty', 'sortDescending'], sortProperty: 'name', sortDescending: false, }) ), sortedShortList: computed('controller.sortProperty', 'controller.sortDescending', function() { let sorted = productMetadata.sortBy(this.get('controller.sortProperty') || 'name'); return this.get('controller.sortDescending') ? sorted.reverse() : sorted; }), }, }; }; ``` ### Working with store As said above, Storybook integration for Ember renders stories into a custom component, that are store-less. If your component relies on an Ember model, for example, you can work around with the same way you would do for query params. ```javascript function createUser() { return on('init', function () { this.user = getOwner(this) .lookup('service:store') .createRecord('user', { lastName: 'Doe', email: 'john.doe@qonto.eu' }); }); } ``` And then in your story: ```javascript export const storeExample = () => { return { template: hbs` `, context: { createUser: createUser(), }, }; }; ``` ### Making Ember import work Because Ember uses a mapping to resolve import like `@ember/array` or `@ember/object` for example, they may not work in Storybook. However, and because the module is already declared in the [babel preset for ember](https://github.com/storybookjs/storybook/blob/next/app/ember/src/server/framework-preset-babel-ember.ts#L19), you should be able to make them work by adding [babel-plugin-ember-modules-api-polyfill](https://github.com/ember-cli/babel-plugin-ember-modules-api-polyfill) to our `package.json`. ### `preview-head` generation race condition The `.storybook/preview-head.html` file is auto-generated and changes based on your `config/environment.js` and whether it’s a static or live-updating build of Storybook. This means that you’ll often see version control diffs for it, which can be bothersome. Since the file is auto-generated, it would be nice to add it to `.gitignore` so it no longer shows up in diffs. Unfortunately, the [documented way](https://github.com/storybookjs/storybook/blob/ada7868f432f43a5787fa1294ddb86c28163b07c/docs/src/pages/guides/guide-ember/index.md#add-storybookember) of starting a live-updating Storybook launches Ember CLI and Storybook in parallel, which means that in many cases, the `preview-head` file will not have been generated by the time Storybook needs it. To work around this if you want to ignore `preview-head`, you could either start Ember CLI and Storybook separately or create a script to launch them in sequence. ### Stories that render blank or distorted In some situations, components don’t render properly in stories, such as when dynamically-calculated container widths are zero or contents are blank. The cause for this is as-yet unknown, but an unfortunate workaround like this utility class can help in the meantime, by delaying the insertion of the component until the container element has been fully rendered: ```javascript import EmberObject from '@ember/object'; import { next } from '@ember/runloop'; export default EmberObject.extend({ init() { this._super(...arguments); this.set('complete', false); next(this, () => { this.set('complete', true); }); }, }); ``` Here’s an example of it being used in a story: ```javascript export let Standard = () => { return { template: hbs`
{{#if delayedTruth.complete}} {{/if}}
`, context: { delayedTruth: DelayedTruth.create(), distributionBarData: [ { label: 'one', value: 10 }, { label: 'two', value: 20 }, ], }, }; }; ``` See the [Contributing](CONTRIBUTING.md) guide for details. License ------------------------------------------------------------------------------ This project is licensed under the [MIT License](LICENSE.md).