Search code examples
javascriptmaterial-uipreacthtmnobuild

MUI with Preact & HTM No Build Setup


Preact shows you can use it with HTM for a no build setup here. Which works well on my phone since I haven't be able to get Vite to work because of symlinks.

MUI shows how to install and use it with Preact as well but not with HTM for a no build setup. https://mui.com/material-ui/getting-started/

I've tried import { Button } from '@mui/material/Button';

It's tried it without brackets and even importing the file directly with import { Button } from '../node_modules/@mui/material/Button.js';

Yet console still shows...

material-ui.development.js:1848 Uncaught TypeError: Cannot read properties of undefined (reading 'useLayoutEffect')
at material-ui.development.js:1848:74
at material-ui.development.js:9:78
at material-ui.development.js:10:3 (anonymous) @ material-ui.development.js:1848 (anonymous) @ material-ui.development.js:9 (anonymous) @ material-ui.development.js:10
127.0.0.1/:1 Uncaught TypeError: Failed to resolve module specifier "@mui/material/Button". Relative references must start with either "/", "./", or "../".

What can I do to get this to work with Preact and htm for a no build setup?

Here's my code...

My package.json:

{
  "name": "project",
  "private": true,
  "version": "0.001",
  "type": "module",
  "scripts": {},
  "dependencies": {
    "@emotion/react": "^11.11.4",
    "@emotion/styled": "^11.11.5",
    "@mui/material": "^5.15.19",
    "@testing-library/jest-dom": "latest",
    "@testing-library/react": "latest",
    "@testing-library/user-event": "latest",
    "htm": "^3.1.1",
    "preact": "^10.22.0",
    "react-scripts": "^5.0.0"
  }
}

My index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Site title</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, interactive-widget=resizes-content">
    <script src="js/standalone.module.js" type="module"></script>
  </head>
  <body>
    <div id="root"></div>

    <script type="module" src="components/App.js"></script>
  </body>
</html>

My App.js:

import { html, render } from '../js/standalone.module.js';
import { Button } from '@mui/material/Button';

function ButtonUsage() {
  return html`
    <${Button} variant="contained">Hello world<//>
  `;
}

render(html`<${ButtonUsage} />`, document.getElementById('root'));

Solution

  • Before we get started, react-scripts, along with Babel, is build tooling. This makes your question a bit unclear. You shouldn't have it in a no-build setup.


    To start with, a bare import specifier like @mui/material/Button is meant for use in Node or, somewhat recently, with import maps -- on it's own, it's an invalid specifier in the web.

    Additionally, as MUI is a React lib, you need to set up aliases to preact/compat and it will not work out-of-the-box in a Preact app (as your errors show).

    Here's an example:

    note: I imagine js/standalone.module.js is htm/preact/standalone, which is better extracted to this form if we're using import maps.

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Site title</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, interactive-widget=resizes-content">
        <script type="importmap">
          {
            "imports": {
              "preact": "https://esm.sh/[email protected]",
              "preact/": "https://esm.sh/[email protected]/",
              "htm/preact": "https://esm.sh/[email protected]/preact?external=preact",
              "@mui/material": "https://esm.sh/@mui/material?alias=react:preact/compat,react-dom:preact/compat&external=preact"
            }
          }
        </script>
      </head>
      <body>
        <div id="root"></div>
    
        <script type="module" src="components/App.js"></script>
      </body>
    </html>
    

    Then in your script:

    import { render } from 'preact';
    import { html } from 'htm/preact';
    import { Button } from '@mui/material';
    
    function ButtonUsage() {
      return html`
        <${Button} variant="contained">Hello world<//>
      `;
    }
    
    render(html`<${ButtonUsage} />`, document.getElementById('root'));
    

    Hope it helps!