Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added isomorphic CSS modules #48

Merged
merged 2 commits into from
Jul 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"eslint": "^0.23.0",
"eslint-config-airbnb": "0.0.6",
"eslint-plugin-react": "^2.5.2",
"extract-text-webpack-plugin": "^0.3.1",
"extract-text-webpack-plugin": "^0.8.1",
"json-loader": "0.5.2",
"node-sass": "^3.2.0",
"react-a11y": "0.1.1",
Expand Down
16 changes: 11 additions & 5 deletions src/components/InfoBar.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import path from 'path';
import React, {Component, PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as infoActions from '../actions/infoActions';
if (__CLIENT__) {
require('./InfoBar.scss');
}

const styles = (function getStyle() {
const stats = require('../../webpack-stats.json');
if (__CLIENT__) {
return require('./InfoBar.scss');
}
return stats.css.modules[path.join(__dirname, './InfoBar.scss')];
})();

class InfoBar extends Component {
static propTypes = {
Expand All @@ -15,11 +21,11 @@ class InfoBar extends Component {
render() {
const {info, load} = this.props;
return (
<div className="info-bar well">
<div className={styles.infoBar + " well"}>
This is an info bar
{' '}
<strong>{info ? info.message : 'no info!'}</strong>
<span className="time">{info && new Date(info.time).toString()}</span>
<span className={styles.time}>{info && new Date(info.time).toString()}</span>
<button className="btn btn-primary" onClick={load}>Reload from server</button>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ app.use((req, res) => {
media="screen, projection" rel="stylesheet" type="text/css"/>
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css"
media="screen, projection" rel="stylesheet" type="text/css"/>
{webpackStats.css.map((css, i) => <link href={css} key={i}
{webpackStats.css.files.map((css, i) => <link href={css} key={i}
media="screen, projection" rel="stylesheet" type="text/css"/>)}
</head>
<body>
Expand Down
20 changes: 13 additions & 7 deletions src/views/App.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from 'path';
import React, {Component, PropTypes} from 'react';
import {Link} from 'react-router';
import {bindActionCreators} from 'redux';
Expand All @@ -9,9 +10,14 @@ import * as authActions from '../actions/authActions';
import {load as loadAuth} from '../actions/authActions';
import InfoBar from '../components/InfoBar';
import {createTransitionHook} from '../universalRouter';
if (__CLIENT__) {
require('./App.scss');
}

const styles = (function getStyle() {
const stats = require('../../webpack-stats.json');
if (__CLIENT__) {
return require('./App.scss');
}
return stats.css.modules[path.join(__dirname, './App.scss')];
})();

class App extends Component {
static propTypes = {
Expand All @@ -37,13 +43,13 @@ class App extends Component {
render() {
const {user} = this.props;
return (
<div className="container app">
<div className={styles.app + " container"}>
<div className="jumbotron">
<h1>React Redux Example</h1>

<p>
by <a href="https://twitter.com/erikras" target="_blank">@erikras</a>
<a className="github" href="https://github.com/erikras/react-redux-universal-hot-example" target="_blank">
<a className={styles.github} href="https://github.com/erikras/react-redux-universal-hot-example" target="_blank">
<i className="fa fa-github"/> View on Github
</a>
</p>
Expand All @@ -64,12 +70,12 @@ class App extends Component {
{!user && <li><Link to="/login">Login</Link></li>}
{user && <li className="logout-link"><a href="/logout" onClick={::this.handleLogout}>Logout</a></li>}
</ul>
{user && <p className="navbar-text logged-in-message">Logged in as <strong>{user.name}</strong>.</p>}
{user && <p className={styles.loggedInMessage + " navbar-text"}>Logged in as <strong>{user.name}</strong>.</p>}
</div>
</nav>
<InfoBar/>

<div className="app-content">
<div className={styles.appContent}>
{this.props.children}
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/views/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
iframe {
border:none;
}
.logged-in-message {
.loggedInMessage {
margin-left: 70px;
}
}
.app-content {
.appContent {
color: blue;
}
14 changes: 10 additions & 4 deletions src/views/Login.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import path from 'path';
import React, {Component, PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {isLoaded as isAuthLoaded} from '../reducers/auth';
import * as authActions from '../actions/authActions';
import {load as loadAuth} from '../actions/authActions';
if (__CLIENT__) {
require('./Login.scss');
}

const styles = (function getStyle() {
const stats = require('../../webpack-stats.json');
if (__CLIENT__) {
return require('./Login.scss');
}
return stats.css.modules[path.join(__dirname, './Login.scss')];
})();

class Login extends Component {
static propTypes = {
Expand Down Expand Up @@ -35,7 +41,7 @@ class Login extends Component {
const {user, logout} = this.props;
const {username} = this.state;
return (
<div className="login-page">
<div className={styles.loginPage}>
<h1>Login</h1>
{!user &&
<div>
Expand Down
4 changes: 2 additions & 2 deletions src/views/Login.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.login-page {
.loginPage {
color: black;
input {
padding: 5px 10px;
Expand All @@ -7,7 +7,7 @@
}
form {
margin: 30px 0;
.btn {
:global(.btn) {
margin-left: 10px;
}
}
Expand Down
16 changes: 11 additions & 5 deletions src/views/Widgets.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import path from 'path';
import React, {Component, PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {isLoaded} from '../reducers/widgets';
import {connect} from 'react-redux';
import * as widgetActions from '../actions/widgetActions';
import {load as loadWidgets} from '../actions/widgetActions';
if (__CLIENT__) {
require('./Widgets.scss');
}

const styles = (function getStyle() {
const stats = require('../../webpack-stats.json');
if (__CLIENT__) {
return require('./Widgets.scss');
}
return stats.css.modules[path.join(__dirname, './Widgets.scss')];
})();

class Widgets extends Component {
static propTypes = {
Expand All @@ -23,10 +29,10 @@ class Widgets extends Component {
refreshClassName += ' fa-spin';
}
return (
<div className="widgets">
<div className={styles.widgets}>
<h1>
Widgets
<button className="refresh-btn btn btn-success" onClick={load}><i className={refreshClassName}/> {' '} Reload Widgets</button>
<button className={styles.refreshBtn + " btn btn-success"} onClick={load}><i className={refreshClassName}/> {' '} Reload Widgets</button>
</h1>
{error &&
<div className="alert alert-danger" role="alert">
Expand Down
2 changes: 1 addition & 1 deletion src/views/Widgets.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
table {
color:black;
}
.refresh-btn {
.refreshBtn {
margin-left: 20px;
}
}
9 changes: 6 additions & 3 deletions webpack/dev.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ var webpack = require('webpack');
var writeStats = require('./utils/writeStats');
var notifyStats = require('./utils/notifyStats');
var assetsPath = path.resolve(__dirname, '../static/dist');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var host = 'localhost';
var port = parseInt(process.env.PORT) + 1 || 3001;

Expand All @@ -27,7 +26,8 @@ module.exports = {
loaders: [
{ test: /\.(jpe?g|png|gif|svg)$/, loader: 'file' },
{ test: /\.js$/, exclude: /node_modules/, loaders: ['react-hot', 'babel?stage=0&optional=runtime&plugins=typecheck']},
{ test: /\.scss$/, loader: 'style!css!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap=true&sourceMapContents=true' }
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.scss$/, loader: 'style!css?modules&importLoaders=2&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap=true&sourceMapContents=true' }
]
},
progress: true,
Expand All @@ -41,6 +41,7 @@ module.exports = {
plugins: [
// hot reload
new webpack.HotModuleReplacementPlugin(),
new webpack.WatchIgnorePlugin([/\.json$/]),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
__CLIENT__: true,
Expand All @@ -54,7 +55,9 @@ module.exports = {
this.plugin('done', notifyStats);
},
function () {
this.plugin('done', writeStats);
this.plugin('done', function(stats) {
writeStats.call(this, stats, 'dev');
});
}
]
};
12 changes: 8 additions & 4 deletions webpack/prod.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ module.exports = {
loaders: [
{ test: /\.(jpe?g|png|gif|svg)$/, loader: 'file' },
{ test: /\.js$/, exclude: /node_modules/, loaders: [strip.loader('debug'), 'babel?stage=0&optional=runtime&plugins=typecheck']},
{ test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css!autoprefixer?browsers=last 2 version!sass') }
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=2!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap=true&sourceMapContents=true') }
]
},
progress: true,
Expand All @@ -38,7 +39,7 @@ module.exports = {
plugins: [

// css files from the extract-text-plugin loader
new ExtractTextPlugin('[name]-[chunkhash].css'),
new ExtractTextPlugin('[name]-[chunkhash].css', {allChunks: true}),
new webpack.DefinePlugin({__CLIENT__: true, __SERVER__: false, __DEVELOPMENT__: false, __DEVTOOLS__: false}),

// ignore dev config
Expand Down Expand Up @@ -68,7 +69,10 @@ module.exports = {
}),

// stats
function() { this.plugin('done', writeStats); }

function () {
this.plugin('done', function(stats) {
writeStats.call(this, stats, 'prod');
});
}
]
};
27 changes: 24 additions & 3 deletions webpack/utils/writeStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ var fs = require('fs'),
path = require('path'),
filepath = path.resolve(__dirname, '../../webpack-stats.json');

module.exports = function writeStats(stats) {
module.exports = function writeStats(stats, env) {

var publicPath = this.options.output.publicPath;

Expand All @@ -29,11 +29,32 @@ module.exports = function writeStats(stats) {
}

var script = getChunks('main', 'js');
var css = getChunks('main', 'css');
var cssFiles = getChunks('main', 'css');

var cssModules = {};

// Is there a way to get this dynamically so it doesn't depend on loader?
var namePrefix = "./~/css-loader?modules&importLoaders=2&localIdentName=[local]___[hash:base64:5]!./~/autoprefixer-loader?browsers=last 2 version!./~/sass-loader?outputStyle=expanded&sourceMap=true&sourceMapContents=true!";

json.modules.filter(function(m) {
if (env === 'prod') {
return /\.scss$/.test(m.name);
}

return m.name.slice(0, namePrefix.length) === namePrefix;
}).forEach(function(m) {
var name = path.resolve(__dirname, '../../', env === 'prod' ? m.name : m.name.slice(namePrefix.length));
var regex = env === 'prod' ? /module\.exports = ((.|\n)+);/ : /exports\.locals = ((.|\n)+);/;

cssModules[name] = JSON.parse(m.source.match(regex)[1]);
});

var content = {
script: script,
css: css
css: {
files: cssFiles,
modules: cssModules
}
};

fs.writeFileSync(filepath, JSON.stringify(content));
Expand Down