Skip to content

Instantly share code, notes, and snippets.

@developit
Last active December 27, 2022 13:59
Show Gist options
  • Save developit/037baaec2a0a1e64e628c84fdd5c1107 to your computer and use it in GitHub Desktop.
Save developit/037baaec2a0a1e64e628c84fdd5c1107 to your computer and use it in GitHub Desktop.

babel-plugin-transform-mui-imports npm

A plugin to make authoring with MUI components efficient, both for humans and bundlers.

Here's why:

✍️ What you write 👏 What your bundler sees
import {
  AppBar,
  List, ListItem, ListItemAction,
  makeStyles
} from "@material-ui/core";
import { Add } from '@material-ui/icons';
import AppBar from "@material-ui/core/esm/AppBar";
import List from "@material-ui/core/esm/List";
import ListItem from "@material-ui/core/esm/ListItem";
import ListItemAction from "@material-ui/core/esm/ListItemAction";
import { makeStyles } from "@material-ui/core/esm/styles";
import Add from "@material-ui/icons/esm/Add";

Installation

  1. Get it from npm:
npm i babel-plugin-transform-mui-imports
  1. Put it in your Babel config:
{ "plugins": ["transform-mui-imports"] }

Try it out on ASTExplorer

module.exports = function ({ types: t }) {
return {
name: "transform-mui-imports",
visitor: {
ImportDeclaration(path, state) {
const source = path.node.source.value;
if (!/(@material-ui\/(core|icons))$/.test(source)) return;
path.replaceWithMultiple(path.get('specifiers').map(s => {
let specificSource = source + (state.opts.esm===false ? '' : '/esm');
let specifier = t.clone(s.node);
// ignore wildcard and default imports that can't be exploded
if (!t.isImportDefaultSpecifier(s) && !t.isImportNamespaceSpecifier(s)) {
const name = s.node.imported.name;
if (name === 'colors') {
// import { colors } from '..' --> import * as colors from '/colors'
specifier = t.importNamespaceSpecifier(specifier.local);
specificSource += '/colors';
}
else if (/^(makeStyles|createStyles|createMuiTheme|responsiveFontSizes|styled|useTheme|withStyles|withTheme|createGenerateClassName|jssPreset|ServerStyleSheets|StylesProvider|MuiThemeProvider|ThemeProvider|easing|duration|hexToRgb|rgbToHex|hslToRgb|decomposeColor|recomposeColor|getContrastRatio|getLuminance|emphasize|fade|darken|lighten)$/.test(name)) {
// core re-exports all of /styles
// import { styled } from '..' --> import { styled } from '/styles'
specifier = t.clone(s.node);
specificSource += '/styles';
}
else {
// import { List } from '..' --> import List from '/esm/List'
specifier = t.importDefaultSpecifier(specifier.local);
specificSource += '/' + name;
}
}
return t.importDeclaration([specifier], t.stringLiteral(specificSource));
}));
}
}
};
}
{
"name": "babel-plugin-transform-mui-imports",
"version": "0.2.0",
"author": "Jason Miller (https://github.com/developit)",
"main": "babel-plugin-transform-mui-imports.js",
"repo": "gist:037baaec2a0",
"homepage": "https://gist.github.com/developit/037baaec2a0a1e64e628c84fdd5c1107",
"scripts": { "prepack": "mv *babel-plugin-transform-mui-imports.md README.md", "postpack": "mv README.md *babel-plugin-transform-mui-imports.md" },
"peerDependencies": {
"@material-ui/core": "^4"
},
"license": "MIT"
}
@azz0r
Copy link

azz0r commented Jan 24, 2020

🦊 update: this is fixed in 0.2.0

Slight issue with this; Probably due to my wildcard import of icons?

    "@material-ui/core": "^4.9.x",
    "@material-ui/icons": "^4.5.x",
    "@material-ui/lab": "^4.0.0-alpha.40",
import React from "react"
import * as Icons from "@material-ui/icons"
import { Choose, When, Otherwise } from "jsx-control-statements"
import { Link as RouterLink } from "react-router-dom"
import { makeStyles } from "@material-ui/core/styles"
import { Box, Link, Typography } from "@material-ui/core"
import { useTheme } from "@material-ui/core/styles"
import useMediaQuery from "@material-ui/core/useMediaQuery"

import { center } from "theme"

import { getSize } from "./index"
import DropdownMenu from "./dropdown"
error stack
ERROR in ./src/layout/menu/browser.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
TypeError: /Users/account/Sites/fedsimulator/src/layout/menu/browser.js: Cannot read property 'name' of undefined
    at ImportDeclaration.path.replaceWithMultiple.path.get.map.s (/Users/account/Sites/fedsimulator/node_modules/babel-plugin-transform-mui-imports/babel-plugin-transform-mui-imports.js:12:42)
    at Array.map (<anonymous>)
    at PluginPass.ImportDeclaration (/Users/account/Sites/fedsimulator/node_modules/babel-plugin-transform-mui-imports/babel-plugin-transform-mui-imports.js:8:57)
    at newFn (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/visitors.js:179:21)
    at NodePath._call (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/path/context.js:55:20)
    at NodePath.call (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/path/context.js:42:17)
    at NodePath.visit (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/path/context.js:90:31)
    at TraversalContext.visitQueue (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/context.js:112:16)
    at TraversalContext.visitMultiple (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/context.js:79:17)
    at TraversalContext.visit (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/context.js:138:19)
    at Function.traverse.node (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/index.js:84:17)
    at NodePath.visit (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/path/context.js:97:18)
    at TraversalContext.visitQueue (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/context.js:112:16)
    at TraversalContext.visitSingle (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/context.js:84:19)
    at TraversalContext.visit (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/context.js:140:19)
    at Function.traverse.node (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/index.js:84:17)
    at traverse (/Users/account/Sites/fedsimulator/node_modules/@babel/traverse/lib/index.js:66:12)
    at transformFile (/Users/account/Sites/fedsimulator/node_modules/@babel/core/lib/transformation/index.js:119:29)
    at runSync (/Users/account/Sites/fedsimulator/node_modules/@babel/core/lib/transformation/index.js:48:5)
    at runAsync (/Users/account/Sites/fedsimulator/node_modules/@babel/core/lib/transformation/index.js:35:14)
    at process.nextTick (/Users/account/Sites/fedsimulator/node_modules/@babel/core/lib/transform.js:34:34)
    at _combinedTickCallback (internal/process/next_tick.js:132:7)
    at process._tickCallback (internal/process/next_tick.js:181:9)
 @ ./src/layout/menu/index.js 3:0-36 11:25-36
 @ ./src/layout/layout.js
 @ ./src/layout/index.js
 @ ./src/routes.js
 @ ./src/index.js

@developit
Copy link
Author

@cozuya @azz0r - thanks for the reports! I've just updated the gist and published babel-plugin-transform-mui-imports@0.2.0 that fixes both issues.

@BjoernRave
Copy link

tried using it with next.js and got this:

/Users/bjoern/projects/inventhora/frontend/node_modules/@material-ui/core/esm/styles/index.js:1
export * from './colorManipulator';
^^^^^^

SyntaxError: Unexpected token 'export'
    at Module._compile (internal/modules/cjs/loader.js:891:18)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)
    at Module.load (internal/modules/cjs/loader.js:811:32)
    at Function.Module._load (internal/modules/cjs/loader.js:723:14)
    at Module.require (internal/modules/cjs/loader.js:848:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at Object.@material-ui/core/esm/styles (/Users/bjoern/projects/inventhora/frontend/.next/server/static/development/pages/_app.js:4186:18)
    at __webpack_require__ (/Users/bjoern/projects/inventhora/frontend/.next/server/static/development/pages/_app.js:23:31)
    at Module../pages/_app.tsx (/Users/bjoern/projects/inventhora/frontend/.next/server/static/development/pages/_app.js:3764:86)
    at __webpack_require__ (/Users/bjoern/projects/inventhora/frontend/.next/server/static/development/pages/_app.js:23:31)
    at Object.0 (/Users/bjoern/projects/inventhora/frontend/.next/server/static/development/pages/_app.js:4031:18)
    at __webpack_require__ (/Users/bjoern/projects/inventhora/frontend/.next/server/static/development/pages/_app.js:23:31)
    at /Users/bjoern/projects/inventhora/frontend/.next/server/static/development/pages/_app.js:91:18
    at Object.<anonymous> (/Users/bjoern/projects/inventhora/frontend/.next/server/static/development/pages/_app.js:94:10)
    at Module._compile (internal/modules/cjs/loader.js:955:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)

@Cretezy
Copy link

Cretezy commented Jan 24, 2020

This is not needed with Next.js as it already does proper tree-shaking on MUI.

If you really want to you use with Next, you can pass the esm option as false:

{
  "presets": ["next/babel"],
  "plugins": [
    ["babel-plugin-transform-mui-imports", { "esm": false }]
  ]
}

@developit
Copy link
Author

@Cretezy @BjoernRave If you have a way to apply the plugin only to Next.js client bundle builds, it should work. The server build doesn't bundle node_modules, so ES Modules usage is not allowed there. A quick Babel preset wrapper might do the trick (no guarantees):

// babel-preset-mui.js
module.exports = function(api) {
  const supportsStaticESM = api.caller(caller => !!caller.supportsStaticESM);
  return {
    plugins: [
      ["babel-plugin-transform-mui-imports", {
        esm: supportsStaticESM
      }]
    ]
  };
}

@supervanya
Copy link

If anyone is importing alpha after updating to 4.12 here is an updated version:

// babel-plugin-transform-mui-imports.js
module.exports = function ({ types: t }) {
  return {
    name: "transform-mui-imports",
    visitor: {
      ImportDeclaration(path, state) {
        const source = path.node.source.value;
        if (!/(@material-ui\/(core|icons))$/.test(source)) return;
        path.replaceWithMultiple(path.get('specifiers').map(s => {
          let specificSource = source + (state.opts.esm===false ? '' : '/esm');
          let specifier = t.clone(s.node);
          // ignore wildcard and default imports that can't be exploded
          if (!t.isImportDefaultSpecifier(s) && !t.isImportNamespaceSpecifier(s)) {
            const name = s.node.imported.name;
            if (name === 'colors') {
              // import { colors } from '..' --> import * as colors from '/colors'
              specifier = t.importNamespaceSpecifier(specifier.local);
              specificSource += '/colors';
            }
            else if (/^(makeStyles|createStyles|createMuiTheme|responsiveFontSizes|styled|useTheme|withStyles|withTheme|createGenerateClassName|jssPreset|ServerStyleSheets|StylesProvider|MuiThemeProvider|ThemeProvider|easing|duration|hexToRgb|rgbToHex|hslToRgb|decomposeColor|recomposeColor|getContrastRatio|getLuminance|emphasize|fade|darken|lighten|alpha)$/.test(name)) {
              // core re-exports all of /styles
              // import { styled } from '..' --> import { styled } from '/styles'
              specifier = t.clone(s.node);
              specificSource += '/styles';
            }
            else {
              // import { List } from '..' --> import List from '/esm/List'
              specifier = t.importDefaultSpecifier(specifier.local);
              specificSource += '/' + name;
            }
          }
          return t.importDeclaration([specifier], t.stringLiteral(specificSource));
        }));
      }
    }
  };
}

I just added it to the repo and imported it in the babelrc.js:

// babelrc.js
const transformMuiImports = require('./babel-plugin-transform-mui-imports');

module.exports = { plugins: [transformMuiImports] };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment