Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[code-infra] Remove commonjs imports in docs #44976

Merged
merged 26 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ module.exports = function getBabelConfig(api) {
ignore: [/@babel[\\|/]runtime/], // Fix a Windows issue.
overrides: [
{
exclude: /\.test\.(js|ts|tsx)$/,
exclude: /\.test\.(m?js|ts|tsx)$/,
plugins: ['@babel/plugin-transform-react-constant-elements'],
},
{
Expand Down
7 changes: 0 additions & 7 deletions docs/config.d.ts

This file was deleted.

15 changes: 4 additions & 11 deletions docs/config.js → docs/config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Valid languages to server-side render in production
const LANGUAGES = ['en'];
export const LANGUAGES = ['en'];

// Server side rendered languages
const LANGUAGES_SSR = ['en'];
export const LANGUAGES_SSR = ['en'];

// Work in progress
const LANGUAGES_IN_PROGRESS = LANGUAGES.slice();
export const LANGUAGES_IN_PROGRESS = LANGUAGES.slice();

const LANGUAGES_IGNORE_PAGES = (pathname) => {
export const LANGUAGES_IGNORE_PAGES = (pathname: string) => {
// We don't have the bandwidth like Qt to translate our blog posts
// https://www.qt.io/zh-cn/blog
if (pathname === '/blog' || pathname.startsWith('/blog/')) {
Expand All @@ -20,10 +20,3 @@ const LANGUAGES_IGNORE_PAGES = (pathname) => {

return false;
};

module.exports = {
LANGUAGES,
LANGUAGES_IN_PROGRESS,
LANGUAGES_SSR,
LANGUAGES_IGNORE_PAGES,
};
16 changes: 6 additions & 10 deletions docs/next.config.mjs → docs/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@ import * as fs from 'fs';
// @ts-ignore
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { createRequire } from 'module';
import { findPages } from './src/modules/utils/find.mjs';
import { NextConfig } from 'next';
import { findPages } from './src/modules/utils/find';
import { LANGUAGES, LANGUAGES_SSR, LANGUAGES_IGNORE_PAGES, LANGUAGES_IN_PROGRESS } from './config';

const currentDirectory = url.fileURLToPath(new URL('.', import.meta.url));
const require = createRequire(import.meta.url);

const withDocsInfra = require('./nextConfigDocsInfra.js');
const {
LANGUAGES,
LANGUAGES_SSR,
LANGUAGES_IGNORE_PAGES,
LANGUAGES_IN_PROGRESS,
} = require('./config.js');
const withDocsInfra = require('./nextConfigDocsInfra');

const workspaceRoot = path.join(currentDirectory, '../');

Expand All @@ -30,7 +26,7 @@ const pkgContent = fs.readFileSync(path.resolve(workspaceRoot, 'package.json'),
const pkg = JSON.parse(pkgContent);

export default withDocsInfra({
webpack: (config, options) => {
webpack: (config: NextConfig, options): NextConfig => {
const plugins = config.plugins.slice();

if (process.env.DOCS_STATS_ENABLED) {
Expand Down Expand Up @@ -276,4 +272,4 @@ export default withDocsInfra({
];
},
}),
});
} satisfies NextConfig);
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"typescript": "tsc -p tsconfig.json && tsc -p scripts/tsconfig.json",
"typescript:transpile": "echo 'Use `pnpm docs:typescript:formatted'` instead && exit 1",
"typescript:transpile:dev": "echo 'Use `pnpm docs:typescript'` instead && exit 1",
"link-check": "node ./scripts/reportBrokenLinks.js"
"link-check": "tsx ./scripts/reportBrokenLinks.js"
},
"dependencies": {
"@babel/core": "^7.26.0",
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import SvgBaseUiLogo, {
} from 'docs/src/icons/SvgBaseUiLogo';
import './global.css';
import '../public/static/components-gallery/base-theme.css';
import config from '../config';
import * as config from '../config';

// Remove the license warning from demonstration purposes
LicenseInfo.setLicenseKey(process.env.NEXT_PUBLIC_MUI_LICENSE);
Expand Down
254 changes: 42 additions & 212 deletions docs/scripts/reportBrokenLinks.js
Original file line number Diff line number Diff line change
@@ -1,182 +1,14 @@
/* eslint-disable no-console */
const path = require('path');
const fse = require('fs-extra');
const { createRender } = require('@mui/internal-markdown');
const { marked } = require('marked');
const { LANGUAGES_IGNORE_PAGES } = require('../config');

// Use renderer to extract all links into a markdown document
function getPageLinks(markdown) {
const hrefs = [];

const renderer = new marked.Renderer();
renderer.link = ({ href }) => {
if (href.startsWith('/')) {
hrefs.push(href);
}
};
marked(markdown, { renderer });
return hrefs;
}

// List all .js files in a folder
function getJsFilesInFolder(folderPath) {
const files = fse.readdirSync(folderPath, { withFileTypes: true });
return files.reduce((acc, file) => {
if (file.isDirectory()) {
const filesInFolder = getJsFilesInFolder(path.join(folderPath, file.name));
return [...acc, ...filesInFolder];
}
if (file.name.endsWith('.js') || file.name.endsWith('.tsx')) {
return [...acc, path.join(folderPath, file.name).replace(/\\/g, '/')];
}
return acc;
}, []);
}

// Returns url assuming it's "./docs/pages/x/..." becomes "mui.com/x/..."
const jsFilePathToUrl = (jsFilePath) => {
const folder = path.dirname(jsFilePath);
const file = path.basename(jsFilePath);

const root = folder.slice(jsFilePath.indexOf('/pages') + '/pages'.length);
const suffix = path.extname(file);
let page = `/${file.slice(0, file.length - suffix.length)}/`;

if (page === '/index/') {
page = '/';
}

return `${root}${page}`;
};

function cleanLink(link) {
const startQueryIndex = link.indexOf('?');
const endQueryIndex = link.indexOf('#', startQueryIndex);

if (startQueryIndex === -1) {
return link;
}
if (endQueryIndex === -1) {
return link.slice(0, startQueryIndex);
}
return `${link.slice(0, startQueryIndex)}${link.slice(endQueryIndex)}`;
}

function getLinksAndAnchors(fileName) {
const headingHashes = {};
const render = createRender({
headingHashes,
options: {
ignoreLanguagePages: LANGUAGES_IGNORE_PAGES,
env: {
SOURCE_CODE_REPO: '',
},
},
});

const data = fse.readFileSync(fileName, { encoding: 'utf8' });
render(data);

const links = getPageLinks(data).map(cleanLink);

return {
hashes: Object.keys(headingHashes),
links,
};
}

const markdownImportRegExp = /'(.*)\?(muiMarkdown|@mui\/markdown)'/g;

const getMdFilesImported = (jsPageFile) => {
// For each JS file extract the markdown rendered if it exists
const fileContent = fse.readFileSync(jsPageFile, 'utf8');
/**
* Content files can be represented by either:
* - 'docsx/data/advanced-components/overview.md?muiMarkdown'; (for mui-x)
* - 'docs/data/advanced-components/overview.md?muiMarkdown';
* - './index.md?muiMarkdown';
*/
const importPaths = fileContent.match(markdownImportRegExp);

if (importPaths === null) {
return [];
}
return importPaths.map((importPath) => {
let cleanImportPath = importPath.replace(markdownImportRegExp, '$1');
if (cleanImportPath.startsWith('.')) {
cleanImportPath = path.join(path.dirname(jsPageFile), cleanImportPath);
} else {
/**
* convert /Users/oliviertassinari/base-ui/docs/pages/base-ui/react-switch/index.js
* and docs-base/data/base/components/switch/switch.md
* into /Users/oliviertassinari/base-ui/docs/data/base/components/switch/switch.md
*/
const cleanImportPathArray = cleanImportPath.split('/');
// Assume that the first folder is /docs or an alias that starts with /docs
cleanImportPathArray.shift();

// Truncate jsPageFile at /docs/ and append cleanImportPath
cleanImportPath = path.join(
jsPageFile.slice(0, jsPageFile.indexOf('/docs/')),
'docs',
cleanImportPathArray.join('/'),
);
}

return cleanImportPath;
});
};

const parseDocFolder = (folderPath, availableLinks = {}, usedLinks = {}) => {
const jsPageFiles = getJsFilesInFolder(folderPath);

const mdFiles = jsPageFiles.flatMap((jsPageFile) => {
const pageUrl = jsFilePathToUrl(jsPageFile);
const importedMds = getMdFilesImported(jsPageFile);

return importedMds.map((fileName) => ({ fileName, url: pageUrl }));
});

// Mark all the existing page as available
jsPageFiles.forEach((jsFilePath) => {
const url = jsFilePathToUrl(jsFilePath);
availableLinks[url] = true;
});

// For each markdown file, extract links
mdFiles.forEach(({ fileName, url }) => {
const { hashes, links } = getLinksAndAnchors(fileName);

links.forEach((link) => {
if (usedLinks[link] === undefined) {
usedLinks[link] = [fileName];
} else {
usedLinks[link].push(fileName);
}
});

hashes.forEach((hash) => {
availableLinks[`${url}#${hash}`] = true;
});
});
};

const getAnchor = (link) => {
const splittedPath = link.split('/');
const potentialAnchor = splittedPath[splittedPath.length - 1];
return potentialAnchor.includes('#') ? potentialAnchor : '';
};

// Export useful method for doing similar checks in other repositories
module.exports = { parseDocFolder, getAnchor };
import path from 'path';
import fse from 'fs-extra';
import { parseDocFolder, getAnchor } from './reportBrokenLinksLib';

/**
* The remaining pat to the code is specific to this repository
*/
const UNSUPPORTED_PATHS = ['/api/', '/careers/', '/store/', '/x/'];

const docsSpaceRoot = path.join(__dirname, '../');
const docsSpaceRoot = path.join(path.dirname(new URL(import.meta.url).pathname), '../');

const buffer = [];
function write(text) {
Expand All @@ -193,43 +25,41 @@ function getPageUrlFromLink(link) {
return rep;
}

if (require.main === module) {
// {[url with hash]: true}
const availableLinks = {};

// {[url with hash]: list of files using this link}
const usedLinks = {};

parseDocFolder(path.join(docsSpaceRoot, './pages/'), availableLinks, usedLinks);

write('Broken links found by `pnpm docs:link-check` that exist:\n');
Object.keys(usedLinks)
.filter((link) => link.startsWith('/'))
.filter((link) => !availableLinks[link])
// these url segments are specific to Base UI and added by scripts (can not be found in markdown)
.filter((link) =>
['components-api', 'hooks-api', '#unstyled'].every((str) => !link.includes(str)),
)
.filter((link) => UNSUPPORTED_PATHS.every((unsupportedPath) => !link.includes(unsupportedPath)))
.sort()
.forEach((linkKey) => {
//
// <!-- #default-branch-switch -->
//
write(`- https://mui.com${linkKey}`);
console.log(`https://mui.com${linkKey}`);

console.log(`used in`);
usedLinks[linkKey].forEach((f) => console.log(`- ${path.relative(docsSpaceRoot, f)}`));
console.log('available anchors on the same page:');
console.log(
Object.keys(availableLinks)
.filter((link) => getPageUrlFromLink(link) === getPageUrlFromLink(linkKey))
.sort()
.map(getAnchor)
.join('\n'),
);
console.log('\n\n');
});
save(buffer);
}
// {[url with hash]: true}
const availableLinks = {};

// {[url with hash]: list of files using this link}
const usedLinks = {};

parseDocFolder(path.join(docsSpaceRoot, './pages/'), availableLinks, usedLinks);

write('Broken links found by `pnpm docs:link-check` that exist:\n');
Object.keys(usedLinks)
.filter((link) => link.startsWith('/'))
.filter((link) => !availableLinks[link])
// these url segments are specific to Base UI and added by scripts (can not be found in markdown)
.filter((link) =>
['components-api', 'hooks-api', '#unstyled'].every((str) => !link.includes(str)),
)
.filter((link) => UNSUPPORTED_PATHS.every((unsupportedPath) => !link.includes(unsupportedPath)))
.sort()
.forEach((linkKey) => {
//
// <!-- #default-branch-switch -->
//
write(`- https://mui.com${linkKey}`);
console.log(`https://mui.com${linkKey}`);

console.log(`used in`);
usedLinks[linkKey].forEach((f) => console.log(`- ${path.relative(docsSpaceRoot, f)}`));
console.log('available anchors on the same page:');
console.log(
Object.keys(availableLinks)
.filter((link) => getPageUrlFromLink(link) === getPageUrlFromLink(linkKey))
.sort()
.map(getAnchor)
.join('\n'),
);
console.log('\n\n');
});
save(buffer);
Loading
Loading