├── .babelrc ├── .gitignore ├── README.md ├── images ├── react.png ├── styled-components.png └── webpack.png ├── package.json ├── src ├── App.js ├── Html.js ├── components │ └── Buttons.js ├── index.html ├── index.js └── server.js ├── test ├── components │ └── Demo.spec.js └── index.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env" 5 | ], 6 | "@babel/preset-react" 7 | ], 8 | "plugins": [ 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_STORE 3 | dist 4 | .node-version 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [react-webpack-example](https://github.com/StevenIseki/react-webpack-example) 2 | 3 | An extremely minimal example of using react, webpack and styled components in production with universal rendering and in development with live reloading... 4 | 5 | Styled components server rendering setup inspired by Dennis Brotzky [guide-to-server-side-rendering-react-with-styled-components](https://medium.com/styled-components/the-simple-guide-to-server-side-rendering-react-with-styled-components-d31c6b2b8fbf) 6 | 7 | ![](images/react.png) 8 | ![](images/styled-components.png) 9 | ![](images/webpack.png) 10 | 11 | ## Dependencies 12 | 13 | * **react** `16.0.0` 14 | * **babel** `7.3.3` 15 | * **webpack** `4.29.4` 16 | * **styled components** `4.1.3` 17 | 18 | ## Run Dev 19 | 20 | * webpack dev server with hot reloading, no server rendering 21 | 22 | ``` 23 | yarn 24 | yarn dev 25 | open http://127.0.0.1:3000 26 | ``` 27 | 28 | ## Run Server 29 | 30 | * Universal server side rendering! 31 | 32 | ``` 33 | yarn 34 | yarn build 35 | yarn prod 36 | open http://127.0.0.1:3000 37 | ``` 38 | 39 | ## Testing 40 | 41 | * Using Enzyme, Tape and Jsdom 42 | 43 | ``` 44 | yarn 45 | yarn test 46 | ``` 47 | 48 | ## License 49 | 50 | [MIT](http://isekivacenz.mit-license.org/) 51 | -------------------------------------------------------------------------------- /images/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svnm/react-webpack-example/00213435a4b172d6a00def4bc992402be51023dc/images/react.png -------------------------------------------------------------------------------- /images/styled-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svnm/react-webpack-example/00213435a4b172d6a00def4bc992402be51023dc/images/styled-components.png -------------------------------------------------------------------------------- /images/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svnm/react-webpack-example/00213435a4b172d6a00def4bc992402be51023dc/images/webpack.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-webpack-example-app", 3 | "version": "1.1.0", 4 | "description": "Example using react, webpack and styled components", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/StevenIseki/react-webpack-example" 8 | }, 9 | "author": "Steven Iseki ", 10 | "license": "MIT", 11 | "scripts": { 12 | "build": "npm run clean && webpack --mode=production", 13 | "clean": "rimraf dist/*", 14 | "dev": "webpack-dev-server --mode=development --port 3000 --inline --hot", 15 | "prod": "node dist/server", 16 | "test": "babel-tape-runner test/*.js" 17 | }, 18 | "devDependencies": { 19 | "@babel/cli": "^7.2.3", 20 | "@babel/core": "7.3.3", 21 | "@babel/preset-env": "7.3.1", 22 | "@babel/preset-react": "7.0.0", 23 | "babel-loader": "8.0.5", 24 | "babel-tape-runner": "^3.0.0", 25 | "enzyme": "3.9.0", 26 | "enzyme-adapter-react-16": "^1.9.1", 27 | "express": "^4.14.0", 28 | "html-webpack-plugin": "3.2.0", 29 | "jsdom": "13.2.0", 30 | "prop-types": "^15.6.0", 31 | "rimraf": "^2.6.3", 32 | "react": "^16.8.4", 33 | "react-dom": "^16.8.4", 34 | "styled-components": "^4.1.3", 35 | "tape": "4.10.1", 36 | "webpack": "4.29.4", 37 | "webpack-cli": "3.2.3", 38 | "webpack-dev-server": "3.1.14", 39 | "webpack-node-externals": "^1.7.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Buttons from './components/Buttons' 4 | 5 | const App = () => ( 6 | 7 | 8 | 9 | ) 10 | 11 | const AppContainer = styled.div` 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | position: fixed; 16 | width: 100%; 17 | height: 100%; 18 | font-size: 40px; 19 | background: linear-gradient(20deg, rgb(219, 112, 147), #daa357); 20 | `; 21 | 22 | export default App 23 | -------------------------------------------------------------------------------- /src/Html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Html 3 | * This Html.js file acts as a template that we insert all our generated 4 | * application strings into before sending it to the client. 5 | */ 6 | 7 | const Html = ({ body, styles, title }) => ` 8 | 9 | 10 | 11 | ${title} 12 | 13 | 14 | ${styles} 15 | 16 | 17 |
${body}
18 | 19 | 20 | ` 21 | 22 | export default Html 23 | -------------------------------------------------------------------------------- /src/components/Buttons.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styled from 'styled-components' 3 | 4 | export default class Buttons extends Component { 5 | handleClick() { 6 | console.log('you clicked me') 7 | } 8 | 9 | render() { 10 | return ( 11 | 12 | 15 |
16 | 19 |
20 | ) 21 | } 22 | } 23 | 24 | const ButtonWrapper = styled.div` 25 | width: 200px; 26 | margin: 0 auto; 27 | ` 28 | 29 | const Button = styled.button` 30 | border-radius: 4px; 31 | color: white; 32 | cursor: pointer; 33 | font-size: 16px; 34 | margin-bottom: 20px; 35 | padding: 15px 30px; 36 | text-align: center; 37 | transition: all 0.5s; 38 | width: 100%; 39 | ${props => getButtonColor(props.color)} 40 | 41 | &:hover { 42 | background-color: transparent; 43 | } 44 | ` 45 | 46 | const getButtonColor = color => { 47 | if (color === 'blue') { 48 | return ` 49 | background-color: #134893; 50 | border: 2px solid #134893; 51 | &:hover { 52 | color: blue; 53 | border: 2px solid #134893; 54 | } 55 | ` 56 | } 57 | if (color === 'red') { 58 | return ` 59 | background-color: #a92619; 60 | border: 2px solid #a92619; 61 | &:hover { 62 | color: #a92619; 63 | border: 2px solid #a92619; 64 | } 65 | ` 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React webpack example 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import App from './App' 4 | 5 | render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import React from 'react' 3 | import { renderToString } from 'react-dom/server' 4 | import App from './App' 5 | import Html from './Html' 6 | import { ServerStyleSheet } from 'styled-components' 7 | 8 | const port = 3000 9 | const ip = 'localhost' 10 | const server = express() 11 | 12 | /* Creating a single index route to server our React app */ 13 | server.get('/', (req, res) => { 14 | const sheet = new ServerStyleSheet() 15 | const body = renderToString(sheet.collectStyles()) 16 | const styles = sheet.getStyleTags() 17 | const title = 'Server side Rendering with react and styled components' 18 | 19 | res.send( 20 | Html({ 21 | body, 22 | styles, 23 | title 24 | }) 25 | ); 26 | }); 27 | 28 | server.listen(port, ip, function (err) { 29 | if (err) { console.log(err); return; } 30 | console.log(`listening on ${ip}:${port}`) 31 | }) 32 | -------------------------------------------------------------------------------- /test/components/Demo.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import React from 'react' 3 | import { shallow } from 'enzyme' 4 | import Demo from '../../src/components/Demo' 5 | 6 | test('Demo component', (t) => { 7 | const component = shallow() 8 | 9 | t.equal( 10 | component.find('button').length, 2, 'the Demo component has 2 button elements' 11 | ) 12 | 13 | t.end() 14 | }); 15 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var jsdom = require('jsdom') 2 | const { JSDOM } = jsdom 3 | const window = (new JSDOM('')).window 4 | global.window = window 5 | global.document = window.document 6 | 7 | import { configure } from 'enzyme' 8 | import Adapter from 'enzyme-adapter-react-16' 9 | configure({ adapter: new Adapter() }) 10 | 11 | /* components */ 12 | require('./components/Demo.spec.js'); 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const nodeExternals = require('webpack-node-externals') 3 | const path = require('path') 4 | const HtmlWebpackPlugin = require('html-webpack-plugin') 5 | 6 | function entry (options) { 7 | if (options.mode === 'production') { 8 | return './src/server.js' 9 | } 10 | return './src/index.js' 11 | } 12 | 13 | function externals (options) { 14 | if (options.mode === 'production') { 15 | return nodeExternals(options) 16 | } 17 | return {} 18 | } 19 | 20 | function output (options) { 21 | if (options.mode === 'production') { 22 | return { path: path.resolve(__dirname, 'dist'), filename: 'server.js', publicPath: '/' } 23 | } 24 | return { filename: 'bundle.js', publicPath: '' } 25 | } 26 | 27 | function plugins (options) { 28 | if (options.mode === 'production') { 29 | return [ 30 | new webpack.DefinePlugin({'process.env': { NODE_ENV: `'production'` }}) 31 | ] 32 | } 33 | return [ 34 | new HtmlWebpackPlugin({ title: 'react webpack example', template: './src/index.html' }) 35 | ] 36 | } 37 | 38 | function target (options) { 39 | if (options.mode === 'production') { 40 | return 'node' 41 | } 42 | return 'web' 43 | } 44 | 45 | 46 | module.exports = (env, options) => { 47 | return { 48 | entry: entry(options), 49 | externals: externals(options), 50 | output: output(options), 51 | module: { 52 | rules: [ 53 | { 54 | test: /\.js$/, 55 | use: [ { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/react'] } } ], 56 | exclude: /node_modules/, 57 | } 58 | ] 59 | }, 60 | plugins: plugins(options), 61 | target: target(options) 62 | } 63 | } 64 | --------------------------------------------------------------------------------