Skip to content

Commit

Permalink
[core] Deeper import of modules
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari committed Sep 9, 2023
1 parent eabff88 commit 80f2978
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 82 deletions.
9 changes: 1 addition & 8 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,6 @@ module.exports = {
{
patterns: [
'@mui/*/*/*',
// Begin block: Packages with files instead of packages in the top level
// Importing from the top level pulls in CommonJS instead of ES modules
// Allowing /icons as to reduce cold-start of dev builds significantly.
// There's nothing to tree-shake when importing from /icons this way:
// '@mui/icons-material/*/',
'@mui/utils/*',
// End block
// Macros are fine since their import path is transpiled away
'!@mui/utils/macros',
'@mui/utils/macros/*',
Expand Down Expand Up @@ -333,7 +326,7 @@ module.exports = {
'error',
{
patterns: [
// Allow deeper imports for TypeScript types. TODO?
// Allow deeper imports for TypeScript types. TODO remove
'@mui/*/*/*/*',
// Macros are fine since they're transpiled into something else
'!@mui/utils/macros/*.macro',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { useAutocomplete } from '@mui/base/useAutocomplete';
import { Popper } from '@mui/base/Popper';
import { styled } from '@mui/system';
import { unstable_useForkRef as useForkRef } from '@mui/utils';
import useForkRef from '@mui/utils/useForkRef'; // TODO import from @mui/base, private package

const Autocomplete = React.forwardRef(function Autocomplete(props, ref) {
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { useAutocomplete, UseAutocompleteProps } from '@mui/base/useAutocomplete';
import { Popper } from '@mui/base/Popper';
import { styled } from '@mui/system';
import { unstable_useForkRef as useForkRef } from '@mui/utils';
import useForkRef from '@mui/utils/useForkRef'; // TODO import from @mui/base, private package

const Autocomplete = React.forwardRef(function Autocomplete(
props: UseAutocompleteProps<(typeof top100Films)[number], false, false, false>,
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-base/src/FormControl/FormControl.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import { unstable_useControlled as useControlled } from '@mui/utils';
import useControlled from '@mui/utils/useControlled'; // TODO import from ../useControlled
import { PolymorphicComponent } from '../utils/PolymorphicComponent';
import { FormControlContext } from './FormControlContext';
import { getFormControlUtilityClass } from './formControlClasses';
Expand Down
3 changes: 2 additions & 1 deletion packages/mui-base/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
"rootDir": "./src"
},
"include": ["src/**/*.ts*"],
"exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*"]
"exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*"],
"references": [{ "path": "../mui-utils/tsconfig.build.json" }]
}
2 changes: 1 addition & 1 deletion packages/mui-lab/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
// This config is for emitting declarations (.d.ts) only
// Actual .ts source files are transpiled via babel
"extends": "./tsconfig.json",
"extends": "./tsconfig",
"compilerOptions": {
"noEmit": false,
"declaration": true,
Expand Down
4 changes: 4 additions & 0 deletions packages/mui-material/scripts/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ const nestedFolder = {
return resolveNestedImport('mui-base', importee);
}

if (importee.indexOf('@mui/utils/') === 0) {
return resolveNestedImport('mui-utils', importee);
}

if (importee.indexOf('@mui/private-theming/') === 0) {
return resolveNestedImport('mui-private-theming', importee);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { unstable_generateUtilityClasses as generateUtilityClasses } from '@mui/utils';
import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; // TODO import from @mui/base, private package
import generateUtilityClass from '../generateUtilityClass';

export interface ListSubheaderClasses {
Expand Down
3 changes: 2 additions & 1 deletion packages/mui-system/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
"rootDir": "./src"
},
"include": ["src/**/*.ts*"],
"exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*"]
"exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*"],
"references": [{ "path": "../mui-utils/tsconfig.build.json" }]
}
15 changes: 15 additions & 0 deletions packages/mui-utils/src/integerPropType.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import PropTypes from 'prop-types';

declare function integerPropType(
props: { [key: string]: any },
propName: string,
componentName: string,
location: string,
propFullName: string,
): Error | null;

declare namespace integerPropType {
let isRequired: PropTypes.Validator<number>;
}

export default integerPropType;
1 change: 1 addition & 0 deletions packages/mui-utils/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Actual .ts source files are transpiled via babel
"extends": "./tsconfig",
"compilerOptions": {
"composite": true,
"declaration": true,
"noEmit": false,
"emitDeclarationOnly": true,
Expand Down
134 changes: 67 additions & 67 deletions scripts/buildTypes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,73 @@ function rewriteImportPath(importPath) {
throw new Error(`Don't know where to rewrite '${importPath}' to`);
}

async function rewriteImportPaths(declarationFile, publishDir) {
const code = await fse.readFile(declarationFile, { encoding: 'utf8' });
const basename = path.basename(declarationFile);

if (
// Only consider React components
basename[0] === basename[0].toUpperCase() &&
code.indexOf("import PropTypes from 'prop-types';") !== -1
) {
throw new Error(
[
`${declarationFile} imports from 'prop-types', this is wrong.`,
"It's likely missing a cast to any on the propTypes declaration:",
'ComponentName.propTypes = { /* prop */ } as any;',
].join('\n'),
);
}

let fixedCode = code;
const changes = [];

// find all type `import()`
// not to be confused with `import type`
const importTypeRegExp = /import\(([^)]+)\)/g;

let importTypeMatch;
// eslint-disable-next-line no-cond-assign -- Waiting for RegExp.prototype.matchAll
while ((importTypeMatch = importTypeRegExp.exec(code)) !== null) {
// First and last character are quotes.
// TypeScript mixes single and double quotes.
const importPath = importTypeMatch[1].slice(1, -1);
// In filesystem semantics `@mui/material` is a relative path.
// But when resolving imports these specifiers are considered "bare specifiers" and work differently.
// We're only interested in imports that are considered "relative path imports".
const isBareImportSpecifier = !importPath.startsWith('.');
if (!isBareImportSpecifier) {
const resolvedImport = path.resolve(declarationFile, importPath);
const importPathFromPublishDir = path.relative(publishDir, resolvedImport);
const isImportReachableWhenPublished = !importPathFromPublishDir.startsWith('.');

if (!isImportReachableWhenPublished) {
try {
const fixedImportPath = rewriteImportPath(
// ensure relative POSIX path
importPathFromPublishDir.replace(/\\/g, '/'),
);
const originalImportType = importTypeMatch[0];
const fixedImportType = importTypeMatch[0].replace(importPath, fixedImportPath);

// Make it easy to visually scan for the created lines.
changes.push(`-${chalk.bgRed(originalImportType)}\n+${chalk.bgGreen(fixedImportType)}`);
fixedCode = fixedCode.replace(originalImportType, fixedImportType);
} catch (error) {
throw new Error(`${declarationFile}: ${error}`);
}
}
}
}

const changed = changes.length > 0;
if (changed) {
await fse.writeFile(declarationFile, fixedCode);
}

return changes;
}

async function main() {
const packageRoot = process.cwd();

Expand All @@ -52,73 +119,6 @@ async function main() {
throw new Error(`Unable to find declaration files in '${publishDir}'`);
}

async function rewriteImportPaths(declarationFile) {
const code = await fse.readFile(declarationFile, { encoding: 'utf8' });
const basename = path.basename(declarationFile);

if (
// Only consider React components
basename[0] === basename[0].toUpperCase() &&
code.indexOf("import PropTypes from 'prop-types';") !== -1
) {
throw new Error(
[
`${declarationFile} imports from 'prop-types', this is wrong.`,
"It's likely missing a cast to any on the propTypes declaration:",
'ComponentName.propTypes = { /* prop */ } as any;',
].join('\n'),
);
}

let fixedCode = code;
const changes = [];

// find all type `import()`
// not to be confused with `import type`
const importTypeRegExp = /import\(([^)]+)\)/g;

let importTypeMatch;
// eslint-disable-next-line no-cond-assign -- Waiting for RegExp.prototype.matchAll
while ((importTypeMatch = importTypeRegExp.exec(code)) !== null) {
// First and last character are quotes.
// TypeScript mixes single and double quotes.
const importPath = importTypeMatch[1].slice(1, -1);
// In filesystem semantics `@mui/material` is a relative path.
// But when resolving imports these specifiers are considered "bare specifiers" and work differently.
// We're only interested in imports that are considered "relative path imports".
const isBareImportSpecifier = !importPath.startsWith('.');
if (!isBareImportSpecifier) {
const resolvedImport = path.resolve(declarationFile, importPath);
const importPathFromPublishDir = path.relative(publishDir, resolvedImport);
const isImportReachableWhenPublished = !importPathFromPublishDir.startsWith('.');

if (!isImportReachableWhenPublished) {
try {
const fixedImportPath = rewriteImportPath(
// ensure relative POSIX path
importPathFromPublishDir.replace(/\\/g, '/'),
);
const originalImportType = importTypeMatch[0];
const fixedImportType = importTypeMatch[0].replace(importPath, fixedImportPath);

// Make it easy to visually scan for the created lines.
changes.push(`-${chalk.bgRed(originalImportType)}\n+${chalk.bgGreen(fixedImportType)}`);
fixedCode = fixedCode.replace(originalImportType, fixedImportType);
} catch (error) {
throw new Error(`${declarationFile}: ${error}`);
}
}
}
}

const changed = changes.length > 0;
if (changed) {
await fse.writeFile(declarationFile, fixedCode);
}

return changes;
}

let rewrittenTally = 0;
let errorTally = 0;
await Promise.all(
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"@mui/types": ["./packages/mui-types"],
"@mui/base": ["./packages/mui-base/src"],
"@mui/base/*": ["./packages/mui-base/src/*"],
"@mui/utils": ["./packages/mui-utils/src"],
"@mui/utils/*": ["./packages/mui-utils/src/*"],
"@mui/docs": ["./packages/mui-docs/src"],
"@mui/docs/*": ["./packages/mui-docs/src/*"],
"@mui/material-next": ["./packages/mui-material-next/src"],
Expand Down

0 comments on commit 80f2978

Please sign in to comment.