Почему isomorphic-style-loader выбрасывает TypeError: Невозможно прочитать свойство 'apply' undefined при использовании в унисон с CSS-модулями

В настоящее время я пытаюсь отобразить приложение на сервере, который работает для HTML и JS, но обнаружил, что мои стили (.less |.scss) не загружаются. Я провел некоторое исследование и понял, не уверен, что мне не хватало isomorphic-style-loader в моей конфигурации Webpack на основе других, сталкивающихся с теми же проблемами. Я настроил его, по крайней мере, так, как я понял, но теперь обнаруживаю, что при запуске приложения я получаю следующую ошибку:

TypeError: Cannot read property 'apply' of undefined at WithStyles.componentWillMount

Я немного новичок во всем, что касается React / Express, но я стараюсь следовать учебным пособиям и обучению по ходу дела, если что-то кажется неуместным, извините, пожалуйста. Я надеюсь увидеть, сможет ли кто-нибудь объяснить, что именно вызывает эту ошибку, и дать мне некоторое представление о том, что я мог бы предпринять, чтобы устранить эту ошибку. Ниже приведен пример кода, который напоминает тот, с которым у меня возникают проблемы, если он помогает каким-либо образом.

(Для справки я следовал учебному пособию по рендерингу серверов Tyler McGinnis React Router и попытался расширить его, добавив стилизацию - ссылка здесь)

Заранее благодарим за предоставленное объяснение того, что может быть причиной этой ошибки.


webpack.config.babel.js

import path from 'path'
import webpack from 'webpack'
import nodeExternals from 'webpack-node-externals'

const paths = {
  browser: path.join(__dirname, './src/browser'),
  server: path.join(__dirname, './src/server'),
  build: path.resolve(__dirname, 'public')
}

let browserConfig = {
  entry: `${paths.browser}/index.js`,
  output: {
    path: paths.build,
    filename: 'bundle.js',
    publicPath: '/'
  },
  module: {
    rules: [
      {
        test: /\.s?(a|c)ss$/,
        use: [
          'isomorphic-style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 1,
              localIdentName: '[name]__[local]___[hash:base64:5]',
              sourceMap: true
            }
          },
          'sass-loader',
          'postcss-loader'
        ]
      }, {
        test: /\.less$/,
        use: [
          'isomorphic-style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 1,
              localIdentName: '[name]__[local]___[hash:base64:5]',
              sourceMap: true
            }
          },
          {
            loader: 'less-loader',
            options: {
              javascriptEnabled: true
            }
          },
          'postcss-loader'
        ]
      }, {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      __isBrowser__: true
    })
  ],
  resolve: {
    extensions: ['.js', '.jsx', '.json', '.css', '.scss', '.sass', '.less']
  }
}

let serverConfig = {
  entry: `${paths.server}/index.js`,
  target: 'node',
  externals: [nodeExternals()],
  output: {
    path: __dirname,
    filename: 'server.js',
    publicPath: '/'
  },
  module: {
    rules: [
      {
        test: /\.s?(a|c)ss$/,
        use: [
          'isomorphic-style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 1,
              localIdentName: '[name]__[local]___[hash:base64:5]',
              sourceMap: true
            }
          },
          'sass-loader',
          'postcss-loader'
        ]
      }, {
        test: /\.less$/,
        use: [
          'isomorphic-style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 1,
              localIdentName: '[name]__[local]___[hash:base64:5]',
              sourceMap: true
            }
          },
          {
            loader: 'less-loader',
            options: {
              javascriptEnabled: true
            }
          },
          'postcss-loader'
        ]
      }, {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      __isBrowser__: false
    })
  ],
  resolve: {
    extensions: ['.js', '.jsx', '.json', '.css', '.scss', '.sass', '.less']
  }
}

module.exports = [browserConfig, serverConfig]

server.js

import express from "express"
import cors from "cors"
import React from "react"
import bodyParser from 'body-parser'
import serialize from "serialize-javascript"
import { renderToString } from "react-dom/server"
import { StaticRouter, matchPath } from "react-router-dom"
import App from '../shared/App'
import routes from '../shared/routes'

const app = express()
const port = process.env.PORT || 3000

app.use(cors())
app.use(bodyParser.json()) // support json encoded bodies
app.use(bodyParser.urlencoded({extended: true})) // support encoded bodies
app.use(express.static("public"))

app.get("*", (req, res, next) => {
  const activeRoute = routes.find((route) => matchPath(req.url, route)) || {}

  const promise = activeRoute.fetchInitialData
    ? activeRoute.fetchInitialData(req.path)
    : Promise.resolve()

  promise.then((data) => {
    const css = new Set()
    const context = { data, insertCss: (...styles) => styles.forEach(style => css.add(style._getCss())) }
    const markup = renderToString(
      <StaticRouter location={req.url} context={context}>
          <App />
      </StaticRouter>
    )

    res.send(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>React on the Server!</title>
          <script src="/bundle.js" defer></script>
          <script>window.__INITIAL_DATA__ = ${serialize(data)}</script>
        </head>
        <body>
          <div id="app">${markup}</div>
        </body>
      </html>
    `)
  }).catch(next)
})

app.listen(port, () => console.log(`Server is listening on port: ${port}`))

routes.js

import AboutMain from './components/About/AboutMain'

const routes =  [
  {
    path: '/about',
    component: AboutMain
  }
]

export default routes

browser.js

// Import the neccessary modules for use in file
import React from 'react' // Main React module
import { hydrate } from 'react-dom' // render alternative for server rendering
import App from '../shared/App'
import { BrowserRouter } from 'react-router-dom' // React Router component for client side routing
import '../shared/components/global.scss' // Only has general rules, which do get applied

hydrate(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('app')
)

App.js

import React, { Component } from 'react'
import routes from './routes'
import { Route, Link, Redirect, Switch } from 'react-router-dom'

class App extends Component {
  render() {
    return (
      <div>    
        <Switch>
          {routes.map(({ path, exact, component: Component, ...rest }) => (
            <Route key={path} path={path} exact={exact} render={(props) => (
              <Component {...props} {...rest} />
            )} />
          ))}
          <Route render={(props) => <NoMatch {...props} /> } />
        </Switch>
      </div>
    )
  }
}

export default App

AboutMain.js

// Importing Required Modules
import React, {Component, Fragment} from 'react' // Importing React, Component, Fragment from "react"
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './about.scss'

class AboutMain extends Component {
  state = {
    phrase: 'We Made It!'
  }

  render() {
    return (
      <Fragment>
        <header className={s.banner}>
          <h1 className={s.heading}>{this.state.phrase}</h1>
        </header>
      </Fragment>
    )
  }
}

export default withStyles(s)(AboutMain) <-- Error Seems to occur here, at least I think.

about.scss

.banner {
  margin: 0 auto;
  padding: 15px;
  border: 2px solid #000;
}

.heading {
  text-transform: uppercase;
  text-decoration: underline;
}

2 ответа

Решение

После просмотра кода всю ночь и бесконечного поиска в гугле. Я нашел исправление, что основная проблема с моим кодом была в webpack.config.babel.js настроить.

Я изменил тест браузера для sass || Scss использовать style-loader, а не isomorphic-style-loader, Я также удалил все isomorphic-style-loader логика из моего приложения (т.е. withStyles, insertCss, так далее.)

Я не уверен, что это был правильный подход, но пока, похоже, это решает мою проблему и не возвращает никаких ошибок или проблем с сетью.

Проблема ушла просто потому, что вы удалили isomorphic-style-loader, Пожалуйста, не принимайте свой собственный ответ. Проблема в том, что вы не предоставили context так insertCss.apply(_context, styles) будет жаловаться, потому что _context является undefined, Чтобы решить проблему, выполните следующие действия.

  1. Создать ContextProvider компонент в том же каталоге App

ContextProvider.js

import React from 'react';
import PropTypes from 'prop-types'
import App from './App'

class ContextProvider extends React.Component {
    static childContextTypes = {
      insertCss: PropTypes.func,
    }

    getChildContext() {
      return { ...this.props.context }
    }

    render () {
      return <App { ...this.props } />
    }
  }

  export default ContextProvider
  1. Обернуть ContextProvider в ОБА ваш browser.js а также server.js, Не забудьте объявить context в обоих файлах.

browser.js (в других приложениях это будет код корневого клиента, т.е. client.js или же index.js)

// Import the neccessary modules for use in file
/* import statements */


const context = {
    insertCss: (...styles) => {
      const removeCss = styles.map(x => x._insertCss());
      return () => {
        removeCss.forEach(f => f());
      };
    },
  }

hydrate(
  <BrowserRouter>
    //ContextProvider wraps around and returns App so we can do this
    <ContextProvider context={context} />
  </BrowserRouter>,
  document.getElementById('app')
)

server.js

//Additional code above

app.get("*", (req, res, next) => {
  const activeRoute = routes.find((route) => matchPath(req.url, route)) || {}

  const promise = activeRoute.fetchInitialData
    ? activeRoute.fetchInitialData(req.path)
    : Promise.resolve()

  promise.then((data) => {
    const css = new Set()
    const context = { insertCss: (...styles) => 
styles.forEach(style => css.add(style._getCss()))}

    const markup = renderToString(
      <StaticRouter location={req.url}>
        <ContextProvider context={context}> 
         <App />
        </ContextProvider>
      </StaticRouter>
    )

    res.send(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>React on the Server!</title>
          <script src="/bundle.js" defer></script>
          <script>window.__INITIAL_DATA__ = ${serialize(data)}</script>
          <style type="text/css">${[...css].join('')}</style>
        </head>
        <body>
          <div id="app">${markup}</div>
        </body>
      </html>
    `)
  }).catch(next)
})

app.listen(port, () => console.log(`Server is listening on port: ${port}`))

Я написал статью, объясняющую это более подробно здесь: https://medium.com/@danielnmai/load-css-in-react-server-side-rendering-with-isomorphic-style-loader-848c8140a096

Другие вопросы по тегам