init project
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled

This commit is contained in:
how2ice
2025-12-12 14:26:25 +09:00
commit 005cf56baf
43188 changed files with 1079531 additions and 0 deletions

53
test/bundling/README.md Normal file
View File

@@ -0,0 +1,53 @@
# Bundle fixtures
A collection of "smoke"-test that verify that the package layout is correct.
`createFixture` is used to create new or update existing fixtures.
The created file might need some manual adjustment since not every edge case is covered.
## Run a fixture
### To test a Pull Request
1. Checkout branch
1. `pnpm install`
1. `pnpm lerna run build --scope "@mui/*"`
1. `pnpm release:pack`
1. Navigate into the fixture you want to test (where the `package.json` is located)
1. `pnpm install --ignore-workspace`
1. `pnpm start`
### To test a published npm dist tag
_For example: `latest` or `next` on npm or a pkg.pr.new published version_
1. Navigate into the fixture you want to test (where the `package.json` is located)
1. Adjust `pnpm.overrides` of the `package.json` file to point to the desired version
1. `pnpm install --ignore-workspace`
1. `pnpm start`
### In CI
You have to run our CircleCI pipeline with the `workflow` parameter set to `bundling`.
With the following API request we're triggering a run of the bundling workflow in
PR #24289:
```bash
curl --request POST \
--url https://circleci.com/api/v2/project/gh/mui/material-ui/pipeline \
--header 'content-type: application/json' \
--header 'Circle-Token: $CIRCLE_TOKEN' \
--data-raw '{"branch":"pull/24289/head","parameters":{"workflow":"bundling"}}'
```
`$CIRCLE_TOKEN` must be set as an environment variable created from https://app.circleci.com/settings/user/tokens.
## Add a new fixture
1. Create a folder in `test/fixtures/bundling`
1. Add the necessary dependencies
1. Re-use the entries for `dependencies` and `pnpm.overrides` for `@mui/*` packages from the other fixtures
1. Create a template
1. Write a factory that fills the template in `test/bundling/scripts/createFixture`
1. Add an entry into the `bundling` CircleCI pipeline (`.circleci/config.yml`)

View File

@@ -0,0 +1,3 @@
{{{imports}}}
{{{usage}}}

View File

@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vite App</title>
</head>
<body>
<script type="module" src="/build/esbuild.fixture.js"></script>
</body>
</html>

View File

@@ -0,0 +1,41 @@
{
"name": "esbuild",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"prestart": "node ../../scripts/createFixture.js esbuild",
"start": "pnpm build && concurrently --success first --kill-others \"pnpm server\" \"node testEsbuildIntegration\"",
"build": "esbuild esbuild.fixture.js --bundle --outfile=build/esbuild.fixture.js --tsconfig=tsconfig.json",
"server": "serve -p 5001 -s build"
},
"dependencies": {
"@emotion/core": "11.0.0",
"@emotion/react": "11.10.4",
"@emotion/styled": "11.10.4",
"@mui/material": "workspace:*",
"@mui/icons-material": "workspace:*",
"@mui/lab": "workspace:*",
"@mui/styled-engine": "workspace:*",
"@mui/system": "workspace:*",
"@mui/utils": "workspace:*",
"esbuild": "0.25.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0"
},
"devDependencies": {
"concurrently": "7.4.0",
"playwright": "1.55.1",
"serve": "14.2.4"
},
"pnpm": {
"overrides": {
"@mui/material": "file:../../../../packed/@mui/material.tgz",
"@mui/icons-material": "file:../../../../packed/@mui/icons-material.tgz",
"@mui/lab": "file:../../../../packed/@mui/lab.tgz",
"@mui/styled-engine": "file:../../../../packed/@mui/styled-engine.tgz",
"@mui/system": "file:../../../../packed/@mui/system.tgz",
"@mui/utils": "file:../../../../packed/@mui/utils.tgz"
}
}
}

View File

@@ -0,0 +1,58 @@
const { chromium } = require('@playwright/test');
/**
* @param {number} timeoutMS
* @returns {Promise<void>}
*/
function sleep(duration) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}
/**
* Attempts page.goto with retries
*
* @remarks The server and runner can be started up simultaneously
* @param {import('@playwright/test').Page} page
* @param {string} url
* @returns {boolean}
*/
async function attemptGoto(page, url) {
const maxAttempts = 10;
const retryTimeoutMS = 250;
let didNavigate = false;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
// eslint-disable-next-line no-await-in-loop
await page.goto(url);
didNavigate = true;
} catch (error) {
// eslint-disable-next-line no-await-in-loop
await sleep(retryTimeoutMS);
}
}
return didNavigate;
}
async function main() {
const browser = await chromium.launch();
const page = await browser.newPage();
page.on('console', (consoleMessage) => {
throw new Error(
`Expected no console messages but got ${consoleMessage.type()}: '${consoleMessage.text()}' `,
);
});
await attemptGoto(page, 'http://localhost:5001/');
await browser.close();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,4 @@
{
"compilerOptions": {},
"exclude": ["node_modules", "build"]
}

View File

@@ -0,0 +1,2 @@
defaults and fully supports es6-module
maintained node versions

View File

@@ -0,0 +1,6 @@
module.exports = {
siteMetadata: {
title: 'gatsby',
},
plugins: [],
};

View File

@@ -0,0 +1,7 @@
{{{imports}}}
{{{usage}}}
export default function Page() {
return null;
}

View File

@@ -0,0 +1,38 @@
{
"name": "gatsby",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"prestart": "node ../../scripts/createFixture.js gatsby",
"start": "pnpm gatsby build && concurrently --success first --kill-others \"pnpm gatsby serve\" \"node testGatsbyIntegration\""
},
"dependencies": {
"@emotion/core": "11.0.0",
"@emotion/react": "11.10.4",
"@emotion/styled": "11.10.4",
"@mui/material": "workspace:*",
"@mui/icons-material": "workspace:*",
"@mui/lab": "workspace:*",
"@mui/styled-engine": "workspace:*",
"@mui/system": "workspace:*",
"@mui/utils": "workspace:*",
"gatsby": "5.13.7",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0"
},
"devDependencies": {
"concurrently": "7.4.0",
"@playwright/test": "1.54.1"
},
"pnpm": {
"overrides": {
"@mui/material": "file:../../../../packed/@mui/material.tgz",
"@mui/icons-material": "file:../../../../packed/@mui/icons-material.tgz",
"@mui/lab": "file:../../../../packed/@mui/lab.tgz",
"@mui/styled-engine": "file:../../../../packed/@mui/styled-engine.tgz",
"@mui/system": "file:../../../../packed/@mui/system.tgz",
"@mui/utils": "file:../../../../packed/@mui/utils.tgz"
}
}
}

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
import IconButton from '@mui/material/IconButton';
import AccessibilityIcon from '@mui/icons-material/Accessibility';
export default function Development() {
return (
<div>
<IconButton>
<AccessibilityIcon />
</IconButton>
</div>
);
}

View File

@@ -0,0 +1,59 @@
const { chromium } = require('@playwright/test');
/**
* @param {number} timeoutMS
* @returns {Promise<void>}
*/
function sleep(duration) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}
/**
* Attempts page.goto with retries
*
* @remarks The server and runner can be started up simultaneously
* @param {import('@playwright/test').Page} page
* @param {string} url
* @returns {boolean}
*/
async function attemptGoto(page, url) {
const maxAttempts = 10;
const retryTimeoutMS = 250;
let didNavigate = false;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
// eslint-disable-next-line no-await-in-loop
await page.goto(url);
didNavigate = true;
} catch (error) {
// eslint-disable-next-line no-await-in-loop
await sleep(retryTimeoutMS);
}
}
return didNavigate;
}
async function main() {
const browser = await chromium.launch();
const page = await browser.newPage();
page.on('console', (consoleMessage) => {
throw new Error(
`Expected no console messages but got ${consoleMessage.type()}: '${consoleMessage.text()}' `,
);
});
await attemptGoto(page, 'http://localhost:9000/gatsby.fixture');
await browser.close();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,7 @@
{{{imports}}}
{{{usage}}}
export default function Page() {
return null;
}

View File

@@ -0,0 +1,13 @@
export default {
webpack5: false,
eslint: {
ignoreDuringBuilds: true,
},
webpack(config, { defaultLoaders }) {
config.module.rules.push({
test: /\/node_modules\/@mui\//,
use: [defaultLoaders.babel],
});
return config;
},
};

View File

@@ -0,0 +1,38 @@
{
"name": "next-webpack4",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"prestart": "node ../../scripts/createFixture.js next-webpack4",
"start": "NODE_OPTIONS=--openssl-legacy-provider pnpm next build && concurrently --success first --kill-others \"pnpm next start\" \"node testNextWebpack4Integration\""
},
"dependencies": {
"@emotion/core": "11.0.0",
"@emotion/react": "11.10.4",
"@emotion/styled": "11.10.4",
"@mui/material": "workspace:*",
"@mui/icons-material": "workspace:*",
"@mui/lab": "workspace:*",
"@mui/styled-engine": "workspace:*",
"@mui/system": "workspace:*",
"@mui/utils": "workspace:*",
"next": "14.2.30",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-is": "^17.0.2"
},
"devDependencies": {
"concurrently": "7.4.0",
"@playwright/test": "1.54.1"
},
"pnpm": {
"overrides": {
"@mui/material": "file:../../../../packed/@mui/material.tgz",
"@mui/icons-material": "file:../../../../packed/@mui/icons-material.tgz",
"@mui/lab": "file:../../../../packed/@mui/lab.tgz",
"@mui/styled-engine": "file:../../../../packed/@mui/styled-engine.tgz",
"@mui/system": "file:../../../../packed/@mui/system.tgz",
"@mui/utils": "file:../../../../packed/@mui/utils.tgz"
}
}
}

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
import IconButton from '@mui/material/IconButton';
import AccessibilityIcon from '@mui/icons-material/Accessibility';
export default function Development() {
return (
<div>
<IconButton>
<AccessibilityIcon />
</IconButton>
</div>
);
}

View File

@@ -0,0 +1,59 @@
const { chromium } = require('@playwright/test');
/**
* @param {number} timeoutMS
* @returns {Promise<void>}
*/
function sleep(duration) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}
/**
* Attempts page.goto with retries
*
* @remarks The server and runner can be started up simultaneously
* @param {import('@playwright/test').Page} page
* @param {string} url
* @returns {boolean}
*/
async function attemptGoto(page, url) {
const maxAttempts = 10;
const retryTimeoutMS = 250;
let didNavigate = false;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
// eslint-disable-next-line no-await-in-loop
await page.goto(url);
didNavigate = true;
} catch (error) {
// eslint-disable-next-line no-await-in-loop
await sleep(retryTimeoutMS);
}
}
return didNavigate;
}
async function main() {
const browser = await chromium.launch();
const page = await browser.newPage();
page.on('console', (consoleMessage) => {
throw new Error(
`Expected no console messages but got ${consoleMessage.type()}: '${consoleMessage.text()}' `,
);
});
await attemptGoto(page, 'http://localhost:5001/next-webpack.fixture');
await browser.close();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,7 @@
{{{imports}}}
{{{usage}}}
export default function Page() {
return null;
}

View File

@@ -0,0 +1,5 @@
export default {
eslint: {
ignoreDuringBuilds: true,
},
};

View File

@@ -0,0 +1,36 @@
{
"name": "next-webpack5",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"start": "pnpm next build && concurrently --success first --kill-others \"pnpm next start\" \"node testNextWebpack5Integration\""
},
"dependencies": {
"@emotion/core": "11.0.0",
"@emotion/react": "11.10.4",
"@emotion/styled": "11.10.4",
"@mui/material": "workspace:*",
"@mui/icons-material": "workspace:*",
"@mui/lab": "workspace:*",
"@mui/styled-engine": "workspace:*",
"@mui/utils": "workspace:*",
"next": "14.2.30",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0"
},
"devDependencies": {
"concurrently": "7.4.0",
"@playwright/test": "1.54.1"
},
"pnpm": {
"overrides": {
"@mui/material": "file:../../../../packed/@mui/material.tgz",
"@mui/icons-material": "file:../../../../packed/@mui/icons-material.tgz",
"@mui/lab": "file:../../../../packed/@mui/lab.tgz",
"@mui/styled-engine": "file:../../../../packed/@mui/styled-engine.tgz",
"@mui/system": "file:../../../../packed/@mui/system.tgz",
"@mui/utils": "file:../../../../packed/@mui/utils.tgz"
}
}
}

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
import IconButton from '@mui/material/IconButton';
import AccessibilityIcon from '@mui/icons-material/Accessibility';
export default function Development() {
return (
<div>
<IconButton>
<AccessibilityIcon />
</IconButton>
</div>
);
}

View File

@@ -0,0 +1,59 @@
const { chromium } = require('@playwright/test');
/**
* @param {number} timeoutMS
* @returns {Promise<void>}
*/
function sleep(duration) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}
/**
* Attempts page.goto with retries
*
* @remarks The server and runner can be started up simultaneously
* @param {import('@playwright/test').Page} page
* @param {string} url
* @returns {boolean}
*/
async function attemptGoto(page, url) {
const maxAttempts = 10;
const retryTimeoutMS = 250;
let didNavigate = false;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
// eslint-disable-next-line no-await-in-loop
await page.goto(url);
didNavigate = true;
} catch (error) {
// eslint-disable-next-line no-await-in-loop
await sleep(retryTimeoutMS);
}
}
return didNavigate;
}
async function main() {
const browser = await chromium.launch();
const page = await browser.newPage();
page.on('console', (consoleMessage) => {
throw new Error(
`Expected no console messages but got ${consoleMessage.type()}: '${consoleMessage.text()}' `,
);
});
await attemptGoto(page, 'http://localhost:5001/next-webpack.fixture');
await browser.close();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,5 @@
import * as ReactIs from 'react-is';
import Accordion2 from '@mui/material/Accordion';
// eslint-disable-next-line no-console
console.assert(ReactIs.isValidElementType(Accordion2));

View File

@@ -0,0 +1,3 @@
{{{requires}}}
{{{usage}}}

View File

@@ -0,0 +1,32 @@
{
"name": "node-esm",
"version": "1.0.0",
"main": "index.js",
"type": "commonjs",
"license": "MIT",
"scripts": {
"prestart": "node ../../scripts/createFixture.js node-cjs",
"start": "node node-cjs.fixture.js"
},
"dependencies": {
"@emotion/core": "11.0.0",
"@emotion/styled": "11.10.4",
"@mui/material": "workspace:*",
"@mui/icons-material": "workspace:*",
"@mui/lab": "workspace:*",
"@mui/styled-engine": "workspace:*",
"@mui/system": "workspace:*",
"@mui/utils": "workspace:*",
"react-is": "18.2.0"
},
"pnpm": {
"overrides": {
"@mui/material": "file:../../../../packed/@mui/material.tgz",
"@mui/icons-material": "file:../../../../packed/@mui/icons-material.tgz",
"@mui/lab": "file:../../../../packed/@mui/lab.tgz",
"@mui/styled-engine": "file:../../../../packed/@mui/styled-engine.tgz",
"@mui/system": "file:../../../../packed/@mui/system.tgz",
"@mui/utils": "file:../../../../packed/@mui/utils.tgz"
}
}
}

View File

@@ -0,0 +1,5 @@
import * as ReactIs from 'react-is';
import Accordion2 from '@mui/material/Accordion';
// eslint-disable-next-line no-console
console.assert(ReactIs.isValidElementType(Accordion2));

View File

@@ -0,0 +1,3 @@
{{{imports}}}
{{{usage}}}

View File

@@ -0,0 +1,32 @@
{
"name": "node-esm",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"license": "MIT",
"scripts": {
"prestart": "node ../../scripts/createFixture.js node-esm",
"start": "node node-esm.fixture.js"
},
"dependencies": {
"@emotion/core": "11.0.0",
"@emotion/styled": "11.10.4",
"@mui/material": "workspace:*",
"@mui/icons-material": "workspace:*",
"@mui/lab": "workspace:*",
"@mui/styled-engine": "workspace:*",
"@mui/system": "workspace:*",
"@mui/utils": "workspace:*",
"react-is": "18.2.0"
},
"pnpm": {
"overrides": {
"@mui/material": "file:../../../../packed/@mui/material.tgz",
"@mui/icons-material": "file:../../../../packed/@mui/icons-material.tgz",
"@mui/lab": "file:../../../../packed/@mui/lab.tgz",
"@mui/styled-engine": "file:../../../../packed/@mui/styled-engine.tgz",
"@mui/system": "file:../../../../packed/@mui/system.tgz",
"@mui/utils": "file:../../../../packed/@mui/utils.tgz"
}
}
}

View File

@@ -0,0 +1,40 @@
{
"name": "snowpack",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"prestart": "node ../../scripts/createFixture.js snowpack",
"start": "pnpm snowpack build && concurrently --success first --kill-others \"pnpm server\" \"node testSnowpackIntegration\"",
"server": "serve -p 5001 -s build"
},
"dependencies": {
"@emotion/core": "11.0.0",
"@emotion/react": "11.10.4",
"@emotion/styled": "11.10.4",
"@mui/material": "workspace:*",
"@mui/icons-material": "workspace:*",
"@mui/lab": "workspace:*",
"@mui/styled-engine": "workspace:*",
"@mui/system": "workspace:*",
"@mui/utils": "workspace:*",
"snowpack": "3.8.8",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0"
},
"devDependencies": {
"concurrently": "7.4.0",
"@playwright/test": "1.54.1",
"serve": "14.2.4"
},
"pnpm": {
"overrides": {
"@mui/material": "file:../../../../packed/@mui/material.tgz",
"@mui/icons-material": "file:../../../../packed/@mui/icons-material.tgz",
"@mui/lab": "file:../../../../packed/@mui/lab.tgz",
"@mui/styled-engine": "file:../../../../packed/@mui/styled-engine.tgz",
"@mui/system": "file:../../../../packed/@mui/system.tgz",
"@mui/utils": "file:../../../../packed/@mui/utils.tgz"
}
}
}

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Starter Snowpack App" />
<title>Starter Snowpack App</title>
<script type="module" src="/build/snowpack.fixture.js"></script>
</head>
<body>
<noscript>Needs JavaScript to test fixture</noscript>
</body>
</html>

View File

@@ -0,0 +1,7 @@
/** @type {import("snowpack").SnowpackUserConfig } */
module.exports = {
mount: {
public: { url: '/', static: true },
src: { url: '/build' },
},
};

View File

@@ -0,0 +1,3 @@
{{{imports}}}
{{{usage}}}

View File

@@ -0,0 +1,69 @@
const { chromium } = require('@playwright/test');
/**
* @param {number} timeoutMS
* @returns {Promise<void>}
*/
function sleep(duration) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}
/**
* Attempts page.goto with retries
*
* @remarks The server and runner can be started up simultaneously
* @param {import('@playwright/test').Page} page
* @param {string} url
* @returns {boolean}
*/
async function attemptGoto(page, url) {
const maxAttempts = 10;
const retryTimeoutMS = 250;
let didNavigate = false;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
// eslint-disable-next-line no-await-in-loop
await page.goto(url);
didNavigate = true;
} catch (error) {
// eslint-disable-next-line no-await-in-loop
await sleep(retryTimeoutMS);
}
}
return didNavigate;
}
async function main() {
const browser = await chromium.launch();
const page = await browser.newPage();
page.on('console', (consoleMessage) => {
// Unclear why snowpack bundles the development build of react-dom
// Unable to reproduce this locally
const isReactDevtoolsMessage =
consoleMessage.type() === 'info' &&
consoleMessage
.text()
.includes('Download the React DevTools for a better development experience:');
if (!isReactDevtoolsMessage) {
throw new Error(
`Expected no console messages but got ${consoleMessage.type()}: '${consoleMessage.text()}' `,
);
}
});
await attemptGoto(page, 'http://localhost:5001/');
await browser.close();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vite App</title>
</head>
<body>
<script type="module" src="/vite.fixture.js"></script>
</body>
</html>

View File

@@ -0,0 +1,40 @@
{
"name": "vite",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"prestart": "node ../../scripts/createFixture.js vite",
"start": "pnpm vite build && concurrently --success first --kill-others \"pnpm server\" \"node testViteIntegration\"",
"server": "serve -p 5001 -s build"
},
"dependencies": {
"@emotion/core": "11.0.0",
"@emotion/react": "11.10.4",
"@emotion/styled": "11.10.4",
"@mui/material": "workspace:*",
"@mui/icons-material": "workspace:*",
"@mui/lab": "workspace:*",
"@mui/styled-engine": "workspace:*",
"@mui/system": "workspace:*",
"@mui/utils": "workspace:*",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0",
"vite": "5.4.21"
},
"devDependencies": {
"concurrently": "7.4.0",
"@playwright/test": "1.54.1",
"serve": "14.2.4"
},
"pnpm": {
"overrides": {
"@mui/material": "file:../../../../packed/@mui/material.tgz",
"@mui/icons-material": "file:../../../../packed/@mui/icons-material.tgz",
"@mui/lab": "file:../../../../packed/@mui/lab.tgz",
"@mui/styled-engine": "file:../../../../packed/@mui/styled-engine.tgz",
"@mui/system": "file:../../../../packed/@mui/system.tgz",
"@mui/utils": "file:../../../../packed/@mui/utils.tgz"
}
}
}

View File

@@ -0,0 +1,59 @@
const { chromium } = require('@playwright/test');
/**
* @param {number} timeoutMS
* @returns {Promise<void>}
*/
function sleep(duration) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}
/**
* Attempts page.goto with retries
*
* @remarks The server and runner can be started up simultaneously
* @param {import('@playwright/test').Page} page
* @param {string} url
* @returns {boolean}
*/
async function attemptGoto(page, url) {
const maxAttempts = 10;
const retryTimeoutMS = 250;
let didNavigate = false;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
// eslint-disable-next-line no-await-in-loop
await page.goto(url);
didNavigate = true;
} catch (error) {
// eslint-disable-next-line no-await-in-loop
await sleep(retryTimeoutMS);
}
}
return didNavigate;
}
async function main() {
const browser = await chromium.launch();
const page = await browser.newPage();
page.on('console', (consoleMessage) => {
throw new Error(
`Expected no console messages but got ${consoleMessage.type()}: '${consoleMessage.text()}' `,
);
});
await attemptGoto(page, 'http://localhost:5001/');
await browser.close();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,11 @@
/**
* @type {import('vite').UserConfig}
*/
const config = {
mode: 'production',
build: {
outDir: 'build',
},
};
export default config;

View File

@@ -0,0 +1,3 @@
{{{imports}}}
{{{usage}}}

View File

@@ -0,0 +1,221 @@
import { promises as fs } from 'fs';
import { URL } from 'url';
import * as process from 'process';
/**
* @typedef {object} FixtureContext
* @property {URL} fixtureUrl
* @property {object} fixtureTemplateValues
*/
/**
* @param {URL} destinationUrl
* @param {string} templateSource
* @param {Record<string, string>} templateValues
*/
async function writeFromTemplate(destinationUrl, templateSource, templateValues) {
const source = Object.entries(templateValues).reduce((partialCode, [name, value]) => {
return partialCode.replace(`{{{${name}}}}`, value);
}, templateSource);
await fs.writeFile(destinationUrl, source);
}
/**
* @param {FixtureContext} context
*/
async function writeNodeESMFixture(context) {
const { fixtureUrl, fixtureTemplateValues } = context;
const destinationPath = new URL('./node-esm.fixture.js', fixtureUrl);
const templateSource = await fs.readFile(new URL('node-esm.template', fixtureUrl), {
encoding: 'utf8',
});
await writeFromTemplate(destinationPath, templateSource, fixtureTemplateValues);
}
/**
* @param {FixtureContext} context
*/
async function writeNodeCJSFixture(context) {
const { fixtureUrl, fixtureTemplateValues } = context;
const destinationPath = new URL('./node-cjs.fixture.js', fixtureUrl);
const templateSource = await fs.readFile(new URL('node-cjs.template', fixtureUrl), {
encoding: 'utf8',
});
await writeFromTemplate(destinationPath, templateSource, fixtureTemplateValues);
}
/**
* @param {FixtureContext} context
*/
async function writeNextWebpackFixture(context) {
const { fixtureUrl, fixtureTemplateValues } = context;
const destinationUrl = new URL('./pages/next-webpack.fixture.js', fixtureUrl);
const templateSource = await fs.readFile(new URL('./next-webpack.template', fixtureUrl), {
encoding: 'utf8',
});
await writeFromTemplate(destinationUrl, templateSource, fixtureTemplateValues);
}
/**
* @param {FixtureContext} context
*/
async function writeSnowpackFixture(context) {
const { fixtureUrl, fixtureTemplateValues } = context;
const destinationUrl = new URL('./src/snowpack.fixture.js', fixtureUrl);
await fs.mkdir(new URL('.', destinationUrl), { recursive: true });
const templateSource = await fs.readFile(new URL('snowpack.template', fixtureUrl), {
encoding: 'utf8',
});
await writeFromTemplate(destinationUrl, templateSource, fixtureTemplateValues);
}
/**
* @param {FixtureContext} context
*/
async function writeViteFixture(context) {
const { fixtureUrl: fixturePath, fixtureTemplateValues } = context;
const destinationPath = new URL('./vite.fixture.js', fixturePath);
const templateSource = await fs.readFile(new URL('vite.template', fixturePath), {
encoding: 'utf8',
});
await writeFromTemplate(destinationPath, templateSource, fixtureTemplateValues);
}
/**
* @param {FixtureContext} context
*/
async function writeEsbuildFixture(context) {
const { fixtureUrl, fixtureTemplateValues } = context;
const destinationPath = new URL('./esbuild.fixture.js', fixtureUrl);
const templateSource = await fs.readFile(new URL('esbuild.template', fixtureUrl), {
encoding: 'utf8',
});
await writeFromTemplate(destinationPath, templateSource, fixtureTemplateValues);
}
/**
* @param {FixtureContext} context
*/
async function writeGatsbyFixture(context) {
const { fixtureUrl: fixturePath, fixtureTemplateValues } = context;
const destinationPath = new URL('./src/pages/gatsby.fixture.js', fixturePath);
const templateSource = await fs.readFile(new URL('gatsby.template', fixturePath), {
encoding: 'utf8',
});
await writeFromTemplate(destinationPath, templateSource, fixtureTemplateValues);
}
async function readFixtureTemplateValues(fileUrl) {
const code = await fs.readFile(fileUrl, { encoding: 'utf8' });
const importsMatch = code.match(/\/\/ #region imports(.+?)\/\/ #endregion/s);
const [imports] = importsMatch;
const lines = imports.split(/\n+/).filter((line) => {
const trimmed = line.trim();
return trimmed && !trimmed.startsWith('//') && !trimmed.startsWith('/*');
});
const requires = lines
.map((line) => {
const [, specifier, module] = /import (.*) from ['"](.*)['"]/.exec(line);
if (specifier.startsWith('*')) {
return `const ${specifier.replace('* as ', '')} = require('${module}')`;
}
if (specifier.startsWith('{')) {
return `const ${specifier.replace(' as ', ': ')} = require('${module}')`;
}
return `const { default: ${specifier} } = require('${module}')`;
})
.join('\n');
const usageMatch = code.match(/\/\/ #region usage(.+?)\/\/ #endregion/s);
const [usage] = usageMatch;
return { imports, usage, requires };
}
function resolveFixtureUrl(fixtureName) {
return new URL(`../fixtures/${fixtureName}/`, import.meta.url);
}
/**
* @param {object} context
* @param {string} context.fixture
*/
async function run(context) {
const { fixture } = context;
if (fixture === undefined) {
throw new Error(`Usage: ${process.argv[1]} <fixture>`);
}
const fixtureTemplateValues = await readFixtureTemplateValues(
new URL('./fixtureTemplateValues.js', import.meta.url),
);
switch (fixture) {
case 'node-cjs':
await writeNodeCJSFixture({
fixtureUrl: resolveFixtureUrl('node-cjs'),
fixtureTemplateValues,
});
break;
case 'node-esm':
await writeNodeESMFixture({
fixtureUrl: resolveFixtureUrl('node-esm'),
fixtureTemplateValues,
});
break;
case 'next-webpack4':
await writeNextWebpackFixture({
fixtureUrl: resolveFixtureUrl('next-webpack4'),
fixtureTemplateValues,
});
break;
case 'next-webpack5':
await writeNextWebpackFixture({
fixtureUrl: resolveFixtureUrl('next-webpack5'),
fixtureTemplateValues,
});
break;
case 'snowpack':
await writeSnowpackFixture({
fixtureUrl: resolveFixtureUrl('snowpack'),
fixtureTemplateValues,
});
break;
case 'vite':
await writeViteFixture({
fixtureUrl: resolveFixtureUrl('vite'),
fixtureTemplateValues,
});
break;
case 'esbuild':
await writeEsbuildFixture({
fixtureUrl: resolveFixtureUrl('esbuild'),
fixtureTemplateValues,
});
break;
// TODO remove, no longer relevant since https://github.com/mui/material-ui/pull/38567
case 'gatsby':
await writeGatsbyFixture({
fixtureUrl: resolveFixtureUrl('gatsby'),
fixtureTemplateValues,
});
break;
default:
throw new TypeError(`Can't handle fixture '${fixture}'`);
}
}
run({ fixture: process.argv[2] }).catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,138 @@
import * as process from 'process';
import packages from './packages.js';
function isComponent(identifier) {
// Components start with Uppercase letter.
return /^[A-Z]/.test(identifier);
}
function isNamespace(identifier) {
return ['colors', 'styles', 'utils'].includes(identifier);
}
function getMuiLocal(imported, source) {
return `${imported}_${source.split('/')[1]}`;
}
/**
* @param {object} context
* @param {boolean} context.asNamedImport
* @param {string} context.local
* @param {string} context.imported
* @param {string} context.source
*/
function createImport(context) {
const { specifier, imported, local = imported, source } = context;
if (specifier === 'named') {
return `import { ${imported} as ${local} } from '${source}';`;
}
if (specifier === 'namespace') {
return `import * as ${local} from '${source}';`;
}
return `import ${local} from '${source}';`;
}
/**
* @param {NodeJS.WritableStream} outStream
*/
function writeImports(outStream) {
outStream.write(
`${createImport({
local: 'ReactIs',
modules: false,
source: 'react-is',
specifier: 'namespace',
})}\n`,
);
outStream.write('// #region imports\n');
outStream.write('/* eslint-disable import/no-duplicates */\n');
Object.entries(packages).forEach(([packageName, topLevelPackages]) => {
topLevelPackages.forEach((topLevelPackageName) => {
if (isNamespace(topLevelPackageName)) {
outStream.write(
`${createImport({
specifier: 'namespace',
local: `${getMuiLocal(topLevelPackageName, packageName)}__pathImport`,
imported: topLevelPackageName,
source: `${packageName}/${topLevelPackageName}`,
})}\n`,
);
} else {
outStream.write(
`${createImport({
specifier: 'named',
local: getMuiLocal(topLevelPackageName, packageName),
imported: topLevelPackageName,
source: packageName,
})}\n`,
);
outStream.write(
`${createImport({
specifier: 'default',
local: `${getMuiLocal(topLevelPackageName, packageName)}__pathImport`,
source: `${packageName}/${topLevelPackageName}`,
})}\n`,
);
}
});
});
outStream.write('/* eslint-enable import/no-duplicates */\n');
outStream.write('// #endregion\n');
}
function getComponentValidator(localIdentifier) {
return `ReactIs.isValidElementType(${localIdentifier})`;
}
function getNamespaceValidator(localIdentifier) {
return `${localIdentifier} !== null && typeof ${localIdentifier} === 'object'`;
}
function getUnknownValidator(localIdentifier) {
return `${localIdentifier} !== undefined`;
}
/**
* @param {NodeJS.WritableStream} outStream
*/
function writeUsage(outStream) {
outStream.write('\n// #region usage\n');
outStream.write('\n/* eslint-disable no-console */');
Object.entries(packages).forEach(([packageName, topLevelPackages]) => {
topLevelPackages.forEach((topLevelPackageName) => {
let getValidator = getUnknownValidator;
if (isNamespace(topLevelPackageName)) {
getValidator = getNamespaceValidator;
} else if (isComponent(topLevelPackageName)) {
getValidator = getComponentValidator;
}
if (!isNamespace(topLevelPackageName)) {
outStream.write(
`console.assert(${getValidator(
getMuiLocal(topLevelPackageName, packageName),
)}, '${topLevelPackageName} named import is not consumable.');\n`,
);
}
outStream.write(
`console.assert(${getValidator(
`${getMuiLocal(topLevelPackageName, packageName)}__pathImport`,
)}, '${topLevelPackageName} path import is not consumable.');\n`,
);
});
});
outStream.write('/* eslint-enable no-console */\n');
outStream.write('// #endregion\n');
}
async function main() {
const outStream = process.stdout;
writeImports(outStream);
writeUsage(outStream);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

206
test/bundling/scripts/packages.js generated Normal file
View File

@@ -0,0 +1,206 @@
export default {
'@mui/material': [
'Accordion',
'AccordionActions',
'AccordionDetails',
'AccordionSummary',
'Alert',
'AlertTitle',
'AppBar',
'Autocomplete',
'Avatar',
'Backdrop',
'Badge',
'BottomNavigation',
'BottomNavigationAction',
'Box',
'Breadcrumbs',
'Button',
'ButtonBase',
'ButtonGroup',
'Card',
'CardActionArea',
'CardActions',
'CardContent',
'CardHeader',
'CardMedia',
'Checkbox',
'Chip',
'CircularProgress',
'ClickAwayListener',
'Collapse',
'colors',
'Container',
'CssBaseline',
'Dialog',
'DialogActions',
'DialogContent',
'DialogContentText',
'DialogTitle',
'Divider',
'Drawer',
'Fab',
'Fade',
'FilledInput',
'FormControl',
'FormControlLabel',
'FormGroup',
'FormHelperText',
'FormLabel',
'Grid',
'Grow',
'Icon',
'IconButton',
'ImageList',
'ImageListItem',
'ImageListItemBar',
'Input',
'InputAdornment',
'InputBase',
'InputLabel',
'LinearProgress',
'Link',
'List',
'ListItem',
'ListItemAvatar',
'ListItemIcon',
'ListItemSecondaryAction',
'ListItemText',
'ListSubheader',
'Menu',
'MenuItem',
'MenuList',
'MobileStepper',
'Modal',
'NativeSelect',
'NoSsr',
'OutlinedInput',
'Pagination',
'PaginationItem',
'Paper',
'Popover',
'Popper',
'Portal',
'Radio',
'RadioGroup',
'Rating',
'ScopedCssBaseline',
'Select',
'Skeleton',
'Slide',
'Slider',
'Snackbar',
'SnackbarContent',
'SpeedDial',
'SpeedDialAction',
'SpeedDialIcon',
'Step',
'StepButton',
'StepConnector',
'StepContent',
'StepIcon',
'StepLabel',
'Stepper',
'styles',
'SvgIcon',
'SwipeableDrawer',
'Switch',
'Tab',
'Table',
'TableBody',
'TableCell',
'TableContainer',
'TableFooter',
'TableHead',
'TablePagination',
'TablePaginationActions',
'TableRow',
'TableSortLabel',
'Tabs',
'TabScrollButton',
'TextareaAutosize',
'TextField',
'ToggleButton',
'ToggleButtonGroup',
'Toolbar',
'Tooltip',
'Typography',
'Unstable_TrapFocus',
'useAutocomplete',
'useMediaQuery',
'usePagination',
'useScrollTrigger',
'utils',
'Zoom',
],
'@mui/icons-material': [
// Icons are generated.
// So the behavior of a single item should be equivalent to all icons.
'Accessibility',
],
'@mui/lab': [
'Alert',
'AlertTitle',
'Autocomplete',
'AvatarGroup',
'LoadingButton',
'Pagination',
'PaginationItem',
'Rating',
'Skeleton',
'SpeedDial',
'SpeedDialAction',
'SpeedDialIcon',
'TabContext',
'TabList',
'TabPanel',
// types only
// 'themeAugmentation',
'Timeline',
'TimelineConnector',
'TimelineContent',
'TimelineDot',
'TimelineItem',
'TimelineOppositeContent',
'TimelineSeparator',
'ToggleButton',
'ToggleButtonGroup',
'TreeItem',
'TreeView',
'useAutocomplete',
],
'@mui/system': [
'borders',
'breakpoints',
'compose',
'display',
'flexbox',
'grid',
// private
// 'memoize',
// 'merge',
'palette',
'positions',
// private
// 'responsivePropType',
'shadows',
'sizing',
'spacing',
'style',
// private
// 'styleFunctionSx',
'typography',
],
'@mui/utils': [
'chainPropTypes',
'deepmerge',
'elementAcceptingRef',
'elementTypeAcceptingRef',
'exactProp',
'formatMuiErrorMessage',
'getDisplayName',
'HTMLElementType',
'ponyfillGlobal',
'refType',
],
};