UNPKG

react-carousel-query

Version:

A infinite carousel component made with react that handles the pagination for you.

553 lines (454 loc) 17.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createCompiler = createCompiler; exports.wrapperJs = void 0; var _mdxHastToJsx = require("@mdx-js/mdx/mdx-hast-to-jsx"); var _parser = require("@babel/parser"); var t = _interopRequireWildcard(require("@babel/types")); var _generator = _interopRequireDefault(require("@babel/generator")); var _camelCase = _interopRequireDefault(require("lodash/camelCase")); var _jsStringEscape = _interopRequireDefault(require("js-string-escape")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const generate = (node, context) => (0, _generator.default)(node, context); // Defined in MDX2.0 // Generate the MDX as is, but append named exports for every // story in the contents const STORY_REGEX = /^<Story[\s>]/; const CANVAS_REGEX = /^<(Preview|Canvas)[\s>]/; const META_REGEX = /^<Meta[\s>]/; const 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) { const attr = elt.attributes.find(n => n.name.name === what); return attr === null || attr === void 0 ? void 0 : attr.value; } const isReserved = name => RESERVED.exec(name); const startsWithNumber = name => /^\d/.exec(name); const sanitizeName = name => { let key = (0, _camelCase.default)(name); if (startsWithNumber(key)) { key = `_${key}`; } else if (isReserved(key)) { key = `${key}Story`; } return key; }; const getStoryKey = (name, counter) => name ? sanitizeName(name) : `story${counter}`; function genAttribute(key, element) { const value = getAttr(element, key); if (t.isJSXExpressionContainer(value)) { const { code } = generate(value.expression, {}); return code; } return undefined; } function genImportStory(ast, storyDef, storyName, context) { const { code: story } = generate(storyDef.expression, {}); const storyKey = `_${story.split('.').pop()}_`; const statements = [`export const ${storyKey} = ${story};`]; if (storyName) { context.storyNameToKey[storyName] = storyKey; statements.push(`${storyKey}.storyName = '${storyName}';`); } else { context.storyNameToKey[storyKey] = storyKey; ast.openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier('name'), t.stringLiteral(storyKey))); } return { [storyKey]: statements.join('\n') }; } function getBodyPart(bodyNode, context) { const body = t.isJSXExpressionContainer(bodyNode) ? bodyNode.expression : bodyNode; let sourceBody = body; if (t.isCallExpression(body) && t.isMemberExpression(body.callee) && t.isIdentifier(body.callee.object) && t.isIdentifier(body.callee.property) && body.callee.property.name === 'bind' && (body.arguments.length === 0 || body.arguments.length === 1 && t.isObjectExpression(body.arguments[0]) && body.arguments[0].properties.length === 0)) { const bound = body.callee.object.name; const namedExport = context.namedExports[bound]; if (namedExport) { sourceBody = namedExport; } } const { code: storyCode } = generate(body, {}); const { code: sourceCode } = generate(sourceBody, {}); return { storyCode, sourceCode, body }; } const idOrNull = attr => t.isStringLiteral(attr) ? attr.value : null; const expressionOrNull = attr => t.isJSXExpressionContainer(attr) ? attr.expression : null; function genStoryExport(ast, context) { const storyName = idOrNull(getAttr(ast.openingElement, 'name')); const storyId = idOrNull(getAttr(ast.openingElement, 'id')); const storyRef = getAttr(ast.openingElement, 'story'); if (!storyId && !storyName && !storyRef) { throw new Error('Expected a Story name, id, or story attribute'); } // We don't generate exports for story references or the smart "current story" if (storyId) { return null; } if (storyRef) { return genImportStory(ast, storyRef, storyName, context); } const statements = []; const storyKey = getStoryKey(storyName, context.counter); const bodyNodes = ast.children.filter(n => !t.isJSXText(n)); let storyCode = null; let sourceCode = null; let storyVal = null; if (!bodyNodes.length) { if (ast.children.length > 0) { // plain text node const { code } = generate(ast.children[0], {}); storyCode = `'${code}'`; sourceCode = storyCode; storyVal = `() => ( ${storyCode} )`; } else { sourceCode = '{}'; storyVal = '{}'; } } else { const bodyParts = bodyNodes.map(bodyNode => getBodyPart(bodyNode, context)); // if we have more than two children // 1. Add line breaks // 2. Enclose in <> ... </> storyCode = bodyParts.map(({ storyCode: code }) => code).join('\n'); sourceCode = bodyParts.map(({ sourceCode: code }) => code).join('\n'); const storyReactCode = bodyParts.length > 1 ? `<>\n${storyCode}\n</>` : storyCode; // keep track if an identifier or function call // avoid breaking change for 5.3 const BIND_REGEX = /\.bind\(.*\)/; if (bodyParts.length === 1) { if (BIND_REGEX.test(bodyParts[0].storyCode)) { storyVal = bodyParts[0].storyCode; } else if (t.isIdentifier(bodyParts[0].body)) { storyVal = `assertIsFn(${storyCode})`; } else if (t.isArrowFunctionExpression(bodyParts[0].body)) { storyVal = `(${storyCode})`; } else { storyVal = `() => ( ${storyReactCode} )`; } } else { storyVal = `() => ( ${storyReactCode} )`; } } statements.push(`export const ${storyKey} = ${storyVal};`); // always preserve the name, since CSF exports can get modified by displayName statements.push(`${storyKey}.storyName = '${storyName}';`); const argTypes = genAttribute('argTypes', ast.openingElement); if (argTypes) statements.push(`${storyKey}.argTypes = ${argTypes};`); const args = genAttribute('args', ast.openingElement); if (args) statements.push(`${storyKey}.args = ${args};`); const parameters = expressionOrNull(getAttr(ast.openingElement, 'parameters')); const source = (0, _jsStringEscape.default)(sourceCode); const sourceParam = `storySource: { source: '${source}' }`; if (parameters) { const { code: params } = generate(parameters, {}); statements.push(`${storyKey}.parameters = { ${sourceParam}, ...${params} };`); } else { statements.push(`${storyKey}.parameters = { ${sourceParam} };`); } const decorators = expressionOrNull(getAttr(ast.openingElement, 'decorators')); if (decorators) { const { code: decos } = generate(decorators, {}); statements.push(`${storyKey}.decorators = ${decos};`); } const loaders = expressionOrNull(getAttr(ast.openingElement, 'loaders')); if (loaders) { const { code: loaderCode } = generate(loaders, {}); statements.push(`${storyKey}.loaders = ${loaderCode};`); } const play = expressionOrNull(getAttr(ast.openingElement, 'play')); if (play) { const { code: playCode } = generate(play, {}); statements.push(`${storyKey}.play = ${playCode};`); } const render = expressionOrNull(getAttr(ast.openingElement, 'render')); if (render) { const { code: renderCode } = generate(render, {}); statements.push(`${storyKey}.render = ${renderCode};`); } context.storyNameToKey[storyName] = storyKey; return { [storyKey]: statements.join('\n') }; } function genCanvasExports(ast, context) { const canvasExports = {}; for (let i = 0; i < ast.children.length; i += 1) { const child = ast.children[i]; if (t.isJSXElement(child) && t.isJSXIdentifier(child.openingElement.name) && child.openingElement.name.name === 'Story') { const storyExport = genStoryExport(child, context); const { code } = generate(child, {}); // @ts-ignore child.value = code; if (storyExport) { Object.assign(canvasExports, storyExport); context.counter += 1; } } } return canvasExports; } function genMeta(ast, options) { const titleAttr = getAttr(ast.openingElement, 'title'); const idAttr = getAttr(ast.openingElement, 'id'); let title = null; if (titleAttr) { if (t.isStringLiteral(titleAttr)) { title = "'".concat((0, _jsStringEscape.default)(titleAttr.value), "'"); } else if (t.isJSXExpressionContainer(titleAttr)) { try { // generate code, so the expression is evaluated by the CSF compiler const { code } = generate(titleAttr.expression, {}); // 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; } } else { console.warn(`Unknown title attr: ${titleAttr.type}`); } } const id = t.isStringLiteral(idAttr) ? `'${idAttr.value}'` : null; const parameters = genAttribute('parameters', ast.openingElement); const decorators = genAttribute('decorators', ast.openingElement); const loaders = genAttribute('loaders', ast.openingElement); const component = genAttribute('component', ast.openingElement); const subcomponents = genAttribute('subcomponents', ast.openingElement); const args = genAttribute('args', ast.openingElement); const argTypes = genAttribute('argTypes', ast.openingElement); const render = genAttribute('render', ast.openingElement); return { title, id, parameters, decorators, loaders, component, subcomponents, args, argTypes, render }; } function getExports(node, context, options) { const { value, type } = node; if (type === 'jsx') { if (STORY_REGEX.exec(value)) { // Single story const ast = (0, _parser.parseExpression)(value, { plugins: ['jsx'] }); const storyExport = genStoryExport(ast, context); const { code } = generate(ast, {}); // eslint-disable-next-line no-param-reassign node.value = code; return storyExport && { stories: storyExport }; } if (CANVAS_REGEX.exec(value)) { // Canvas/Preview, possibly containing multiple stories const ast = (0, _parser.parseExpression)(value, { plugins: ['jsx'] }); const canvasExports = genCanvasExports(ast, context); // We're overwriting the Canvas tag here with a version that // has the `name` attribute (e.g. `<Story name="..." story={...} />`) // even if the user didn't provide one. We need the name attribute when // we render the node at runtime. const { code } = generate(ast, {}); // eslint-disable-next-line no-param-reassign node.value = code; return { stories: canvasExports }; } if (META_REGEX.exec(value)) { const ast = (0, _parser.parseExpression)(value, { plugins: ['jsx'] }); return { meta: genMeta(ast, 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 const wrapperJs = ` componentMeta.parameters = componentMeta.parameters || {}; componentMeta.parameters.docs = { ...(componentMeta.parameters.docs || {}), page: () => <AddContext mdxStoryNameToKey={mdxStoryNameToKey} mdxComponentAnnotations={componentMeta}><MDXContent /></AddContext>, }; `.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 exports.wrapperJs = wrapperJs; function stringifyMeta(meta) { let result = '{ '; Object.entries(meta).forEach(([key, val]) => { if (val) { result += `${key}: ${val}, `; } }); result += ' }'; return result; } const hasStoryChild = node => { if (node.openingElement && t.isJSXIdentifier(node.openingElement.name) && node.openingElement.name.name === 'Story') { return !!node; } if (node.children && node.children.length > 0) { return !!node.children.find(child => hasStoryChild(child)); } return false; }; const getMdxSource = children => encodeURI(children.map(el => generate(el, {}).code).join('\n')); // Parse out the named exports from a node, where the key // is the variable name and the value is the AST of the // variable declaration initializer const getNamedExports = node => { const namedExports = {}; const ast = (0, _parser.parse)(node.value, { sourceType: 'module', plugins: ['jsx'] // FIXME!!! presets: ['env] }); if (t.isFile(ast) && t.isProgram(ast.program) && ast.program.body.length === 1) { const exported = ast.program.body[0]; if (t.isExportNamedDeclaration(exported) && t.isVariableDeclaration(exported.declaration) && exported.declaration.declarations.length === 1) { const declaration = exported.declaration.declarations[0]; if (t.isVariableDeclarator(declaration) && t.isIdentifier(declaration.id)) { const { name } = declaration.id; namedExports[name] = declaration.init; } } } return namedExports; }; function extractExports(root, options) { const namedExports = {}; root.children.forEach(child => { if (child.type === 'jsx') { try { const ast = (0, _parser.parseExpression)(child.value, { plugins: ['jsx'] }); if (t.isJSXOpeningElement(ast.openingElement) && ['Preview', 'Canvas'].includes(ast.openingElement.name.name) && !hasStoryChild(ast)) { const canvasAst = ast.openingElement; canvasAst.attributes.push(t.jsxAttribute(t.jsxIdentifier('mdxSource'), t.stringLiteral(getMdxSource(ast.children)))); } const { code } = generate(ast, {}); // eslint-disable-next-line no-param-reassign child.value = code; } catch { /** 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>` * */ } } else if (child.type === 'export') { Object.assign(namedExports, getNamedExports(child)); } }); // we're overriding default export const storyExports = []; const includeStories = []; let metaExport = null; const context = { counter: 0, storyNameToKey: {}, root, namedExports }; root.children.forEach(n => { const exports = getExports(n, context, options); if (exports) { const { stories, meta } = exports; if (stories) { Object.entries(stories).forEach(([key, story]) => { 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.parameters = { docsOnly: true };'); includeStories.push('__page'); } } else { metaExport = {}; } metaExport.includeStories = JSON.stringify(includeStories); const defaultJsx = (0, _mdxHastToJsx.toJSX)(root, {}, { ...options, skipExport: true }); const fullJsx = ['import { assertIsFn, AddContext } from "@storybook/addon-docs";', defaultJsx, ...storyExports, `const componentMeta = ${stringifyMeta(metaExport)};`, `const mdxStoryNameToKey = ${JSON.stringify(context.storyNameToKey)};`, wrapperJs, 'export default componentMeta;'].join('\n\n'); return fullJsx; } function createCompiler(mdxOptions) { return function compiler(options = {}) { this.Compiler = root => extractExports(root, options); }; }