Short answer: Install webpack@1, babel-loader@5, react@0.13, configure webpack.config.js with JSX transform and HMR. No CLI tools, no abstractions - just config files you control.
Create React App launched in July 2016. Before that, every React project started with manual Webpack configuration. This is what a working production setup looked like in early 2015.
The Stack (March 2015)
- React 0.13.0 (released February 2015 - introduced ES6 class components)
- Webpack 1.7
- Babel 5 (single package, before the plugins split in Babel 6)
- webpack-dev-server 1.7
npm init -y
npm install --save react react-dom
npm install --save-dev webpack webpack-dev-server babel-loader babel-core
npm install --save-dev babel-preset-react babel-preset-es2015
webpack.config.js
// webpack.config.js - 2015 production setup
var webpack = require('webpack');
var path = require('path');
var isProd = process.env.NODE_ENV === 'production';
module.exports = {
entry: {
app: isProd
? './src/index.js'
: ['webpack/hot/dev-server', './src/index.js']
},
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/dist/'
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
// In 2015, no 'stage-0' needed - we kept to stable spec
}
},
{
test: /\.css$/,
loader: 'style!css'
// style-loader + css-loader chained - ! syntax was Webpack 1 convention
}
]
},
resolve: {
extensions: ['', '.js', '.jsx']
// Empty string required in Webpack 1 to resolve files without extension
},
plugins: isProd ? [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false }
}),
new webpack.optimize.DedupePlugin()
// DedupePlugin: remove duplicate modules - useful with React ecosystem
] : [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
// NoErrorsPlugin: don't emit bundles with errors - prevents broken HMR
]
};
.babelrc
{
"presets": ["es2015", "react"],
"env": {
"development": {
"plugins": ["react-transform"],
"extra": {
"react-transform": {
"transforms": [{
"transform": "react-transform-hmr",
"imports": ["react"],
"locals": ["module"]
}]
}
}
}
}
}
Additional install for HMR:
npm install --save-dev babel-plugin-react-transform react-transform-hmr
package.json scripts
{
"scripts": {
"start": "webpack-dev-server --hot --inline --port 3000",
"build": "NODE_ENV=production webpack --progress --colors",
"build:win": "set NODE_ENV=production && webpack --progress --colors"
}
}
src/index.js - entry point
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// React 0.13: ReactDOM was split out from React in this release
// Previously: React.render() - now: ReactDOM.render()
ReactDOM.render(
<App />,
document.getElementById('app')
);
src/App.jsx - ES6 class component
import React, { Component } from 'react';
// React 0.13 introduced ES6 class components
// Previously you'd use React.createClass({...})
class App extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
// In 2015: had to bind methods manually
// Arrow functions in class properties (class fields) didn't exist yet
this.increment = this.increment.bind(this);
}
increment() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<h1>Count: {this.state.count}</h1>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
export default App;
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>React App 2015</title>
</head>
<body>
<div id="app"></div>
<!-- bundle.js served by webpack-dev-server in dev, from /dist/ in prod -->
<script src="/dist/bundle.js"></script>
</body>
</html>
Common Problems in 2015
Problem: HMR reloads full page instead of component
Cause: react-transform-hmr not applied. Check .babelrc has "env": { "development": {...}} block and NODE_ENV=development is set.
Problem: "Cannot resolve module 'react/lib/ReactMount'"
Cause: react-transform-hmr version mismatch with React 0.13. Fix:
npm install react-transform-hmr@1.0.1
Problem: CSS imported in JS causes "Cannot find module" error
Install missing loaders:
npm install --save-dev style-loader css-loader
Production Build Output (2015)
A typical app at this stage:
bundle.js: ~180 KB minified (React 0.13 was ~120 KB alone)- Gzipped: ~52 KB
- Build time: 8-12 seconds on a 2013 MacBook Pro
By comparison, React 18 + modern tooling produces similar gzipped sizes but with far more functionality. The raw numbers haven't changed dramatically - the developer experience has.
What Changed After 2015
In October 2015, Babel 6 broke this config: the single babel-core became a plugin system, requiring explicit preset packages. In February 2016, Webpack 2 beta introduced import() dynamic loading. In July 2016, Create React App eliminated all of this configuration.
The manual setup taught you what CRA hides: chunk splitting, tree shaking, loader chains, plugin order. That knowledge still applies when CRA's defaults aren't enough.
Aunimeda builds modern web frontends - from single-page applications to complex multi-locale sites.
Contact us to discuss your frontend project. See also: Web Development, Corporate Website Development