UNPKG

@storybook/addon-docs

Version:
459 lines (347 loc) • 14.1 kB
"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;