Skip to content

Commit

Permalink
Bug 1805573 - make it possible to write .stories.md or .stories.mdx d…
Browse files Browse the repository at this point in the history
…ocumentation r=mstriemer,tgiles

This patch both fixes the dependency issues that we were running into when writing `.stories.mdx` stories and gives us the ability to write `.stories.md` docs only vanilla markdown stories.

To get the MDX stories working I had to clear out my `package-lock.json` and install `@mdx-js/react` with the `--legacy-peer-deps` flag as described here: storybookjs/storybook#18094 (comment)

Let me know if you run into issues with installing dependencies and running Storybook given all the changes. There was a different solution I explored in January to enable MDX2 support, but I couldn't seem to resolve all the dependency version mismatch issues there anymore. There will probably be follow up work to update all of this after Storybook v7 is officially released in March.

To enable us to write `.stories.md` docs only stories I added a custom Webpack loader that takes markdown and transforms it into docs only MDX, then runs that through all the other necessary loaders. There's definitely some follow up work here too - for example right now all our docs only pages get put in a "Docs" folder because I ran out of steam for exploring a way for us to specify/parse out a path and name for the docs stories.

Differential Revision: https://phabricator.services.mozilla.com/D168268
  • Loading branch information
hannajones committed Feb 2, 2023
1 parent 49de997 commit 3a587db
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 14 deletions.
44 changes: 43 additions & 1 deletion browser/components/storybook/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ const projectRoot = path.resolve(__dirname, "../../../../");

module.exports = {
stories: [
"../**/*.stories.md",
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
"../stories/**/*.stories.@(js|jsx|mjs|ts|tsx|md)",
`${projectRoot}/toolkit/**/*.stories.@(js|jsx|mjs|ts|tsx)`,
],
// Additions to the staticDirs might also need to get added to
Expand Down Expand Up @@ -48,6 +49,12 @@ module.exports = {
config.resolve.alias[
"lit.all.mjs"
] = `${projectRoot}/toolkit/content/widgets/vendor/lit.all.mjs`;
// @mdx-js/[email protected] versions don't get hoisted to the root node_modules
// folder due to the versions of React it accepts as a peer dependency. That
// means we have to go one level deeper and look in the node_modules of
// @storybook/addon-docs, which depends on @mdx-js/react.
config.resolve.alias["@mdx-js/react"] =
"browser/components/storybook/node_modules/@storybook/addon-docs/node_modules/@mdx-js/react";

// The @storybook/web-components project uses lit-html. Redirect it to our
// bundled version.
Expand All @@ -64,6 +71,41 @@ module.exports = {
loader: path.resolve(__dirname, "./chrome-uri-loader.js"),
});

// We're adding a rule for files matching this pattern in order to support
// writing docs only stories in plain markdown.
const MD_STORY_REGEX = /(stories|story)\.md$/;

// Find the existing rule for MDX stories.
let mdxStoryTest = /(stories|story)\.mdx$/.toString();
let mdxRule = config.module.rules.find(
rule => rule.test.toString() === mdxStoryTest
);

// Use a custom Webpack loader to transform our markdown stories into MDX,
// then run our new MDX through the same loaders that Storybook usually uses
// for MDX files. This is how we get a docs page from plain markdown.
config.module.rules.push({
test: MD_STORY_REGEX,
use: [
...mdxRule.use,
{ loader: path.resolve(__dirname, "./markdown-story-loader.js") },
],
});

// Find the existing rule for markdown files.
let markdownTest = /\.md$/.toString();
let markdownRuleIndex = config.module.rules.findIndex(
rule => rule.test.toString() === markdownTest
);
let markdownRule = config.module.rules[markdownRuleIndex];

// Modify the existing markdown rule so it doesn't process .stories.md
// files, but still treats any other markdown files as asset/source.
config.module.rules[markdownRuleIndex] = {
...markdownRule,
exclude: MD_STORY_REGEX,
};

config.optimization = {
splitChunks: false,
runtimeChunk: false,
Expand Down
79 changes: 79 additions & 0 deletions browser/components/storybook/.storybook/markdown-story-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env node */

/**
* This file contains a Webpack loader that takes markdown as its source and
* outputs a docs only MDX Storybook story. This enables us to write docs only
* pages in plain markdown by specifying a `.stories.md` extension.
*
* For more context on docs only stories, see:
* https://storybook.js.org/docs/web-components/writing-docs/mdx#documentation-only-mdx
*
* The MDX generated by the loader will then get run through the same loaders
* Storybook usually uses to transform MDX files.
*/

const path = require("path");

/**
* Takes a file path and returns a string to use as the story title, capitalized
* and split into multiple words. The file name gets transformed into the story
* name, which will be visible in the Storybook sidebar. For example, either:
*
* /stories/hello-world.stories.md or /stories/helloWorld.md
*
* will result in a story named "Hello World".
*
* @param {string} filePath - path of the file being processed.
* @returns {string} The title of the story.
*/
function getDocsStoryTitle(filePath) {
let fileName = path.basename(filePath, ".stories.md");
let pascalCaseName = toPascalCase(fileName);
return pascalCaseName.match(/[A-Z][a-z]+/g)?.join(" ") || pascalCaseName;
}

/**
* Transforms a string into PascalCase e.g. hello-world becomes HelloWorld.
* @param {string} str - String in any case.
* @returns {string} The string converted to PascalCase.
*/
function toPascalCase(str) {
return str
.match(/[a-z0-9]+/gi)
.map(text => text[0].toUpperCase() + text.substring(1))
.join("");
}

/**
* The WebpackLoader export. Takes markdown as its source and returns a docs
* only MDX story. For now we're filing all docs only stories under "Docs", but
* that likely won't be desireable long term.
*
* @param {string} source - The markdown source to rewrite to MDX.
*/
module.exports = function markdownStoryLoader(source) {
// `this.resourcePath` is the path of the file being processed.
let storyTitle = getDocsStoryTitle(this.resourcePath);

// Unfortunately the indentation/spacing here seems to be important for the
// MDX parser to know what to do in the next step of the Webpack process.
let mdxSource = `
import { Meta, Description } from "@storybook/addon-docs";
<Meta
title="Docs/${storyTitle}"
parameters={{
previewTabs: {
canvas: { hidden: true },
},
viewMode: "docs",
}}
/>
${source}`;

return mdxSource;
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,18 @@ commands, or with your personal npm/node that happens to be compatible.
This is the recommended approach for installing dependencies and running
Storybook locally.

To start Storybook the first time (or if it's been a while since you last
installed), run:
To install dependencies and start Storybook, just run:

```sh
# This uses npm ci under the hood to install the package-lock.json exactly.
./mach storybook install
```

Once you've got your dependencies installed you can start Storybook. You should
run your local build to test in Storybook since chrome:// URLs are currently
being pulled from the running browser, so any changes to common-shared.css for
example will come from your build.

```sh
# Start the Storybook server.
./mach storybook
```

This single command will first install any missing dependencies then start the
local Storybook server. You should run your local build to test in Storybook
since chrome:// URLs are currently being pulled from the running browser, so any
changes to common-shared.css for example will come from your build.

The Storybook server will continue running and will watch for component file
changes. To access your local Storybook preview you can use the `launch`
subcommand:
Expand Down Expand Up @@ -181,7 +175,7 @@ throughout Firefox. You can import the component into `html`/`xhtml` files via a
`script` tag with `type="module"`:

```html
<script type="module" src="chrome://global/content/elements/your-component-name.mjs">
<script type="module" src="chrome://global/content/elements/your-component-name.mjs"/>
```
### Common pitfalls
Expand Down

0 comments on commit 3a587db

Please sign in to comment.