问题
I'm writing an isomorphic React app based on :
https://github.com/choonkending/react-webpack-node
Instead of css modules used in the examples I'd like to use scss though. And for some reason I'm having a really hard time getting them to work. My first step was to remove the css
webpack loaders from both the server and the client configs replacing them with scss
-specific loaders (as well as removing postcss
) :
loaders: [
'style-loader',
'css-loader?modules&localIdentName=[name]_[local]_[hash:base64:3]',
'sass-loader?sourceMap',
]
But this throws ReferenceError: window is not defined
when building as style-loader is apparently not suitable for server-side rendering. So my next idea was to use isomorphic-style-loader. As far as I understand to get it working I need to decorate my component with their higher order component withStyles
:
import React, { PropTypes } from 'react';
import classNames from 'classnames';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from '../assets/scss/common/index.scss';
const App = (props, context) => (
<div className={classNames('app')}>
<h1 className="home_header">Welcome!</h1>
{props.children}
</div>
);
export default withStyles(s)(App);
and then do some trickery in the code rendering page on the server. But the problem is, example from the package docs shows a flux action fired inside Express (https://libraries.io/npm/isomorphic-style-loader#webpack-configuration), and the boilerplate that I'm using uses react-router
. So I'm kinda lost as how should I inject this object with insertCss
into context. I tried this :
import React from 'react';
import { renderToString } from 'react-dom/server';
import { RouterContext, match, createMemoryHistory } from 'react-router';
import axios from 'axios';
import { Provider } from 'react-redux';
import createRoutes from 'routes.jsx';
import configureStore from 'store/configureStore';
import headconfig from 'components/Meta';
import { fetchComponentDataBeforeRender } from 'api/fetchComponentDataBeforeRender';
const clientConfig = {
host: process.env.HOSTNAME || 'localhost',
port: process.env.PORT || '3001'
};
// configure baseURL for axios requests (for serverside API calls)
axios.defaults.baseURL = `http://${clientConfig.host}:${clientConfig.port}`;
function renderFullPage(renderedContent, initialState, head = {
title: 'cs3',
css: ''
}) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
${head.title}
${head.link}
<style type="text/css">${head.css.join('')}</style>
</head>
<body>
<div id="app">${renderedContent}</div>
<script type="text/javascript">window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};</script>
<script type="text/javascript" charset="utf-8" src="/assets/app.js"></script>
</body>
</html>
`;
}
export default function render(req, res) {
const history = createMemoryHistory();
const store = configureStore({
project: {}
}, history);
const routes = createRoutes(store);
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
const css = [];
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
const context = { insertCss: (styles) => css.push(styles._getCss()) };
const InitialView = (
<Provider context={context} store={store}>
<RouterContext {...renderProps} />
</Provider>
);
fetchComponentDataBeforeRender(store.dispatch, renderProps.components, renderProps.params)
.then(() => {
const componentHTML = renderToString(InitialView);
const initialState = store.getState();
res.status(200).end(renderFullPage(componentHTML, initialState, {
title: 'foo',
css
}));
})
.catch(() => {
res.end(renderFullPage('', {}));
});
} else {
res.status(404).send('Not Found');
}
});
}
but I'm still getting Warning: Failed context type: Required context 'insertCss' was not specified in 'WithStyles(App)'.
Any ideas how to tackle this ? And more importantly - is there no easier way to do it ? This seems like a lot of additional work.
回答1:
There's a few parts to handling scss compilation with webpack when you're doing server-side rendering. First of all, you don't want node trying to import .scss
files into your components.
So set a global variable WEBPACK: true
in your webpack config:
plugins: [
new webpack.DefinePlugin({
'process.env': {
WEBPACK: JSON.stringify(true),
}
})
],
And in your components, only attempt to import .scss
files if the component is being handled by webpack (either during build or development):
if (process.env.WEBPACK) require('../assets/scss/common/index.scss');
If you only have one Sass file per component (you should) then this is always just a one-liner. Any additional Sass files can be imported inside index.scss
if you need to.
Then in your config you probably still want the css loader, so it should look like this for your dev server:
{
test: /\.s?css$/,
loaders: ['style', 'css', 'sass']
},
And something like this for you build config:
{
test: /\.s?css$/,
loader: ExtractTextPlugin.extract('style', 'css!sass')
},
来源:https://stackoverflow.com/questions/38709372/scss-compilation-in-an-isomorphic-react-app