-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
40 changed files
with
2,099 additions
and
760 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# React Proto - React TypeScript Boilerplate | ||
|
||
![node.js@22](https://img.shields.io/badge/node.js-22-339933?style=for-the-badge&logo=nodedotjs) ![typescript@5](https://img.shields.io/badge/typescript-5-3178C6?style=for-the-badge&logo=typescript) ![reactjs@18](https://img.shields.io/badge/Reactjs-18-61DAFB?style=for-the-badge&logo=react) ![webpack@5](https://img.shields.io/badge/webpack-5-8dd6f9?style=for-the-badge&logo=webpack) ![[email protected]](https://img.shields.io/badge/sass-1.7-CC6699?style=for-the-badge&logo=sass) ![ts-standard](https://img.shields.io/badge/standard-ts-F3DF49?style=for-the-badge&logo=standardjs) | ||
![node.js@22](https://img.shields.io/badge/node.js-22-339933?style=for-the-badge&logo=nodedotjs) ![typescript@5](https://img.shields.io/badge/typescript-5-3178C6?style=for-the-badge&logo=typescript) ![reactjs@18](https://img.shields.io/badge/Reactjs-18-61DAFB?style=for-the-badge&logo=react) ![rspack@1](https://img.shields.io/badge/rspack-1-f0965b?style=for-the-badge) ![webpack@5](https://img.shields.io/badge/webpack-5-8dd6f9?style=for-the-badge&logo=webpack) ![[email protected]](https://img.shields.io/badge/sass-1.7-CC6699?style=for-the-badge&logo=sass) ![ts-standard](https://img.shields.io/badge/standard-ts-F3DF49?style=for-the-badge&logo=standardjs) | ||
|
||
<img align="right" width="100" src="src/assets/images/logo.png"> | ||
|
||
|
@@ -10,6 +10,14 @@ This project is a compilation of different approaches in React development that | |
|
||
You can also check a [React Proto Lite](https://github.com/StopNGo/react-proto-lite) - Template React project for fast SPA prototyping. It contains only everything necessary for Single Page Application projects without any server side parts. | ||
|
||
## Huge Update: Migrating to Rspack | ||
|
||
Starting from version `2.0.0`, this project uses [Rspack](https://rspack.dev/) as the primary bundler. | ||
|
||
Rspack is a high performance JavaScript bundler written in Rust. It offers strong compatibility with the webpack ecosystem, allowing for seamless replacement of webpack, and provides lightning fast build speeds. | ||
|
||
Webpack is still available as an option ([rspack vs webpack](#rspack-vs-webpack) and [switching back to webpack](#switching-back-to-webpack)). | ||
|
||
--- | ||
- [Issue](#issue) | ||
- [What's Inside](#whats-inside) | ||
|
@@ -26,7 +34,7 @@ You can also check a [React Proto Lite](https://github.com/StopNGo/react-proto-l | |
|
||
Every new React developer knows that React is a library, not a complete framework. Thus, it provides maximum flexibility. However, a lot of knowledge is required to create a fully functional web application powered with React. | ||
|
||
That is why there exist such a famous framework as [Next.js](https://nextjs.org/) as well as a tool [Create React App (CRA)](https://create-react-app.dev/). | ||
That is why there exist such a famous framework as [Next.js](https://nextjs.org/) as well as a tool [Create React App (CRA)](https://create-react-app.dev/) or [Rsbuild for React](https://rsbuild.dev/guide/framework/react). | ||
|
||
Despite the advantages that such tools have, there are some cons that their user may face: | ||
|
||
|
@@ -46,7 +54,7 @@ Thus, the goal of this project is to **collect in one place all the most common | |
Core: | ||
|
||
- **React** 18+ (**Preact** 10+ as an option, see [comparison](#react-vs-preact) below) | ||
- **webpack** 5+ (with optional **SWC** support and SSR or static build; [why not Vite?](#why-not-vite)) | ||
- **Rspack** 1 (**webpack** 5+ as an option) with **SWC** support and SSR or static build ([why not Vite?](#why-not-vite), [rspack vs webpack](#rspack-vs-webpack) and [switching back to webpack](#switching-back-to-webpack)) | ||
- **TypeScript** (with strict rules, including webpack configuration) | ||
|
||
SSR: | ||
|
@@ -130,15 +138,17 @@ Live preview: | |
|
||
`git clone https://github.com/StopNGo/react-proto` | ||
|
||
2. Install all packages: | ||
2. Delete the `_webpack` folder if you are not going to use webpack bundler or [switch to it](#switching-back-to-webpack) before installing the packages. | ||
|
||
3. Install all packages: | ||
|
||
`npm i` | ||
|
||
3. Run project in a development mode: | ||
4. Run project in a development mode: | ||
|
||
`npm start` | ||
|
||
4. Open your browser with the next address: | ||
5. Open your browser with the next address: | ||
|
||
`http://localhost:8080/` | ||
|
||
|
@@ -176,6 +186,20 @@ Live preview: | |
|
||
`npm run build:static:report` | ||
|
||
### Switching back to webpack | ||
|
||
- Copy the contents of `_webpack` folder (except `README.md`) to the root of the project. | ||
|
||
- Delete the `rspack.config.ts` file. | ||
|
||
- Delete the `rspack` and `_webpack` folders. | ||
|
||
- If you have a previous installation, clean the `node_modules` folder. | ||
|
||
- Then install the packages: | ||
|
||
`npm i` | ||
|
||
### Updating packages | ||
|
||
All packages in this project are pinned to latest versions at the time of publishing to exclude version-based conflicts and errors and to guarantee proper work of the code in this repository. | ||
|
@@ -203,19 +227,43 @@ Vite is an excellent new generation bundler that could speed up your development | |
|
||
As for the speed: you can check this article - [Storybook Performance: Vite vs Webpack](https://storybook.js.org/blog/storybook-performance-from-webpack-to-vite/). As you can see - Webpack could still be fast enough. React Proto has such configurations. In `webpack\constants.ts` you can switch on SWC and Lazy Compilation. | ||
|
||
Also, I'm looking forward to [Turbopack](https://turbo.build/pack) - the Rust-powered successor to Webpack. Now it is available only in Next.js, but I hope the future migration from the Wepback will be smooth because the principle of configuration should be the same. | ||
Starting from version `2.0.0`, this project uses [Rspack](https://rspack.dev/) as the primary bundler. This bundler written in Rust and offers strong compatibility with the webpack ecosystem, so, performance should be much better ([rspack vs webpack](#rspack-vs-webpack)). | ||
|
||
I'm also looking forward to [Turbopack](https://turbo.build/pack) — another Rust-powered successor to Webpack. Currently, it's available only in Next.js, but it might be released as a standalone CLI tool in the future. | ||
|
||
### Rspack vs webpack | ||
|
||
Rspack is a high performance JavaScript bundler written in Rust. It offers strong compatibility with the webpack ecosystem, allowing for seamless replacement of webpack, and provides lightning fast build speeds. | ||
|
||
Here is a comparison between Rspack 1 with the built-in SWC loader and Webpack 5+ with the external SWC loader, while building the SSR version of the sample application on the same hardware configuration: | ||
|
||
| | Rspack | webpack | | ||
| ------- | --------- | -------- | | ||
| Server | 5.35 s | 6.83 s | | ||
| Client | 5.32 s | 7.50 s | | ||
|
||
Of course, the larger the project, the greater the performance advantage. However, if you need more webpack compatibility or hot module reloading while developing with [Preact](#react-vs-preact), you can always [switch back to the webpack bundler](#switching-back-to-webpack). | ||
|
||
Also, the optimization process of Rspack is currently slightly worse. Check the bundle size comparison for the non-SSR version of the sample application in this repository: | ||
|
||
| | Rspack | webpack | | ||
| ------- | --------- | -------- | | ||
| Parsed | 284.47 KB | 262.9 KB | | ||
| Gzipped | 90.29 KB | 86.84 KB | | ||
### React vs Preact | ||
|
||
In `webpack\constants.ts` you can choose to use [Preact](https://preactjs.com/) library instead React itself (`IS_PREACT` boolean constant). | ||
|
||
Preact is a fast and compact React-compatible Virtual DOM library. But because its community is much smaller, you can face with some incompatibility with React API and functionality, especially with new ones. Also some tests show some frame drops during moving a lot of elements. Below you can see a bundle size comparison of no-SSR version of the sample application of this repository (according to Webpack Bundle Analyzer): | ||
Preact is a fast and compact React-compatible Virtual DOM library. But because its community is much smaller, you can face with some incompatibility with React API and functionality, especially with new ones. Also some tests show some frame drops during moving a lot of elements. Below you can see a bundle size comparison of no-SSR version of the sample application of this repository that was built with Rspack (according to Webpack Bundle Analyzer): | ||
|
||
| | React | Preact | | ||
| ------- | --------- | -------- | | ||
| Parsed | 262.9 KB | 150.55 KB | | ||
| Gzipped | 86.84 KB | 52.09 KB | | ||
|
||
**Important Note** | ||
At the moment, the Rspack version of the project does not support hot module reloading during development with Preact. Compared to Webpack, it requires some additional tricky configuration, which I will probably add in the near future. However, if you need HMR (and you definitely do!), you can develop your project using React and then build it with Preact. Or just [switch your project back to the webpack](#switching-back-to-webpack). | ||
|
||
### Why not any common i18n package? | ||
You can freely integrate any React compatible i18n solution. But if React Proto already uses Redux and RTK, why just not use them for this task? Therefore, I have created a custom internationalization solution with a minimum additional code. It supports translations dynamic loading, server side rendering based on user acceptable languages, strict typing, etc. At the moment it just does not support string processing like pluralization, but it could easily be added later. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
## Webpack Version | ||
|
||
To use the webpack version of the project: | ||
|
||
1. Copy the contents of this folder (except `README.md`) to the root of the project. | ||
|
||
2. Delete the `rspack.config.ts` file. | ||
|
||
3. Delete the `rspack` and `_webpack` folders. | ||
|
||
4. If you have a previous installation, clean the `node_modules` folder. | ||
|
||
5. Then install the packages: | ||
|
||
`npm i` | ||
|
||
That's it! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
{ | ||
"name": "react-proto", | ||
"version": "2.0.0", | ||
"description": "React TypeScript Boilerplate", | ||
"author": "Max L Stop&Go", | ||
"license": "ISC", | ||
"sideEffects": [ | ||
"*.css", | ||
"*.scss" | ||
], | ||
"scripts": { | ||
"clean": "rimraf dist", | ||
"start:webpack": "cross-env NODE_ENV=development webpack --mode=development", | ||
"start:server": "wait-on dist/server.js && node dist/server.js", | ||
"dev": "npm run clean && npm-run-all --print-label --parallel start:webpack start:server", | ||
"start": "nodemon --exec npm run dev --watch src/server --ext ts,tsx,json ", | ||
"build": "npm run clean && cross-env NODE_ENV=production webpack --mode production", | ||
"build:report": "npm run build --withReport", | ||
"run": "node dist/server.js", | ||
"start:static": "npm run clean && cross-env NODE_ENV=development NO_SSR=true webpack serve --mode development --open", | ||
"build:static": "npm run clean && cross-env NODE_ENV=production NO_SSR=true webpack --mode production", | ||
"build:static:report": "npm run build:static --withReport", | ||
"prettier": "prettier \"src/**/*\" --write --single-quote --no-semi --ignore-unknown --trailing-comma none --jsx-single-quote", | ||
"lint": "ts-standard . && stylelint **/*.{css,scss}", | ||
"lint:fix": "npm run prettier && ts-standard . --fix && stylelint --fix **/*.{css,scss}", | ||
"test": "jest", | ||
"add-comp": "node scripts/add-comp.js" | ||
}, | ||
"devDependencies": { | ||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15", | ||
"@svgr/webpack": "8.1.0", | ||
"@swc/core": "1.7.26", | ||
"@testing-library/jest-dom": "6.5.0", | ||
"@testing-library/preact": "3.2.4", | ||
"@testing-library/react": "16.0.1", | ||
"@testing-library/user-event": "14.5.2", | ||
"@types/compression": "1.7.5", | ||
"@types/cookie-parser": "1.4.7", | ||
"@types/express": "5.0.0", | ||
"@types/jest": "29.5.13", | ||
"@types/loadable__component": "5.13.9", | ||
"@types/loadable__server": "5.12.11", | ||
"@types/node": "22.7.5", | ||
"@types/react": "18.3.11", | ||
"@types/react-dom": "18.3.0", | ||
"@types/react-helmet": "6.1.11", | ||
"@types/react-router-dom": "5.3.3", | ||
"@types/serialize-javascript": "5.0.4", | ||
"@types/webpack-bundle-analyzer": "4.7.0", | ||
"@types/webpack-env": "1.18.5", | ||
"@types/webpack-hot-middleware": "2.25.9", | ||
"@types/webpack-node-externals": "3.0.4", | ||
"classnames": "2.5.1", | ||
"copy-webpack-plugin": "12.0.2", | ||
"cross-env": "7.0.3", | ||
"css-hot-loader": "1.4.4", | ||
"css-loader": "7.1.2", | ||
"csso-webpack-plugin": "2.0.0-beta.3", | ||
"express": "4.21.1", | ||
"fork-ts-checker-webpack-plugin": "9.0.2", | ||
"html-webpack-plugin": "5.6.0", | ||
"jest": "29.7.0", | ||
"jest-environment-jsdom": "29.7.0", | ||
"mini-css-extract-plugin": "2.9.1", | ||
"nodemon": "3.1.7", | ||
"npm-run-all": "4.1.5", | ||
"null-loader": "4.0.1", | ||
"postcss": "8.4.47", | ||
"postcss-scss": "4.0.9", | ||
"prettier": "3.3.3", | ||
"rimraf": "6.0.1", | ||
"sass": "1.79.4", | ||
"sass-loader": "16.0.2", | ||
"stylelint": "16.9.0", | ||
"stylelint-config-clean-order": "6.1.0", | ||
"stylelint-config-standard-scss": "13.1.0", | ||
"swc-loader": "0.2.6", | ||
"ts-jest": "29.2.5", | ||
"ts-loader": "9.5.1", | ||
"ts-node": "10.9.2", | ||
"ts-standard": "12.0.2", | ||
"typescript": "5.6.2", | ||
"typescript-plugin-css-modules": "5.1.0", | ||
"wait-on": "8.0.1", | ||
"webpack": "5.95.0", | ||
"webpack-bundle-analyzer": "4.10.2", | ||
"webpack-cli": "5.1.4", | ||
"webpack-dev-middleware": "7.4.2", | ||
"webpack-dev-server": "5.1.0", | ||
"webpack-hot-middleware": "2.26.1", | ||
"webpack-node-externals": "3.0.0" | ||
}, | ||
"dependencies": { | ||
"@apollo/react-ssr": "4.0.0", | ||
"@loadable/server": "5.16.5", | ||
"@loadable/webpack-plugin": "5.15.2", | ||
"@reduxjs/toolkit": "2.2.8", | ||
"@types/loadable__webpack-plugin": "5.7.6", | ||
"compression": "1.7.4", | ||
"cookie-parser": "1.4.7", | ||
"cross-fetch": "4.0.0", | ||
"helmet": "8.0.0", | ||
"identity-obj-proxy": "3.0.0", | ||
"preact": "10.24.2", | ||
"react": "18.3.1", | ||
"react-dom": "18.3.1", | ||
"react-helmet-async": "2.0.5", | ||
"react-redux": "9.1.2", | ||
"react-router-dom": "6.26.2", | ||
"serialize-javascript": "6.0.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { DEV_SERVER_PORT, IS_DEV } from '_webpack/constants' | ||
|
||
export const SERVER_PORT: number = IS_DEV ? DEV_SERVER_PORT : 3000 | ||
|
||
export const IS_RENDER_TO_STREAM: boolean = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import helmet from 'helmet' | ||
import { randomUUID } from 'crypto' | ||
import { Response, Request, NextFunction } from 'express' | ||
import { IS_DEV } from '_webpack/constants' | ||
|
||
const nonce = (_req: Request, res: Response, next: NextFunction): void => { | ||
res.locals.cspNonce = Buffer.from(randomUUID()).toString('base64') | ||
next() | ||
} | ||
|
||
const csp = (req: Request, res: Response, next: NextFunction): void => { | ||
const middleware = helmet({ | ||
contentSecurityPolicy: { | ||
useDefaults: true, | ||
directives: { | ||
defaultSrc: ["'self'", 'pokeapi.co', 'localhost:*'], | ||
imgSrc: ["'self'", 'raw.githubusercontent.com'], | ||
scriptSrc: [ | ||
"'self'", | ||
`'nonce-${String(res.locals.cspNonce)}'`, | ||
IS_DEV ? "'unsafe-eval'" : '' | ||
] | ||
} | ||
}, | ||
crossOriginEmbedderPolicy: { policy: 'credentialless' }, | ||
noSniff: false, | ||
originAgentCluster: false | ||
}) | ||
|
||
return middleware(req, res, next) | ||
} | ||
|
||
export { nonce, csp } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import webpack from 'webpack' | ||
import { RequestHandler } from 'express' | ||
import devMiddleware from 'webpack-dev-middleware' | ||
import hotMiddleware from 'webpack-hot-middleware' | ||
|
||
import { clientConfig as config } from '_webpack/client.config' | ||
|
||
const compiler = webpack({ ...config, mode: 'development' }) | ||
|
||
export const devMiddlewareInstance = devMiddleware(compiler, { | ||
serverSideRender: true, | ||
writeToDisk: true, | ||
publicPath: | ||
config.output?.publicPath != null ? String(config.output.publicPath) : '/' | ||
}) | ||
|
||
export function hotReload (): RequestHandler[] { | ||
return [devMiddlewareInstance, hotMiddleware(compiler)] | ||
} | ||
|
||
export default hotReload |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import path from 'path' | ||
import express, { RequestHandler } from 'express' | ||
import compression from 'compression' | ||
import cookieParser from 'cookie-parser' | ||
import { ChunkExtractor } from '@loadable/server' | ||
|
||
import { csp, serverRenderer, nonce } from 'server/middlewares' | ||
import { IS_RENDER_TO_STREAM, SERVER_PORT } from 'server/constants' | ||
import { DIST_DIR, IS_DEV, SRC_DIR } from '_webpack/constants' | ||
|
||
const { PORT = SERVER_PORT } = process.env | ||
|
||
const runServer = (hotReload?: () => RequestHandler[]): void => { | ||
const app = express() | ||
const statsFile = path.resolve('./dist/stats.json') | ||
const chunkExtractor = new ChunkExtractor({ statsFile }) | ||
|
||
app | ||
.use(nonce) | ||
.use(csp) | ||
.use(express.json()) | ||
.use(compression()) | ||
.use(express.static(path.resolve(DIST_DIR))) | ||
.use(cookieParser()) | ||
|
||
if (IS_DEV) { | ||
if (hotReload != null) { | ||
app.get('/*', [...hotReload()]) | ||
} | ||
} else { | ||
app.get('/sw.js', (_req, res) => { | ||
res.sendFile(path.join(SRC_DIR, 'sw.js')) | ||
}) | ||
} | ||
|
||
app.get('/*', serverRenderer(chunkExtractor)) | ||
|
||
app.listen(PORT, () => { | ||
console.log( | ||
`App listening on port ${PORT}! (render to ${ | ||
IS_RENDER_TO_STREAM ? 'stream' : 'string' | ||
})` | ||
) | ||
}) | ||
} | ||
|
||
if (IS_DEV) { | ||
;(async () => { | ||
const { hotReload, devMiddlewareInstance } = await import( | ||
'./middlewares/hotReload' | ||
) | ||
devMiddlewareInstance.waitUntilValid(() => { | ||
runServer(hotReload) | ||
}) | ||
})() | ||
.then(() => {}) | ||
.catch((er) => console.log(er)) | ||
} else { | ||
runServer() | ||
} |
Oops, something went wrong.