Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] Support demo previews with comments #3577

Merged
merged 5 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ jobs:
steps:
- checkout
- install_js
- run:
name: '`pnpm docs:typescript:formatted` changes committed?'
command: |
pnpm docs:typescript:formatted --disable-cache
pnpm check-changes
- run:
name: '`pnpm prettier:all` changes committed?'
command: |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<DashboardLayout>
<Box
sx={{
py: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography>Dashboard content goes here.</Typography>
</Box>
</DashboardLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<DashboardLayout>
<Box
sx={{
py: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography>Dashboard content goes here.</Typography>
</Box>
</DashboardLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<DashboardLayout>
<Box
sx={{
py: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography>Dashboard content goes here.</Typography>
</Box>
</DashboardLayout>
6 changes: 5 additions & 1 deletion docs/scripts/formattedTSDemos.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ async function transpileFile(tsxPath, project) {
transformOptions.plugins = transformOptions.plugins.concat([
[
require.resolve('docs/src/modules/utils/babel-plugin-jsx-preview'),
{ maxLines: 16, outputFilename: `${tsxPath}.preview` },
{
maxLines: 16,
outputFilename: `${tsxPath}.preview`,
wrapperTypes: ['div', 'Box', 'Stack', 'AppProvider'],
},
],
]);
}
Expand Down
113 changes: 86 additions & 27 deletions docs/src/modules/utils/babel-plugin-jsx-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ const fs = require('fs');

const pluginName = 'babel-plugin-jsx-preview';

const wrapperTypes = ['div', 'Box', 'Stack'];

/**
* @typedef {import('@babel/core')} babel
*/

/**
*
* @param {babel.NodePath<babel.types.JSXElement>} path
* @param {babel.PluginPass} state
*/
function getPreviewNodes(path) {
function getPreviewNodes(path, state) {
const wrapperTypes = state.opts.wrapperTypes ?? [];
/**
* @type {(babel.types.JSXElement['children'])}
*/
let previewNode = [];
let previewNodes = [];

previewNode = [path.node];
previewNodes = [path.node];
const name = path.get('openingElement').get('name');
if (
name.isJSXIdentifier() &&
Expand All @@ -37,15 +37,76 @@ function getPreviewNodes(path) {
// ^^^^ Blank JSXText including newline
// </Stack>
// )
previewNode = path.node.children.filter((child, index, children) => {
previewNodes = path.node.children.filter((child, index, children) => {
const isSurroundingBlankJSXText =
(index === 0 || index === children.length - 1) &&
child.type === 'JSXText' &&
!/[^\s]+/.test(child.value);
return !isSurroundingBlankJSXText;
});
}
return previewNode;
return previewNodes;
}

/**
*
* @param {string[]} lines
* @returns {string[]}
*/
function trimEmptyLines(lines) {
const start = lines.findIndex((line) => line.trim() !== '');
const end = lines.findLastIndex((line) => line.trim() !== '');
return lines.slice(start, end + 1);
}

/**
*
* @param {string[]} lines
* @returns {string[]}
*/
function dedentLines(lines) {
const trimmedLines = trimEmptyLines(lines);
const indentation = trimmedLines[0]?.match(/^\s*/)?.[0].length ?? 0;
return trimmedLines.map((line) => line.slice(indentation));
}

/** @type {(input: string) => boolean} */
const isPreviewStart = (line) =>
['// preview-start', '{/* preview-start */}'].includes(line.trim());
/** @type {(input: string) => boolean} */
const isPreviewEnd = (line) => ['// preview-end', '{/* preview-end */}'].includes(line.trim());

/**
*
* @param {string} code
* @returns {string | null}
*/
function extractExplicitPreview(code) {
const lines = code.split(/\n/);

const ranges = [];

let start = -1;
for (const [index, line] of lines.entries()) {
if (isPreviewStart(line) && start < 0) {
start = index;
} else if (isPreviewEnd(line) && start >= 0) {
ranges.push([start, index]);
start = -1;
}
}

const previewSections = ranges.map(([startLine, endLine]) => {
const previewLines = lines.slice(startLine + 1, endLine);
const dedentedPreviewLines = dedentLines(previewLines);
return dedentedPreviewLines.join('\n');
});

if (previewSections.length > 0) {
return previewSections.join('\n\n// ...\n\n');
}

return null;
}

/**
Expand All @@ -60,18 +121,7 @@ export default function babelPluginJsxPreview() {
return {
name: pluginName,
visitor: {
JSXElement(path) {
const comments = path.node.leadingComments || [];
const hasComment = comments.some((comment) => comment.value.trim() === 'preview');
if (hasComment) {
previewNodes = getPreviewNodes(path);
}
},
ExportDefaultDeclaration(path) {
if (previewNodes) {
return;
}

ExportDefaultDeclaration(path, state) {
const declarationPath = path.get('declaration');
if (!declarationPath.isFunctionDeclaration()) {
return;
Expand All @@ -83,33 +133,42 @@ export default function babelPluginJsxPreview() {
const returnedJSXPath = lastReturnPath.get('argument');

if (returnedJSXPath.isJSXElement()) {
previewNodes = getPreviewNodes(returnedJSXPath);
previewNodes = getPreviewNodes(returnedJSXPath, state);
}
},
},
post(state) {
const { maxLines, outputFilename } = state.opts.plugins.find((plugin) => {
return plugin.key === pluginName;
}).options;
const previewPlugin = state.opts.plugins?.find((plugin) => plugin.key === pluginName);
if (!previewPlugin) {
throw new Error(`Can't find the ${pluginName} plugin.`);
}

const { maxLines, outputFilename } = previewPlugin.options;

let hasPreview = false;
if (previewNodes.length > 0) {

const explicitPreview = extractExplicitPreview(state.code);

if (explicitPreview) {
fs.writeFileSync(outputFilename, explicitPreview);
hasPreview = true;
} else if (previewNodes.length > 0) {
const startNode = previewNodes[0];
const endNode = previewNodes.slice(-1)[0];
const preview = state.code.slice(startNode.start, endNode.end);
const previewLines = preview.split(/\n/);
// The first line is already trimmed either due to trimmed blank JSXText or because it's a single node which babel already trims.
// The last line is therefore the meassure for indentation
const indentation = previewLines.slice(-1)[0].match(/^\s*/)[0].length;
const deindentedPreviewLines = preview.split(/\n/).map((line, index) => {
const dedentedPreviewLines = preview.split(/\n/).map((line, index) => {
if (index === 0) {
return line;
}
return line.slice(indentation);
});

if (deindentedPreviewLines.length <= maxLines) {
fs.writeFileSync(outputFilename, deindentedPreviewLines.join('\n'));
if (previewLines.length <= maxLines) {
fs.writeFileSync(outputFilename, dedentedPreviewLines.join('\n'));
hasPreview = true;
}
}
Expand Down
Loading