From 03276563f6a48c4de8812a5c5cb5603e6d81e461 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 22 Dec 2021 11:56:14 +0700 Subject: [PATCH 01/37] refactor e2e tests --- test/e2e-website/material-current.spec.ts | 182 ++++++++++++++++++ ...rial-docs.spec.ts => material-new.spec.ts} | 87 ++++----- test/e2e-website/playwright.config.ts | 16 +- 3 files changed, 217 insertions(+), 68 deletions(-) create mode 100644 test/e2e-website/material-current.spec.ts rename test/e2e-website/{material-docs.spec.ts => material-new.spec.ts} (59%) diff --git a/test/e2e-website/material-current.spec.ts b/test/e2e-website/material-current.spec.ts new file mode 100644 index 00000000000000..85627407437648 --- /dev/null +++ b/test/e2e-website/material-current.spec.ts @@ -0,0 +1,182 @@ +import { test as base, expect } from '@playwright/test'; +import kebabCase from 'lodash/kebabCase'; +import FEATURE_TOGGLE from 'docs/src/featureToggle'; +import { TestFixture } from './playwright.config'; + +const test = base.extend({}); + +test.describe.parallel('Material docs', () => { + test('should have correct link with hash in the TOC', async ({ page }) => { + await page.goto(`/getting-started/installation/`); + + const anchors = page.locator('[aria-label="Page table of contents"] ul a'); + + const anchorTexts = await anchors.allTextContents(); + + await Promise.all( + anchorTexts.map((text, index) => { + return expect(anchors.nth(index)).toHaveAttribute( + 'href', + `/getting-started/installation/#${kebabCase(text)}`, + ); + }), + ); + }); + + test.describe.parallel('Demo page', () => { + test('should have correct link for API section', async ({ page }) => { + await page.goto(`/components/cards/`); + + const anchors = await page.locator('div > h2#heading-api ~ ul a'); + + const anchorTexts = await anchors.allTextContents(); + + await Promise.all( + anchorTexts.map((text, index) => { + return expect(anchors.nth(index)).toHaveAttribute( + 'href', + FEATURE_TOGGLE.enable_product_scope + ? `/material/api/mui-material/${kebabCase(text)}` + : `/api/${kebabCase(text)}/`, + ); + }), + ); + }); + + test('should have correct link for sidebar anchor', async ({ page }) => { + await page.goto(`/components/cards/`); + + const anchor = await page.locator('nav[aria-label="documentation"] ul a:text-is("Card")'); + + await expect(anchor).toHaveAttribute( + 'href', + FEATURE_TOGGLE.enable_product_scope ? `/material/react-cards/` : `/components/cards/`, + ); + }); + }); + + test.describe.parallel('API page', () => { + test('should have correct link for sidebar anchor', async ({ page }) => { + await page.goto(`/api/card/`); + + const anchor = await page.locator('nav[aria-label="documentation"] ul a:text-is("Card")'); + + await expect(anchor).toHaveAttribute('app-drawer-active', ''); + await expect(anchor).toHaveAttribute( + 'href', + FEATURE_TOGGLE.enable_product_scope ? `/material/api/mui-material/card/` : `/api/card/`, + ); + }); + + test('all the links in the main content should have correct prefix', async ({ page }) => { + await page.goto(`/api/card/`); + + const anchors = await page.locator('div#main-content a'); + + const handles = await anchors.elementHandles(); + + const links = await Promise.all(handles.map((elm) => elm.getAttribute('href'))); + + if (FEATURE_TOGGLE.enable_product_scope) { + links.forEach((link) => { + if ( + ['/getting-started', '/customization', '/guides', '/discover-more'].some((path) => + link.includes(path), + ) + ) { + expect(link.startsWith(`/material`)).toBeTruthy(); + } + + if (link.startsWith('/material') && link.includes('api')) { + expect(link).toMatch(/\/material\/api\/mui-(material|lab)\/.*/); + } + + expect(link).not.toMatch(/components/); // there should be no `components` in the url anymore + + if (link.startsWith('/system')) { + expect(link.startsWith('/system')).toBeTruthy(); + expect(link.match(/\/system{1}/g)).toHaveLength(1); // should not have repeated `/system/system/*` + } + + if (link.startsWith('/styles')) { + expect(link.startsWith('/styles')).toBeTruthy(); + expect(link.match(/\/styles{1}/g)).toHaveLength(1); // should not have repeated `/system/system/*` + } + }); + } else { + links.forEach((link) => { + expect(link.startsWith('/material')).toBeFalsy(); + }); + } + }); + }); + + test.describe.parallel('Search', () => { + test('should have correct link when searching component', async ({ page }) => { + await page.goto(`/getting-started/installation/`, { waitUntil: 'networkidle' }); + + try { + await page.keyboard.press('Meta+k'); + await page.waitForSelector('input#docsearch-input', { timeout: 2000 }); + } catch (error) { + await page.keyboard.press('Meta+k'); // retry + } + + await page.type('input#docsearch-input', 'card', { delay: 50 }); + + const anchor = await page.locator('.DocSearch-Hits a:has-text("Card")'); + + if (FEATURE_TOGGLE.enable_product_scope) { + // the old url doc should point to the new location + await expect(anchor.first()).toHaveAttribute('href', `/material/react-cards/#main-content`); + } else { + await expect(anchor.first()).toHaveAttribute('href', `/components/cards/#main-content`); + } + }); + + test('should have correct link when searching API', async ({ page }) => { + await page.goto(`/getting-started/installation/`, { waitUntil: 'networkidle' }); + + try { + await page.keyboard.press('Meta+k'); + await page.waitForSelector('input#docsearch-input', { timeout: 2000 }); + } catch (error) { + await page.keyboard.press('Meta+k'); // retry + } + + await page.type('input#docsearch-input', 'card api', { delay: 50 }); + + const anchor = await page.locator('.DocSearch-Hits a:has-text("Card API")'); + + if (FEATURE_TOGGLE.enable_product_scope) { + await expect(anchor.first()).toHaveAttribute( + 'href', + `/api/mui-material/card/#main-content`, + ); + } else { + await expect(anchor.first()).toHaveAttribute('href', `/api/card/#main-content`); + } + }); + + test('should have correct link when searching lab API', async ({ page }) => { + await page.goto(`/getting-started/installation/`); + + await page.waitForLoadState('networkidle'); // wait for docsearch + + await page.keyboard.press('Meta+k'); + + await page.type('input#docsearch-input', 'loading api', { delay: 50 }); + + const anchor = await page.locator('.DocSearch-Hits a:has-text("LoadingButton API")'); + + if (FEATURE_TOGGLE.enable_product_scope) { + await expect(anchor.first()).toHaveAttribute( + 'href', + `/api/mui-lab/loading-button/#main-content`, + ); + } else { + await expect(anchor.first()).toHaveAttribute('href', `/api/loading-button/#main-content`); + } + }); + }); +}); diff --git a/test/e2e-website/material-docs.spec.ts b/test/e2e-website/material-new.spec.ts similarity index 59% rename from test/e2e-website/material-docs.spec.ts rename to test/e2e-website/material-new.spec.ts index e73f032deb4c96..43d4dcacdc0fbf 100644 --- a/test/e2e-website/material-docs.spec.ts +++ b/test/e2e-website/material-new.spec.ts @@ -5,16 +5,13 @@ import { TestFixture } from './playwright.config'; const test = base.extend({}); -test.beforeEach(async ({ materialUrlPrefix }) => { - test.skip( - !!materialUrlPrefix && !FEATURE_TOGGLE.enable_product_scope, - "Migration haven't started yet", - ); +test.beforeEach(async ({}) => { + test.skip(!FEATURE_TOGGLE.enable_product_scope, "Migration haven't started yet"); }); test.describe.parallel('Material docs', () => { - test('should have correct link with hash in the TOC', async ({ page, materialUrlPrefix }) => { - await page.goto(`${materialUrlPrefix}/getting-started/installation/`); + test('should have correct link with hash in the TOC', async ({ page }) => { + await page.goto(`/material/getting-started/installation/`); const anchors = page.locator('[aria-label="Page table of contents"] ul a'); @@ -24,15 +21,15 @@ test.describe.parallel('Material docs', () => { anchorTexts.map((text, index) => { return expect(anchors.nth(index)).toHaveAttribute( 'href', - `${materialUrlPrefix}/getting-started/installation/#${kebabCase(text)}`, + `/material/getting-started/installation/#${kebabCase(text)}`, ); }), ); }); test.describe.parallel('Demo page', () => { - test('should have correct link for API section', async ({ page, materialUrlPrefix }) => { - await page.goto(`${materialUrlPrefix}/components/cards/`); + test('should have correct link for API section', async ({ page }) => { + await page.goto(`/material/react-cards/`); const anchors = await page.locator('div > h2#heading-api ~ ul a'); @@ -42,36 +39,33 @@ test.describe.parallel('Material docs', () => { anchorTexts.map((text, index) => { return expect(anchors.nth(index)).toHaveAttribute( 'href', - `${materialUrlPrefix}/api/${kebabCase(text)}/`, + `/material/api/mui-material/${kebabCase(text)}/`, ); }), ); }); - test('should have correct link for sidebar anchor', async ({ page, materialUrlPrefix }) => { - await page.goto(`${materialUrlPrefix}/components/cards/`); + test('should have correct link for sidebar anchor', async ({ page }) => { + await page.goto(`/material/react-cards/`); const anchor = await page.locator('nav[aria-label="documentation"] ul a:text-is("Card")'); - await expect(anchor).toHaveAttribute('href', `${materialUrlPrefix}/components/cards/`); + await expect(anchor).toHaveAttribute('href', `/material/react-cards/`); }); }); test.describe.parallel('API page', () => { - test('should have correct link for sidebar anchor', async ({ page, materialUrlPrefix }) => { - await page.goto(`${materialUrlPrefix}/api/card/`); + test('should have correct link for sidebar anchor', async ({ page }) => { + await page.goto(`/material/api/mui-material/card/`); const anchor = await page.locator('nav[aria-label="documentation"] ul a:text-is("Card")'); await expect(anchor).toHaveAttribute('app-drawer-active', ''); - await expect(anchor).toHaveAttribute('href', `${materialUrlPrefix}/api/card/`); + await expect(anchor).toHaveAttribute('href', `/material/api/mui-material/card/`); }); - test('all the links in the main content should have correct prefix', async ({ - page, - materialUrlPrefix, - }) => { - await page.goto(`${materialUrlPrefix}/api/card/`); + test('all the links in the main content should have correct prefix', async ({ page }) => { + await page.goto(`/material/api/mui-material/card/`); const anchors = await page.locator('div#main-content a'); @@ -81,24 +75,25 @@ test.describe.parallel('Material docs', () => { links.forEach((link) => { if ( - [ - '/getting-started', - '/components', - '/api', - '/customization', - '/guides', - '/discover-more', - ].some((path) => link.replace(materialUrlPrefix, '').startsWith(path)) + ['/getting-started', '/customization', '/guides', '/discover-more'].some((path) => + link.includes(path), + ) ) { - expect(link.startsWith(materialUrlPrefix)).toBeTruthy(); + expect(link.startsWith(`/material`)).toBeTruthy(); } - if (link.replace(materialUrlPrefix, '').startsWith('/system')) { + if (link.startsWith('/material') && link.includes('api')) { + expect(link).toMatch(/\/material\/api\/mui-(material|lab)\/.*/); + } + + expect(link).not.toMatch(/components/); // there should be no `components` in the url anymore + + if (link.startsWith('/system')) { expect(link.startsWith('/system')).toBeTruthy(); expect(link.match(/\/system{1}/g)).toHaveLength(1); // should not have repeated `/system/system/*` } - if (link.replace(materialUrlPrefix, '').startsWith('/styles')) { + if (link.startsWith('/styles')) { expect(link.startsWith('/styles')).toBeTruthy(); expect(link.match(/\/styles{1}/g)).toHaveLength(1); // should not have repeated `/system/system/*` } @@ -107,11 +102,8 @@ test.describe.parallel('Material docs', () => { }); test.describe.parallel('Search', () => { - test('should have correct link when searching component', async ({ - page, - materialUrlPrefix, - }) => { - await page.goto(`${materialUrlPrefix}/getting-started/installation/`); + test('should have correct link when searching component', async ({ page }) => { + await page.goto(`/material/getting-started/installation/`); await page.waitForLoadState('networkidle'); // wait for docsearch @@ -121,22 +113,11 @@ test.describe.parallel('Material docs', () => { const anchor = await page.locator('.DocSearch-Hits a:has-text("Card")'); - if (FEATURE_TOGGLE.enable_product_scope && !materialUrlPrefix) { - // the old url doc should point to the new location - await expect(anchor.first()).toHaveAttribute( - 'href', - `/material/components/cards/#main-content`, - ); - } else { - await expect(anchor.first()).toHaveAttribute( - 'href', - `${materialUrlPrefix}/components/cards/#main-content`, - ); - } + await expect(anchor.first()).toHaveAttribute('href', `/material/react-cards/#main-content`); }); - test('should have correct link when searching API', async ({ page, materialUrlPrefix }) => { - await page.goto(`${materialUrlPrefix}/getting-started/installation/`); + test('should have correct link when searching API', async ({ page }) => { + await page.goto(`/material/getting-started/installation/`); await page.waitForLoadState('networkidle'); // wait for docsearch @@ -148,7 +129,7 @@ test.describe.parallel('Material docs', () => { await expect(anchor.first()).toHaveAttribute( 'href', - `${materialUrlPrefix}/api/card/#main-content`, + `/material/api/mui-material/card/#main-content`, ); }); }); diff --git a/test/e2e-website/playwright.config.ts b/test/e2e-website/playwright.config.ts index 3e037ae1ff2e6d..817b0d5975a5e3 100644 --- a/test/e2e-website/playwright.config.ts +++ b/test/e2e-website/playwright.config.ts @@ -1,6 +1,6 @@ import { PlaywrightTestConfig } from '@playwright/test'; -export type TestFixture = { materialUrlPrefix: string }; +export type TestFixture = {}; const config: PlaywrightTestConfig = { reportSlowTests: { @@ -10,20 +10,6 @@ const config: PlaywrightTestConfig = { use: { baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'https://mui.com', }, - projects: [ - { - name: 'current', - use: { - materialUrlPrefix: '', - }, - }, - { - name: 'new', - use: { - materialUrlPrefix: '/material', - }, - }, - ], }; export default config; From 73c3a3a3a8c1e43462a055283d29b80e748d56fd Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 22 Dec 2021 14:05:45 +0700 Subject: [PATCH 02/37] refactor findActivePage --- docs/pages/_app.js | 28 +----- docs/src/modules/utils/findActivePage.test.js | 96 +++++++++++++++++++ docs/src/modules/utils/findActivePage.ts | 16 ++++ 3 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 docs/src/modules/utils/findActivePage.test.js create mode 100644 docs/src/modules/utils/findActivePage.ts diff --git a/docs/pages/_app.js b/docs/pages/_app.js index d506bd7ddb6ace..9b4ef1c4ae47b1 100644 --- a/docs/pages/_app.js +++ b/docs/pages/_app.js @@ -7,7 +7,6 @@ LicenseInfo.setLicenseKey(process.env.NEXT_PUBLIC_MUI_LICENSE); import 'docs/src/modules/components/bootstrap'; // --- Post bootstrap ----- import * as React from 'react'; -import find from 'lodash/find'; import { loadCSS } from 'fg-loadcss/src/loadCSS'; import NextHead from 'next/head'; import PropTypes from 'prop-types'; @@ -27,6 +26,7 @@ import { } from 'docs/src/modules/utils/i18n'; import DocsStyledEngineProvider from 'docs/src/modules/utils/StyledEngineProvider'; import createEmotionCache from 'docs/src/createEmotionCache'; +import findActivePage from 'docs/src/modules/utils/findActivePage'; // Client-side cache, shared for the whole session of the user in the browser. const clientSideEmotionCache = createEmotionCache(); @@ -156,32 +156,6 @@ Tip: you can access the documentation \`theme\` object directly in the console. 'font-family:monospace;color:#1976d2;font-size:12px;', ); } - -function findActivePage(currentPages, pathname) { - const activePage = find(currentPages, (page) => { - if (page.children) { - if (pathname.indexOf(`${page.pathname}/`) === 0) { - // Check if one of the children matches (for /components) - return findActivePage(page.children, pathname); - } - } - - // Should be an exact match if no children - return pathname === page.pathname; - }); - - if (!activePage) { - return null; - } - - // We need to drill down - if (activePage.pathname !== pathname) { - return findActivePage(activePage.children, pathname); - } - - return activePage; -} - function AppWrapper(props) { const { children, emotionCache, pageProps } = props; diff --git a/docs/src/modules/utils/findActivePage.test.js b/docs/src/modules/utils/findActivePage.test.js new file mode 100644 index 00000000000000..47819675bd67c3 --- /dev/null +++ b/docs/src/modules/utils/findActivePage.test.js @@ -0,0 +1,96 @@ +import { expect } from 'chai'; +import findActivePage from './findActivePage'; + +describe('findActivePage', () => { + describe('old structure', () => { + const pages = [ + { + pathname: '/getting-started', + icon: 'DescriptionIcon', + children: [{ pathname: '/getting-started/installation' }], + }, + { + pathname: '/components', + icon: 'ToggleOnIcon', + children: [ + { + pathname: '/components', + subheader: '/components/inputs', + children: [ + { pathname: '/components/autocomplete' }, + { pathname: '/components/buttons', title: 'Button' }, + { pathname: '/components/button-group' }, + { pathname: '/components/checkboxes', title: 'Checkbox' }, + { pathname: '/components/floating-action-button' }, + { pathname: '/components/radio-buttons', title: 'Radio button' }, + { pathname: '/components/rating' }, + { pathname: '/components/selects', title: 'Select' }, + { pathname: '/components/slider' }, + { pathname: '/components/switches', title: 'Switch' }, + { pathname: '/components/text-fields', title: 'Text field' }, + { pathname: '/components/transfer-list' }, + { pathname: '/components/toggle-button' }, + ], + }, + ], + }, + ]; + it('return first level page', () => { + expect(findActivePage(pages, '/getting-started')).to.deep.equal({ + pathname: '/getting-started', + icon: 'DescriptionIcon', + children: [{ pathname: '/getting-started/installation' }], + }); + }); + + it('return nested page', () => { + expect(findActivePage(pages, '/getting-started/installation')).to.deep.equal({ + pathname: '/getting-started/installation', + }); + }); + + it('return deep nested page', () => { + expect(findActivePage(pages, '/components/radio-buttons')).to.deep.equal({ + pathname: '/components/radio-buttons', + title: 'Radio button', + }); + }); + }); + + describe('new structure', () => { + const pages = [ + { + pathname: '/material/components', + icon: 'ToggleOnIcon', + children: [ + { + pathname: '/material/components', + subheader: '/components/inputs', + children: [ + { pathname: '/material/react-autocomplete' }, + { pathname: '/material/react-buttons', title: 'Button' }, + { pathname: '/material/react-button-group' }, + { pathname: '/material/react-checkboxes', title: 'Checkbox' }, + { pathname: '/material/react-floating-action-button' }, + { pathname: '/material/react-radio-buttons', title: 'Radio button' }, + { pathname: '/material/react-rating' }, + { pathname: '/material/react-selects', title: 'Select' }, + { pathname: '/material/react-slider' }, + { pathname: '/material/react-switches', title: 'Switch' }, + { pathname: '/material/react-text-fields', title: 'Text field' }, + { pathname: '/material/react-transfer-list' }, + { pathname: '/material/react-toggle-button' }, + ], + }, + ], + }, + ]; + + it('return deep nested page', () => { + expect(findActivePage(pages, '/material/react-radio-buttons')).to.deep.equal({ + pathname: '/material/react-radio-buttons', + title: 'Radio button', + }); + }); + }); +}); diff --git a/docs/src/modules/utils/findActivePage.ts b/docs/src/modules/utils/findActivePage.ts new file mode 100644 index 00000000000000..9d9edd1ef54f3c --- /dev/null +++ b/docs/src/modules/utils/findActivePage.ts @@ -0,0 +1,16 @@ +import { MuiPage } from 'docs/src/pages'; + +export default function findActivePage(currentPages: MuiPage[], pathname: string): MuiPage | null { + const map: Record = {}; + + const traverse = (array: MuiPage[]) => { + array.forEach((item) => { + map[item.pathname] = item; + traverse(item.children || []); + }); + }; + + traverse(currentPages); + + return map[pathname] || null; +} From dc62e59c07a329f63db7f3e612b398e828fcec03 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 22 Dec 2021 14:29:29 +0700 Subject: [PATCH 03/37] add new pages data --- docs/data/material/pages.ts | 239 +++++++++++++++++++++++++++++++++ docs/data/material/pagesApi.ts | 1 + docs/data/styles/pages.ts | 14 ++ docs/data/system/pages.ts | 27 ++++ 4 files changed, 281 insertions(+) create mode 100644 docs/data/material/pages.ts create mode 100644 docs/data/material/pagesApi.ts create mode 100644 docs/data/styles/pages.ts create mode 100644 docs/data/system/pages.ts diff --git a/docs/data/material/pages.ts b/docs/data/material/pages.ts new file mode 100644 index 00000000000000..43a33b5595350f --- /dev/null +++ b/docs/data/material/pages.ts @@ -0,0 +1,239 @@ +import pagesApi from './pagesApi'; + +const pages = [ + { + pathname: '/material/getting-started', + icon: 'DescriptionIcon', + children: [ + { pathname: '/material/getting-started/installation' }, + { pathname: '/material/getting-started/usage' }, + { pathname: '/material/getting-started/example-projects' }, + { pathname: '/material/getting-started/templates' }, + { pathname: '/material/getting-started/learn' }, + { pathname: '/material/getting-started/faq', title: 'FAQs' }, + { pathname: '/material/getting-started/supported-components' }, + { pathname: '/material/getting-started/supported-platforms' }, + { pathname: '/material/getting-started/support' }, + ], + }, + { + pathname: '/material/components', + icon: 'ToggleOnIcon', + children: [ + { + pathname: '/material/components/inputs', + subheader: 'inputs', + children: [ + { pathname: '/material/react-autocomplete' }, + { pathname: '/material/react-buttons', title: 'Button' }, + { pathname: '/material/react-button-group' }, + { pathname: '/material/react-checkboxes', title: 'Checkbox' }, + { pathname: '/material/react-floating-action-button' }, + { pathname: '/material/react-radio-buttons', title: 'Radio button' }, + { pathname: '/material/react-rating' }, + { pathname: '/material/react-selects', title: 'Select' }, + { pathname: '/material/react-slider' }, + { pathname: '/material/react-switches', title: 'Switch' }, + { pathname: '/material/react-text-fields', title: 'Text field' }, + { pathname: '/material/react-transfer-list' }, + { pathname: '/material/react-toggle-button' }, + ], + }, + { + pathname: '/material/components/data-display', + subheader: 'data-display', + children: [ + { pathname: '/material/react-avatars', title: 'Avatar' }, + { pathname: '/material/react-badges', title: 'Badge' }, + { pathname: '/material/react-chips', title: 'Chip' }, + { pathname: '/material/react-dividers', title: 'Divider' }, + { pathname: '/material/react-icons' }, + { pathname: '/material/react-material-icons' }, + { pathname: '/material/react-lists', title: 'List' }, + { pathname: '/material/react-tables', title: 'Table' }, + { pathname: '/material/react-tooltips', title: 'Tooltip' }, + { pathname: '/material/react-typography' }, + ], + }, + { + pathname: '/material/components/feedback', + subheader: 'feedback', + children: [ + { pathname: '/material/react-alert' }, + { pathname: '/material/react-backdrop' }, + { pathname: '/material/react-dialogs' }, + { pathname: '/material/react-progress' }, + { pathname: '/material/react-skeleton' }, + { pathname: '/material/react-snackbars', title: 'Snackbar' }, + ], + }, + { + pathname: '/material/components/surfaces', + subheader: 'surfaces', + children: [ + { pathname: '/material/react-accordion' }, + { pathname: '/material/react-app-bar' }, + { pathname: '/material/react-cards', title: 'Card' }, + { pathname: '/material/react-paper' }, + ], + }, + { + pathname: '/material/components/navigation', + subheader: 'navigation', + children: [ + { pathname: '/material/react-bottom-navigation' }, + { pathname: '/material/react-breadcrumbs' }, + { pathname: '/material/react-drawers', title: 'Drawer' }, + { pathname: '/material/react-links', title: 'Link' }, + { pathname: '/material/react-menus', title: 'Menu' }, + { pathname: '/material/react-pagination' }, + { pathname: '/material/react-speed-dial' }, + { pathname: '/material/react-steppers', title: 'Stepper' }, + { pathname: '/material/react-tabs' }, + ], + }, + { + pathname: '/material/components/layout', + subheader: 'layout', + children: [ + { pathname: '/material/react-box' }, + { pathname: '/material/react-container' }, + { pathname: '/material/react-grid' }, + { pathname: '/material/react-stack' }, + { pathname: '/material/react-image-list' }, + { pathname: '/material/react-hidden' }, + ], + }, + { + pathname: '/material/components/utils', + subheader: 'utils', + children: [ + { pathname: '/material/react-click-away-listener' }, + { pathname: '/material/react-css-baseline', title: 'CSS Baseline' }, + { pathname: '/material/react-modal' }, + { pathname: '/material/react-no-ssr', title: 'No SSR' }, + { pathname: '/material/react-popover' }, + { pathname: '/material/react-popper' }, + { pathname: '/material/react-portal' }, + { pathname: '/material/react-textarea-autosize' }, + { pathname: '/material/react-transitions' }, + { pathname: '/material/react-use-media-query', title: 'useMediaQuery' }, + ], + }, + { + pathname: '/x/data-grid', + subheader: 'data-grid', + }, + { + pathname: '/material/lab', + subheader: 'lab', + children: [ + { pathname: '/material/about-the-lab', title: 'About the lab 🧪' }, + { + pathname: '/material/lab/pickers', + subheader: 'pickers', + title: 'Date / Time', + children: [ + { pathname: '/material/react-pickers', title: 'Introduction' }, + { pathname: '/material/react-date-picker' }, + { + pathname: '/material/react-date-range-picker', + title: 'Date Range Picker ⚡️', + }, + { pathname: '/material/react-date-time-picker' }, + { pathname: '/material/react-time-picker' }, + ], + }, + { pathname: '/material/react-masonry' }, + { pathname: '/material/react-timeline' }, + { pathname: '/material/react-trap-focus' }, + { pathname: '/material/react-tree-view' }, + ], + }, + ], + }, + { + title: 'Component API', + pathname: '/material/api', + icon: 'CodeIcon', + children: [ + ...pagesApi, + { + pathname: '/x/api/mui-data-grid', + title: 'Data Grid', + }, + ], + }, + { + pathname: '/material/customization', + icon: 'CreateIcon', + children: [ + { + pathname: '/material/customization', + subheader: '/material/customization/theme', + children: [ + { pathname: '/material/customization/theming' }, + { pathname: '/material/customization/palette' }, + { pathname: '/material/customization/dark-mode', title: 'Dark mode' }, + { pathname: '/material/customization/typography' }, + { pathname: '/material/customization/spacing' }, + { pathname: '/material/customization/breakpoints' }, + { pathname: '/material/customization/density' }, + { pathname: '/material/customization/z-index', title: 'z-index' }, + { pathname: '/material/customization/transitions' }, + { pathname: '/material/customization/theme-components', title: 'Components' }, + { pathname: '/material/customization/default-theme', title: 'Default Theme' }, + ], + }, + { pathname: '/material/customization/how-to-customize' }, + { pathname: '/material/customization/color' }, + { pathname: '/material/customization/unstyled-components' }, + ], + }, + { + pathname: '/material/guides', + title: 'How To Guides', + icon: 'VisibilityIcon', + children: [ + { pathname: '/material/guides/api', title: 'API Design Approach' }, + { pathname: '/material/guides/classname-generator', title: 'ClassName Generator' }, + { pathname: '/material/guides/understand-mui-packages', title: 'Understand MUI packages' }, + { pathname: '/material/guides/typescript', title: 'TypeScript' }, + { pathname: '/material/guides/interoperability', title: 'Style Library Interoperability' }, + { pathname: '/material/guides/styled-engine' }, + { pathname: '/material/guides/minimizing-bundle-size' }, + { pathname: '/material/guides/composition' }, + { pathname: '/material/guides/routing' }, + { pathname: '/material/guides/server-rendering' }, + { pathname: '/material/guides/responsive-ui', title: 'Responsive UI' }, + { + pathname: '/material/guides/pickers-migration', + title: 'Migration from @material-ui/pickers', + }, + { pathname: '/material/guides/migration-v4', title: 'Migration From v4' }, + { pathname: '/material/guides/migration-v3', title: 'Migration From v3' }, + { pathname: '/material/guides/migration-v0x', title: 'Migration From v0.x' }, + { pathname: '/material/guides/testing' }, + { pathname: '/material/guides/localization' }, + { pathname: '/material/guides/content-security-policy', title: 'Content Security Policy' }, + { pathname: '/material/guides/right-to-left', title: 'Right-to-left' }, + { pathname: '/material/guides/flow' }, + ], + }, + { + pathname: '/material/discover-more', + icon: 'AddIcon', + children: [ + { pathname: '/material/discover-more/showcase' }, + { pathname: '/material/discover-more/related-projects' }, + { pathname: '/material/discover-more/roadmap' }, + { pathname: '/material/discover-more/backers', title: 'Sponsors & Backers' }, + { pathname: '/material/discover-more/vision' }, + { pathname: '/material/discover-more/changelog' }, + { pathname: '/material/discover-more/languages' }, + { pathname: '/about', title: 'About us' }, + ], + }, +]; + +export default pages; diff --git a/docs/data/material/pagesApi.ts b/docs/data/material/pagesApi.ts new file mode 100644 index 00000000000000..e0a30c5dfa3e4f --- /dev/null +++ b/docs/data/material/pagesApi.ts @@ -0,0 +1 @@ +module.exports = []; diff --git a/docs/data/styles/pages.ts b/docs/data/styles/pages.ts new file mode 100644 index 00000000000000..7ff5a6e8c4ce93 --- /dev/null +++ b/docs/data/styles/pages.ts @@ -0,0 +1,14 @@ +const pages = [ + { + pathname: '/styles', + title: 'Styles (legacy)', + icon: 'StyleIcon', + children: [ + { pathname: '/styles/basics' }, + { pathname: '/styles/advanced' }, + { pathname: '/styles/api', title: 'API' }, + ], + }, +]; + +export default pages; diff --git a/docs/data/system/pages.ts b/docs/data/system/pages.ts new file mode 100644 index 00000000000000..a497e79626902b --- /dev/null +++ b/docs/data/system/pages.ts @@ -0,0 +1,27 @@ +const pages = [ + { + pathname: '/system', + icon: 'BuildIcon', + children: [ + { pathname: '/system/basics' }, + { pathname: '/system/properties' }, + { pathname: '/system/the-sx-prop', title: 'The sx prop' }, + { pathname: '/system/borders' }, + { pathname: '/system/display' }, + { pathname: '/system/flexbox' }, + { pathname: '/system/grid' }, + { pathname: '/system/palette' }, + { pathname: '/system/positions' }, + { pathname: '/system/shadows' }, + { pathname: '/system/sizing' }, + { pathname: '/system/spacing' }, + { pathname: '/system/screen-readers' }, + { pathname: '/system/typography' }, + { pathname: '/system/advanced' }, + { pathname: '/system/box' }, + { pathname: '/system/styled', title: 'styled' }, + ], + }, +]; + +export default pages; From 371864be2466ab3e279b7360ddca076e8353f03e Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 22 Dec 2021 15:16:20 +0700 Subject: [PATCH 04/37] refactor restructure --- docs/scripts/restructure.ts | 102 ++++++++------------------ docs/scripts/restructureUtils.test.ts | 35 +++------ docs/scripts/restructureUtils.ts | 40 +++++----- 3 files changed, 59 insertions(+), 118 deletions(-) diff --git a/docs/scripts/restructure.ts b/docs/scripts/restructure.ts index 7a1177a3c2ab8f..64d129bfd6006b 100644 --- a/docs/scripts/restructure.ts +++ b/docs/scripts/restructure.ts @@ -1,9 +1,7 @@ import fs from 'fs'; import path from 'path'; import prettier from 'prettier'; -import pages from 'docs/src/pages'; import { - refactorJsonContent, getNewDataLocation, getNewPageLocation, productPathnames, @@ -29,35 +27,6 @@ function writePrettifiedFile(filename: string, data: string, options: object = { }); } -const prefixSource = (arraySource: string, pathnames: string[], product: string) => { - let target = arraySource; - - // prefix with `/${product}/` - pathnames.forEach((pathname) => { - const replace = `"${pathname}/([-/a-z]*)"`; - target = target.replace(new RegExp(replace, 'g'), `"/${product}${pathname}/$1"`); - target = target.replace( - new RegExp(`"pathname":"${pathname}"`, 'g'), - `"pathname":"/${product}${pathname}"`, - ); - }); - - return target; -}; - -const createProductPagesData = (arraySource: string, product: string) => { - // prepare source code - const source = ` -const pages = ${arraySource} - -export default pages - `; - - // create new folder and add prettified file. - fs.mkdirSync(`${workspaceRoot}/docs/data/${product}`, { recursive: true }); - writePrettifiedFile(`${workspaceRoot}/docs/data/${product}/pages.ts`, source); -}; - const appendSource = (target: string, template: string, source: string) => { const match = source.match(/^(.*)$/m); if (match && target.includes(match[0])) { @@ -109,65 +78,52 @@ function run() { */ (['styles', 'system', 'material'] as const).forEach((product) => { const pathnames = productPathnames[product] as unknown as string[]; - const productPages = pages.filter((item) => pathnames.includes(item.pathname)); - - let arraySource = JSON.stringify(productPages); - - if (product === 'material') { - arraySource = prefixSource(arraySource, [...pathnames, '/api'], 'material'); - } - - createProductPagesData(arraySource, product); // update _app.js to use product pages updateAppToUseProductPagesData(product); pathnames.forEach((pathname) => { - if (pathname !== '/api-docs') { - // clone js/md data to new location - const dataDir = readdirDeep(path.resolve(`docs/src/pages${pathname}`)); - dataDir.forEach((filePath) => { - const info = getNewDataLocation(filePath, product); - // pathname could be a directory - if (info) { - let data = fs.readFileSync(filePath, { encoding: 'utf-8' }); - if (filePath.endsWith('.md')) { - data = markdown.removeDemoRelativePath(data); - data = markdown.addMaterialPrefixToLinks(data); - if (product === 'material') { - data = markdown.addProductFrontmatter(data, 'material'); - } + // clone js/md data to new location + const dataDir = readdirDeep(path.resolve(`docs/src/pages${pathname}`)); + dataDir.forEach((filePath) => { + const info = getNewDataLocation(filePath, product); + // pathname could be a directory + if (info) { + let data = fs.readFileSync(filePath, { encoding: 'utf-8' }); + if (filePath.endsWith('.md')) { + data = markdown.removeDemoRelativePath(data); + data = markdown.addMaterialPrefixToLinks(data); + if (product === 'material') { + data = markdown.addProductFrontmatter(data, 'material'); } - fs.mkdirSync(info.directory, { recursive: true }); - fs.writeFileSync(info.path, data); // (A) } - }); - } + fs.mkdirSync(info.directory, { recursive: true }); + fs.writeFileSync(info.path, data); // (A) + } + }); const pagesDir = readdirDeep(path.resolve(`docs/pages${pathname}`)); pagesDir.forEach((filePath) => { if (product === 'material') { - // clone pages to new location - const info = getNewPageLocation(filePath); - // pathname could be a directory - if (info) { - let data = fs.readFileSync(filePath, { encoding: 'utf-8' }); - - if (filePath.endsWith('.json')) { - data = refactorJsonContent(data); - } + if (!filePath.includes('api-docs')) { + // clone pages to new location + const info = getNewPageLocation(filePath); + // pathname could be a directory + if (info) { + let data = fs.readFileSync(filePath, { encoding: 'utf-8' }); + + if (filePath.endsWith('.js')) { + data = data.replace('/src/pages/', `/data/${product}/`); // point to data path (A) in new directory + } - if (filePath.endsWith('.js')) { - data = data.replace('/src/pages/', `/products/${product}/`); // point to data path (A) in new directory + fs.mkdirSync(info.directory, { recursive: true }); + fs.writeFileSync(info.path, data); } - - fs.mkdirSync(info.directory, { recursive: true }); - fs.writeFileSync(info.path, data); } } else { let data = fs.readFileSync(filePath, { encoding: 'utf-8' }); if (filePath.endsWith('.js')) { - data = data.replace('/src/pages/', `/products/`); // point to data path (A) in new directory + data = data.replace('/src/pages/', `/data/`); // point to data path (A) in new directory } fs.writeFileSync(filePath, data); } diff --git a/docs/scripts/restructureUtils.test.ts b/docs/scripts/restructureUtils.test.ts index cc1f2f33474a40..0b84ce8ae56cf7 100644 --- a/docs/scripts/restructureUtils.test.ts +++ b/docs/scripts/restructureUtils.test.ts @@ -1,10 +1,5 @@ import { expect } from 'chai'; -import { - markdown, - refactorJsonContent, - getNewDataLocation, - getNewPageLocation, -} from './restructureUtils'; +import { markdown, getNewDataLocation, getNewPageLocation } from './restructureUtils'; describe('restructure utils', () => { describe('refactorMarkdownContent', () => { @@ -71,24 +66,6 @@ githubLabel: 'component: Avatar' }); }); - describe('refactorJsonContent', () => { - it('add prefix to demos value', () => { - expect( - refactorJsonContent( - `"demos": ""`, - ), - ).to.equal( - `"demos": ""`, - ); - }); - - it('add prefix to pathname value', () => { - expect( - refactorJsonContent(`"inheritance": { "component": "Paper", "pathname": "/api/paper/" },`), - ).to.equal(`"inheritance": { "component": "Paper", "pathname": "/material/api/paper/" },`); - }); - }); - it('getNewDataLocation', () => { expect( getNewDataLocation( @@ -115,5 +92,15 @@ githubLabel: 'component: Avatar' directory: 'material-ui/docs/pages/material/getting-started', path: 'material-ui/docs/pages/material/getting-started/installation.js', }); + + expect(getNewPageLocation('material-ui/docs/pages/components/buttons.js')).to.deep.equal({ + directory: 'material-ui/docs/pages/material', + path: 'material-ui/docs/pages/material/react-buttons.js', + }); + + expect(getNewPageLocation('material-ui/docs/pages/components/about-the-lab.js')).to.deep.equal({ + directory: 'material-ui/docs/pages/material', + path: 'material-ui/docs/pages/material/about-the-lab.js', + }); }); }); diff --git a/docs/scripts/restructureUtils.ts b/docs/scripts/restructureUtils.ts index 6d854fc72def48..822adf3f637bed 100644 --- a/docs/scripts/restructureUtils.ts +++ b/docs/scripts/restructureUtils.ts @@ -1,12 +1,5 @@ export const productPathnames = { - material: [ - '/getting-started', - '/components', - '/api-docs', - '/customization', - '/guides', - '/discover-more', - ], + material: ['/getting-started', '/components', '/customization', '/guides', '/discover-more'], system: ['/system'], styles: ['/styles'], } as const; @@ -16,7 +9,10 @@ export const markdown = { content.replace(/"pages\/[/\-a-zA-Z]*\/([a-zA-Z]*\.js)"/gm, `"$1"`), addMaterialPrefixToLinks: (content: string) => { productPathnames.material.forEach((path) => { - content = content.replace(new RegExp(`\\(${path}`, 'g'), `(/material${path}`); + content = content.replace( + new RegExp(`\\(${path}`, 'g'), + `(/material${path.replace('/components/', '/react-')}`, + ); }); return content; }, @@ -24,18 +20,6 @@ export const markdown = { content.replace('---', `---\nproduct: ${product}`), }; -export const refactorJsonContent = (content: string) => { - let result = content; - - // i. add prefix to "demos" key - result = result.replace(/href=\\"\/components/g, 'href=\\"/material/components'); - - // ii. add prefix to "pathname" value - result = result.replace(/"pathname": "\/api/g, '"pathname": "/material/api'); - - return result; -}; - export const getNewDataLocation = ( filePath: string, product: string, @@ -50,6 +34,8 @@ export const getNewDataLocation = ( }; }; +const nonComponents = ['about-the-lab']; + export const getNewPageLocation = ( filePath: string, ): { directory: string; path: string } | null => { @@ -57,6 +43,18 @@ export const getNewPageLocation = ( if (!match) { return null; } + if (filePath.includes('components')) { + if (nonComponents.some((path) => filePath.includes(path))) { + return { + directory: match[1].replace('docs/pages/components', 'docs/pages/material'), + path: filePath.replace('docs/pages/components/', 'docs/pages/material/'), + }; + } + return { + directory: match[1].replace('docs/pages/components', 'docs/pages/material'), + path: filePath.replace('docs/pages/components/', 'docs/pages/material/react-'), + }; + } return { directory: match[1].replace('docs/pages', 'docs/pages/material'), path: filePath.replace('docs/pages', 'docs/pages/material'), From 52d2903026c8cced1aa8b5a6d9716599b3dc7e53 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 22 Dec 2021 17:23:36 +0700 Subject: [PATCH 05/37] mapping api to package --- docs/packages/markdown/loader.js | 40 +++++++++++++++++++++++-- docs/packages/markdown/parseMarkdown.js | 14 ++++----- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/docs/packages/markdown/loader.js b/docs/packages/markdown/loader.js index a1e05cc995e163..e865924e2893ea 100644 --- a/docs/packages/markdown/loader.js +++ b/docs/packages/markdown/loader.js @@ -1,4 +1,4 @@ -const { promises: fs } = require('fs'); +const { promises: fs, readdirSync } = require('fs'); const path = require('path'); const { prepareMarkdown } = require('./parseMarkdown'); @@ -29,6 +29,42 @@ function moduleIDToJSIdentifier(moduleID) { .join(''); } +const componentPackageMapping = { + material: {}, +}; + +const packages = [ + { + name: 'mui-material', + product: 'material', + paths: [ + path.join(__dirname, '../../../packages/mui-lab/src'), + path.join(__dirname, '../../../packages/mui-material/src'), + path.join(__dirname, '../../../packages/mui-base/src'), + ], + }, + { + name: 'mui-base', + product: 'base', + paths: [path.join(__dirname, '../../../packages/mui-base/src')], + }, +]; + +packages.forEach((pkg) => { + pkg.paths.forEach((pkgPath) => { + const packageName = pkgPath.match(/packages\/([^/]+)\/src/)?.[1]; + if (!packageName) { + throw new Error(`cannot find package name from path: ${pkgPath}`); + } + const filePaths = readdirSync(pkgPath); + filePaths.forEach((folder) => { + if (folder.match(/^[A-Z]/)) { + componentPackageMapping[pkg.product][folder] = packageName; + } + }); + }); +}); + /** * @type {import('webpack').loader.Loader} */ @@ -72,7 +108,7 @@ module.exports = async function demoLoader() { // win32 to posix .replace(/\\/g, '/') .replace(/^\/src\/pages\//, ''); - const { docs } = prepareMarkdown({ pageFilename, translations }); + const { docs } = prepareMarkdown({ pageFilename, translations, componentPackageMapping }); const demos = {}; const demoModuleIDs = new Set(); diff --git a/docs/packages/markdown/parseMarkdown.js b/docs/packages/markdown/parseMarkdown.js index df90d278eb45d5..94a1ee981184fc 100644 --- a/docs/packages/markdown/parseMarkdown.js +++ b/docs/packages/markdown/parseMarkdown.js @@ -242,7 +242,7 @@ function createRender(context) { * @param {string} config.pageFilename - posix filename relative to nextjs pages directory */ function prepareMarkdown(config) { - const { pageFilename, translations } = config; + const { pageFilename, translations, componentPackageMapping = {} } = config; const demos = {}; /** @@ -267,12 +267,12 @@ function prepareMarkdown(config) { ## API ${headers.components - .map( - (component) => - `- [\`<${component} />\`](${headers.product ? `/${headers.product}` : ''}/api/${kebabCase( - component, - )}/)`, - ) + .map((component) => { + const componentPkg = componentPackageMapping[headers.product]?.[component]; + return `- [\`<${component} />\`](${headers.product ? `/${headers.product}` : ''}/api/${ + componentPkg ? `${componentPkg}/` : '' + }${kebabCase(component)}/)`; + }) .join('\n')} `); } From 65b80110c05e2d13518ac05c468f7eb5bb10840c Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 22 Dec 2021 17:39:15 +0700 Subject: [PATCH 06/37] update apiUrl & demoUrl to match new structure --- docs/scripts/buildApiUtils.test.ts | 16 ++++----- docs/scripts/buildApiUtils.ts | 55 +++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/docs/scripts/buildApiUtils.test.ts b/docs/scripts/buildApiUtils.test.ts index 080ba4acad3cdb..55a813e160902d 100644 --- a/docs/scripts/buildApiUtils.test.ts +++ b/docs/scripts/buildApiUtils.test.ts @@ -46,44 +46,44 @@ describe('buildApiUtils', () => { describe('getMaterialPathInfo', () => { it('[mui-material] return correct apiUrl', () => { const info = getMaterialPathInfo(`/packages/mui-material/src/Button/Button.js`); - expect(info.apiUrl).to.equal(`/material/api/button`); + expect(info.apiUrl).to.equal(`/material/api/mui-material/button`); }); it('[mui-material] return correct demoUrl', () => { const info = getMaterialPathInfo(`/docs/data/material/components/buttons/buttons.md`); - expect(info.demoUrl).to.equal(`/material/components/buttons`); + expect(info.demoUrl).to.equal(`/material/react-buttons`); }); it('[mui-lab] return correct apiUrl', () => { const info = getMaterialPathInfo(`/packages/mui-lab/src/LoadingButton/LoadingButton.js`); - expect(info.apiUrl).to.equal(`/material/api/loading-button`); + expect(info.apiUrl).to.equal(`/material/api/mui-lab/loading-button`); }); it('[mui-lab] return correct demoUrl', () => { const info = getMaterialPathInfo(`/docs/data/material/components/buttons/buttons.md`); - expect(info.demoUrl).to.equal(`/material/components/buttons`); + expect(info.demoUrl).to.equal(`/material/react-buttons`); }); it('[mui-base] return correct apiUrl', () => { const info = getMaterialPathInfo(`/packages/mui-base/src/ButtonUnstyled/ButtonUnstyled.tsx`); - expect(info.apiUrl).to.equal(`/material/api/button-unstyled`); + expect(info.apiUrl).to.equal(`/material/api/mui-base/button-unstyled`); }); it('[mui-base] return correct demoUrl', () => { const info = getMaterialPathInfo(`/docs/data/material/components/buttons/buttons.md`); - expect(info.demoUrl).to.equal(`/material/components/buttons`); + expect(info.demoUrl).to.equal(`/material/react-buttons`); }); }); describe('getBasePathInfo', () => { it('return correct apiUrl', () => { const info = getBasePathInfo(`/packages/mui-base/src/ButtonUnstyled/ButtonUnstyled.tsx`); - expect(info.apiUrl).to.equal(`/base/api/button-unstyled`); + expect(info.apiUrl).to.equal(`/base/api/mui-base/button-unstyled`); }); it('return correct demoUrl', () => { const info = getBasePathInfo(`/docs/data/base/components/button-unstyled/button-unstyled.md`); - expect(info.demoUrl).to.equal(`/base/components/button-unstyled`); + expect(info.demoUrl).to.equal(`/base/react-button-unstyled`); }); }); }); diff --git a/docs/scripts/buildApiUtils.ts b/docs/scripts/buildApiUtils.ts index 30579aa88dc1ac..746b95bd6151cf 100644 --- a/docs/scripts/buildApiUtils.ts +++ b/docs/scripts/buildApiUtils.ts @@ -1,3 +1,4 @@ +import fs from 'fs'; import path from 'path'; import kebabCase from 'lodash/kebabCase'; @@ -38,20 +39,66 @@ export const getGeneralPathInfo = (filename: string) => { }; }; +const componentPackageMapping = { + material: {} as Record, + base: {} as Record, +}; + +const packages = [ + { + name: 'mui-material', + product: 'material', + paths: [ + path.join(__dirname, '../../packages/mui-lab/src'), + path.join(__dirname, '../../packages/mui-material/src'), + path.join(__dirname, '../../packages/mui-base/src'), + ], + }, + { + name: 'mui-base', + product: 'base', + paths: [path.join(__dirname, '../../packages/mui-base/src')], + }, +]; + +packages.forEach((pkg) => { + pkg.paths.forEach((pkgPath) => { + const packageName = pkgPath.match(/packages\/([^/]+)\/src/)?.[1]; + if (!packageName) { + throw new Error(`cannot find package name from path: ${pkgPath}`); + } + const filePaths = fs.readdirSync(pkgPath); + filePaths.forEach((folder) => { + if (folder.match(/^[A-Z]/)) { + // @ts-ignore + componentPackageMapping[pkg.product][folder] = packageName; + } + }); + }); +}); + export const getMaterialPathInfo = (filename: string) => { filename = normalizeFilePath(filename); const componentName = filename.match(/.*\/([^/]+)\.(tsx|js)/)?.[1]; + const componentPkg = componentPackageMapping.material?.[componentName ?? '']; return { - apiUrl: `/material/api/${kebabCase(componentName)}`, - demoUrl: filename.replace(/^.*\/data/, '').replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), + apiUrl: `/material/api/${componentPkg}/${kebabCase(componentName)}`, + demoUrl: filename + .replace(/^.*\/data/, '') + .replace('components/', 'react-') + .replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), }; }; export const getBasePathInfo = (filename: string) => { filename = normalizeFilePath(filename); const componentName = filename.match(/.*\/([^/]+)\.(tsx|js)/)?.[1]; + const componentPkg = componentPackageMapping.base?.[componentName ?? '']; return { - apiUrl: `/base/api/${kebabCase(componentName)}`, - demoUrl: filename.replace(/^.*\/data/, '').replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), + apiUrl: `/base/api/${componentPkg}/${kebabCase(componentName)}`, + demoUrl: filename + .replace(/^.*\/data/, '') + .replace('components/', 'react-') + .replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), }; }; From c41f425f26e7a96d5c04623a49ee7fde202315a7 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 09:20:38 +0700 Subject: [PATCH 07/37] support new structure --- docs/packages/markdown/loader.js | 1 + docs/scripts/buildApi.ts | 96 +++++++++++++++++++++++++------- docs/scripts/buildApiUtils.ts | 4 ++ 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/docs/packages/markdown/loader.js b/docs/packages/markdown/loader.js index e865924e2893ea..bc97fe06fddc88 100644 --- a/docs/packages/markdown/loader.js +++ b/docs/packages/markdown/loader.js @@ -31,6 +31,7 @@ function moduleIDToJSIdentifier(moduleID) { const componentPackageMapping = { material: {}, + base: {}, }; const packages = [ diff --git a/docs/scripts/buildApi.ts b/docs/scripts/buildApi.ts index 5a2c66d7e2ace4..808a74fbcf84e5 100644 --- a/docs/scripts/buildApi.ts +++ b/docs/scripts/buildApi.ts @@ -82,6 +82,61 @@ async function removeOutdatedApiDocsTranslations(components: readonly ReactApi[] ); } +const getAllFiles = (dirPath: string, arrayOfFiles: string[] = []) => { + const files = fse.readdirSync(dirPath); + + files.forEach((file) => { + if (fse.statSync(`${dirPath}/${file}`).isDirectory()) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + arrayOfFiles = getAllFiles(`${dirPath}/${file}`, arrayOfFiles); + } else { + arrayOfFiles.push(path.join(__dirname, dirPath, '/', file)); + } + }); + + return arrayOfFiles; +}; + +function findApiPages(relativeFolder: string) { + let pages: Array<{ pathname: string }> = []; + let filePaths = []; + try { + filePaths = getAllFiles(path.join(process.cwd(), relativeFolder)); + } catch (error) { + // eslint-disable-next-line no-console + console.log(error); + return []; + } + filePaths.forEach((itemPath) => { + if (itemPath.endsWith('.js')) { + const pathname = itemPath + .replace(new RegExp(`\\${path.sep}`, 'g'), '/') + .replace(/^.*\/pages/, '') + .replace('.js', '') + .replace('.tsx', '') + .replace(/^\/index$/, '/') // Replace `index` by `/`. + .replace(/\/index$/, ''); + + pages.push({ pathname: `${relativeFolder.replace(/^.*\/pages/, '')}/${pathname}` }); + } + }); + + // sort by pathnames without '-' so that e.g. card comes before card-action + pages = pages.sort((a, b) => { + const pathnameA = a.pathname.replace(/-/g, ''); + const pathnameB = b.pathname.replace(/-/g, ''); + if (pathnameA < pathnameB) { + return -1; + } + if (pathnameA > pathnameB) { + return 1; + } + return 0; + }); + + return pages; +} + interface Settings { input: { /** @@ -99,17 +154,21 @@ interface Settings { markdownDirectory: string; }; output: { - /** - * API page + json content output directory - */ - pagesDirectory: string; /** * The output path of `pagesApi` generated from `input.pageDirectory` */ apiManifestPath: string; }; + apiPages: Array<{ pathname: string }>; productUrlPrefix: string; - getPathInfo: (filename: string) => { apiUrl: string; demoUrl: string }; + getPathInfo: (filename: string) => { + apiUrl: string; + demoUrl: string; + /** + * API page + json content output directory + */ + pagesDirectory: string; + }; } /** @@ -127,9 +186,12 @@ const BEFORE_MIGRATION_SETTINGS: Settings[] = [ markdownDirectory: path.join(process.cwd(), 'docs/src/pages'), }, output: { - pagesDirectory: path.join(process.cwd(), 'docs/pages/api-docs'), apiManifestPath: path.join(process.cwd(), 'docs/src/pagesApi.js'), }, + apiPages: (() => { + const pages = findPages({ front: true }, path.join(process.cwd(), 'docs/pages')); + return pages.find(({ pathname }) => pathname.indexOf('api') !== -1)?.children ?? []; + })(), productUrlPrefix: '', getPathInfo: getGeneralPathInfo, }, @@ -155,9 +217,9 @@ const MIGRATION_SETTINGS: Settings[] = [ markdownDirectory: path.join(process.cwd(), 'docs/data'), }, output: { - pagesDirectory: path.join(process.cwd(), 'docs/pages/material/api-docs'), apiManifestPath: path.join(process.cwd(), 'docs/data/material/pagesApi.js'), }, + apiPages: (() => findApiPages('docs/pages/material/api/mui-material'))(), productUrlPrefix: '/material', getPathInfo: getMaterialPathInfo, }, @@ -181,9 +243,9 @@ const POST_MIGRATION_SETTINGS: Settings[] = [ markdownDirectory: path.join(process.cwd(), 'docs/data'), }, output: { - pagesDirectory: path.join(process.cwd(), 'docs/pages/material/api-docs'), apiManifestPath: path.join(process.cwd(), 'docs/data/material/pagesApi.js'), }, + apiPages: (() => findApiPages('docs/pages/material/api'))(), productUrlPrefix: '/material', getPathInfo: getMaterialPathInfo, }, @@ -194,16 +256,16 @@ const POST_MIGRATION_SETTINGS: Settings[] = [ markdownDirectory: path.join(process.cwd(), 'docs/data'), }, output: { - pagesDirectory: path.join(process.cwd(), 'docs/pages/base/api-docs'), apiManifestPath: path.join(process.cwd(), 'docs/data/base/pagesApi.js'), }, + apiPages: (() => findApiPages('docs/pages/base/api'))(), productUrlPrefix: '/base', getPathInfo: getBasePathInfo, }, // add other products, eg. joy, data-grid, ...etc ]; -const ACTIVE_SETTINGS = BEFORE_MIGRATION_SETTINGS; +const ACTIVE_SETTINGS = POST_MIGRATION_SETTINGS; async function run(argv: { grep?: string }) { const grep = argv.grep == null ? null : new RegExp(argv.grep); @@ -215,9 +277,7 @@ async function run(argv: { grep?: string }) { */ const componentDirectories = setting.input.libDirectory; const apiPagesManifestPath = setting.output.apiManifestPath; - const pagesDirectory = setting.output.pagesDirectory; - mkdirSync(pagesDirectory, { mode: 0o777, recursive: true }); const manifestDir = apiPagesManifestPath.match(/(.*)\/[^/]+\./)?.[1]; if (manifestDir) { mkdirSync(manifestDir, { recursive: true }); @@ -284,12 +344,14 @@ async function run(argv: { grep?: string }) { const { filename } = component; const pathInfo = setting.getPathInfo(filename); + mkdirSync(pathInfo.pagesDirectory, { mode: 0o777, recursive: true }); + return buildComponentApi(filename, { ttpProgram: program, pagesMarkdown, apiUrl: pathInfo.apiUrl, productUrlPrefix: setting.productUrlPrefix, - outputPagesDirectory: setting.output.pagesDirectory, + outputPagesDirectory: pathInfo.pagesDirectory, }); } catch (error: any) { error.message = `${path.relative(process.cwd(), component.filename)}: ${error.message}`; @@ -312,13 +374,7 @@ async function run(argv: { grep?: string }) { allBuilds = [...allBuilds, ...builds]; - const pages = findPages({ front: true }, setting.input.pageDirectory); - const apiPages = pages.find(({ pathname }) => pathname.indexOf('api') !== -1)?.children; - if (apiPages === undefined) { - throw new TypeError('Unable to find pages under /api'); - } - - const source = `module.exports = ${JSON.stringify(apiPages)}`; + const source = `module.exports = ${JSON.stringify(setting.apiPages)}`; writePrettifiedFile(apiPagesManifestPath, source); await resolvedPromise; diff --git a/docs/scripts/buildApiUtils.ts b/docs/scripts/buildApiUtils.ts index 746b95bd6151cf..69e76be127d538 100644 --- a/docs/scripts/buildApiUtils.ts +++ b/docs/scripts/buildApiUtils.ts @@ -36,6 +36,7 @@ export const getGeneralPathInfo = (filename: string) => { return { apiUrl: `/api/${kebabCase(componentName)}`, demoUrl: filename.replace(/^.*\/pages/, '').replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), + pagesDirectory: path.join(process.cwd(), 'docs/pages/api-docs'), }; }; @@ -79,6 +80,7 @@ packages.forEach((pkg) => { export const getMaterialPathInfo = (filename: string) => { filename = normalizeFilePath(filename); + const packageName = filename.match(/packages\/([^/]+)\/src/)?.[1]; const componentName = filename.match(/.*\/([^/]+)\.(tsx|js)/)?.[1]; const componentPkg = componentPackageMapping.material?.[componentName ?? '']; return { @@ -87,6 +89,7 @@ export const getMaterialPathInfo = (filename: string) => { .replace(/^.*\/data/, '') .replace('components/', 'react-') .replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), + pagesDirectory: path.join(process.cwd(), `docs/pages/material/api/${packageName}`), }; }; @@ -100,5 +103,6 @@ export const getBasePathInfo = (filename: string) => { .replace(/^.*\/data/, '') .replace('components/', 'react-') .replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), + pagesDirectory: path.join(process.cwd(), 'docs/pages/base/api/mui-base'), }; }; From 70e4f08e599b4273dfe4939f51ca582a0c41cad3 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 10:21:37 +0700 Subject: [PATCH 08/37] add filepath extracter --- docs/scripts/buildApiUtils.test.ts | 69 ++++++++++++++++++++++++++++++ docs/scripts/buildApiUtils.ts | 15 +++++++ 2 files changed, 84 insertions(+) diff --git a/docs/scripts/buildApiUtils.test.ts b/docs/scripts/buildApiUtils.test.ts index 55a813e160902d..71f85efd9b17cc 100644 --- a/docs/scripts/buildApiUtils.test.ts +++ b/docs/scripts/buildApiUtils.test.ts @@ -1,10 +1,12 @@ import { expect } from 'chai'; +import sinon from 'sinon'; import { findComponentDemos, getMuiName, getGeneralPathInfo, getMaterialPathInfo, getBasePathInfo, + extractPackageFilePath, } from './buildApiUtils'; describe('buildApiUtils', () => { @@ -23,6 +25,73 @@ describe('buildApiUtils', () => { ).to.deep.equal(['/material/components/accordion', '/material/components/accordion-details']); }); + describe('extractPackageFilePath', () => { + it('return info if path is a package (material)', () => { + const result = extractPackageFilePath( + '/material-ui/packages/mui-material/src/Button/Button.js', + ); + sinon.assert.match(result, { + packagePath: 'mui-material', + muiPackage: 'mui-material', + name: 'Button', + }); + }); + + it('return info if path is a package (lab)', () => { + const result = extractPackageFilePath( + '/material-ui/packages/mui-lab/src/LoadingButton/LoadingButton.js', + ); + sinon.assert.match(result, { + packagePath: 'mui-lab', + muiPackage: 'mui-lab', + name: 'LoadingButton', + }); + }); + + it('return info if path is a package (base)', () => { + const result = extractPackageFilePath( + '/material-ui/packages/mui-base/src/TabUnstyled/TabUnstyled.tsx', + ); + sinon.assert.match(result, { + packagePath: 'mui-base', + muiPackage: 'mui-base', + name: 'TabUnstyled', + }); + }); + + it('return info if path is a package (data-grid)', () => { + const result = extractPackageFilePath( + '/material-ui/packages/grid/x-data-grid/src/DataGrid.tsx', + ); + sinon.assert.match(result, { + packagePath: 'x-data-grid', + muiPackage: 'mui-data-grid', + name: 'DataGrid', + }); + }); + + it('return info if path is a package (data-grid-pro)', () => { + const result = extractPackageFilePath( + '/material-ui/packages/grid/x-data-grid-pro/src/DataGridPro.tsx', + ); + sinon.assert.match(result, { + packagePath: 'x-data-grid-pro', + muiPackage: 'mui-data-grid-pro', + name: 'DataGridPro', + }); + }); + + it('return null if path is not a package', () => { + const result = extractPackageFilePath( + '/material-ui/docs/pages/material/getting-started/getting-started.md', + ); + sinon.assert.match(result, { + packagePath: null, + name: null, + }); + }); + }); + it('getMuiName return name without Unstyled', () => { expect(getMuiName('ButtonUnstyled')).to.equal('MuiButton'); }); diff --git a/docs/scripts/buildApiUtils.ts b/docs/scripts/buildApiUtils.ts index 69e76be127d538..de7b257c20293e 100644 --- a/docs/scripts/buildApiUtils.ts +++ b/docs/scripts/buildApiUtils.ts @@ -78,6 +78,21 @@ packages.forEach((pkg) => { }); }); +export const extractPackageFilePath = (filePath: string) => { + filePath = filePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'); + const match = filePath.match( + /.*\/packages.*\/(?[^/]+)\/src\/(.*\/)?(?[^/]+)\.(js|tsx|ts|d\.ts)/, + ); + const result = { + packagePath: match ? match.groups?.packagePath! : null, + name: match ? match.groups?.name! : null, + }; + return { + ...result, + muiPackage: result.packagePath?.replace('x-', 'mui-'), + }; +}; + export const getMaterialPathInfo = (filename: string) => { filename = normalizeFilePath(filename); const packageName = filename.match(/packages\/([^/]+)\/src/)?.[1]; From 4b5ec1675b0b5f9ce6386f34573b1b081bb208cc Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 10:47:34 +0700 Subject: [PATCH 09/37] refactor code --- .../ApiBuilders/ComponentApiBuilder.ts | 117 +++------ docs/scripts/buildApi.ts | 157 +++--------- docs/scripts/buildApiUtils.test.ts | 191 ++++++++------ docs/scripts/buildApiUtils.ts | 237 ++++++++++++++---- docs/src/modules/utils/find.js | 44 ++++ docs/src/modules/utils/helpers.test.js | 16 +- docs/src/modules/utils/helpers.ts | 2 +- package.json | 2 +- 8 files changed, 425 insertions(+), 341 deletions(-) diff --git a/docs/scripts/ApiBuilders/ComponentApiBuilder.ts b/docs/scripts/ApiBuilders/ComponentApiBuilder.ts index 3d3338f326c8b4..727b6eb52acff7 100644 --- a/docs/scripts/ApiBuilders/ComponentApiBuilder.ts +++ b/docs/scripts/ApiBuilders/ComponentApiBuilder.ts @@ -15,7 +15,6 @@ import generatePropTypeDescription, { getChained, } from 'docs/src/modules/utils/generatePropTypeDescription'; import { renderInline as renderMarkdownInline } from '@mui/markdown'; -import { pageToTitle } from 'docs/src/modules/utils/helpers'; import createDescribeableProp, { DescribeablePropDescriptor, } from 'docs/src/modules/utils/createDescribeableProp'; @@ -23,27 +22,24 @@ import generatePropDescription from 'docs/src/modules/utils/generatePropDescript import parseStyles, { Styles } from 'docs/src/modules/utils/parseStyles'; import generateUtilityClass from '@mui/base/generateUtilityClass'; import * as ttp from 'typescript-to-proptypes'; -import { getLineFeed, getUnstyledFilename } from '../helpers'; -import { findComponentDemos, getMuiName } from '../buildApiUtils'; +import { getUnstyledFilename } from '../helpers'; +import { ComponentInfo } from '../buildApiUtils'; const DEFAULT_PRETTIER_CONFIG_PATH = path.join(process.cwd(), 'prettier.config.js'); -interface ReactApi extends ReactDocgenApi { - /** - * list of page pathnames - * @example ['/components/Accordion'] - */ - demos: readonly string[]; +export interface ReactApi extends ReactDocgenApi { + demos: ReturnType; EOL: string; filename: string; - apiUrl: string; + apiPathname: string; forwardsRefTo: string | undefined; - inheritance: { component: string; pathname: string } | null; + inheritance: ReturnType; /** * react component name * @example 'Accordion' */ name: string; + muiName: string; description: string; spread: boolean | undefined; /** @@ -88,21 +84,6 @@ export function writePrettifiedFile( }); } -const parseFile = (filename: string) => { - const src = readFileSync(filename, 'utf8'); - return { - src, - shouldSkip: - filename.indexOf('internal') !== -1 || - !!src.match(/@ignore - internal component\./) || - !!src.match(/@ignore - do not document\./), - spread: !src.match(/ = exactProp\(/), - name: path.parse(filename).name, - EOL: getLineFeed(src), - inheritedComponent: src.match(/\/\/ @inheritedComponent (.*)/)?.[1], - }; -}; - /** * Produces markdown of the description that can be hosted anywhere. * @@ -218,11 +199,7 @@ async function annotateComponentDefinition(api: ReactApi) { let inheritanceAPILink = null; if (api.inheritance !== null) { - const url = api.inheritance.pathname.startsWith('/') - ? `${HOST}${api.inheritance.pathname}` - : api.inheritance.pathname; - - inheritanceAPILink = `[${api.inheritance.component} API](${url})`; + inheritanceAPILink = `[${api.inheritance.name} API](${HOST}${api.inheritance.pathname})`; } const markdownLines = (await computeApiDescription(api, { host: HOST })).split('\n'); @@ -233,13 +210,13 @@ async function annotateComponentDefinition(api: ReactApi) { markdownLines.push( 'Demos:', '', - ...api.demos.map((demoPathname) => { - return `- [${pageToTitle({ pathname: demoPathname })}](${HOST}${demoPathname}/)`; + ...api.demos.map((item) => { + return `- [${item.name}](${HOST}${item.demoPathname})`; }), '', ); - markdownLines.push('API:', '', `- [${api.name} API](${HOST}${api.apiUrl}/)`); + markdownLines.push('API:', '', `- [${api.name} API](${HOST}${api.apiPathname})`); if (api.inheritance !== null) { markdownLines.push(`- inherits ${inheritanceAPILink}`); } @@ -284,18 +261,6 @@ function extractClassConditions(descriptions: any) { return classConditions; } -/** - * Generate list of component demos - */ -function generateDemoList(reactAPI: ReactApi): string { - return ``; -} - /** * @param filepath - absolute path * @example toGithubPath('/home/user/material-ui/packages/Accordion') === '/packages/Accordion' @@ -372,7 +337,9 @@ const generateApiPage = (outputDirectory: string, reactApi: ReactApi) => { forwardsRefTo: reactApi.forwardsRefTo, filename: toGithubPath(reactApi.filename), inheritance: reactApi.inheritance, - demos: generateDemoList(reactApi), + demos: `
    ${reactApi.demos + .map((item) => `
  • ${item.name}
  • `) + .join('\n')}
`, cssComponent: cssComponents.indexOf(reactApi.name) >= 0, }; @@ -520,25 +487,26 @@ const attachPropsTable = (reactApi: ReactApi) => { * - Add the comment in the component filename with its demo & API urls (including the inherited component). * this process is done by sourcing markdown files and filter matched `components` in the frontmatter */ -const generateComponentApi = async ( - filename: string, - options: { - outputPagesDirectory: string; - productUrlPrefix: string; - apiUrl: string; - ttpProgram: ttp.ts.Program; - pagesMarkdown: Array<{ components: string[]; pathname: string }>; - }, -) => { - const { ttpProgram: program, pagesMarkdown, outputPagesDirectory } = options; - const { shouldSkip, name, spread, EOL, inheritedComponent } = parseFile(filename); +const generateComponentApi = async (componentInfo: ComponentInfo, program: ttp.ts.Program) => { + const { + filename, + name, + muiName, + apiPathname, + apiPagesDirectory, + getInheritance, + getDemos, + readFile, + } = componentInfo; + + const { shouldSkip, spread, EOL, src } = readFile(); if (shouldSkip) { return null; } const reactApi: ReactApi = docgenParse( - readFileSync(filename, 'utf8'), + src, null, defaultHandlers.concat(muiDefaultPropsHandler), { filename }, @@ -588,9 +556,10 @@ const generateComponentApi = async ( } reactApi.filename = filename; reactApi.name = name; - reactApi.apiUrl = options.apiUrl; + reactApi.muiName = muiName; + reactApi.apiPathname = apiPathname; reactApi.EOL = EOL; - reactApi.demos = findComponentDemos(name, pagesMarkdown); + reactApi.demos = getDemos(); if (reactApi.demos.length === 0) { throw new Error( 'Unable to find demos. \n' + @@ -603,30 +572,14 @@ const generateComponentApi = async ( // no Object.assign to visually check for collisions reactApi.forwardsRefTo = testInfo.forwardsRefTo; reactApi.spread = testInfo.spread ?? spread; - - const inheritedComponentName = testInfo.inheritComponent || inheritedComponent; - if (inheritedComponentName) { - reactApi.inheritance = { - component: inheritedComponentName, - pathname: - inheritedComponentName === 'Transition' - ? 'http://reactcommunity.org/react-transition-group/transition/#Transition-props' - : `${options.productUrlPrefix}/api/${kebabCase(inheritedComponentName)}/`, - }; - } else { - reactApi.inheritance = null; - } - + reactApi.inheritance = getInheritance(testInfo.inheritComponent); reactApi.styles = await parseStyles(reactApi, program); if (reactApi.styles.classes.length > 0 && !reactApi.name.endsWith('Unstyled')) { - reactApi.styles.name = getMuiName(reactApi.name); + reactApi.styles.name = reactApi.muiName; } reactApi.styles.classes.forEach((key) => { - const globalClass = generateUtilityClass( - reactApi.styles.name || getMuiName(reactApi.name), - key, - ); + const globalClass = generateUtilityClass(reactApi.styles.name || reactApi.muiName, key); reactApi.styles.globalClasses[key] = globalClass; }); @@ -638,7 +591,7 @@ const generateComponentApi = async ( // Generate pages, json and translations generateApiTranslations(path.join(process.cwd(), 'docs/translations/api-docs'), reactApi); - generateApiPage(outputPagesDirectory, reactApi); + generateApiPage(apiPagesDirectory, reactApi); // Add comment about demo & api links (including inherited component) to the component file await annotateComponentDefinition(reactApi); diff --git a/docs/scripts/buildApi.ts b/docs/scripts/buildApi.ts index 808a74fbcf84e5..3952c1f44325a9 100644 --- a/docs/scripts/buildApi.ts +++ b/docs/scripts/buildApi.ts @@ -1,56 +1,24 @@ -import { mkdirSync, readFileSync } from 'fs'; +import { mkdirSync } from 'fs'; import * as fse from 'fs-extra'; import path from 'path'; -import * as _ from 'lodash'; import kebabCase from 'lodash/kebabCase'; import * as yargs from 'yargs'; -import { ReactDocgenApi } from 'react-docgen'; -import { findPages, findPagesMarkdown, findComponents } from 'docs/src/modules/utils/find'; -import { getHeaders } from '@mui/markdown'; -import { Styles } from 'docs/src/modules/utils/parseStyles'; +import { findPages, findComponents } from 'docs/src/modules/utils/find'; import * as ttp from 'typescript-to-proptypes'; -import { getGeneralPathInfo, getMaterialPathInfo, getBasePathInfo } from './buildApiUtils'; -import buildComponentApi, { writePrettifiedFile } from './ApiBuilders/ComponentApiBuilder'; +import { + ComponentInfo, + getGenericComponentInfo, + getMaterialComponentInfo, + getBaseComponentInfo, + extractApiPage, +} from './buildApiUtils'; +import buildComponentApi, { + writePrettifiedFile, + ReactApi, +} from './ApiBuilders/ComponentApiBuilder'; const apiDocsTranslationsDirectory = path.resolve('docs', 'translations', 'api-docs'); -interface ReactApi extends ReactDocgenApi { - /** - * list of page pathnames - * @example ['/components/Accordion'] - */ - demos: readonly string[]; - EOL: string; - filename: string; - apiUrl: string; - forwardsRefTo: string | undefined; - inheritance: { component: string; pathname: string } | null; - /** - * react component name - * @example 'Accordion' - */ - name: string; - description: string; - spread: boolean | undefined; - /** - * result of path.readFileSync from the `filename` in utf-8 - */ - src: string; - styles: Styles; - propsTable: _.Dictionary<{ - default: string | undefined; - required: boolean | undefined; - type: { name: string | undefined; description: string | undefined }; - deprecated: true | undefined; - deprecationInfo: string | undefined; - }>; - translations: { - componentDescription: string; - propDescriptions: { [key: string]: string | undefined }; - classDescriptions: { [key: string]: { description: string; conditions?: string } }; - }; -} - async function removeOutdatedApiDocsTranslations(components: readonly ReactApi[]): Promise { const componentDirectories = new Set(); const files = await fse.readdir(apiDocsTranslationsDirectory); @@ -109,15 +77,9 @@ function findApiPages(relativeFolder: string) { } filePaths.forEach((itemPath) => { if (itemPath.endsWith('.js')) { - const pathname = itemPath - .replace(new RegExp(`\\${path.sep}`, 'g'), '/') - .replace(/^.*\/pages/, '') - .replace('.js', '') - .replace('.tsx', '') - .replace(/^\/index$/, '/') // Replace `index` by `/`. - .replace(/\/index$/, ''); - - pages.push({ pathname: `${relativeFolder.replace(/^.*\/pages/, '')}/${pathname}` }); + const data = extractApiPage(itemPath); + + pages.push({ pathname: data.apiPathname }); } }); @@ -143,15 +105,6 @@ interface Settings { * Component directories to be used to generate API */ libDirectory: string[]; - /** - * The directory to get api pathnames to generate pagesApi - */ - pageDirectory: string; - /** - * The directory that contains markdown files to be used to find demos - * related to the processed component - */ - markdownDirectory: string; }; output: { /** @@ -159,16 +112,8 @@ interface Settings { */ apiManifestPath: string; }; - apiPages: Array<{ pathname: string }>; - productUrlPrefix: string; - getPathInfo: (filename: string) => { - apiUrl: string; - demoUrl: string; - /** - * API page + json content output directory - */ - pagesDirectory: string; - }; + getApiPages: () => Array<{ pathname: string }>; + getComponentInfo: (filename: string) => ComponentInfo; } /** @@ -182,18 +127,15 @@ const BEFORE_MIGRATION_SETTINGS: Settings[] = [ path.join(process.cwd(), 'packages/mui-material/src'), path.join(process.cwd(), 'packages/mui-lab/src'), ], - pageDirectory: path.join(process.cwd(), 'docs/pages'), - markdownDirectory: path.join(process.cwd(), 'docs/src/pages'), }, output: { apiManifestPath: path.join(process.cwd(), 'docs/src/pagesApi.js'), }, - apiPages: (() => { + getApiPages: () => { const pages = findPages({ front: true }, path.join(process.cwd(), 'docs/pages')); return pages.find(({ pathname }) => pathname.indexOf('api') !== -1)?.children ?? []; - })(), - productUrlPrefix: '', - getPathInfo: getGeneralPathInfo, + }, + getComponentInfo: getGenericComponentInfo, }, ]; @@ -213,15 +155,12 @@ const MIGRATION_SETTINGS: Settings[] = [ path.join(process.cwd(), 'packages/mui-material/src'), path.join(process.cwd(), 'packages/mui-lab/src'), ], - pageDirectory: path.join(process.cwd(), 'docs/pages/material'), - markdownDirectory: path.join(process.cwd(), 'docs/data'), }, output: { apiManifestPath: path.join(process.cwd(), 'docs/data/material/pagesApi.js'), }, - apiPages: (() => findApiPages('docs/pages/material/api/mui-material'))(), - productUrlPrefix: '/material', - getPathInfo: getMaterialPathInfo, + getApiPages: () => findApiPages('docs/pages/material/api/mui-material'), + getComponentInfo: getMaterialComponentInfo, }, ]; @@ -233,34 +172,29 @@ const MIGRATION_SETTINGS: Settings[] = [ // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unused-vars const POST_MIGRATION_SETTINGS: Settings[] = [ + ...BEFORE_MIGRATION_SETTINGS, { input: { libDirectory: [ path.join(process.cwd(), 'packages/mui-material/src'), path.join(process.cwd(), 'packages/mui-lab/src'), ], - pageDirectory: path.join(process.cwd(), 'docs/pages/material'), - markdownDirectory: path.join(process.cwd(), 'docs/data'), }, output: { apiManifestPath: path.join(process.cwd(), 'docs/data/material/pagesApi.js'), }, - apiPages: (() => findApiPages('docs/pages/material/api'))(), - productUrlPrefix: '/material', - getPathInfo: getMaterialPathInfo, + getApiPages: () => findApiPages('docs/pages/material/api'), + getComponentInfo: getMaterialComponentInfo, }, { input: { libDirectory: [path.join(process.cwd(), 'packages/mui-base/src')], - pageDirectory: path.join(process.cwd(), 'docs/pages/base'), - markdownDirectory: path.join(process.cwd(), 'docs/data'), }, output: { apiManifestPath: path.join(process.cwd(), 'docs/data/base/pagesApi.js'), }, - apiPages: (() => findApiPages('docs/pages/base/api'))(), - productUrlPrefix: '/base', - getPathInfo: getBasePathInfo, + getApiPages: () => findApiPages('docs/pages/base/api'), + getComponentInfo: getBaseComponentInfo, }, // add other products, eg. joy, data-grid, ...etc ]; @@ -283,27 +217,6 @@ async function run(argv: { grep?: string }) { mkdirSync(manifestDir, { recursive: true }); } - /** - * pageMarkdown: Array<{ components: string[]; filename: string; pathname: string }> - * - * e.g.: - * [{ - * pathname: '/components/accordion', - * filename: '/Users/user/Projects/material-ui/docs/src/pages/components/badges/accordion-ja.md', - * components: [ 'Accordion', 'AccordionActions', 'AccordionDetails', 'AccordionSummary' ] - * }, ...] - */ - const pagesMarkdown = findPagesMarkdown(setting.input.markdownDirectory) - .map((markdown) => { - const markdownSource = readFileSync(markdown.filename, 'utf8'); - return { - ...markdown, - pathname: setting.getPathInfo(markdown.filename).demoUrl, - components: getHeaders(markdownSource).components, - }; - }) - .filter((markdown) => markdown.components.length > 0); - /** * components: Array<{ filename: string }> * e.g. @@ -342,17 +255,11 @@ async function run(argv: { grep?: string }) { const componentBuilds = components.map(async (component) => { try { const { filename } = component; - const pathInfo = setting.getPathInfo(filename); + const componentInfo = setting.getComponentInfo(filename); - mkdirSync(pathInfo.pagesDirectory, { mode: 0o777, recursive: true }); + mkdirSync(componentInfo.apiPagesDirectory, { mode: 0o777, recursive: true }); - return buildComponentApi(filename, { - ttpProgram: program, - pagesMarkdown, - apiUrl: pathInfo.apiUrl, - productUrlPrefix: setting.productUrlPrefix, - outputPagesDirectory: pathInfo.pagesDirectory, - }); + return buildComponentApi(componentInfo, program); } catch (error: any) { error.message = `${path.relative(process.cwd(), component.filename)}: ${error.message}`; throw error; @@ -374,7 +281,7 @@ async function run(argv: { grep?: string }) { allBuilds = [...allBuilds, ...builds]; - const source = `module.exports = ${JSON.stringify(setting.apiPages)}`; + const source = `module.exports = ${JSON.stringify(setting.getApiPages())}`; writePrettifiedFile(apiPagesManifestPath, source); await resolvedPromise; diff --git a/docs/scripts/buildApiUtils.test.ts b/docs/scripts/buildApiUtils.test.ts index 71f85efd9b17cc..3049435e0d4f2a 100644 --- a/docs/scripts/buildApiUtils.test.ts +++ b/docs/scripts/buildApiUtils.test.ts @@ -1,35 +1,29 @@ +import path from 'path'; +import fs from 'fs'; import { expect } from 'chai'; import sinon from 'sinon'; import { - findComponentDemos, - getMuiName, - getGeneralPathInfo, - getMaterialPathInfo, - getBasePathInfo, - extractPackageFilePath, + extractApiPage, + extractPackageFile, + getGenericComponentInfo, + getMaterialComponentInfo, + getBaseComponentInfo, } from './buildApiUtils'; describe('buildApiUtils', () => { - it('findComponentDemos return matched component', () => { - expect( - findComponentDemos('Accordion', [ - { - pathname: '/material/components/accordion', - components: ['Accordion', 'AccordionDetails'], - }, - { - pathname: '/material/components/accordion-details', - components: ['Accordion', 'AccordionDetails'], - }, - ]), - ).to.deep.equal(['/material/components/accordion', '/material/components/accordion-details']); + describe('extractApiPage', () => { + it('return info for api page', () => { + expect( + extractApiPage('/material-ui/docs/pages/material/api/mui-material/accordion-actions.js'), + ).to.deep.equal({ + apiPathname: '/material/api/mui-material/accordion-actions', + }); + }); }); describe('extractPackageFilePath', () => { it('return info if path is a package (material)', () => { - const result = extractPackageFilePath( - '/material-ui/packages/mui-material/src/Button/Button.js', - ); + const result = extractPackageFile('/material-ui/packages/mui-material/src/Button/Button.js'); sinon.assert.match(result, { packagePath: 'mui-material', muiPackage: 'mui-material', @@ -38,7 +32,7 @@ describe('buildApiUtils', () => { }); it('return info if path is a package (lab)', () => { - const result = extractPackageFilePath( + const result = extractPackageFile( '/material-ui/packages/mui-lab/src/LoadingButton/LoadingButton.js', ); sinon.assert.match(result, { @@ -49,7 +43,7 @@ describe('buildApiUtils', () => { }); it('return info if path is a package (base)', () => { - const result = extractPackageFilePath( + const result = extractPackageFile( '/material-ui/packages/mui-base/src/TabUnstyled/TabUnstyled.tsx', ); sinon.assert.match(result, { @@ -60,9 +54,7 @@ describe('buildApiUtils', () => { }); it('return info if path is a package (data-grid)', () => { - const result = extractPackageFilePath( - '/material-ui/packages/grid/x-data-grid/src/DataGrid.tsx', - ); + const result = extractPackageFile('/material-ui/packages/grid/x-data-grid/src/DataGrid.tsx'); sinon.assert.match(result, { packagePath: 'x-data-grid', muiPackage: 'mui-data-grid', @@ -71,7 +63,7 @@ describe('buildApiUtils', () => { }); it('return info if path is a package (data-grid-pro)', () => { - const result = extractPackageFilePath( + const result = extractPackageFile( '/material-ui/packages/grid/x-data-grid-pro/src/DataGridPro.tsx', ); sinon.assert.match(result, { @@ -82,7 +74,7 @@ describe('buildApiUtils', () => { }); it('return null if path is not a package', () => { - const result = extractPackageFilePath( + const result = extractPackageFile( '/material-ui/docs/pages/material/getting-started/getting-started.md', ); sinon.assert.match(result, { @@ -92,67 +84,108 @@ describe('buildApiUtils', () => { }); }); - it('getMuiName return name without Unstyled', () => { - expect(getMuiName('ButtonUnstyled')).to.equal('MuiButton'); - }); - - it('getMuiName return name without Styled', () => { - expect(getMuiName('StyledInputBase')).to.equal('MuiInputBase'); - }); + describe('getGenericComponentInfo', () => { + it('return correct apiPathname', () => { + const info = getGenericComponentInfo( + path.join(process.cwd(), `/packages/mui-material/src/Button/Button.js`), + ); + sinon.assert.match(info, { + name: 'Button', + apiPathname: '/api/button/', + muiName: 'MuiButton', + apiPagesDirectory: sinon.match((value) => value.endsWith('docs/pages/api-docs')), + }); - describe('getGeneralPathInfo', () => { - it('return correct apiUrl', () => { - const info = getGeneralPathInfo(`/packages/mui-material/src/Button/Button.js`); - expect(info.apiUrl).to.equal(`/api/button`); - }); + expect(info.getInheritance('ButtonBase')).to.deep.equal({ + name: 'ButtonBase', + pathname: '/api/button-base/', + }); - it('return correct demoUrl', () => { - const info = getGeneralPathInfo(`/docs/src/pages/components/buttons/buttons.md`); - expect(info.demoUrl).to.equal(`/components/buttons`); + expect(info.getDemos()).to.deep.equal([ + { + name: 'Button Group', + demoPathname: '/components/button-group/', + }, + { + name: 'Buttons', + demoPathname: '/components/buttons/', + }, + ]); }); }); - describe('getMaterialPathInfo', () => { - it('[mui-material] return correct apiUrl', () => { - const info = getMaterialPathInfo(`/packages/mui-material/src/Button/Button.js`); - expect(info.apiUrl).to.equal(`/material/api/mui-material/button`); - }); - - it('[mui-material] return correct demoUrl', () => { - const info = getMaterialPathInfo(`/docs/data/material/components/buttons/buttons.md`); - expect(info.demoUrl).to.equal(`/material/react-buttons`); - }); - - it('[mui-lab] return correct apiUrl', () => { - const info = getMaterialPathInfo(`/packages/mui-lab/src/LoadingButton/LoadingButton.js`); - expect(info.apiUrl).to.equal(`/material/api/mui-lab/loading-button`); - }); - - it('[mui-lab] return correct demoUrl', () => { - const info = getMaterialPathInfo(`/docs/data/material/components/buttons/buttons.md`); - expect(info.demoUrl).to.equal(`/material/react-buttons`); - }); + describe('getMaterialComponentInfo', () => { + it('return correct info for material component file', () => { + const info = getMaterialComponentInfo( + path.join(process.cwd(), `/packages/mui-material/src/Button/Button.js`), + ); + sinon.assert.match(info, { + name: 'Button', + apiPathname: '/material/api/mui-material/button/', + muiName: 'MuiButton', + apiPagesDirectory: sinon.match((value) => + value.endsWith('docs/pages/material/api/mui-material'), + ), + }); - it('[mui-base] return correct apiUrl', () => { - const info = getMaterialPathInfo(`/packages/mui-base/src/ButtonUnstyled/ButtonUnstyled.tsx`); - expect(info.apiUrl).to.equal(`/material/api/mui-base/button-unstyled`); - }); + expect(info.getInheritance('ButtonBase')).to.deep.equal({ + name: 'ButtonBase', + pathname: '/material/api/mui-material/button-base/', + }); - it('[mui-base] return correct demoUrl', () => { - const info = getMaterialPathInfo(`/docs/data/material/components/buttons/buttons.md`); - expect(info.demoUrl).to.equal(`/material/react-buttons`); + let existed = false; + try { + fs.readdirSync(path.join(process.cwd(), 'docs/data')); + existed = true; + } catch (error) { + // eslint-disable-next-line no-empty + } + if (existed) { + expect(info.getDemos()).to.deep.equal([ + { + name: 'Button Group', + demoPathname: '/material/react-button-group/', + }, + { + name: 'Buttons', + demoPathname: '/material/react-buttons/', + }, + ]); + } }); }); - describe('getBasePathInfo', () => { - it('return correct apiUrl', () => { - const info = getBasePathInfo(`/packages/mui-base/src/ButtonUnstyled/ButtonUnstyled.tsx`); - expect(info.apiUrl).to.equal(`/base/api/mui-base/button-unstyled`); - }); + describe('getBaseComponentInfo', () => { + it('return correct info for base component file', () => { + const info = getBaseComponentInfo( + path.join(process.cwd(), `/packages/mui-base/src/ButtonUnstyled/ButtonUnstyled.tsx`), + ); + sinon.assert.match(info, { + name: 'ButtonUnstyled', + apiPathname: '/base/api/mui-base/button-unstyled/', + muiName: 'MuiButton', + apiPagesDirectory: sinon.match((value) => value.endsWith('docs/pages/base/api/mui-base')), + }); - it('return correct demoUrl', () => { - const info = getBasePathInfo(`/docs/data/base/components/button-unstyled/button-unstyled.md`); - expect(info.demoUrl).to.equal(`/base/react-button-unstyled`); + info.readFile(); + + expect(info.getInheritance()).to.deep.equal(null); + + let existed = false; + try { + fs.readdirSync(path.join(process.cwd(), 'docs/data')); + existed = true; + } catch (error) { + // eslint-disable-next-line no-empty + } + if (existed) { + expect(info.getDemos()).to.deep.equal([ + { + name: 'Buttons', + demoPathname: '/material/react-buttons/', + }, + ]); + } }); }); }); diff --git a/docs/scripts/buildApiUtils.ts b/docs/scripts/buildApiUtils.ts index de7b257c20293e..83851be28cc2a6 100644 --- a/docs/scripts/buildApiUtils.ts +++ b/docs/scripts/buildApiUtils.ts @@ -1,45 +1,29 @@ import fs from 'fs'; import path from 'path'; import kebabCase from 'lodash/kebabCase'; +import { getHeaders } from '@mui/markdown'; +import { findPagesMarkdown, findPagesMarkdownNew } from 'docs/src/modules/utils/find'; +import { getLineFeed } from 'docs/scripts/helpers'; +import { pageToTitle } from 'docs/src/modules/utils/helpers'; -export function findComponentDemos( +function findComponentDemos( componentName: string, pagesMarkdown: ReadonlyArray<{ pathname: string; components: readonly string[] }>, ) { - const demos = pagesMarkdown - .filter((page) => { - return page.components.includes(componentName); - }) - .map((page) => { - return page.pathname; - }); - - return Array.from(new Set(demos)); + const filteredMarkdowns = pagesMarkdown + .filter((page) => page.components.includes(componentName)) + .map((page) => page.pathname); + return Array.from(new Set(filteredMarkdowns)) // get unique filenames + .map((pathname) => ({ + name: pageToTitle({ pathname }) || '', + demoPathname: `${pathname}/`, + })); } -export function getMuiName(name: string) { +function getMuiName(name: string) { return `Mui${name.replace('Unstyled', '').replace('Styled', '')}`; } -function normalizeFilePath(filename: string) { - return filename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'); -} - -/** - * Provide information from the filename, can be component or markdown. (will be removed once migration is done) - * component example: /Users/siriwatknp/Personal-Repos/material-ui/packages/mui-material/src/Button/Button.js - * markdown example: /Users/siriwatknp/Personal-Repos/material-ui/docs/src/pages/components/buttons/buttons.md - */ -export const getGeneralPathInfo = (filename: string) => { - filename = normalizeFilePath(filename); - const componentName = filename.match(/.*\/([^/]+)\.(tsx|js)/)?.[1]; - return { - apiUrl: `/api/${kebabCase(componentName)}`, - demoUrl: filename.replace(/^.*\/pages/, '').replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), - pagesDirectory: path.join(process.cwd(), 'docs/pages/api-docs'), - }; -}; - const componentPackageMapping = { material: {} as Record, base: {} as Record, @@ -78,7 +62,7 @@ packages.forEach((pkg) => { }); }); -export const extractPackageFilePath = (filePath: string) => { +export const extractPackageFile = (filePath: string) => { filePath = filePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'); const match = filePath.match( /.*\/packages.*\/(?[^/]+)\/src\/(.*\/)?(?[^/]+)\.(js|tsx|ts|d\.ts)/, @@ -93,31 +77,180 @@ export const extractPackageFilePath = (filePath: string) => { }; }; -export const getMaterialPathInfo = (filename: string) => { - filename = normalizeFilePath(filename); - const packageName = filename.match(/packages\/([^/]+)\/src/)?.[1]; - const componentName = filename.match(/.*\/([^/]+)\.(tsx|js)/)?.[1]; - const componentPkg = componentPackageMapping.material?.[componentName ?? '']; +export const extractApiPage = (filePath: string) => { + filePath = filePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'); + return { + apiPathname: filePath + .replace(/^.*\/pages/, '') + .replace(/\.(js|tsx)/, '') + .replace(/^\/index$/, '/') // Replace `index` by `/`. + .replace(/\/index$/, ''), + }; +}; + +const parseFile = (filename: string) => { + const src = fs.readFileSync(filename, 'utf8'); + return { + src, + shouldSkip: + filename.indexOf('internal') !== -1 || + !!src.match(/@ignore - internal component\./) || + !!src.match(/@ignore - do not document\./), + spread: !src.match(/ = exactProp\(/), + EOL: getLineFeed(src), + inheritedComponent: src.match(/\/\/ @inheritedComponent (.*)/)?.[1], + }; +}; + +export type ComponentInfo = { + /** + * Full path to the file + */ + filename: string; + /** + * Component name + */ + name: string; + /** + * Component name with `Mui` prefix + */ + muiName: string; + apiPathname: string; + readFile: () => { + src: string; + spread: boolean; + shouldSkip: boolean; + EOL: string; + inheritedComponent?: string; + }; + getInheritance: (inheritedComponent?: string) => null | { + /** + * Component name + */ + name: string; + /** + * API pathname + */ + pathname: string; + }; + getDemos: () => Array<{ name: string; demoPathname: string }>; + apiPagesDirectory: string; +}; + +export const getGenericComponentInfo = (filename: string): ComponentInfo => { + const { name } = extractPackageFile(filename); + let srcInfo: null | ReturnType = null; + if (!name) { + throw new Error(`Could not find the component name from: ${filename}`); + } return { - apiUrl: `/material/api/${componentPkg}/${kebabCase(componentName)}`, - demoUrl: filename - .replace(/^.*\/data/, '') - .replace('components/', 'react-') - .replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), - pagesDirectory: path.join(process.cwd(), `docs/pages/material/api/${packageName}`), + filename, + name, + muiName: getMuiName(name), + apiPathname: `/api/${kebabCase(name)}/`, + apiPagesDirectory: path.join(process.cwd(), 'docs/pages/api-docs'), + readFile() { + srcInfo = parseFile(filename); + return srcInfo; + }, + getInheritance(inheritedComponent = srcInfo?.inheritedComponent) { + if (!inheritedComponent) { + return null; + } + return { + name: inheritedComponent, + pathname: + inheritedComponent === 'Transition' + ? 'http://reactcommunity.org/react-transition-group/transition/#Transition-props' + : `/api/${kebabCase(inheritedComponent)}/`, + }; + }, + getDemos: () => { + const allMarkdowns = findPagesMarkdown().map((markdown) => ({ + ...markdown, + components: getHeaders(fs.readFileSync(markdown.filename, 'utf8')).components as string[], + })); + return findComponentDemos(name, allMarkdowns); + }, }; }; -export const getBasePathInfo = (filename: string) => { - filename = normalizeFilePath(filename); - const componentName = filename.match(/.*\/([^/]+)\.(tsx|js)/)?.[1]; - const componentPkg = componentPackageMapping.base?.[componentName ?? '']; +export const getMaterialComponentInfo = (filename: string): ComponentInfo => { + const { name, muiPackage } = extractPackageFile(filename); + let srcInfo: null | ReturnType = null; + if (!name) { + throw new Error(`Could not find the component name from: ${filename}`); + } + const componentPkg = componentPackageMapping.material?.[name ?? '']; return { - apiUrl: `/base/api/${componentPkg}/${kebabCase(componentName)}`, - demoUrl: filename - .replace(/^.*\/data/, '') - .replace('components/', 'react-') - .replace(/\/[^/]+\.(md|js|ts|tsx)/, ''), - pagesDirectory: path.join(process.cwd(), 'docs/pages/base/api/mui-base'), + filename, + name, + muiName: getMuiName(name), + apiPathname: `/material/api/${componentPkg}/${kebabCase(name)}/`, + apiPagesDirectory: path.join(process.cwd(), `docs/pages/material/api/${muiPackage}`), + readFile() { + srcInfo = parseFile(filename); + return srcInfo; + }, + getInheritance(inheritedComponent = srcInfo?.inheritedComponent) { + if (!inheritedComponent) { + return null; + } + const inheritedPkg = componentPackageMapping.material?.[inheritedComponent ?? '']; + return { + name: inheritedComponent, + pathname: + inheritedComponent === 'Transition' + ? 'http://reactcommunity.org/react-transition-group/transition/#Transition-props' + : `/material/api/${inheritedPkg}/${kebabCase(inheritedComponent)}/`, + }; + }, + getDemos: () => { + const allMarkdowns = findPagesMarkdownNew().map((markdown) => ({ + ...markdown, + components: getHeaders(fs.readFileSync(markdown.filename, 'utf8')).components as string[], + })); + return findComponentDemos(name, allMarkdowns); + }, + }; +}; + +export const getBaseComponentInfo = (filename: string): ComponentInfo => { + const { name, muiPackage } = extractPackageFile(filename); + let srcInfo: null | ReturnType = null; + if (!name) { + throw new Error(`Could not find the component name from: ${filename}`); + } + const componentPkg = componentPackageMapping.base?.[name ?? '']; + return { + filename, + name, + muiName: getMuiName(name), + apiPathname: `/base/api/${componentPkg}/${kebabCase(name)}/`, + apiPagesDirectory: path.join(process.cwd(), `docs/pages/base/api/${muiPackage}`), + readFile() { + srcInfo = parseFile(filename); + return srcInfo; + }, + getInheritance(inheritedComponent = srcInfo?.inheritedComponent) { + if (!inheritedComponent) { + return null; + } + const inheritedPkg = componentPackageMapping.base?.[inheritedComponent ?? '']; + return { + name: inheritedComponent, + pathname: + inheritedComponent === 'Transition' + ? 'http://reactcommunity.org/react-transition-group/transition/#Transition-props' + : `/base/api/${inheritedPkg}/${kebabCase(inheritedComponent)}/`, + }; + }, + getDemos: () => { + const allMarkdowns = findPagesMarkdownNew().map((markdown) => ({ + ...markdown, + components: getHeaders(fs.readFileSync(markdown.filename, 'utf8')).components as string[], + })); + return findComponentDemos(name, allMarkdowns); + }, }; }; diff --git a/docs/src/modules/utils/find.js b/docs/src/modules/utils/find.js index 57c8b548d6ece5..8c6c2d26e662a4 100644 --- a/docs/src/modules/utils/find.js +++ b/docs/src/modules/utils/find.js @@ -46,6 +46,49 @@ function findPagesMarkdown( return pagesMarkdown; } +/** + * Returns the markdowns of the documentation in a flat array. + * @param {string} [directory] + * @param {Array<{ filename: string, pathname: string }>} [pagesMarkdown] + * @returns {Array<{ filename: string, pathname: string }>} + */ +function findPagesMarkdownNew( + directory = path.resolve(__dirname, '../../../data'), + pagesMarkdown = [], +) { + const items = fs.readdirSync(directory); + + items.forEach((item) => { + const itemPath = path.resolve(directory, item); + + if (fs.statSync(itemPath).isDirectory()) { + findPagesMarkdownNew(itemPath, pagesMarkdown); + return; + } + + if (!markdownRegex.test(item)) { + return; + } + + let pathname = itemPath + .replace(new RegExp(`\\${path.sep}`, 'g'), '/') + .replace(/^.*\/data/, '') + .replace('.md', ''); + + // Remove the last pathname segment. + pathname = pathname.split('/').slice(0, 4).join('/'); + + pagesMarkdown.push({ + // Relative location in the path (URL) system. + pathname: pathname.replace('components/', 'react-'), + // Relative location in the file system. + filename: itemPath, + }); + }); + + return pagesMarkdown; +} + const componentRegex = /^(Unstable_)?([A-Z][a-z]+)+\.(js|tsx)/; /** @@ -159,5 +202,6 @@ function findPages( module.exports = { findPages, findPagesMarkdown, + findPagesMarkdownNew, findComponents, }; diff --git a/docs/src/modules/utils/helpers.test.js b/docs/src/modules/utils/helpers.test.js index 75595ebc132a88..630c56a56b2deb 100644 --- a/docs/src/modules/utils/helpers.test.js +++ b/docs/src/modules/utils/helpers.test.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { getDependencies } from './helpers'; +import { getDependencies, pageToTitle } from './helpers'; describe('docs getDependencies helpers', () => { before(() => { @@ -10,6 +10,20 @@ describe('docs getDependencies helpers', () => { delete process.env.SOURCE_CODE_REPO; }); + it('should return correct title', () => { + expect(pageToTitle({ pathname: '/docs/src/pages/components/buttons/buttons.md' })).to.equal( + 'Buttons', + ); + expect(pageToTitle({ pathname: '/components' })).to.equal('Components'); + expect(pageToTitle({ pathname: '/customization/how-to-customize' })).to.equal( + 'How To Customize', + ); + }); + + it('should remove `react-` prefix', () => { + expect(pageToTitle({ pathname: '/docs/pages/material/react-buttons.js' })).to.equal('Buttons'); + }); + const s1 = ` import * as React from 'react'; import PropTypes from 'prop-types'; diff --git a/docs/src/modules/utils/helpers.ts b/docs/src/modules/utils/helpers.ts index 4a28d4d55c4b06..3993867bd474e8 100644 --- a/docs/src/modules/utils/helpers.ts +++ b/docs/src/modules/utils/helpers.ts @@ -36,7 +36,7 @@ export function pageToTitle(page: Page): string | null { } const path = page.subheader || page.pathname; - const name = path.replace(/.*\//, ''); + const name = path.replace(/.*\//, '').replace('react-', '').replace(/\..*/, ''); if (path.indexOf('/api') === 0) { return upperFirst(camelCase(name)); diff --git a/package.json b/package.json index 19592fbb12a5d9..56dd9bb00bbba9 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "release:publish": "lerna publish from-package --dist-tag latest --contents build", "release:publish:dry-run": "lerna publish from-package --dist-tag latest --contents build --registry=\"http://localhost:4873/\"", "release:tag": "node scripts/releaseTag", - "docs:api": "rimraf ./docs/pages/**/api-docs && yarn docs:api:build", + "docs:api": "rimraf ./docs/pages/**/api-docs ./docs/pages/**/api && yarn docs:api:build", "docs:api:build": "cross-env BABEL_ENV=development __NEXT_EXPORT_TRAILING_SLASH=true babel-node --extensions \".tsx,.ts,.js\" ./docs/scripts/buildApi.ts", "docs:build": "yarn workspace docs build", "docs:build-sw": "yarn workspace docs build-sw", From d7727092747c24f7a43e72562ba4f3196df04aef Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 16:00:59 +0700 Subject: [PATCH 10/37] remove unused file --- docs/data/material/pagesApi.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 docs/data/material/pagesApi.ts diff --git a/docs/data/material/pagesApi.ts b/docs/data/material/pagesApi.ts deleted file mode 100644 index e0a30c5dfa3e4f..00000000000000 --- a/docs/data/material/pagesApi.ts +++ /dev/null @@ -1 +0,0 @@ -module.exports = []; From 1eb597c8d2a62e66e6e4ceec4022dbf40cf167ea Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 18:09:03 +0700 Subject: [PATCH 11/37] fix inheritance --- docs/scripts/ApiBuilders/ComponentApiBuilder.ts | 9 +++++++-- docs/scripts/buildApiUtils.test.ts | 4 ++-- docs/scripts/buildApiUtils.ts | 8 ++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/scripts/ApiBuilders/ComponentApiBuilder.ts b/docs/scripts/ApiBuilders/ComponentApiBuilder.ts index 727b6eb52acff7..d6268b8390f735 100644 --- a/docs/scripts/ApiBuilders/ComponentApiBuilder.ts +++ b/docs/scripts/ApiBuilders/ComponentApiBuilder.ts @@ -199,7 +199,7 @@ async function annotateComponentDefinition(api: ReactApi) { let inheritanceAPILink = null; if (api.inheritance !== null) { - inheritanceAPILink = `[${api.inheritance.name} API](${HOST}${api.inheritance.pathname})`; + inheritanceAPILink = `[${api.inheritance.name} API](${HOST}${api.inheritance.apiPathname})`; } const markdownLines = (await computeApiDescription(api, { host: HOST })).split('\n'); @@ -336,7 +336,12 @@ const generateApiPage = (outputDirectory: string, reactApi: ReactApi) => { spread: reactApi.spread, forwardsRefTo: reactApi.forwardsRefTo, filename: toGithubPath(reactApi.filename), - inheritance: reactApi.inheritance, + inheritance: reactApi.inheritance + ? { + component: reactApi.inheritance.name, + pathname: reactApi.inheritance.apiPathname, + } + : null, demos: `
    ${reactApi.demos .map((item) => `
  • ${item.name}
  • `) .join('\n')}
`, diff --git a/docs/scripts/buildApiUtils.test.ts b/docs/scripts/buildApiUtils.test.ts index 3049435e0d4f2a..f75262ff5b6471 100644 --- a/docs/scripts/buildApiUtils.test.ts +++ b/docs/scripts/buildApiUtils.test.ts @@ -98,7 +98,7 @@ describe('buildApiUtils', () => { expect(info.getInheritance('ButtonBase')).to.deep.equal({ name: 'ButtonBase', - pathname: '/api/button-base/', + apiPathname: '/api/button-base/', }); expect(info.getDemos()).to.deep.equal([ @@ -130,7 +130,7 @@ describe('buildApiUtils', () => { expect(info.getInheritance('ButtonBase')).to.deep.equal({ name: 'ButtonBase', - pathname: '/material/api/mui-material/button-base/', + apiPathname: '/material/api/mui-material/button-base/', }); let existed = false; diff --git a/docs/scripts/buildApiUtils.ts b/docs/scripts/buildApiUtils.ts index 83851be28cc2a6..acf0caec7025cc 100644 --- a/docs/scripts/buildApiUtils.ts +++ b/docs/scripts/buildApiUtils.ts @@ -131,7 +131,7 @@ export type ComponentInfo = { /** * API pathname */ - pathname: string; + apiPathname: string; }; getDemos: () => Array<{ name: string; demoPathname: string }>; apiPagesDirectory: string; @@ -159,7 +159,7 @@ export const getGenericComponentInfo = (filename: string): ComponentInfo => { } return { name: inheritedComponent, - pathname: + apiPathname: inheritedComponent === 'Transition' ? 'http://reactcommunity.org/react-transition-group/transition/#Transition-props' : `/api/${kebabCase(inheritedComponent)}/`, @@ -199,7 +199,7 @@ export const getMaterialComponentInfo = (filename: string): ComponentInfo => { const inheritedPkg = componentPackageMapping.material?.[inheritedComponent ?? '']; return { name: inheritedComponent, - pathname: + apiPathname: inheritedComponent === 'Transition' ? 'http://reactcommunity.org/react-transition-group/transition/#Transition-props' : `/material/api/${inheritedPkg}/${kebabCase(inheritedComponent)}/`, @@ -239,7 +239,7 @@ export const getBaseComponentInfo = (filename: string): ComponentInfo => { const inheritedPkg = componentPackageMapping.base?.[inheritedComponent ?? '']; return { name: inheritedComponent, - pathname: + apiPathname: inheritedComponent === 'Transition' ? 'http://reactcommunity.org/react-transition-group/transition/#Transition-props' : `/base/api/${inheritedPkg}/${kebabCase(inheritedComponent)}/`, From c28ea2d1cab623594deb25b328a1f6d237583092 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 18:09:38 +0700 Subject: [PATCH 12/37] remove unnecessary / --- docs/src/modules/components/AppNavDrawer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/modules/components/AppNavDrawer.js b/docs/src/modules/components/AppNavDrawer.js index 25a4c8d68ddf53..23f9e73c9a1f24 100644 --- a/docs/src/modules/components/AppNavDrawer.js +++ b/docs/src/modules/components/AppNavDrawer.js @@ -116,7 +116,7 @@ function reduceChildRoutes(context) { if (page.children && page.children.length > 1) { const title = pageToTitleI18n(page, t); - const topLevel = activePage ? activePage.pathname.indexOf(`${page.pathname}/`) === 0 : false; + const topLevel = activePage ? activePage.pathname.indexOf(`${page.pathname}`) === 0 : false; items.push( Date: Thu, 23 Dec 2021 18:11:47 +0700 Subject: [PATCH 13/37] manually update pages --- docs/data/material/pages.ts | 5 +++-- docs/tsconfig.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/data/material/pages.ts b/docs/data/material/pages.ts index 43a33b5595350f..7a16d417fb1774 100644 --- a/docs/data/material/pages.ts +++ b/docs/data/material/pages.ts @@ -17,7 +17,8 @@ const pages = [ ], }, { - pathname: '/material/components', + pathname: '/material/react-', + title: 'Components', icon: 'ToggleOnIcon', children: [ { @@ -125,7 +126,7 @@ const pages = [ subheader: 'data-grid', }, { - pathname: '/material/lab', + pathname: '/material', subheader: 'lab', children: [ { pathname: '/material/about-the-lab', title: 'About the lab 🧪' }, diff --git a/docs/tsconfig.json b/docs/tsconfig.json index d541bdd030a46d..b3282c841557ef 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../tsconfig.json", - "include": ["next-env.d.ts", "types", "src", "pages"], + "include": ["next-env.d.ts", "types", "src", "pages", "data"], "compilerOptions": { "allowJs": true, "isolatedModules": true, From 97a75a64589a87e6cf52eae91c4b28d4bbaafd37 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 21:58:19 +0700 Subject: [PATCH 14/37] fix current e2e tests --- test/e2e-website/material-current.spec.ts | 77 +++-------------------- 1 file changed, 9 insertions(+), 68 deletions(-) diff --git a/test/e2e-website/material-current.spec.ts b/test/e2e-website/material-current.spec.ts index 85627407437648..1041a0d9558a32 100644 --- a/test/e2e-website/material-current.spec.ts +++ b/test/e2e-website/material-current.spec.ts @@ -1,6 +1,5 @@ import { test as base, expect } from '@playwright/test'; import kebabCase from 'lodash/kebabCase'; -import FEATURE_TOGGLE from 'docs/src/featureToggle'; import { TestFixture } from './playwright.config'; const test = base.extend({}); @@ -33,12 +32,7 @@ test.describe.parallel('Material docs', () => { await Promise.all( anchorTexts.map((text, index) => { - return expect(anchors.nth(index)).toHaveAttribute( - 'href', - FEATURE_TOGGLE.enable_product_scope - ? `/material/api/mui-material/${kebabCase(text)}` - : `/api/${kebabCase(text)}/`, - ); + return expect(anchors.nth(index)).toHaveAttribute('href', `/api/${kebabCase(text)}/`); }), ); }); @@ -48,10 +42,7 @@ test.describe.parallel('Material docs', () => { const anchor = await page.locator('nav[aria-label="documentation"] ul a:text-is("Card")'); - await expect(anchor).toHaveAttribute( - 'href', - FEATURE_TOGGLE.enable_product_scope ? `/material/react-cards/` : `/components/cards/`, - ); + await expect(anchor).toHaveAttribute('href', `/components/cards/`); }); }); @@ -62,10 +53,7 @@ test.describe.parallel('Material docs', () => { const anchor = await page.locator('nav[aria-label="documentation"] ul a:text-is("Card")'); await expect(anchor).toHaveAttribute('app-drawer-active', ''); - await expect(anchor).toHaveAttribute( - 'href', - FEATURE_TOGGLE.enable_product_scope ? `/material/api/mui-material/card/` : `/api/card/`, - ); + await expect(anchor).toHaveAttribute('href', `/api/card/`); }); test('all the links in the main content should have correct prefix', async ({ page }) => { @@ -77,37 +65,9 @@ test.describe.parallel('Material docs', () => { const links = await Promise.all(handles.map((elm) => elm.getAttribute('href'))); - if (FEATURE_TOGGLE.enable_product_scope) { - links.forEach((link) => { - if ( - ['/getting-started', '/customization', '/guides', '/discover-more'].some((path) => - link.includes(path), - ) - ) { - expect(link.startsWith(`/material`)).toBeTruthy(); - } - - if (link.startsWith('/material') && link.includes('api')) { - expect(link).toMatch(/\/material\/api\/mui-(material|lab)\/.*/); - } - - expect(link).not.toMatch(/components/); // there should be no `components` in the url anymore - - if (link.startsWith('/system')) { - expect(link.startsWith('/system')).toBeTruthy(); - expect(link.match(/\/system{1}/g)).toHaveLength(1); // should not have repeated `/system/system/*` - } - - if (link.startsWith('/styles')) { - expect(link.startsWith('/styles')).toBeTruthy(); - expect(link.match(/\/styles{1}/g)).toHaveLength(1); // should not have repeated `/system/system/*` - } - }); - } else { - links.forEach((link) => { - expect(link.startsWith('/material')).toBeFalsy(); - }); - } + links.forEach((link) => { + expect(link.startsWith('/material')).toBeFalsy(); + }); }); }); @@ -126,12 +86,7 @@ test.describe.parallel('Material docs', () => { const anchor = await page.locator('.DocSearch-Hits a:has-text("Card")'); - if (FEATURE_TOGGLE.enable_product_scope) { - // the old url doc should point to the new location - await expect(anchor.first()).toHaveAttribute('href', `/material/react-cards/#main-content`); - } else { - await expect(anchor.first()).toHaveAttribute('href', `/components/cards/#main-content`); - } + await expect(anchor.first()).toHaveAttribute('href', `/components/cards/#main-content`); }); test('should have correct link when searching API', async ({ page }) => { @@ -148,14 +103,7 @@ test.describe.parallel('Material docs', () => { const anchor = await page.locator('.DocSearch-Hits a:has-text("Card API")'); - if (FEATURE_TOGGLE.enable_product_scope) { - await expect(anchor.first()).toHaveAttribute( - 'href', - `/api/mui-material/card/#main-content`, - ); - } else { - await expect(anchor.first()).toHaveAttribute('href', `/api/card/#main-content`); - } + await expect(anchor.first()).toHaveAttribute('href', `/api/card/#main-content`); }); test('should have correct link when searching lab API', async ({ page }) => { @@ -169,14 +117,7 @@ test.describe.parallel('Material docs', () => { const anchor = await page.locator('.DocSearch-Hits a:has-text("LoadingButton API")'); - if (FEATURE_TOGGLE.enable_product_scope) { - await expect(anchor.first()).toHaveAttribute( - 'href', - `/api/mui-lab/loading-button/#main-content`, - ); - } else { - await expect(anchor.first()).toHaveAttribute('href', `/api/loading-button/#main-content`); - } + await expect(anchor.first()).toHaveAttribute('href', `/api/loading-button/#main-content`); }); }); }); From ebb2fa4d3d01b7510ee638ee3b9cd644a678ade2 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 21:58:35 +0700 Subject: [PATCH 15/37] change feature toggle to commonjs --- docs/src/featureToggle.js | 6 ++++++ docs/src/featureToggle.ts | 7 ------- 2 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 docs/src/featureToggle.js delete mode 100644 docs/src/featureToggle.ts diff --git a/docs/src/featureToggle.js b/docs/src/featureToggle.js new file mode 100644 index 00000000000000..8e3a88e57a2070 --- /dev/null +++ b/docs/src/featureToggle.js @@ -0,0 +1,6 @@ +// need to use commonjs export so that docs/packages/markdown can use +module.exports = { + nav_products: true, + enable_product_scope: false, + enable_website_banner: false, +}; diff --git a/docs/src/featureToggle.ts b/docs/src/featureToggle.ts deleted file mode 100644 index 1d4e86ecfc3cac..00000000000000 --- a/docs/src/featureToggle.ts +++ /dev/null @@ -1,7 +0,0 @@ -const FEATURE_TOGGLE = { - nav_products: true, - enable_product_scope: false, - enable_website_banner: false, -}; - -export default FEATURE_TOGGLE; From 71b63d4ae93d211ad42b5da9706f1b6e776625cd Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 21:59:27 +0700 Subject: [PATCH 16/37] use feature toggle when parsing markdown --- docs/packages/markdown/parseMarkdown.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/packages/markdown/parseMarkdown.js b/docs/packages/markdown/parseMarkdown.js index 94a1ee981184fc..de770320f83d0f 100644 --- a/docs/packages/markdown/parseMarkdown.js +++ b/docs/packages/markdown/parseMarkdown.js @@ -2,6 +2,7 @@ const marked = require('marked'); const kebabCase = require('lodash/kebabCase'); const textToHash = require('./textToHash'); const prism = require('./prism'); +const FEATURE_TOGGLE = require('../../src/featureToggle'); const headerRegExp = /---[\r\n]([\s\S]*)[\r\n]---/; const titleRegExp = /# (.*)[\r\n]/; @@ -268,6 +269,9 @@ function prepareMarkdown(config) { ${headers.components .map((component) => { + if (!FEATURE_TOGGLE.enable_product_scope) { + return `- [\`<${component} />\`](/api/${kebabCase(component)}/)`; + } const componentPkg = componentPackageMapping[headers.product]?.[component]; return `- [\`<${component} />\`](${headers.product ? `/${headers.product}` : ''}/api/${ componentPkg ? `${componentPkg}/` : '' From 956670fce91c4d4c5db69aa0bf7f0da0aee83a23 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Thu, 23 Dec 2021 22:06:15 +0700 Subject: [PATCH 17/37] fix build api scripts to support all cases --- .../ApiBuilders/ComponentApiBuilder.ts | 18 +++++- docs/scripts/buildApi.ts | 62 +++++++------------ docs/scripts/buildApiUtils.test.ts | 36 ++++++++++- docs/scripts/restructure.ts | 8 ++- docs/src/modules/utils/find.js | 21 +++++-- 5 files changed, 95 insertions(+), 50 deletions(-) diff --git a/docs/scripts/ApiBuilders/ComponentApiBuilder.ts b/docs/scripts/ApiBuilders/ComponentApiBuilder.ts index d6268b8390f735..03aeebd673a7e8 100644 --- a/docs/scripts/ApiBuilders/ComponentApiBuilder.ts +++ b/docs/scripts/ApiBuilders/ComponentApiBuilder.ts @@ -199,7 +199,11 @@ async function annotateComponentDefinition(api: ReactApi) { let inheritanceAPILink = null; if (api.inheritance !== null) { - inheritanceAPILink = `[${api.inheritance.name} API](${HOST}${api.inheritance.apiPathname})`; + inheritanceAPILink = `[${api.inheritance.name} API](${ + api.inheritance.apiPathname.startsWith('http') + ? api.inheritance.apiPathname + : `${HOST}${api.inheritance.apiPathname}` + })`; } const markdownLines = (await computeApiDescription(api, { host: HOST })).split('\n'); @@ -211,12 +215,20 @@ async function annotateComponentDefinition(api: ReactApi) { 'Demos:', '', ...api.demos.map((item) => { - return `- [${item.name}](${HOST}${item.demoPathname})`; + return `- [${item.name}](${ + item.demoPathname.startsWith('http') ? item.demoPathname : `${HOST}${item.demoPathname}` + })`; }), '', ); - markdownLines.push('API:', '', `- [${api.name} API](${HOST}${api.apiPathname})`); + markdownLines.push( + 'API:', + '', + `- [${api.name} API](${ + api.apiPathname.startsWith('http') ? api.apiPathname : `${HOST}${api.apiPathname}` + })`, + ); if (api.inheritance !== null) { markdownLines.push(`- inherits ${inheritanceAPILink}`); } diff --git a/docs/scripts/buildApi.ts b/docs/scripts/buildApi.ts index 3952c1f44325a9..f57d4eb3b9f45a 100644 --- a/docs/scripts/buildApi.ts +++ b/docs/scripts/buildApi.ts @@ -116,9 +116,6 @@ interface Settings { getComponentInfo: (filename: string) => ComponentInfo; } -/** - * This is the refactored version of the current API building process, nothing's changed. - */ const BEFORE_MIGRATION_SETTINGS: Settings[] = [ { input: { @@ -139,40 +136,7 @@ const BEFORE_MIGRATION_SETTINGS: Settings[] = [ }, ]; -/** - * Once the preparation is done (as described in https://github.com/mui-org/material-ui/issues/30091), swithc to this settings. - * It will generate API for the current & `/material` paths, then set the redirect to link `/api/*` to `/material/api/*` - * At this point, `mui-base` content is still live in with `mui-material`. - */ -// @ts-ignore -// eslint-disable-next-line @typescript-eslint/no-unused-vars const MIGRATION_SETTINGS: Settings[] = [ - ...BEFORE_MIGRATION_SETTINGS, - { - input: { - libDirectory: [ - path.join(process.cwd(), 'packages/mui-base/src'), - path.join(process.cwd(), 'packages/mui-material/src'), - path.join(process.cwd(), 'packages/mui-lab/src'), - ], - }, - output: { - apiManifestPath: path.join(process.cwd(), 'docs/data/material/pagesApi.js'), - }, - getApiPages: () => findApiPages('docs/pages/material/api/mui-material'), - getComponentInfo: getMaterialComponentInfo, - }, -]; - -/** - * Once redirects are stable - * - Create `mui-base` content in `docs/pages/base/*` and switch to this settings. - * - Remove old content directories, eg. `docs/pages/components/*`, ...etc - */ -// @ts-ignore -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const POST_MIGRATION_SETTINGS: Settings[] = [ - ...BEFORE_MIGRATION_SETTINGS, { input: { libDirectory: [ @@ -197,14 +161,35 @@ const POST_MIGRATION_SETTINGS: Settings[] = [ getComponentInfo: getBaseComponentInfo, }, // add other products, eg. joy, data-grid, ...etc + { + // use old config so that component type definition does not change by `annotateComponentDefinition` + // TODO: remove this setting at cleanup phase + input: { + libDirectory: [ + path.join(process.cwd(), 'packages/mui-base/src'), + path.join(process.cwd(), 'packages/mui-material/src'), + path.join(process.cwd(), 'packages/mui-lab/src'), + ], + }, + output: { + apiManifestPath: path.join(process.cwd(), 'docs/src/pagesApi.js'), + }, + getApiPages: () => { + const pages = findPages({ front: true }, path.join(process.cwd(), 'docs/pages')); + return pages.find(({ pathname }) => pathname.indexOf('api') !== -1)?.children ?? []; + }, + getComponentInfo: getGenericComponentInfo, + }, ]; -const ACTIVE_SETTINGS = POST_MIGRATION_SETTINGS; +// TODO: Switch to MIGRATION_SETTINGS once ready to migrate content +const ACTIVE_SETTINGS = BEFORE_MIGRATION_SETTINGS || MIGRATION_SETTINGS; async function run(argv: { grep?: string }) { const grep = argv.grep == null ? null : new RegExp(argv.grep); let allBuilds: Array> = []; await ACTIVE_SETTINGS.reduce(async (resolvedPromise, setting) => { + await resolvedPromise; const workspaceRoot = path.resolve(__dirname, '../../'); /** * @type {string[]} @@ -283,8 +268,7 @@ async function run(argv: { grep?: string }) { const source = `module.exports = ${JSON.stringify(setting.getApiPages())}`; writePrettifiedFile(apiPagesManifestPath, source); - - await resolvedPromise; + return Promise.resolve(); }, Promise.resolve()); if (grep === null) { diff --git a/docs/scripts/buildApiUtils.test.ts b/docs/scripts/buildApiUtils.test.ts index f75262ff5b6471..6172e0af1eb229 100644 --- a/docs/scripts/buildApiUtils.test.ts +++ b/docs/scripts/buildApiUtils.test.ts @@ -2,6 +2,7 @@ import path from 'path'; import fs from 'fs'; import { expect } from 'chai'; import sinon from 'sinon'; +import FEATURE_TOGGLE from '../src/featureToggle'; import { extractApiPage, extractPackageFile, @@ -85,7 +86,7 @@ describe('buildApiUtils', () => { }); describe('getGenericComponentInfo', () => { - it('return correct apiPathname', () => { + it.only('return correct apiPathname', () => { const info = getGenericComponentInfo( path.join(process.cwd(), `/packages/mui-material/src/Button/Button.js`), ); @@ -112,9 +113,37 @@ describe('buildApiUtils', () => { }, ]); }); + + it('Icon return correct Demos annotation', () => { + const info = getGenericComponentInfo( + path.join(process.cwd(), `/packages/mui-material/src/Icon/Icon.js`), + ); + sinon.assert.match(info, { + name: 'Icon', + apiPathname: '/api/icon/', + muiName: 'MuiIcon', + apiPagesDirectory: sinon.match((value) => value.endsWith('docs/pages/api-docs')), + }); + + expect(info.getDemos()).to.deep.equal([ + { + name: 'Icons', + demoPathname: '/components/icons/', + }, + { + name: 'Material Icons', + demoPathname: '/components/material-icons/', + }, + ]); + }); }); describe('getMaterialComponentInfo', () => { + beforeEach(function test() { + if (!FEATURE_TOGGLE.enable_product_scope) { + this.skip(); + } + }); it('return correct info for material component file', () => { const info = getMaterialComponentInfo( path.join(process.cwd(), `/packages/mui-material/src/Button/Button.js`), @@ -156,6 +185,11 @@ describe('buildApiUtils', () => { }); describe('getBaseComponentInfo', () => { + beforeEach(function test() { + if (!FEATURE_TOGGLE.enable_product_scope) { + this.skip(); + } + }); it('return correct info for base component file', () => { const info = getBaseComponentInfo( path.join(process.cwd(), `/packages/mui-base/src/ButtonUnstyled/ButtonUnstyled.tsx`), diff --git a/docs/scripts/restructure.ts b/docs/scripts/restructure.ts index 64d129bfd6006b..8e6145c45eb851 100644 --- a/docs/scripts/restructure.ts +++ b/docs/scripts/restructure.ts @@ -99,6 +99,8 @@ function run() { } fs.mkdirSync(info.directory, { recursive: true }); fs.writeFileSync(info.path, data); // (A) + + fs.rmSync(filePath); } }); @@ -113,17 +115,19 @@ function run() { let data = fs.readFileSync(filePath, { encoding: 'utf-8' }); if (filePath.endsWith('.js')) { - data = data.replace('/src/pages/', `/data/${product}/`); // point to data path (A) in new directory + data = data.replace('/src/pages/', `/data/material/`); // point to data path (A) in new directory } fs.mkdirSync(info.directory, { recursive: true }); fs.writeFileSync(info.path, data); + + fs.writeFileSync(filePath, data); } } } else { let data = fs.readFileSync(filePath, { encoding: 'utf-8' }); if (filePath.endsWith('.js')) { - data = data.replace('/src/pages/', `/data/`); // point to data path (A) in new directory + data = data.replace('/src/pages/', `/data/${product}`); // point to data path (A) in new directory } fs.writeFileSync(filePath, data); } diff --git a/docs/src/modules/utils/find.js b/docs/src/modules/utils/find.js index 8c6c2d26e662a4..a0ec3160d16a76 100644 --- a/docs/src/modules/utils/find.js +++ b/docs/src/modules/utils/find.js @@ -1,5 +1,6 @@ const fs = require('fs'); const path = require('path'); +const FEATURE_TOGGLE = require('../../featureToggle'); const markdownRegex = /\.md$/; @@ -10,7 +11,9 @@ const markdownRegex = /\.md$/; * @returns {Array<{ filename: string, pathname: string }>} */ function findPagesMarkdown( - directory = path.resolve(__dirname, '../../../src/pages'), + directory = FEATURE_TOGGLE.enable_product_scope + ? path.resolve(__dirname, '../../../data') + : path.resolve(__dirname, '../../../src/pages'), pagesMarkdown = [], ) { const items = fs.readdirSync(directory); @@ -27,10 +30,18 @@ function findPagesMarkdown( return; } - let pathname = itemPath - .replace(new RegExp(`\\${path.sep}`, 'g'), '/') - .replace(/^.*\/pages/, '') - .replace('.md', ''); + let pathname = ''; + if (FEATURE_TOGGLE.enable_product_scope) { + pathname = itemPath + .replace(new RegExp(`\\${path.sep}`, 'g'), '/') + .replace(/^.*\/material[^-]/, '/') + .replace('.md', ''); + } else { + pathname = itemPath + .replace(new RegExp(`\\${path.sep}`, 'g'), '/') + .replace(/^.*\/pages/, '') + .replace('.md', ''); + } // Remove the last pathname segment. pathname = pathname.split('/').slice(0, 3).join('/'); From 36e2323e48a1a8e075345fe69b4288007c83ef08 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 11:01:19 +0700 Subject: [PATCH 18/37] include preview when migrate to data --- docs/scripts/restructureUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/scripts/restructureUtils.ts b/docs/scripts/restructureUtils.ts index 822adf3f637bed..424b3d2094e457 100644 --- a/docs/scripts/restructureUtils.ts +++ b/docs/scripts/restructureUtils.ts @@ -24,7 +24,7 @@ export const getNewDataLocation = ( filePath: string, product: string, ): { directory: string; path: string } | null => { - const match = filePath.match(/^(.*)\/[^/]+\.(ts|js|tsx|md|json)$/); + const match = filePath.match(/^(.*)\/[^/]+\.(ts|js|tsx|md|json|tsx\.preview)$/); if (!match) { return null; } @@ -39,7 +39,7 @@ const nonComponents = ['about-the-lab']; export const getNewPageLocation = ( filePath: string, ): { directory: string; path: string } | null => { - const match = filePath.match(/^(.*)\/[^/]+\.(ts|js|tsx|md|json)$/); + const match = filePath.match(/^(.*)\/[^/]+\.(ts|js|tsx|md|json|tsx\.preview)$/); if (!match) { return null; } From 40c908167c51d6d8801f458628bc5b7b713cbd30 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 11:12:06 +0700 Subject: [PATCH 19/37] add script to turn on enable_product_scope --- docs/scripts/restructure.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/scripts/restructure.ts b/docs/scripts/restructure.ts index 8e6145c45eb851..01f0c6ca38f0fd 100644 --- a/docs/scripts/restructure.ts +++ b/docs/scripts/restructure.ts @@ -134,6 +134,17 @@ function run() { }); }); }); + + // Turn feature toggle `enable_product_scope: true` + const featureTogglePath = path.join(process.cwd(), 'docs/src/featureToggle.js'); + let featureToggle = fs.readFileSync(featureTogglePath, { encoding: 'utf8' }); + + featureToggle = featureToggle.replace( + `enable_product_scope: false`, + `enable_product_scope: true`, + ); + + fs.writeFileSync(featureTogglePath, featureToggle); } run(); From 7a8d188816e01db623225986799a9d7ae0e2ecd9 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 11:16:06 +0700 Subject: [PATCH 20/37] fix lint --- docs/data/material/pagesApi.js | 1 + docs/scripts/buildApi.ts | 1 - docs/scripts/buildApiUtils.test.ts | 10 +++------- 3 files changed, 4 insertions(+), 8 deletions(-) create mode 100644 docs/data/material/pagesApi.js diff --git a/docs/data/material/pagesApi.js b/docs/data/material/pagesApi.js new file mode 100644 index 00000000000000..e0a30c5dfa3e4f --- /dev/null +++ b/docs/data/material/pagesApi.js @@ -0,0 +1 @@ +module.exports = []; diff --git a/docs/scripts/buildApi.ts b/docs/scripts/buildApi.ts index f57d4eb3b9f45a..7e872e87335989 100644 --- a/docs/scripts/buildApi.ts +++ b/docs/scripts/buildApi.ts @@ -55,7 +55,6 @@ const getAllFiles = (dirPath: string, arrayOfFiles: string[] = []) => { files.forEach((file) => { if (fse.statSync(`${dirPath}/${file}`).isDirectory()) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars arrayOfFiles = getAllFiles(`${dirPath}/${file}`, arrayOfFiles); } else { arrayOfFiles.push(path.join(__dirname, dirPath, '/', file)); diff --git a/docs/scripts/buildApiUtils.test.ts b/docs/scripts/buildApiUtils.test.ts index 6172e0af1eb229..25f41a079fb9ce 100644 --- a/docs/scripts/buildApiUtils.test.ts +++ b/docs/scripts/buildApiUtils.test.ts @@ -86,7 +86,7 @@ describe('buildApiUtils', () => { }); describe('getGenericComponentInfo', () => { - it.only('return correct apiPathname', () => { + it('return correct apiPathname', () => { const info = getGenericComponentInfo( path.join(process.cwd(), `/packages/mui-material/src/Button/Button.js`), ); @@ -166,9 +166,7 @@ describe('buildApiUtils', () => { try { fs.readdirSync(path.join(process.cwd(), 'docs/data')); existed = true; - } catch (error) { - // eslint-disable-next-line no-empty - } + } catch (error) {} if (existed) { expect(info.getDemos()).to.deep.equal([ { @@ -209,9 +207,7 @@ describe('buildApiUtils', () => { try { fs.readdirSync(path.join(process.cwd(), 'docs/data')); existed = true; - } catch (error) { - // eslint-disable-next-line no-empty - } + } catch (error) {} if (existed) { expect(info.getDemos()).to.deep.equal([ { From 79e7bb35e561312bda38d79e463bfc47e714bc71 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 11:37:30 +0700 Subject: [PATCH 21/37] exclude pages.ts for ts formatted --- docs/scripts/formattedTSDemos.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scripts/formattedTSDemos.js b/docs/scripts/formattedTSDemos.js index 8cc123a47d3f8c..f2cea30ac5f638 100644 --- a/docs/scripts/formattedTSDemos.js +++ b/docs/scripts/formattedTSDemos.js @@ -9,7 +9,7 @@ * List of demos to ignore when transpiling * Example: "app-bar/BottomAppBar.tsx" */ -const ignoreList = []; +const ignoreList = ['/pages.ts']; const fse = require('fs-extra'); const path = require('path'); From 0683778e8010a95126ce71a87f75ddaa685d637e Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 11:39:55 +0700 Subject: [PATCH 22/37] fix lint --- docs/scripts/buildApiUtils.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/scripts/buildApiUtils.test.ts b/docs/scripts/buildApiUtils.test.ts index 25f41a079fb9ce..fe8fe55a67c11b 100644 --- a/docs/scripts/buildApiUtils.test.ts +++ b/docs/scripts/buildApiUtils.test.ts @@ -166,6 +166,7 @@ describe('buildApiUtils', () => { try { fs.readdirSync(path.join(process.cwd(), 'docs/data')); existed = true; + // eslint-disable-next-line no-empty } catch (error) {} if (existed) { expect(info.getDemos()).to.deep.equal([ @@ -207,6 +208,7 @@ describe('buildApiUtils', () => { try { fs.readdirSync(path.join(process.cwd(), 'docs/data')); existed = true; + // eslint-disable-next-line no-empty } catch (error) {} if (existed) { expect(info.getDemos()).to.deep.equal([ From 23c4a5f5cedab8da6642628024ecc3e4c746be5d Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 12:57:34 +0700 Subject: [PATCH 23/37] fix syntax not support --- docs/packages/markdown/loader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/packages/markdown/loader.js b/docs/packages/markdown/loader.js index bc97fe06fddc88..453e2f4a3f2382 100644 --- a/docs/packages/markdown/loader.js +++ b/docs/packages/markdown/loader.js @@ -53,7 +53,8 @@ const packages = [ packages.forEach((pkg) => { pkg.paths.forEach((pkgPath) => { - const packageName = pkgPath.match(/packages\/([^/]+)\/src/)?.[1]; + const match = pkgPath.match(/packages\/([^/]+)\/src/); + const packageName = match ? match[1] : null; if (!packageName) { throw new Error(`cannot find package name from path: ${pkgPath}`); } From ba8c42e83924fc01e7b440fd5473fd9ed3c07e25 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 13:31:55 +0700 Subject: [PATCH 24/37] set redirects --- docs/next.config.js | 59 +++++++++++++++++++++++++++++++++++++++ docs/src/featureToggle.js | 4 ++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/docs/next.config.js b/docs/next.config.js index c0fbed64b1336b..561407c80e43d1 100644 --- a/docs/next.config.js +++ b/docs/next.config.js @@ -3,6 +3,7 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const pkg = require('../package.json'); const { findPages } = require('./src/modules/utils/find'); const { LANGUAGES, LANGUAGES_SSR } = require('./src/modules/constants'); +const FEATURE_TOGGLE = require('./src/featureToggle'); const workspaceRoot = path.join(__dirname, '../'); @@ -226,6 +227,64 @@ module.exports = { { source: '/api/:rest*/', destination: '/api-docs/:rest*/' }, ]; }, + async redirects() { + if (!FEATURE_TOGGLE.enable_redirects) { + return []; + } + return [ + { + source: '/getting-started/:path*', + destination: '/material/getting-started/:path*', + permanent: true, + }, + { + source: '/customization/:path*', + destination: '/material/customization/:path*', + permanent: true, + }, + { + source: '/guides/:path*', + destination: '/material/guides/:path*', + permanent: true, + }, + { + source: '/discover-more/:path*', + destination: '/material/discover-more/:path*', + permanent: true, + }, + { + source: '/components/about-the-lab', + destination: '/material/about-the-lab', + permanent: true, + }, + { + source: '/components/data-grid/:path*', + destination: '/x/react-data-grid/:path*', + permanent: true, + }, + { + source: '/components/:path*', + destination: '/material/react-:path*', + permanent: true, + }, + { + source: '/api/data-grid/:path*', + destination: '/x/api/mui-data-grid/:path*', + permanent: true, + }, + { + source: + '/api/:path(date-picker|date-time-picker|time-picker|calendar-picker|calendar-picker-skeleton|desktop-picker|mobile-date-picker|month-picker|pickers-day|static-date-picker|year-picker|masonry|timeline|timeline-connector|timeline-content|timeline-dot|timeline-item|timeline-opposite-content|timeline-separator|unstable-trap-focus|tree-item|tree-view)', + destination: '/material/api/mui-lab/:path*', + permanent: true, + }, + { + source: '/api/:path*', + destination: '/material/api/mui-material/:path*', + permanent: true, + }, + ]; + }, // Can be turned on when https://github.com/vercel/next.js/issues/24640 is fixed optimizeFonts: false, }; diff --git a/docs/src/featureToggle.js b/docs/src/featureToggle.js index 8e3a88e57a2070..716f5660c09cf7 100644 --- a/docs/src/featureToggle.js +++ b/docs/src/featureToggle.js @@ -1,6 +1,8 @@ // need to use commonjs export so that docs/packages/markdown can use module.exports = { nav_products: true, - enable_product_scope: false, enable_website_banner: false, + // TODO: cleanup once migration is done + enable_product_scope: false, // related to new structure change + enable_redirects: false, // related to new structure change }; From 75ba14730025ef1b728caef4838d5f3f5185ab85 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 15:10:39 +0700 Subject: [PATCH 25/37] fix redirects --- docs/next.config.js | 111 ++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/docs/next.config.js b/docs/next.config.js index 561407c80e43d1..c4f544a0f87d73 100644 --- a/docs/next.config.js +++ b/docs/next.config.js @@ -228,62 +228,63 @@ module.exports = { ]; }, async redirects() { - if (!FEATURE_TOGGLE.enable_redirects) { - return []; + if (FEATURE_TOGGLE.enable_redirects) { + return [ + { + source: '/getting-started/:path*', + destination: '/material/getting-started/:path*', + permanent: false, + }, + { + source: '/customization/:path*', + destination: '/material/customization/:path*', + permanent: false, + }, + { + source: '/guides/:path*', + destination: '/material/guides/:path*', + permanent: false, + }, + { + source: '/discover-more/:path*', + destination: '/material/discover-more/:path*', + permanent: false, + }, + { + source: '/components/about-the-lab', + destination: '/material/about-the-lab', + permanent: false, + }, + { + source: '/components/data-grid/:path*', + destination: '/x/react-data-grid/:path*', + permanent: false, + }, + { + source: '/components/:path*', + destination: '/material/react-:path*', + permanent: false, + }, + { + source: '/api/data-grid/:path*', + destination: '/x/api/mui-data-grid/:path*', + permanent: false, + }, + { + source: + // if this regex change, make sure to update `replaceMarkdownLinks` + '/api/:path(loading-button|tab-list|tab-panel|date-picker|date-time-picker|time-picker|calendar-picker|calendar-picker-skeleton|desktop-picker|mobile-date-picker|month-picker|pickers-day|static-date-picker|year-picker|masonry|timeline|timeline-connector|timeline-content|timeline-dot|timeline-item|timeline-opposite-content|timeline-separator|unstable-trap-focus|tree-item|tree-view)', + destination: '/material/api/mui-lab/:path*', + permanent: false, + }, + { + source: '/api/:path*', + destination: '/material/api/mui-material/:path*', + permanent: false, + }, + ]; } - return [ - { - source: '/getting-started/:path*', - destination: '/material/getting-started/:path*', - permanent: true, - }, - { - source: '/customization/:path*', - destination: '/material/customization/:path*', - permanent: true, - }, - { - source: '/guides/:path*', - destination: '/material/guides/:path*', - permanent: true, - }, - { - source: '/discover-more/:path*', - destination: '/material/discover-more/:path*', - permanent: true, - }, - { - source: '/components/about-the-lab', - destination: '/material/about-the-lab', - permanent: true, - }, - { - source: '/components/data-grid/:path*', - destination: '/x/react-data-grid/:path*', - permanent: true, - }, - { - source: '/components/:path*', - destination: '/material/react-:path*', - permanent: true, - }, - { - source: '/api/data-grid/:path*', - destination: '/x/api/mui-data-grid/:path*', - permanent: true, - }, - { - source: - '/api/:path(date-picker|date-time-picker|time-picker|calendar-picker|calendar-picker-skeleton|desktop-picker|mobile-date-picker|month-picker|pickers-day|static-date-picker|year-picker|masonry|timeline|timeline-connector|timeline-content|timeline-dot|timeline-item|timeline-opposite-content|timeline-separator|unstable-trap-focus|tree-item|tree-view)', - destination: '/material/api/mui-lab/:path*', - permanent: true, - }, - { - source: '/api/:path*', - destination: '/material/api/mui-material/:path*', - permanent: true, - }, - ]; + return []; }, // Can be turned on when https://github.com/vercel/next.js/issues/24640 is fixed optimizeFonts: false, From 34e626f389ecb02f5a4c804b3895ffb401c4f922 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 15:12:48 +0700 Subject: [PATCH 26/37] replace links in markdown when location is new --- docs/src/modules/components/ApiPage.js | 5 +- docs/src/modules/components/MarkdownDocs.js | 10 +- .../utils/replaceMarkdownLinks.test.ts | 163 ++++++++++++++++++ .../src/modules/utils/replaceMarkdownLinks.ts | 23 +++ 4 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 docs/src/modules/utils/replaceMarkdownLinks.test.ts create mode 100644 docs/src/modules/utils/replaceMarkdownLinks.ts diff --git a/docs/src/modules/components/ApiPage.js b/docs/src/modules/components/ApiPage.js index 89db4a084d05af..ff198ae460e852 100644 --- a/docs/src/modules/components/ApiPage.js +++ b/docs/src/modules/components/ApiPage.js @@ -1,6 +1,7 @@ /* eslint-disable react/no-danger */ import * as React from 'react'; import PropTypes from 'prop-types'; +import { useRouter } from 'next/router'; import clsx from 'clsx'; import { exactProp } from '@mui/utils'; import { styled } from '@mui/material/styles'; @@ -10,6 +11,7 @@ import { useTranslate, useUserLanguage } from 'docs/src/modules/utils/i18n'; import HighlightedCode from 'docs/src/modules/components/HighlightedCode'; import MarkdownElement from 'docs/src/modules/components/MarkdownElement'; import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs'; +import replaceMarkdownLinks from 'docs/src/modules/utils/replaceMarkdownLinks'; const Asterisk = styled('abbr')(({ theme }) => ({ color: theme.palette.error.main })); @@ -180,6 +182,7 @@ Heading.propTypes = { }; function ApiDocs(props) { + const router = useRouter(); const { descriptions, pageContent } = props; const t = useTranslate(); const userLanguage = useUserLanguage(); @@ -345,7 +348,7 @@ import { ${componentName} } from '${source}';`} ) : null} - + diff --git a/docs/src/modules/components/MarkdownDocs.js b/docs/src/modules/components/MarkdownDocs.js index fd0cb9b26e581a..bf94c106210293 100644 --- a/docs/src/modules/components/MarkdownDocs.js +++ b/docs/src/modules/components/MarkdownDocs.js @@ -1,11 +1,13 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { useRouter } from 'next/router'; import Demo from 'docs/src/modules/components/Demo'; import MarkdownElement from 'docs/src/modules/components/MarkdownElement'; import { exactProp } from '@mui/utils'; import ComponentLinkHeader from 'docs/src/modules/components/ComponentLinkHeader'; import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs'; import { useTranslate, useUserLanguage } from 'docs/src/modules/utils/i18n'; +import replaceMarkdownLinks from 'docs/src/modules/utils/replaceMarkdownLinks'; // TODO: Only import on demand via @mui/markdown/loader const markdownComponents = { @@ -19,6 +21,7 @@ function noComponent(moduleID) { } function MarkdownDocs(props) { + const router = useRouter(); const { disableAd = false, disableToc = false, demos = {}, docs, demoComponents } = props; const userLanguage = useUserLanguage(); @@ -37,7 +40,12 @@ function MarkdownDocs(props) { > {rendered.map((renderedMarkdownOrDemo, index) => { if (typeof renderedMarkdownOrDemo === 'string') { - return ; + return ( + + ); } if (renderedMarkdownOrDemo.component) { diff --git a/docs/src/modules/utils/replaceMarkdownLinks.test.ts b/docs/src/modules/utils/replaceMarkdownLinks.test.ts new file mode 100644 index 00000000000000..e754c6ecd24f15 --- /dev/null +++ b/docs/src/modules/utils/replaceMarkdownLinks.test.ts @@ -0,0 +1,163 @@ +import { expect } from 'chai'; +import replaceMarkdownLinks, { + replaceAPILinks, + replaceComponentLinks, +} from './replaceMarkdownLinks'; + +describe('replaceMarkdownLinks', () => { + it('replace correct component links', () => { + expect( + replaceComponentLinks(` + + `), + ).to.equal(` + + `); + }); + + it('should do nothing if the components have updated', () => { + expect( + replaceComponentLinks(` + + `), + ).to.equal(` + + `); + }); + + it('replace correct API links', () => { + expect( + replaceAPILinks(` +

API

+ `), + ).to.equal(` +

API

+ `); + }); + + it('should do nothing if the APIs have updated', () => { + expect( + replaceAPILinks(` +

API

+ `), + ).to.equal(` +

API

+ `); + }); + + it('only replace links for new routes (/material/* & /x/*)', () => { + expect( + replaceMarkdownLinks( + ` + + `, + '/material/react-buttons', + ), + ).to.equal(` + + `); + }); + + it('does nothing for old routes', () => { + expect( + replaceMarkdownLinks( + ` + + `, + '/components/buttons/', + ), + ).to.equal(` + + `); + }); +}); diff --git a/docs/src/modules/utils/replaceMarkdownLinks.ts b/docs/src/modules/utils/replaceMarkdownLinks.ts new file mode 100644 index 00000000000000..5feeaf8a6af673 --- /dev/null +++ b/docs/src/modules/utils/replaceMarkdownLinks.ts @@ -0,0 +1,23 @@ +export const replaceComponentLinks = (markdown: string) => { + return markdown + .replace(/href="\/components\/data-grid([^"]*)"/gm, 'href="/x/react-data-grid$1"') + .replace(/href="\/components\/([^"]+)"/gm, 'href="/material/react-$1"'); +}; + +export const replaceAPILinks = (markdown: string) => { + return markdown + .replace(/href="\/api\/data-grid([^"]*)"/gm, 'href="/x/api/mui-data-grid$1"') + .replace( + /href="\/api\/(loading-button|tab-list|tab-panel|date-picker|date-time-picker|time-picker|calendar-picker|calendar-picker-skeleton|desktop-picker|mobile-date-picker|month-picker|pickers-day|static-date-picker|year-picker|masonry|timeline|timeline-connector|timeline-content|timeline-dot|timeline-item|timeline-opposite-content|timeline-separator|unstable-trap-focus|tree-item|tree-view)([^"]*)"/gm, + 'href="/material/api/mui-lab/$1$2"', + ) + .replace(/href="\/api\/([^"-]+-unstyled)([^"]*)"/gm, 'href="/material/api/mui-base/$1$2"') + .replace(/href="\/api\/([^"]*)"/gm, 'href="/material/api/mui-material/$1"'); +}; + +export default function replaceMarkdownLinks(markdown: string, asPath: string) { + if (asPath.startsWith('/material/') || asPath.startsWith('/x/')) { + return replaceAPILinks(replaceComponentLinks(markdown)); + } + return markdown; +} From c763801eba5033f15ffb3ad687f6bba6e27bccb5 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 15:13:57 +0700 Subject: [PATCH 27/37] don't replace links --- docs/scripts/restructure.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/scripts/restructure.ts b/docs/scripts/restructure.ts index 01f0c6ca38f0fd..09e41c47edc8f4 100644 --- a/docs/scripts/restructure.ts +++ b/docs/scripts/restructure.ts @@ -92,7 +92,6 @@ function run() { let data = fs.readFileSync(filePath, { encoding: 'utf-8' }); if (filePath.endsWith('.md')) { data = markdown.removeDemoRelativePath(data); - data = markdown.addMaterialPrefixToLinks(data); if (product === 'material') { data = markdown.addProductFrontmatter(data, 'material'); } From 8c9200b7d67dc532d62ca473d9a0b3f66f2cfddd Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 15:17:19 +0700 Subject: [PATCH 28/37] skip some tests for new structure --- test/e2e-website/material-new.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/e2e-website/material-new.spec.ts b/test/e2e-website/material-new.spec.ts index 43d4dcacdc0fbf..f66937665da180 100644 --- a/test/e2e-website/material-new.spec.ts +++ b/test/e2e-website/material-new.spec.ts @@ -101,8 +101,9 @@ test.describe.parallel('Material docs', () => { }); }); + // enable these tests once new pages are indexed on algolia test.describe.parallel('Search', () => { - test('should have correct link when searching component', async ({ page }) => { + test.skip('should have correct link when searching component', async ({ page }) => { await page.goto(`/material/getting-started/installation/`); await page.waitForLoadState('networkidle'); // wait for docsearch @@ -116,7 +117,7 @@ test.describe.parallel('Material docs', () => { await expect(anchor.first()).toHaveAttribute('href', `/material/react-cards/#main-content`); }); - test('should have correct link when searching API', async ({ page }) => { + test.skip('should have correct link when searching API', async ({ page }) => { await page.goto(`/material/getting-started/installation/`); await page.waitForLoadState('networkidle'); // wait for docsearch From 4115a7725f58c80a5ba7585ab12db1d812f9101d Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 15:26:32 +0700 Subject: [PATCH 29/37] remove url replacement on search --- docs/src/modules/components/AppSearch.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/src/modules/components/AppSearch.js b/docs/src/modules/components/AppSearch.js index cfb02914a2f545..7242865fcd7d97 100644 --- a/docs/src/modules/components/AppSearch.js +++ b/docs/src/modules/components/AppSearch.js @@ -304,14 +304,6 @@ export default function AppSearch() { parseUrl.href = item.url; } - // TODO: remove this logic once the migration to new structure is done. - if (FEATURE_TOGGLE.enable_product_scope) { - parseUrl.href = parseUrl.href.replace( - /(? Date: Fri, 24 Dec 2021 15:53:04 +0700 Subject: [PATCH 30/37] point translation to new urls --- docs/src/modules/utils/i18n.js | 31 +++----- .../utils/replaceMarkdownLinks.test.ts | 74 +++++++++++++++++++ .../src/modules/utils/replaceMarkdownLinks.ts | 26 +++++-- 3 files changed, 102 insertions(+), 29 deletions(-) diff --git a/docs/src/modules/utils/i18n.js b/docs/src/modules/utils/i18n.js index 1862c3b9c1fcfc..8d51ecfc32e547 100644 --- a/docs/src/modules/utils/i18n.js +++ b/docs/src/modules/utils/i18n.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import * as React from 'react'; -import FEATURE_TOGGLE from 'docs/src/featureToggle'; +import { useRouter } from 'next/router'; +import replaceMarkdownLinks from 'docs/src/modules/utils/replaceMarkdownLinks'; function mapTranslations(req) { const translations = {}; @@ -62,28 +63,16 @@ export function useSetUserLanguage() { const warnedOnce = {}; export function useTranslate() { + const router = useRouter(); const userLanguage = useUserLanguage(); return React.useMemo( () => function translate(key, options = {}) { - function prefixMaterial(translation) { - if (typeof translation === 'string' && FEATURE_TOGGLE.enable_product_scope) { - let prefixed = translation; - [ - '/getting-started', - '/components', - '/api-docs', - '/customization', - '/guides', - '/discover-more', - ].forEach((pathname) => { - prefixed = prefixed.replace( - new RegExp(`href="${pathname}`, 'g'), - `href="/material${pathname}`, - ); - }); - return prefixed; + // TODO: remove this function once migration is done + function pointToNewHref(translation) { + if (typeof translation === 'string') { + return replaceMarkdownLinks(translation, router.asPath); } return translation; } @@ -104,11 +93,11 @@ export function useTranslate() { console.error(`Missing translation for ${fullKey}`); warnedOnce[fullKey] = true; } - return prefixMaterial(getPath(translations.en, key)); + return pointToNewHref(getPath(translations.en, key)); } - return prefixMaterial(translation); + return pointToNewHref(translation); }, - [userLanguage], + [userLanguage, router.asPath], ); } diff --git a/docs/src/modules/utils/replaceMarkdownLinks.test.ts b/docs/src/modules/utils/replaceMarkdownLinks.test.ts index e754c6ecd24f15..15b2dda1e1bc15 100644 --- a/docs/src/modules/utils/replaceMarkdownLinks.test.ts +++ b/docs/src/modules/utils/replaceMarkdownLinks.test.ts @@ -1,10 +1,51 @@ import { expect } from 'chai'; import replaceMarkdownLinks, { + replaceMaterialLinks, replaceAPILinks, replaceComponentLinks, } from './replaceMarkdownLinks'; describe('replaceMarkdownLinks', () => { + it('replace material related links', () => { + expect( + replaceMaterialLinks(` + + `), + ).to.equal(` + + `); + }); + + it('should not change if links have been updated', () => { + expect( + replaceMaterialLinks(` + + `), + ).to.equal(` + + `); + }); + it('replace correct component links', () => { expect( replaceComponentLinks(` @@ -115,6 +156,10 @@ describe('replaceMarkdownLinks', () => {
  • DataGridPro
  • Styles
  • System
  • + reading this guide on minimizing bundle size + default props + Get started +
  • Tree view
  • `, '/material/react-buttons', @@ -134,10 +179,31 @@ describe('replaceMarkdownLinks', () => {
  • DataGridPro
  • Styles
  • System
  • + reading this guide on minimizing bundle size + default props + Get started +
  • Tree view
  • `); }); + it('should work with json', () => { + const json = { + importDifference: + 'You can learn about the difference by reading this guide on minimizing bundle size.', + }; + expect(replaceMarkdownLinks(JSON.stringify(json), '/material/api/')).to.equal( + '{"importDifference":"You can learn about the difference by reading this guide on minimizing bundle size."}', + ); + const json2 = { + styleOverrides: + 'The name {{componentStyles.name}} can be used when providing default props or style overrides in the theme.', + }; + expect(replaceMarkdownLinks(JSON.stringify(json2), '/material/api/')).to.equal( + '{"styleOverrides":"The name {{componentStyles.name}} can be used when providing default props or style overrides in the theme."}', + ); + }); + it('does nothing for old routes', () => { expect( replaceMarkdownLinks( @@ -147,6 +213,10 @@ describe('replaceMarkdownLinks', () => {
  • Buttons
  • Tree view
  • Demo
  • + reading this guide on minimizing bundle size + default props + Get started +
  • Tree view
  • `, '/components/buttons/', @@ -157,6 +227,10 @@ describe('replaceMarkdownLinks', () => {
  • Buttons
  • Tree view
  • Demo
  • + reading this guide on minimizing bundle size + default props + Get started +
  • Tree view
  • `); }); diff --git a/docs/src/modules/utils/replaceMarkdownLinks.ts b/docs/src/modules/utils/replaceMarkdownLinks.ts index 5feeaf8a6af673..747b7413382ba4 100644 --- a/docs/src/modules/utils/replaceMarkdownLinks.ts +++ b/docs/src/modules/utils/replaceMarkdownLinks.ts @@ -1,23 +1,33 @@ +export const replaceMaterialLinks = (markdown: string) => { + return markdown.replace( + /href=(\\*?)"\/(guides|customization|getting-started|discover-more)\/([^"]*)"/gm, + 'href=$1"/material/$2/$3"', + ); +}; + export const replaceComponentLinks = (markdown: string) => { return markdown - .replace(/href="\/components\/data-grid([^"]*)"/gm, 'href="/x/react-data-grid$1"') - .replace(/href="\/components\/([^"]+)"/gm, 'href="/material/react-$1"'); + .replace(/href=(\\*?)"\/components\/data-grid([^"]*)"/gm, 'href=$1"/x/react-data-grid$2"') + .replace(/href=(\\*?)"\/components\/([^"]+)"/gm, 'href=$1"/material/react-$2"'); }; export const replaceAPILinks = (markdown: string) => { return markdown - .replace(/href="\/api\/data-grid([^"]*)"/gm, 'href="/x/api/mui-data-grid$1"') + .replace(/href=(\\*?)"\/api\/data-grid([^"]*)"/gm, 'href=$1"/x/api/mui-data-grid$2"') + .replace( + /href=(\\*?)"\/api\/(loading-button|tab-list|tab-panel|date-picker|date-time-picker|time-picker|calendar-picker|calendar-picker-skeleton|desktop-picker|mobile-date-picker|month-picker|pickers-day|static-date-picker|year-picker|masonry|timeline|timeline-connector|timeline-content|timeline-dot|timeline-item|timeline-opposite-content|timeline-separator|unstable-trap-focus|tree-item|tree-view)([^"]*)"/gm, + 'href=$1"/material/api/mui-lab/$2$3"', + ) .replace( - /href="\/api\/(loading-button|tab-list|tab-panel|date-picker|date-time-picker|time-picker|calendar-picker|calendar-picker-skeleton|desktop-picker|mobile-date-picker|month-picker|pickers-day|static-date-picker|year-picker|masonry|timeline|timeline-connector|timeline-content|timeline-dot|timeline-item|timeline-opposite-content|timeline-separator|unstable-trap-focus|tree-item|tree-view)([^"]*)"/gm, - 'href="/material/api/mui-lab/$1$2"', + /href=(\\*?)"\/api\/([^"-]+-unstyled)([^"]*)"/gm, + 'href=$1"/material/api/mui-base/$2$3"', ) - .replace(/href="\/api\/([^"-]+-unstyled)([^"]*)"/gm, 'href="/material/api/mui-base/$1$2"') - .replace(/href="\/api\/([^"]*)"/gm, 'href="/material/api/mui-material/$1"'); + .replace(/href=(\\*?)"\/api\/([^"]*)"/gm, 'href=$1"/material/api/mui-material/$2"'); }; export default function replaceMarkdownLinks(markdown: string, asPath: string) { if (asPath.startsWith('/material/') || asPath.startsWith('/x/')) { - return replaceAPILinks(replaceComponentLinks(markdown)); + return replaceMaterialLinks(replaceAPILinks(replaceComponentLinks(markdown))); } return markdown; } From 20917416bf1245e3a283a29b6402269635cd7f3a Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 16:12:11 +0700 Subject: [PATCH 31/37] use feature toggle and fix e2e --- docs/scripts/buildApi.ts | 5 ++++- test/e2e-website/material-current.spec.ts | 25 ++++++++++++----------- test/e2e-website/material-new.spec.ts | 21 ++++++++++++++----- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/docs/scripts/buildApi.ts b/docs/scripts/buildApi.ts index 7e872e87335989..e1df2891a2756c 100644 --- a/docs/scripts/buildApi.ts +++ b/docs/scripts/buildApi.ts @@ -16,6 +16,7 @@ import buildComponentApi, { writePrettifiedFile, ReactApi, } from './ApiBuilders/ComponentApiBuilder'; +import FEATURE_TOGGLE from 'docs/src/featureToggle'; const apiDocsTranslationsDirectory = path.resolve('docs', 'translations', 'api-docs'); @@ -182,7 +183,9 @@ const MIGRATION_SETTINGS: Settings[] = [ ]; // TODO: Switch to MIGRATION_SETTINGS once ready to migrate content -const ACTIVE_SETTINGS = BEFORE_MIGRATION_SETTINGS || MIGRATION_SETTINGS; +const ACTIVE_SETTINGS = FEATURE_TOGGLE.enable_product_scope + ? MIGRATION_SETTINGS + : BEFORE_MIGRATION_SETTINGS; async function run(argv: { grep?: string }) { const grep = argv.grep == null ? null : new RegExp(argv.grep); diff --git a/test/e2e-website/material-current.spec.ts b/test/e2e-website/material-current.spec.ts index 1041a0d9558a32..60c82fb0e000f5 100644 --- a/test/e2e-website/material-current.spec.ts +++ b/test/e2e-website/material-current.spec.ts @@ -1,4 +1,4 @@ -import { test as base, expect } from '@playwright/test'; +import { test as base, expect, Page } from '@playwright/test'; import kebabCase from 'lodash/kebabCase'; import { TestFixture } from './playwright.config'; @@ -72,15 +72,21 @@ test.describe.parallel('Material docs', () => { }); test.describe.parallel('Search', () => { - test('should have correct link when searching component', async ({ page }) => { - await page.goto(`/getting-started/installation/`, { waitUntil: 'networkidle' }); - + const retryToggleSearch = async (page: Page, count = 3) => { try { await page.keyboard.press('Meta+k'); await page.waitForSelector('input#docsearch-input', { timeout: 2000 }); } catch (error) { - await page.keyboard.press('Meta+k'); // retry + if (count === 0) { + throw error; + } + await retryToggleSearch(page, count - 1); } + }; + test('should have correct link when searching component', async ({ page }) => { + await page.goto(`/getting-started/installation/`, { waitUntil: 'networkidle' }); + + await retryToggleSearch(page); await page.type('input#docsearch-input', 'card', { delay: 50 }); @@ -92,12 +98,7 @@ test.describe.parallel('Material docs', () => { test('should have correct link when searching API', async ({ page }) => { await page.goto(`/getting-started/installation/`, { waitUntil: 'networkidle' }); - try { - await page.keyboard.press('Meta+k'); - await page.waitForSelector('input#docsearch-input', { timeout: 2000 }); - } catch (error) { - await page.keyboard.press('Meta+k'); // retry - } + await retryToggleSearch(page); await page.type('input#docsearch-input', 'card api', { delay: 50 }); @@ -111,7 +112,7 @@ test.describe.parallel('Material docs', () => { await page.waitForLoadState('networkidle'); // wait for docsearch - await page.keyboard.press('Meta+k'); + await retryToggleSearch(page); await page.type('input#docsearch-input', 'loading api', { delay: 50 }); diff --git a/test/e2e-website/material-new.spec.ts b/test/e2e-website/material-new.spec.ts index f66937665da180..97d8f28945a313 100644 --- a/test/e2e-website/material-new.spec.ts +++ b/test/e2e-website/material-new.spec.ts @@ -1,4 +1,4 @@ -import { test as base, expect } from '@playwright/test'; +import { test as base, expect, Page } from '@playwright/test'; import kebabCase from 'lodash/kebabCase'; import FEATURE_TOGGLE from 'docs/src/featureToggle'; import { TestFixture } from './playwright.config'; @@ -82,11 +82,11 @@ test.describe.parallel('Material docs', () => { expect(link.startsWith(`/material`)).toBeTruthy(); } - if (link.startsWith('/material') && link.includes('api')) { + if (link.startsWith('/material/api/')) { expect(link).toMatch(/\/material\/api\/mui-(material|lab)\/.*/); } - expect(link).not.toMatch(/components/); // there should be no `components` in the url anymore + expect(link).not.toMatch(/\/components/); // there should be no `/components` in the url anymore if (link.startsWith('/system')) { expect(link.startsWith('/system')).toBeTruthy(); @@ -103,12 +103,23 @@ test.describe.parallel('Material docs', () => { // enable these tests once new pages are indexed on algolia test.describe.parallel('Search', () => { + const retryToggleSearch = async (page: Page, count = 3) => { + try { + await page.keyboard.press('Meta+k'); + await page.waitForSelector('input#docsearch-input', { timeout: 2000 }); + } catch (error) { + if (count === 0) { + throw error; + } + await retryToggleSearch(page, count - 1); + } + }; test.skip('should have correct link when searching component', async ({ page }) => { await page.goto(`/material/getting-started/installation/`); await page.waitForLoadState('networkidle'); // wait for docsearch - await page.keyboard.press('Meta+k'); + await retryToggleSearch(page); await page.type('input#docsearch-input', 'card', { delay: 50 }); @@ -122,7 +133,7 @@ test.describe.parallel('Material docs', () => { await page.waitForLoadState('networkidle'); // wait for docsearch - await page.keyboard.press('Meta+k'); + await retryToggleSearch(page); await page.type('input#docsearch-input', 'card api', { delay: 50 }); From baf0b260020c0ebcd18358c143770bd0034da357 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Fri, 24 Dec 2021 16:33:41 +0700 Subject: [PATCH 32/37] use common js --- docs/packages/markdown/parseMarkdown.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/packages/markdown/parseMarkdown.js b/docs/packages/markdown/parseMarkdown.js index de770320f83d0f..be3b1fc2fdc3ee 100644 --- a/docs/packages/markdown/parseMarkdown.js +++ b/docs/packages/markdown/parseMarkdown.js @@ -272,7 +272,8 @@ ${headers.components if (!FEATURE_TOGGLE.enable_product_scope) { return `- [\`<${component} />\`](/api/${kebabCase(component)}/)`; } - const componentPkg = componentPackageMapping[headers.product]?.[component]; + const productPackage = componentPackageMapping[headers.product]; + const componentPkg = productPackage ? productPackage[component] : null; return `- [\`<${component} />\`](${headers.product ? `/${headers.product}` : ''}/api/${ componentPkg ? `${componentPkg}/` : '' }${kebabCase(component)}/)`; From f5e0b6ebd6c0fb0204a0645bca6dc046a20db214 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Sat, 25 Dec 2021 09:53:35 +0700 Subject: [PATCH 33/37] fix lint --- docs/scripts/buildApi.ts | 8 ++++---- ...MarkdownLinks.test.ts => replaceMarkdownLinks.test.js} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename docs/src/modules/utils/{replaceMarkdownLinks.test.ts => replaceMarkdownLinks.test.js} (100%) diff --git a/docs/scripts/buildApi.ts b/docs/scripts/buildApi.ts index e1df2891a2756c..d625ff39d1cbfa 100644 --- a/docs/scripts/buildApi.ts +++ b/docs/scripts/buildApi.ts @@ -3,20 +3,20 @@ import * as fse from 'fs-extra'; import path from 'path'; import kebabCase from 'lodash/kebabCase'; import * as yargs from 'yargs'; -import { findPages, findComponents } from 'docs/src/modules/utils/find'; import * as ttp from 'typescript-to-proptypes'; +import { findPages, findComponents } from 'docs/src/modules/utils/find'; +import FEATURE_TOGGLE from 'docs/src/featureToggle'; import { ComponentInfo, getGenericComponentInfo, getMaterialComponentInfo, getBaseComponentInfo, extractApiPage, -} from './buildApiUtils'; +} from 'docs/scripts/buildApiUtils'; import buildComponentApi, { writePrettifiedFile, ReactApi, -} from './ApiBuilders/ComponentApiBuilder'; -import FEATURE_TOGGLE from 'docs/src/featureToggle'; +} from 'docs/scripts/ApiBuilders/ComponentApiBuilder'; const apiDocsTranslationsDirectory = path.resolve('docs', 'translations', 'api-docs'); diff --git a/docs/src/modules/utils/replaceMarkdownLinks.test.ts b/docs/src/modules/utils/replaceMarkdownLinks.test.js similarity index 100% rename from docs/src/modules/utils/replaceMarkdownLinks.test.ts rename to docs/src/modules/utils/replaceMarkdownLinks.test.js From 21f8b3120fc3ddc0ebb72c3ef946a465b000913c Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Sat, 25 Dec 2021 09:53:47 +0700 Subject: [PATCH 34/37] revert the change and fix it in another PR --- docs/src/modules/components/AppSearch.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/src/modules/components/AppSearch.js b/docs/src/modules/components/AppSearch.js index 7242865fcd7d97..cfb02914a2f545 100644 --- a/docs/src/modules/components/AppSearch.js +++ b/docs/src/modules/components/AppSearch.js @@ -304,6 +304,14 @@ export default function AppSearch() { parseUrl.href = item.url; } + // TODO: remove this logic once the migration to new structure is done. + if (FEATURE_TOGGLE.enable_product_scope) { + parseUrl.href = parseUrl.href.replace( + /(? Date: Mon, 27 Dec 2021 08:47:08 +0700 Subject: [PATCH 35/37] unskip e2e tests --- test/e2e-website/material-new.spec.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/e2e-website/material-new.spec.ts b/test/e2e-website/material-new.spec.ts index 97d8f28945a313..a20577fe6ff666 100644 --- a/test/e2e-website/material-new.spec.ts +++ b/test/e2e-website/material-new.spec.ts @@ -101,7 +101,6 @@ test.describe.parallel('Material docs', () => { }); }); - // enable these tests once new pages are indexed on algolia test.describe.parallel('Search', () => { const retryToggleSearch = async (page: Page, count = 3) => { try { @@ -114,7 +113,7 @@ test.describe.parallel('Material docs', () => { await retryToggleSearch(page, count - 1); } }; - test.skip('should have correct link when searching component', async ({ page }) => { + test('should have correct link when searching component', async ({ page }) => { await page.goto(`/material/getting-started/installation/`); await page.waitForLoadState('networkidle'); // wait for docsearch @@ -128,7 +127,7 @@ test.describe.parallel('Material docs', () => { await expect(anchor.first()).toHaveAttribute('href', `/material/react-cards/#main-content`); }); - test.skip('should have correct link when searching API', async ({ page }) => { + test('should have correct link when searching API', async ({ page }) => { await page.goto(`/material/getting-started/installation/`); await page.waitForLoadState('networkidle'); // wait for docsearch From 0519f25ed87f28067226092a60046f77caffc263 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Mon, 27 Dec 2021 09:00:03 +0700 Subject: [PATCH 36/37] remove unused feature toggle --- docs/packages/markdown/parseMarkdown.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/packages/markdown/parseMarkdown.js b/docs/packages/markdown/parseMarkdown.js index be3b1fc2fdc3ee..1b55da0c902334 100644 --- a/docs/packages/markdown/parseMarkdown.js +++ b/docs/packages/markdown/parseMarkdown.js @@ -2,7 +2,6 @@ const marked = require('marked'); const kebabCase = require('lodash/kebabCase'); const textToHash = require('./textToHash'); const prism = require('./prism'); -const FEATURE_TOGGLE = require('../../src/featureToggle'); const headerRegExp = /---[\r\n]([\s\S]*)[\r\n]---/; const titleRegExp = /# (.*)[\r\n]/; @@ -269,9 +268,10 @@ function prepareMarkdown(config) { ${headers.components .map((component) => { - if (!FEATURE_TOGGLE.enable_product_scope) { - return `- [\`<${component} />\`](/api/${kebabCase(component)}/)`; - } + return `- [\`<${component} />\`](/api/${kebabCase(component)}/)`; + + // TODO: enable the code below once the migration is done. + // eslint-disable-next-line no-unreachable const productPackage = componentPackageMapping[headers.product]; const componentPkg = productPackage ? productPackage[component] : null; return `- [\`<${component} />\`](${headers.product ? `/${headers.product}` : ''}/api/${ From de65ae5fa29a4227b83c336264bb6bb13eb7d500 Mon Sep 17 00:00:00 2001 From: siriwatknp Date: Wed, 5 Jan 2022 13:52:15 +0700 Subject: [PATCH 37/37] fix `base` related urls --- docs/data/base/pages.ts | 12 +++++++ docs/data/base/pagesApi.js | 1 + docs/packages/markdown/loader.js | 1 + docs/packages/markdown/parseMarkdown.js | 31 ++++++++++++++++--- docs/scripts/restructure.ts | 3 ++ .../utils/replaceMarkdownLinks.test.js | 8 ++--- .../src/modules/utils/replaceMarkdownLinks.ts | 7 ++--- docs/src/modules/utils/replaceUrl.test.js | 6 ++-- docs/src/modules/utils/replaceUrl.ts | 13 +++++--- test/e2e-website/material-new.spec.ts | 8 +++++ 10 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 docs/data/base/pages.ts create mode 100644 docs/data/base/pagesApi.js diff --git a/docs/data/base/pages.ts b/docs/data/base/pages.ts new file mode 100644 index 00000000000000..8951bdb06eaa01 --- /dev/null +++ b/docs/data/base/pages.ts @@ -0,0 +1,12 @@ +import pagesApi from './pagesApi'; + +const pages = [ + { + title: 'Component API', + pathname: '/base/api', + icon: 'CodeIcon', + children: pagesApi, + }, +]; + +export default pages; diff --git a/docs/data/base/pagesApi.js b/docs/data/base/pagesApi.js new file mode 100644 index 00000000000000..e0a30c5dfa3e4f --- /dev/null +++ b/docs/data/base/pagesApi.js @@ -0,0 +1 @@ +module.exports = []; diff --git a/docs/packages/markdown/loader.js b/docs/packages/markdown/loader.js index eb799afc9c6d94..ebe2d6d910781b 100644 --- a/docs/packages/markdown/loader.js +++ b/docs/packages/markdown/loader.js @@ -61,6 +61,7 @@ packages.forEach((pkg) => { const filePaths = readdirSync(pkgPath); filePaths.forEach((folder) => { if (folder.match(/^[A-Z]/)) { + // filename starts with Uppercase = component componentPackageMapping[pkg.product][folder] = packageName; } }); diff --git a/docs/packages/markdown/parseMarkdown.js b/docs/packages/markdown/parseMarkdown.js index 1b55da0c902334..231df587eb3cab 100644 --- a/docs/packages/markdown/parseMarkdown.js +++ b/docs/packages/markdown/parseMarkdown.js @@ -251,6 +251,25 @@ function prepareMarkdown(config) { const docs = {}; const headingHashes = {}; + /** + * @param {string} product + * @example 'material' + * @param {string} componentPkg + * @example 'mui-base' + * @param {string} component + * @example 'ButtonUnstyled' + * @returns {string} + */ + function resolveComponentApiUrl(product, componentPkg, component) { + if (!product || !componentPkg) { + return `/api/${kebabCase(component)}/`; + } + if (componentPkg === 'mui-base') { + return `/base/api/${componentPkg}/${kebabCase(component)}/`; + } + return `/${product}/api/${componentPkg}/${kebabCase(component)}/`; + } + translations // Process the English markdown before the other locales. // English ToC anchor links are used in all languages @@ -272,11 +291,13 @@ ${headers.components // TODO: enable the code below once the migration is done. // eslint-disable-next-line no-unreachable - const productPackage = componentPackageMapping[headers.product]; - const componentPkg = productPackage ? productPackage[component] : null; - return `- [\`<${component} />\`](${headers.product ? `/${headers.product}` : ''}/api/${ - componentPkg ? `${componentPkg}/` : '' - }${kebabCase(component)}/)`; + const componentPkgMap = componentPackageMapping[headers.product]; + const componentPkg = componentPkgMap ? componentPkgMap[component] : null; + return `- [\`<${component} />\`](${resolveComponentApiUrl( + headers.product, + componentPkg, + component, + )})`; }) .join('\n')} `); diff --git a/docs/scripts/restructure.ts b/docs/scripts/restructure.ts index 09e41c47edc8f4..ad8cd428896f0c 100644 --- a/docs/scripts/restructure.ts +++ b/docs/scripts/restructure.ts @@ -134,6 +134,9 @@ function run() { }); }); + // include `base` pages in `_app.js` + updateAppToUseProductPagesData('base'); + // Turn feature toggle `enable_product_scope: true` const featureTogglePath = path.join(process.cwd(), 'docs/src/featureToggle.js'); let featureToggle = fs.readFileSync(featureTogglePath, { encoding: 'utf8' }); diff --git a/docs/src/modules/utils/replaceMarkdownLinks.test.js b/docs/src/modules/utils/replaceMarkdownLinks.test.js index 15b2dda1e1bc15..853ca325c9b1f5 100644 --- a/docs/src/modules/utils/replaceMarkdownLinks.test.js +++ b/docs/src/modules/utils/replaceMarkdownLinks.test.js @@ -105,7 +105,7 @@ describe('replaceMarkdownLinks', () => {

    API

    • <Button />
    • <ButtonBase />
    • -
    • <ButtonUnstyled />
    • +
    • <ButtonUnstyled />
    • <IconButton />
    • <LoadingButton />
    • DataGrid
    • @@ -122,7 +122,7 @@ describe('replaceMarkdownLinks', () => {

      API

      @@ -131,7 +131,7 @@ describe('replaceMarkdownLinks', () => {

      API

      @@ -172,7 +172,7 @@ describe('replaceMarkdownLinks', () => {
    • Demo
    • <Button />
    • <ButtonBase />
    • -
    • <ButtonUnstyled />
    • +
    • <ButtonUnstyled />
    • <IconButton />
    • <LoadingButton />
    • DataGrid
    • diff --git a/docs/src/modules/utils/replaceMarkdownLinks.ts b/docs/src/modules/utils/replaceMarkdownLinks.ts index 747b7413382ba4..b066e69e348653 100644 --- a/docs/src/modules/utils/replaceMarkdownLinks.ts +++ b/docs/src/modules/utils/replaceMarkdownLinks.ts @@ -18,15 +18,12 @@ export const replaceAPILinks = (markdown: string) => { /href=(\\*?)"\/api\/(loading-button|tab-list|tab-panel|date-picker|date-time-picker|time-picker|calendar-picker|calendar-picker-skeleton|desktop-picker|mobile-date-picker|month-picker|pickers-day|static-date-picker|year-picker|masonry|timeline|timeline-connector|timeline-content|timeline-dot|timeline-item|timeline-opposite-content|timeline-separator|unstable-trap-focus|tree-item|tree-view)([^"]*)"/gm, 'href=$1"/material/api/mui-lab/$2$3"', ) - .replace( - /href=(\\*?)"\/api\/([^"-]+-unstyled)([^"]*)"/gm, - 'href=$1"/material/api/mui-base/$2$3"', - ) + .replace(/href=(\\*?)"\/api\/([^"-]+-unstyled)([^"]*)"/gm, 'href=$1"/base/api/mui-base/$2$3"') .replace(/href=(\\*?)"\/api\/([^"]*)"/gm, 'href=$1"/material/api/mui-material/$2"'); }; export default function replaceMarkdownLinks(markdown: string, asPath: string) { - if (asPath.startsWith('/material/') || asPath.startsWith('/x/')) { + if (asPath.startsWith('/material/') || asPath.startsWith('/x/') || asPath.startsWith('/base/')) { return replaceMaterialLinks(replaceAPILinks(replaceComponentLinks(markdown))); } return markdown; diff --git a/docs/src/modules/utils/replaceUrl.test.js b/docs/src/modules/utils/replaceUrl.test.js index e72ee08341bc5a..d786f8f1d11191 100644 --- a/docs/src/modules/utils/replaceUrl.test.js +++ b/docs/src/modules/utils/replaceUrl.test.js @@ -55,7 +55,7 @@ describe('replaceUrl', () => { it('replace correct API links', () => { expect(replaceAPILinks(`/api/button/`)).to.equal(`/material/api/mui-material/button/`); expect(replaceAPILinks(`/api/button-unstyled/`)).to.equal( - `/material/api/mui-base/button-unstyled/`, + `/base/api/mui-base/button-unstyled/`, ); expect(replaceAPILinks(`/api/loading-button/`)).to.equal( `/material/api/mui-lab/loading-button/`, @@ -71,8 +71,8 @@ describe('replaceUrl', () => { expect(replaceAPILinks(`/material/api/mui-material/button/`)).to.equal( `/material/api/mui-material/button/`, ); - expect(replaceAPILinks(`/material/api/mui-base/button-unstyled/`)).to.equal( - `/material/api/mui-base/button-unstyled/`, + expect(replaceAPILinks(`/base/api/mui-base/button-unstyled/`)).to.equal( + `/base/api/mui-base/button-unstyled/`, ); expect(replaceAPILinks(`/material/api/mui-lab/loading-button/`)).to.equal( `/material/api/mui-lab/loading-button/`, diff --git a/docs/src/modules/utils/replaceUrl.ts b/docs/src/modules/utils/replaceUrl.ts index ca6de245e2ea50..b22464501797c1 100644 --- a/docs/src/modules/utils/replaceUrl.ts +++ b/docs/src/modules/utils/replaceUrl.ts @@ -16,7 +16,12 @@ export const replaceComponentLinks = (url: string) => { }; export const replaceAPILinks = (url: string) => { - if (url.startsWith('/x') || url.startsWith('/material') || !url.startsWith('/api')) { + if ( + url.startsWith('/x') || + url.startsWith('/material') || + url.startsWith('/base/') || + !url.startsWith('/api') + ) { return url; } url = url @@ -25,16 +30,16 @@ export const replaceAPILinks = (url: string) => { /\/api\/(loading-button|tab-list|tab-panel|date-picker|date-time-picker|time-picker|calendar-picker|calendar-picker-skeleton|desktop-picker|mobile-date-picker|month-picker|pickers-day|static-date-picker|year-picker|masonry|timeline|timeline-connector|timeline-content|timeline-dot|timeline-item|timeline-opposite-content|timeline-separator|unstable-trap-focus|tree-item|tree-view)(.*)/, '/material/api/mui-lab/$1$2', ) - .replace(/\/api\/([^/]+-unstyled)(.*)/, '/material/api/mui-base/$1$2'); + .replace(/\/api\/([^/]+-unstyled)(.*)/, '/base/api/mui-base/$1$2'); - if (url.startsWith('/x') || url.startsWith('/material')) { + if (url.startsWith('/x') || url.startsWith('/material') || url.startsWith('/base/')) { return url; } return url.replace(/\/api\/(.*)/, '/material/api/mui-material/$1'); }; export default function replaceUrl(url: string, asPath: string) { - if (asPath.startsWith('/material/') || asPath.startsWith('/x/')) { + if (asPath.startsWith('/material/') || asPath.startsWith('/x/') || asPath.startsWith('/base/')) { return replaceMaterialLinks(replaceAPILinks(replaceComponentLinks(url))); } return url; diff --git a/test/e2e-website/material-new.spec.ts b/test/e2e-website/material-new.spec.ts index a20577fe6ff666..451a34499ca541 100644 --- a/test/e2e-website/material-new.spec.ts +++ b/test/e2e-website/material-new.spec.ts @@ -45,6 +45,14 @@ test.describe.parallel('Material docs', () => { ); }); + test('should have correct API link to mui-base', async ({ page }) => { + await page.goto(`/material/react-buttons/`); + + await expect(page.locator('a[href="/base/api/mui-base/button-unstyled/"]')).toContainText( + '', + ); + }); + test('should have correct link for sidebar anchor', async ({ page }) => { await page.goto(`/material/react-cards/`);