SCSS compilation in an isomorphic React app

回眸只為那壹抹淺笑 提交于 2019-12-14 03:42:45

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!