Skip to content

Commit

Permalink
fix: module resolution of third-party libraries (#23)
Browse files Browse the repository at this point in the history
This PR changes how module resolution works in `@lazarv/react-server`.
This is a major change and needs more test cases in the future to
maintain stability and quality.

Add support for the following third-party libraries:
- @tanstack/react-query
- @mui/material
- @mantine/core

Some packages need to be specifically marked as `external` to keep them
usable.

Examples:
- `lucide-react` in
https://github.com/lazarv/react-server/blob/fix/external-use-client/docs/vite.config.mjs#L7
- `better-sqlite3` in
https://github.com/lazarv/react-server/blob/fix/external-use-client/examples/todo/react-server.config.json#L3

The `external` packages can be specified by adding a `resolve.external`
section to `react-server.config.json` (`.mjs` or `.ts`) or
`vite.config.mjs` (`.ts`).

```json
{
  "resolve": {
    "external": ["better-sqlite3"]
  }
}
```

This PR should also fix the following issues:
- Mantine / use client not respected
#20
- MUI 6.x / React__namespace.createContext is not a function
#22

To showcase usage of these third-party libraries, this PR also adds new
examples:
-
https://github.com/lazarv/react-server/tree/fix/external-use-client/examples/react-query
-
https://github.com/lazarv/react-server/tree/fix/external-use-client/examples/mui
-
https://github.com/lazarv/react-server/tree/fix/external-use-client/examples/mantine

Some low-level packages needed special attention:
- hoist-non-react-statics
- prop-types
- react-is
  • Loading branch information
lazarv authored Sep 9, 2024
1 parent 84ec058 commit da250e2
Show file tree
Hide file tree
Showing 56 changed files with 2,506 additions and 194 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
- "docs/**"
workflow_dispatch:

permissions:
pull-requests: write

jobs:
deploy:
name: Deploy 🚀
Expand Down Expand Up @@ -45,7 +48,7 @@ jobs:
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

- name: Build docs
run: pnpm run docs:build
run: pnpm run docs-build

- name: Deploy Project Artifacts to Vercel
if: ${{ github.event_name == 'pull_request' }}
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,12 @@ pnpm --filter ./examples/todo dev --open
pnpm --filter ./examples/photos dev --open
pnpm --filter ./examples/express dev
pnpm --filter ./examples/nestjs start:dev
pnpm --filter ./examples/spa --open
pnpm --filter ./examples/react-router --open
pnpm --filter ./examples/tanstack-router --open
pnpm --filter ./examples/spa dev --open
pnpm --filter ./examples/react-router dev --open
pnpm --filter ./examples/tanstack-router dev --open
pnpm --filter ./examples/react-query dev --open
pnpm --filter ./examples/mui dev --open
pnpm --filter ./examples/mantine dev --open
```

You will need to have `pnpm` installed. Follow instructions at https://pnpm.io/installation.
Expand Down
2 changes: 1 addition & 1 deletion docs/vite.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import svgr from "vite-plugin-svgr";
export default {
plugins: [react(), svgr()],
resolve: {
noExternal: ["@vercel/analytics", "@vercel/speed-insights"],
external: ["lucide-react"],
},
};
20 changes: 20 additions & 0 deletions examples/mantine/app/layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import "@mantine/core/styles.css";

import { createTheme, MantineProvider } from "@mantine/core";

const theme = createTheme({
/** Put your mantine theme override here */
});

export default function Layout({ children }) {
return (
<html lang="en">
<body>
<MantineProvider theme={theme}>
<h1>Layout</h1>
{children}
</MantineProvider>
</body>
</html>
);
}
12 changes: 12 additions & 0 deletions examples/mantine/app/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client";

import { Button } from "@mantine/core";

export default function Home() {
return (
<div>
<h1>Home</h1>
<Button onClick={() => alert("Hello Mantine!")}>Hello Mantine!</Button>
</div>
);
}
24 changes: 24 additions & 0 deletions examples/mantine/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@lazarv/react-server-example-mantine",
"private": true,
"description": "@lazarv/react-server Mantine UI example application",
"scripts": {
"dev": "react-server",
"build": "react-server build",
"start": "react-server start"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@lazarv/react-server": "workspace:^",
"@lazarv/react-server-router": "workspace:^",
"@mantine/core": "^7.12.2",
"@mantine/hooks": "^7.12.2"
},
"devDependencies": {
"postcss": "^8.4.43",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1"
}
}
14 changes: 14 additions & 0 deletions examples/mantine/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
plugins: {
"postcss-preset-mantine": {},
"postcss-simple-vars": {
variables: {
"mantine-breakpoint-xs": "36em",
"mantine-breakpoint-sm": "48em",
"mantine-breakpoint-md": "62em",
"mantine-breakpoint-lg": "75em",
"mantine-breakpoint-xl": "88em",
},
},
},
};
22 changes: 22 additions & 0 deletions examples/mantine/react-server.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export default {
root: "app",
public: "public",
page: {
include: ["**/page.jsx"],
},
layout: {
include: ["**/layout.jsx"],
},
build: {
chunkSizeWarningLimit: 1000,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("@mantine/core")) {
return "@mantine/core";
}
},
},
},
},
};
15 changes: 15 additions & 0 deletions examples/mui/app/about/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import { Link as ReactServerLink } from "@lazarv/react-server/navigation";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";

export default function About() {
return (
<Box sx={{ maxWidth: "sm" }}>
<Button variant="contained" component={ReactServerLink} to="/">
Go to the home page
</Button>
</Box>
);
}
20 changes: 20 additions & 0 deletions examples/mui/app/components/Copyright.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import MuiLink from "@mui/material/Link";
import Typography from "@mui/material/Typography";

export default function Copyright() {
return (
<Typography
variant="body2"
align="center"
sx={{
color: "text.secondary",
}}
>
{"Copyright © "}
<MuiLink color="inherit" href="https://mui.com/">
Your Website
</MuiLink>{" "}
{new Date().getFullYear()}.
</Typography>
);
}
37 changes: 37 additions & 0 deletions examples/mui/app/components/Layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";

import Box from "@mui/material/Box";
import Container from "@mui/material/Container";
import CssBaseline from "@mui/material/CssBaseline";
import { ThemeProvider } from "@mui/material/styles";
import Typography from "@mui/material/Typography";

import theme from "../theme";
import Copyright from "./Copyright";
import ProTip from "./ProTip";

export default function Providers({ children }) {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Container maxWidth="lg">
<Box
sx={{
my: 4,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
>
<Typography variant="h4" component="h1" sx={{ mb: 2 }}>
Material UI - Next.js App Router example in JavaScript
</Typography>
{children}
<ProTip />
<Copyright />
</Box>
</Container>
</ThemeProvider>
);
}
16 changes: 16 additions & 0 deletions examples/mui/app/components/ProTip.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import LightbulbOutlined from "@mui/icons-material/esm/LightbulbOutlined";
import Link from "@mui/material/Link";
import Typography from "@mui/material/Typography";

export default function ProTip() {
return (
<Typography sx={{ mt: 6, mb: 3, color: "text.secondary" }}>
<LightbulbOutlined sx={{ mr: 1, verticalAlign: "middle" }} />
{"Pro tip: See more "}
<Link href="https://mui.com/material-ui/getting-started/templates/">
templates
</Link>
{" in the Material UI documentation."}
</Typography>
);
}
21 changes: 21 additions & 0 deletions examples/mui/app/layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";

import Layout from "./components/Layout";

export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MUI</title>
</head>
<body>
<Layout>{children}</Layout>
</body>
</html>
);
}
12 changes: 12 additions & 0 deletions examples/mui/app/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client";

import { Link as ReactServerLink } from "@lazarv/react-server/navigation";
import Link from "@mui/material/Link";

export default function Home() {
return (
<Link to="/about" color="secondary" component={ReactServerLink}>
Go to the about page
</Link>
);
}
23 changes: 23 additions & 0 deletions examples/mui/app/theme.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createTheme } from "@mui/material/styles";

const theme = createTheme({
palette: {
mode: "light",
},
typography: {
fontFamily: "Roboto",
},
components: {
MuiAlert: {
styleOverrides: {
root: ({ ownerState }) => ({
...(ownerState.severity === "info" && {
backgroundColor: "#60a5fa",
}),
}),
},
},
},
});

export default theme;
25 changes: 25 additions & 0 deletions examples/mui/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@lazarv/react-server-example-mui",
"private": true,
"description": "@lazarv/react-server Material UI example application",
"scripts": {
"dev": "react-server",
"build": "react-server build",
"start": "react-server start"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.0.14",
"@lazarv/react-server": "workspace:^",
"@lazarv/react-server-router": "workspace:^",
"@mui/icons-material": "^6.0.2",
"@mui/material": "^6.0.2",
"@mui/styled-engine": "^6.0.2",
"@mui/system": "^6.0.2",
"@mui/utils": "^6.0.2"
}
}
24 changes: 24 additions & 0 deletions examples/mui/react-server.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export default {
root: "app",
public: "public",
page: {
include: ["**/page.jsx"],
},
layout: {
include: ["**/layout.jsx"],
},
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("@emotion/react")) {
return "@emotion/react";
}
if (id.includes("@mui/")) {
return "@mui/material";
}
},
},
},
},
};
1 change: 1 addition & 0 deletions examples/photos/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vercel
21 changes: 21 additions & 0 deletions examples/react-query/app/comments-server.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// app/posts/comments-server.jsx
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";

import Comments from "./comments";
import { getComments } from "./get-comments";
import { getQueryClient } from "./get-query-client";

export default function CommentsServerComponent() {
const queryClient = getQueryClient();

queryClient.prefetchQuery({
queryKey: ["posts-comments"],
queryFn: getComments,
});

return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Comments />
</HydrationBoundary>
);
}
Loading

0 comments on commit da250e2

Please sign in to comment.