如何在无构建环境下结合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';
报错信息
- 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
- 模块解析错误:
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
相关产品推荐
相关产品推荐

