You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在无构建环境下结合Preact、HTM使用MUI组件?

无构建环境下结合Preact+HTM使用MUI的解决方案

问题背景

Preact官方支持无构建环境搭配HTM使用,该方案在手机端运行正常,但因符号链接问题无法使用Vite构建工具。MUI官方提供了与Preact的集成方案,但未提及无构建环境下结合HTM的实现方式。

尝试过的导入方式

  • 裸模块导入:import { Button } from '@mui/material/Button';
  • 直接导入本地node_modules文件:import { Button } from '../node_modules/@mui/material/Button.js';

报错信息

  1. React API缺失错误:
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
  1. 模块解析错误:
127.0.0.1/:1 Uncaught TypeError: Failed to resolve module specifier "@mui/material/Button". Relative references must start with either "/", "./", or "../".

现有代码

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"
  }
}

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>

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'));

解决方案

1. 全局绑定Preact作为React别名

MUI内部依赖React API,需要让MUI认为Preact就是React。在index.html中添加以下脚本,在导入其他模块前完成别名绑定:

<script type="module">
  import * as React from 'https://esm.sh/preact/compat';
  window.React = React;
  window.ReactDOM = {
    createRoot: (el) => ({
      render: (vdom) => React.createElement(el, null, vdom)
    })
  };
</script>

2. 使用ES模块CDN导入MUI组件

浏览器原生不支持裸模块(如@mui/material/Button),改用支持Preact别名的ES模块CDN导入组件:
修改App.js中的导入语句:

import { Button } from 'https://esm.sh/@mui/material/Button?alias=react:preact/compat&alias=react-dom:preact/compat';

3. 确保HTM与Preact正确绑定

如果你的standalone.module.js不是Preact+HTM的绑定版本,直接从CDN导入并绑定:

import { h, render } from 'https://esm.sh/preact';
import htm from 'https://esm.sh/htm';
const html = htm.bind(h);

4. 完整修改后的代码示例

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">
    <!-- 先绑定React别名 -->
    <script type="module">
      import * as React from 'https://esm.sh/preact/compat';
      window.React = React;
      window.ReactDOM = {
        createRoot: (el) => ({
          render: (vdom) => React.createElement(el, null, vdom)
        })
      };
    </script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="components/App.js"></script>
  </body>
</html>

App.js

import { h, render } from 'https://esm.sh/preact';
import htm from 'https://esm.sh/htm';
import { Button } from 'https://esm.sh/@mui/material/Button?alias=react:preact/compat&alias=react-dom:preact/compat';

const html = htm.bind(h);

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

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

内容的提问来源于stack exchange,提问作者Michael Schwartz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 05:00:58