I'm implementing a Preact app and would like to use MUI. Everything seems to be referencing appropriately in VSCode, but when I try to access it on the browser nothing gets rendered and this console error pops up:
react-jsx-runtime.development.js:87 Warning: Failed prop type: Invalid prop
children
supplied toInnerThemeProvider
, expected a ReactNode. at InnerThemeProvider (http://localhost:8080/main.js:54463:70) printWarning @ react-jsx-runtime.development.js:87 error @ react-jsx-runtime.development.js:61 checkPropTypes @ react-jsx-runtime.development.js:631 validatePropTypes @ react-jsx-runtime.development.js:1164 jsxWithValidation @ react-jsx-runtime.development.js:1284 jsxWithValidationDynamic @ react-jsx-runtime.development.js:1301 ThemeProvider @ ThemeProvider.js:34 M @ preact.js:1 I @ preact.js:1 b @ preact.js:1 I @ preact.js:1 b @ preact.js:1 I @ preact.js:1 N @ preact.js:1 (anonymous) @ main.tsx:24 (anonymous) @ main.tsx:24 (anonymous) @ main.tsx:24
I've tried messing around on webpack and tsconfig, but honestly unless I can find it online I'm just guessing, and it doesn't look like anyone else is having this exact problem. I'm thinking it has to be an aliasing thing since it's mentioning react stuff.
The file structure is this:
.
├── index.ts
├── main.pug
├── main.tsx
├── package.json
├── tsconfig.json
└── webpack.config.js
There's only 6 small files of interest (I've cut it down as much as I know how):
- index.ts: the file that runs the server
import express = require("express");
import { Server } from 'http';
import { Application, Request, Response } from "express";
const PORT = 8080;
const setupServer: () => Promise<void> = async () => {
let app = express();
const http = new Server(app);
app.use(express.json());
app.use(express.static(__dirname));
// setup pug template engine
app.engine("pug", require("pug").__express);
app.set("views",__dirname);
app.get("*", async (req: Request, res: Response) => {
res.render("./main.pug");
});
// console.log that your server is up and listening
http.listen(PORT, function() {
console.log(`Listening on port ${PORT}`);
});
};
setupServer();
- main.tsx: the beahvior file referenced by the pug file
import { h, render } from "preact";
import { Button, createTheme, ThemeProvider } from '@mui/material';
import { red } from '@mui/material/colors';
const theme = createTheme({
palette: {
primary: {
main: red[500],
},
},
});
function Main() {
return (
<ThemeProvider theme={theme}>
<Button variant="contained">Hello World</Button>
</ThemeProvider>
);
};
render(<Main />, document.body);
- webpack.config.js
"use strict";
const { merge } = require('webpack-merge');
const nodeExternals = require('webpack-node-externals');
module.exports = env => {
// shared by all bundle configs
const baseConfig = {
mode: "development",
devtool: "source-map",
resolve: {
extensions: [".js", ".json", ".ts", ".tsx"],
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader"]
},
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
]
}
};
// client config
const clientMainBundleConfig = merge(baseConfig,{
name: 'main',
entry: { 'main': __dirname "/main.tsx" },
target: 'web',
output: {
path: __dirname,
filename: "[name].js"
},
resolve: {
alias: {
"preact/hooks": require.resolve('preact/hooks'),
"preact": require.resolve('preact')
}
}
});
// the config for the index file on server side
const serverBundleConfig = merge(baseConfig,{
target: 'node',
name: 'index',
externals: [nodeExternals()],
entry: { 'index': __dirname "/index.ts" },
output: {
path: __dirname,
filename: "[name].js"
},
});
return [serverBundleConfig,clientMainBundleConfig];
};
- tsconfig.json
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",
"baseUrl": "./",
"target": "es6",
"module": "commonjs",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"sourceMap": true,
"paths": {
"react": ["./node_modules/preact/compat"],
"react-dom": ["./node_modules/preact/compat"]
},
},
"exclude":[
"./node_modules"
]
}
- package.json
{
"name": "preact-mui",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --stats-error-details",
"start": "node ./index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@emotion/core": "^11.0.0",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@mui/material": "^5.11.0",
"@types/express": "^4.17.15",
"@types/express-session": "^1.17.5",
"@types/node": "^18.11.15",
"express": "^4.18.2",
"http": "^0.0.1-security",
"preact": "^10.11.3",
"preact-cli": "^3.4.1",
"preact-render-to-string": "^5.2.6",
"preact-router": "^4.1.0",
"pug": "^3.0.2",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack-cli": "^5.0.1",
"webpack-merge": "^5.8.0",
"webpack-node-externals": "^3.0.0"
}
}
- main.pug (I guess this isn't really interesting but just for completeness):
<!DOCTYPE html>
html(lang="en")
head
meta(charset="utf-8")
meta(http-equiv="X-UA-Compatible", content="IE=edge")
meta(name="viewport", content="width=device-width, initial-scale=1")
body.container-fluid
div#mainDiv
script(type='text/javascript', src="./main.js").
I tried aliasing react and react-dom towards preact/compat, hoping that since it's mentioning react if I just redirect it back towards preact then it would automagically correct itself, but low and behold that didn't work. Then I tried updating the tsconfig.json to include the below per the mui guide:
"compilerOptions": {
"lib": ["es6", "dom"],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
...
But still nothing changed.
Nothing's really showing up on google/SO so not sure where to go from here.
CodePudding user response:
It looks like you've misunderstood what aliasing is.
Aliasing is giving X when Y is asked for. In your code, you're aliasing preact
and preact/hooks
to themselves, which does nothing at all.
Now, you need to alias as React and Preact components are not the same shape; you cannot pass a React component to Preact without errors being thrown (or vice versa). preact/compat
is a translation layer that facilitates doing this, however.
You need to add this into your base config:
resolve: {
alias: {
"react": "preact/compat",
"react-dom/test-utils": "preact/test-utils",
"react-dom": "preact/compat", // Must be below test-utils
"react/jsx-runtime": "preact/jsx-runtime"
}
}
https://preactjs.com/guide/v10/getting-started#aliasing-in-webpack