@storybook/addon-docs
Version:
Superior documentation for your components
459 lines (347 loc) • 14.1 kB
JavaScript
"use strict";
require("core-js/modules/es.symbol");
require("core-js/modules/es.symbol.description");
require("core-js/modules/es.symbol.iterator");
require("core-js/modules/es.array.concat");
require("core-js/modules/es.array.find");
require("core-js/modules/es.array.for-each");
require("core-js/modules/es.array.is-array");
require("core-js/modules/es.array.iterator");
require("core-js/modules/es.array.join");
require("core-js/modules/es.array.map");
require("core-js/modules/es.date.to-string");
require("core-js/modules/es.function.name");
require("core-js/modules/es.object.assign");
require("core-js/modules/es.object.define-property");
require("core-js/modules/es.object.entries");
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.regexp.exec");
require("core-js/modules/es.regexp.to-string");
require("core-js/modules/es.string.iterator");
require("core-js/modules/es.string.replace");
require("core-js/modules/es.string.trim");
require("core-js/modules/web.dom-collections.for-each");
require("core-js/modules/web.dom-collections.iterator");
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var mdxToJsx = require('@mdx-js/mdx/mdx-hast-to-jsx');
var parser = require('@babel/parser');
var generate = require('@babel/generator')["default"];
var camelCase = require('lodash/camelCase');
var jsStringEscape = require('js-string-escape'); // Generate the MDX as is, but append named exports for every
// story in the contents
var STORY_REGEX = /^<Story[\s>]/;
var PREVIEW_REGEX = /^<Preview[\s>]/;
var META_REGEX = /^<Meta[\s>]/;
var RESERVED = /^(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|await|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/;
function getAttr(elt, what) {
var attr = elt.attributes.find(function (n) {
return n.name.name === what;
});
return attr && attr.value;
}
var isReserved = function isReserved(name) {
return RESERVED.exec(name);
};
var startsWithNumber = function startsWithNumber(name) {
return /^\d/.exec(name);
};
var sanitizeName = function sanitizeName(name) {
var key = camelCase(name);
if (startsWithNumber(key)) {
key = "_".concat(key);
} else if (isReserved(key)) {
key = "".concat(key, "Story");
}
return key;
};
var getStoryKey = function getStoryKey(name, counter) {
return name ? sanitizeName(name) : "story".concat(counter);
};
function genStoryExport(ast, context) {
var storyName = getAttr(ast.openingElement, 'name');
var storyId = getAttr(ast.openingElement, 'id');
storyName = storyName && storyName.value;
storyId = storyId && storyId.value;
if (!storyId && !storyName) {
throw new Error('Expected a story name or ID attribute');
} // We don't generate exports for story references or the smart "current story"
if (storyId || !storyName) {
return null;
} // console.log('genStoryExport', JSON.stringify(ast, null, 2));
var statements = [];
var storyKey = getStoryKey(storyName, context.counter);
var body = ast.children.find(function (n) {
return n.type !== 'JSXText';
});
var storyCode = null;
if (!body) {
// plain text node
var _generate = generate(ast.children[0], {}),
code = _generate.code;
storyCode = "'".concat(code, "'");
} else {
if (body.type === 'JSXExpressionContainer') {
// FIXME: handle fragments
body = body.expression;
}
var _generate2 = generate(body, {}),
_code = _generate2.code;
storyCode = _code;
}
var storyVal = null;
switch (body && body.type) {
// We don't know what type the identifier is, but this code
// assumes it's a function from CSF. Let's see who complains!
case 'Identifier':
storyVal = "assertIsFn(".concat(storyCode, ")");
break;
case 'ArrowFunctionExpression':
storyVal = "(".concat(storyCode, ")");
break;
default:
storyVal = "() => (\n ".concat(storyCode, "\n )");
break;
}
statements.push("export const ".concat(storyKey, " = ").concat(storyVal, ";"));
statements.push("".concat(storyKey, ".story = {};")); // always preserve the name, since CSF exports can get modified by displayName
statements.push("".concat(storyKey, ".story.name = '").concat(storyName, "';"));
var parameters = getAttr(ast.openingElement, 'parameters');
parameters = parameters && parameters.expression;
var source = jsStringEscape(storyCode);
if (parameters) {
var _generate3 = generate(parameters, {}),
params = _generate3.code; // FIXME: hack in the story's source as a parameter
statements.push("".concat(storyKey, ".story.parameters = { mdxSource: '").concat(source, "', ...").concat(params, " };"));
} else {
statements.push("".concat(storyKey, ".story.parameters = { mdxSource: '").concat(source, "' };"));
}
var decorators = getAttr(ast.openingElement, 'decorators');
decorators = decorators && decorators.expression;
if (decorators) {
var _generate4 = generate(decorators, {}),
decos = _generate4.code;
statements.push("".concat(storyKey, ".story.decorators = ").concat(decos, ";"));
} // eslint-disable-next-line no-param-reassign
context.storyNameToKey[storyName] = storyKey;
return _defineProperty({}, storyKey, statements.join('\n'));
}
function genPreviewExports(ast, context) {
// console.log('genPreviewExports', JSON.stringify(ast, null, 2));
var previewExports = {};
for (var i = 0; i < ast.children.length; i += 1) {
var child = ast.children[i];
if (child.type === 'JSXElement' && child.openingElement.name.name === 'Story') {
var storyExport = genStoryExport(child, context);
if (storyExport) {
Object.assign(previewExports, storyExport); // eslint-disable-next-line no-param-reassign
context.counter += 1;
}
}
}
return previewExports;
}
function genMeta(ast, options) {
var title = getAttr(ast.openingElement, 'title');
var id = getAttr(ast.openingElement, 'id');
var parameters = getAttr(ast.openingElement, 'parameters');
var decorators = getAttr(ast.openingElement, 'decorators');
if (title) {
if (title.type === 'StringLiteral') {
title = "'".concat(jsStringEscape(title.value), "'");
} else {
try {
// generate code, so the expression is evaluated by the CSF compiler
var _generate5 = generate(title, {}),
code = _generate5.code; // remove the curly brackets at start and end of code
title = code.replace(/^\{(.+)\}$/, '$1');
} catch (e) {
// eat exception if title parsing didn't go well
// eslint-disable-next-line no-console
console.warn('Invalid title:', options.filepath);
title = undefined;
}
}
}
id = id && "'".concat(id.value, "'");
if (parameters && parameters.expression) {
var _generate6 = generate(parameters.expression, {}),
params = _generate6.code;
parameters = params;
}
if (decorators && decorators.expression) {
var _generate7 = generate(decorators.expression, {}),
decos = _generate7.code;
decorators = decos;
}
return {
title: title,
id: id,
parameters: parameters,
decorators: decorators
};
}
function getExports(node, counter, options) {
var value = node.value,
type = node.type;
if (type === 'jsx') {
if (STORY_REGEX.exec(value)) {
// Single story
var ast = parser.parseExpression(value, {
plugins: ['jsx']
});
var storyExport = genStoryExport(ast, counter);
return storyExport && {
stories: storyExport
};
}
if (PREVIEW_REGEX.exec(value)) {
// Preview, possibly containing multiple stories
var _ast = parser.parseExpression(value, {
plugins: ['jsx']
});
return {
stories: genPreviewExports(_ast, counter)
};
}
if (META_REGEX.exec(value)) {
// Preview, possibly containing multiple stories
var _ast2 = parser.parseExpression(value, {
plugins: ['jsx']
});
return {
meta: genMeta(_ast2, options)
};
}
}
return null;
} // insert `mdxStoryNameToKey` and `mdxComponentMeta` into the context so that we
// can reconstruct the Story ID dynamically from the `name` at render time
var wrapperJs = "\ncomponentMeta.parameters = componentMeta.parameters || {};\ncomponentMeta.parameters.docs = {\n ...(componentMeta.parameters.docs || {}),\n page: () => <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentMeta={componentMeta}><MDXContent /></AddContext>,\n};\n".trim(); // Use this rather than JSON.stringify because `Meta`'s attributes
// are already valid code strings, so we want to insert them raw
// rather than add an extra set of quotes
function stringifyMeta(meta) {
var result = '{ ';
Object.entries(meta).forEach(function (_ref2) {
var _ref3 = _slicedToArray(_ref2, 2),
key = _ref3[0],
val = _ref3[1];
if (val) {
result += "".concat(key, ": ").concat(val, ", ");
}
});
result += ' }';
return result;
}
var hasStoryChild = function hasStoryChild(node) {
if (node.openingElement && node.openingElement.name.name === 'Story') {
return node;
}
if (node.children && node.children.length > 0) {
return node.children.find(function (child) {
return hasStoryChild(child);
});
}
return null;
};
function extractExports(node, options) {
node.children.forEach(function (child) {
if (child.type === 'jsx') {
try {
var ast = parser.parseExpression(child.value, {
plugins: ['jsx']
});
if (ast.openingElement && ast.openingElement.type === 'JSXOpeningElement' && ast.openingElement.name.name === 'Preview' && !hasStoryChild(ast)) {
var previewAst = ast.openingElement;
previewAst.attributes.push({
type: 'JSXAttribute',
name: {
type: 'JSXIdentifier',
name: 'mdxSource'
},
value: {
type: 'StringLiteral',
value: encodeURI(ast.children.map(function (el) {
return generate(el, {
quotes: 'double'
}).code;
}).join('\n'))
}
});
}
var _generate8 = generate(ast, {}),
code = _generate8.code; // eslint-disable-next-line no-param-reassign
child.value = code;
} catch (_unused) {
/** catch erroneous child.value string where the babel parseExpression makes exception
* https://github.com/mdx-js/mdx/issues/767
* eg <button>
* <div>hello world</div>
*
* </button>
* generates error
* 1. child.value =`<button>\n <div>hello world</div`
* 2. child.value =`\n`
* 3. child.value =`</button>`
*
*/
}
}
}); // we're overriding default export
var defaultJsx = mdxToJsx.toJSX(node, {}, Object.assign({}, options, {
skipExport: true
}));
var storyExports = [];
var includeStories = [];
var metaExport = null;
var context = {
counter: 0,
storyNameToKey: {}
};
node.children.forEach(function (n) {
var exports = getExports(n, context, options);
if (exports) {
var stories = exports.stories,
meta = exports.meta;
if (stories) {
Object.entries(stories).forEach(function (_ref4) {
var _ref5 = _slicedToArray(_ref4, 2),
key = _ref5[0],
story = _ref5[1];
includeStories.push(key);
storyExports.push(story);
});
}
if (meta) {
if (metaExport) {
throw new Error('Meta can only be declared once');
}
metaExport = meta;
}
}
});
if (metaExport) {
if (!storyExports.length) {
storyExports.push('export const __page = () => { throw new Error("Docs-only story"); };');
storyExports.push('__page.story = { parameters: { docsOnly: true } };');
includeStories.push('__page');
}
} else {
metaExport = {};
}
metaExport.includeStories = JSON.stringify(includeStories);
var fullJsx = ['import { assertIsFn, AddContext } from "@storybook/addon-docs/blocks";', defaultJsx].concat(storyExports, ["const componentMeta = ".concat(stringifyMeta(metaExport), ";"), "const mdxStoryNameToKey = ".concat(JSON.stringify(context.storyNameToKey), ";"), wrapperJs, 'export default componentMeta;']).join('\n\n');
return fullJsx;
}
function createCompiler(mdxOptions) {
return function compiler() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.Compiler = function (tree) {
return extractExports(tree, options, mdxOptions);
};
};
}
module.exports = createCompiler;