├── .gitignore
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
├── App
│ ├── index.styl
│ └── index.tsx
├── Client.tsx
├── Common
│ └── Styles
│ │ ├── reset.styl
│ │ └── variables.styl
├── Html
│ ├── Browser.html
│ └── Server.tsx
├── Pages
│ ├── Content
│ │ ├── index.styl
│ │ └── index.tsx
│ ├── Home
│ │ ├── index.styl
│ │ └── index.tsx
│ └── Routes.ts
└── Server.tsx
├── tsconfig.json
├── webpack.client.js
├── webpack.config.js
└── webpack.server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 |
4 | # System Files
5 | .DS_Store
6 | Thumbs.db
7 | [Dd]esktop.ini
8 |
9 | # Links
10 | *.lnk
11 | .idea/
12 | *.PEM
13 | .vscode/
14 |
15 | # Logs
16 | npm-debug.log
17 |
18 | # Block build and server
19 | /dist
20 | /server
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react_webpack",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "watch": "webpack-dev-server --config webpack.client.js --mode development",
8 | "build": "webpack --config webpack.client.js --mode production",
9 | "dev": "webpack --config webpack.client.js --mode development",
10 | "start": "webpack --config webpack.server.js --mode production && node ./server/main.js"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "devDependencies": {
16 | "@types/express": "^4.17.8",
17 | "@types/react": "^16.9.49",
18 | "@types/react-dom": "^16.9.8",
19 | "@types/react-router-dom": "^5.1.5",
20 | "autoprefixer": "9.8.6",
21 | "clean-webpack-plugin": "^3.0.0",
22 | "css-loader": "^4.3.0",
23 | "express": "^4.17.1",
24 | "html-webpack-plugin": "^4.4.1",
25 | "mini-css-extract-plugin": "^0.11.2",
26 | "postcss-csso": "^4.0.0",
27 | "postcss-loader": "^4.0.2",
28 | "style-loader": "^1.2.1",
29 | "stylus": "^0.54.8",
30 | "stylus-loader": "^3.0.2",
31 | "ts-loader": "^8.0.3",
32 | "typescript": "^4.0.2",
33 | "uglifyjs-webpack-plugin": "^2.2.0",
34 | "webpack": "^4.44.2",
35 | "webpack-cli": "^3.3.12",
36 | "webpack-dev-server": "^3.11.0",
37 | "webpack-node-externals": "^2.5.2",
38 | "webpack-notifier": "^1.8.0"
39 | },
40 | "dependencies": {
41 | "react": "^16.13.1",
42 | "react-dom": "^16.13.1",
43 | "react-router-dom": "^5.2.0"
44 | },
45 | "browserslist": [
46 | "last 2 versions"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer'),
4 | require('postcss-csso'),
5 | ]
6 | }
--------------------------------------------------------------------------------
/src/App/index.styl:
--------------------------------------------------------------------------------
1 | @import "./../Common/Styles/reset.styl"
2 |
3 | html
4 | font-size 10px
5 |
6 | html, body, #root
7 | height 100%
8 |
9 | body
10 | -webkit-font-smoothing antialiased
11 | -webkit-text-size-adjust 100%
12 | -webkit-tap-highlight-color rgba(0,0,0,0)
13 |
14 | font-family $font-main
15 |
16 | background-color $background
17 |
18 | .menu
19 | display flex
20 |
21 | padding 20px
22 |
23 | & a
24 | display inline-block
25 |
26 | font-size 1.6rem
27 |
28 | color #fff
29 |
30 | text-decoration none
31 |
32 | padding-left 10px
33 | padding-right 10px
--------------------------------------------------------------------------------
/src/App/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | Link,
4 | Switch,
5 | Route,
6 | } from 'react-router-dom'
7 | import { Pages } from 'Pages/Routes'
8 |
9 | export function App() {
10 | require('./index.styl')
11 |
12 | return
13 |
14 | {Pages.map((page, index) => {page.title})}
15 |
16 |
17 |
18 | {Pages.map((page, index) => )}
19 |
20 |
21 | }
--------------------------------------------------------------------------------
/src/Client.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { BrowserRouter } from 'react-router-dom'
4 | import { App } from 'App'
5 |
6 | const entryBlock = document.getElementById('root')
7 | const renderFunction: ReactDOM.Renderer = entryBlock.hasChildNodes() ? ReactDOM.hydrate : ReactDOM.render
8 |
9 | renderFunction(
10 |
11 | , entryBlock)
--------------------------------------------------------------------------------
/src/Common/Styles/reset.styl:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p,
2 | blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img,
3 | ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i,
4 | center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption,
5 | tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed,
6 | figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section,
7 | summary, time, mark, audio, video
8 | margin 0
9 | padding 0
10 | border 0
11 | font-size 100%
12 | font inherit
13 | vertical-align baseline
14 | box-sizing border-box
15 |
16 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav,
17 | section
18 | display block
19 |
20 | body
21 | line-height 1
22 |
23 | ol, ul
24 | list-style none
25 |
26 | blockquote, q
27 | quotes none
28 |
29 | :before, :after
30 | content ''
31 | content none
32 |
33 | table
34 | border-collapse collapse
35 | border-spacing 0
--------------------------------------------------------------------------------
/src/Common/Styles/variables.styl:
--------------------------------------------------------------------------------
1 | $font-main = Helvetica, Arial, sans-serif
2 |
3 | $background = #24292e
--------------------------------------------------------------------------------
/src/Html/Browser.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Starter Pack
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Html/Server.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface Html {
4 | scripts: Array
5 | }
6 |
7 | export function Html({ children, scripts }: React.PropsWithChildren) {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | React Starter Pack
15 |
16 |
17 | {children}
18 | {scripts.map((script, index) => )}
19 |
20 |
21 | )
22 | }
--------------------------------------------------------------------------------
/src/Pages/Content/index.styl:
--------------------------------------------------------------------------------
1 | .content
2 | padding 0 30px
3 |
4 | & h1
5 | color #fff
6 |
7 | font-size 2rem
--------------------------------------------------------------------------------
/src/Pages/Content/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export function Content() {
4 | require('./index.styl')
5 |
6 | return
9 | }
--------------------------------------------------------------------------------
/src/Pages/Home/index.styl:
--------------------------------------------------------------------------------
1 | .home
2 | padding 0 30px
3 |
4 | & h1
5 | color #d73a49
6 |
7 | font-size 2rem
--------------------------------------------------------------------------------
/src/Pages/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export function Home() {
4 | require('./index.styl')
5 |
6 | return
9 | }
--------------------------------------------------------------------------------
/src/Pages/Routes.ts:
--------------------------------------------------------------------------------
1 | import { ComponentType } from 'react'
2 | import { RouteComponentProps } from 'react-router-dom'
3 |
4 | import { Home } from 'Pages/Home'
5 | import { Content } from 'Pages/Content'
6 |
7 | interface Route {
8 | link: string
9 | title: string
10 | component: ComponentType> | ComponentType
11 | }
12 |
13 | export const Pages: Array = [
14 | {
15 | link: '/',
16 | title: 'Home',
17 | component: Home,
18 | },
19 | {
20 | link: '/content',
21 | title: 'Content',
22 | component: Content,
23 | },
24 | ]
--------------------------------------------------------------------------------
/src/Server.tsx:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import express from 'express'
3 | import React from 'react'
4 | import ReactDOMServer from 'react-dom/server'
5 | import { StaticRouter } from 'react-router'
6 | import { App } from 'App'
7 | import { Html } from './Html/Server'
8 |
9 | const port = 3000
10 | const server = express()
11 | const jsFiles: Array = []
12 |
13 | fs.readdirSync('./dist/assets').forEach(file => {
14 | if (file.split('.').pop() === 'js') jsFiles.push('/assets/' + file)
15 | })
16 |
17 | server.use('/assets', express.static('./dist/assets'))
18 |
19 | server.get('*', async (req, res) => {
20 | ReactDOMServer.renderToNodeStream(
21 |
22 |
23 |
24 | ).pipe(res)
25 | })
26 |
27 | server.listen(port, () => console.log(`Listening on port ${port}`))
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "jsx": "react",
5 | "noEmitOnError": true,
6 | "noUnusedLocals": true,
7 | "removeComments": true,
8 | "noImplicitAny": true,
9 | "esModuleInterop": true,
10 | "baseUrl": "./",
11 | "paths": {
12 | "App": ["src/App"],
13 | "Pages/*": ["src/Pages/*"],
14 | }
15 | },
16 | "exclude": [
17 | "node_modules"
18 | ]
19 | }
--------------------------------------------------------------------------------
/webpack.client.js:
--------------------------------------------------------------------------------
1 | const path = require("path")
2 | const HtmlWebpackPlugin = require('html-webpack-plugin')
3 | const WebpackNotifierPlugin = require('webpack-notifier')
4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
5 | const { CleanWebpackPlugin } = require('clean-webpack-plugin')
6 | const webpackConfig = require('./webpack.config')
7 |
8 | module.exports = (env, argv) => {
9 | const watchMode = argv.liveReload || false
10 | const modeEnv = argv.mode || 'development'
11 | const isProd = modeEnv === 'production'
12 | const config = webpackConfig(modeEnv)
13 |
14 | const optimizations = {
15 | splitChunks: {
16 | cacheGroups: {
17 | vendors: {
18 | name: 'vendors',
19 | test: /node_modules/,
20 | chunks: 'all',
21 | enforce: true,
22 | },
23 | },
24 | },
25 | minimizer: [],
26 | }
27 |
28 | if (isProd) {
29 | optimizations.minimizer.push(new UglifyJsPlugin())
30 | }
31 |
32 | return {
33 | devServer: {
34 | contentBase: path.join(__dirname, "dist"),
35 | compress: true,
36 | port: 4200,
37 | watchContentBase: true,
38 | progress: true,
39 | hot: true,
40 | open: true,
41 | historyApiFallback: true,
42 | },
43 | resolve: config.resolve,
44 | module: {
45 | rules: [
46 | config.modules.js,
47 | config.modules.stylus,
48 | ],
49 | },
50 | plugins: [
51 | new CleanWebpackPlugin(),
52 | new HtmlWebpackPlugin({
53 | template: './src/Html/Browser.html',
54 | }),
55 | new WebpackNotifierPlugin({ alwaysNotify: false }),
56 | ],
57 | entry: {
58 | main: './src/Client.tsx',
59 | },
60 | output: {
61 | filename: watchMode ? 'assets/[name].[hash].js' : 'assets/[name].[chunkhash].js',
62 | path: path.resolve(__dirname, 'dist'),
63 | publicPath: '/',
64 | },
65 | performance: {
66 | hints: false,
67 | },
68 | optimization: optimizations,
69 | }
70 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path")
2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
3 |
4 | module.exports = (env) => {
5 | const modules = {
6 | js: {
7 | test: /\.ts(x?)$/,
8 | exclude: /node_modules/,
9 | use: [
10 | {
11 | loader: "ts-loader",
12 | },
13 | ],
14 | },
15 | stylus: {
16 | test: /\.styl$/,
17 | use: [
18 | {
19 | loader: "style-loader",
20 | },
21 | {
22 | loader: "css-loader",
23 | },
24 | {
25 | loader: "stylus-loader",
26 | options: {
27 | import: [
28 | path.resolve(__dirname, 'src/Common/Styles/variables.styl'),
29 | ],
30 | }
31 | },
32 | ],
33 | },
34 | stylusIsomorph: {
35 | test: /\.styl$/,
36 | use: [
37 | {
38 | loader: MiniCssExtractPlugin.loader,
39 | },
40 | {
41 | loader: "css-loader",
42 | },
43 | {
44 | loader: "stylus-loader",
45 | options: {
46 | import: [
47 | path.resolve(__dirname, './src/Common/Styles/variables.styl'),
48 | ],
49 | }
50 | },
51 | ],
52 | },
53 | }
54 |
55 | if (env === 'production') {
56 | modules.stylus.use.splice(2, 0, { loader: "postcss-loader" })
57 | modules.stylusIsomorph.use.splice(2, 0, { loader: "postcss-loader" })
58 | }
59 |
60 | const resolve = {
61 | extensions: [".ts", ".tsx", ".js", ".jsx"],
62 | alias: {
63 | App: path.resolve(__dirname, 'src/App/'),
64 | Pages: path.resolve(__dirname, 'src/Pages/'),
65 | },
66 | }
67 |
68 | return {
69 | modules,
70 | resolve,
71 | }
72 | }
--------------------------------------------------------------------------------
/webpack.server.js:
--------------------------------------------------------------------------------
1 | const path = require("path")
2 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
3 | const nodeExternals = require('webpack-node-externals')
4 | const { CleanWebpackPlugin } = require('clean-webpack-plugin')
5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
6 | const webpackConfig = require('./webpack.config')
7 |
8 | module.exports = (env, argv) => {
9 | const modeEnv = argv.mode || 'development'
10 | const config = webpackConfig(modeEnv)
11 |
12 | const optimizations = {
13 | minimizer: [
14 | new UglifyJsPlugin(),
15 | ],
16 | }
17 |
18 | return {
19 | plugins: [
20 | new CleanWebpackPlugin(),
21 | new MiniCssExtractPlugin(),
22 | ],
23 | resolve: config.resolve,
24 | module: {
25 | rules: [
26 | config.modules.js,
27 | config.modules.stylusIsomorph,
28 | ],
29 | },
30 | entry: {
31 | main: './src/Server.tsx',
32 | },
33 | output: {
34 | filename: '[name].js',
35 | path: path.resolve(__dirname, 'server'),
36 | },
37 | performance: {
38 | hints: false,
39 | },
40 | optimization: optimizations,
41 | target: 'node',
42 | externals: [nodeExternals()],
43 | }
44 | }
--------------------------------------------------------------------------------