Компиляция SCSS в изоморфном приложении React

Я пишу изоморфное приложение React на основе:

https://github.com/choonkending/react-webpack-node

Однако вместо модулей css, используемых в примерах, я бы хотел использовать scss. И почему-то мне очень трудно заставить их работать. Моим первым шагом было удаление загрузчиков css webpack как с сервера, так и с клиента конфиги, заменяя их scss специфичными загрузчиками (а также удаляя postcss):

  loaders: [
    'style-loader',
    'css-loader?modules&localIdentName=[name]_[local]_[hash:base64:3]',
    'sass-loader?sourceMap',
  ]

Но это бросает ReferenceError: window is not defined при сборке, поскольку загрузчик стилей явно не подходит для рендеринга на стороне сервера. Итак, моей следующей идеей было использовать isomorphic-style-loader. Насколько я понимаю, чтобы заставить его работать, мне нужно украсить мой компонент их компонентом более высокого порядка 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);

а затем проделайте некоторые трюки на странице рендеринга кода на сервере. Но проблема в том, что пример из документации пакета показывает действие потока, запущенное внутри Express (https://libraries.io/npm/isomorphic-style-loader#webpack-configuration), а шаблон, который я использую, использует react-router. Так что я немного не понимаю, как мне вставить этот объект с insertCss в контекст. Я пробовал это:

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');
    }
  });
}

но я все еще получаю Warning: Failed context type: Required context 'insertCss' was not specified in 'WithStyles(App)'. Есть идеи, как с этим справиться? И что еще более важно - нет более простого способа сделать это? Кажется, это требует много дополнительной работы.


person sasklacz    schedule 01.08.2016    source источник


Ответы (1)


Когда вы выполняете рендеринг на стороне сервера, есть несколько частей для обработки компиляции scss с помощью webpack. Прежде всего, вы не хотите, чтобы узел пытался импортировать .scss файлы в ваши компоненты.

Итак, установите глобальную переменную WEBPACK: true в конфигурации вашего веб-пакета:

plugins: [
    new webpack.DefinePlugin({
        'process.env': {
            WEBPACK: JSON.stringify(true),
        }
    })
],

И в ваших компонентах пытайтесь импортировать .scss файлы только в том случае, если компонент обрабатывается веб-пакетом (либо во время сборки, либо во время разработки):

if (process.env.WEBPACK) require('../assets/scss/common/index.scss');

Если у вас есть только один файл Sass для каждого компонента (вы должны), то это всегда однострочный файл. Любые дополнительные файлы Sass можно импортировать внутрь index.scss, если вам нужно.

Тогда в вашей конфигурации вы, вероятно, все еще хотите использовать загрузчик css, поэтому для вашего сервера разработки он должен выглядеть так:

{
    test: /\.s?css$/,
    loaders: ['style', 'css', 'sass']

},

И что-то вроде этого для вас конфиг сборки:

{
    test: /\.s?css$/,
    loader: ExtractTextPlugin.extract('style', 'css!sass')
},
person David Gilbertson    schedule 02.08.2016
comment
Круто, этот работает. Но мне кажется, что я теряю некоторые преимущества этого изоморфного загрузчика, поскольку он должен был загружать только минимально необходимый CSS. Работа с этой переменной плагина webpack внутри моих компонентов - тоже не самое чистое решение, но сейчас мне придется с этим смириться. - person sasklacz; 02.08.2016