为什么isomorphic-style-loader会抛出TypeError:无法读取属性' apply'与CSS模块

时间:2018-05-22 20:21:11

标签: javascript reactjs express webpack isomorphic-style-loader

我目前正在尝试在服务器上呈现应用程序,该服务器适用于HTML和JS,但发现我的样式(.less | .scss)不会加载。我做了一些研究,并且不确定,我在Webpack配置中错过了isomorphic-style-loader,基于遇到同样问题的其他人。我设置了它,至少我理解它,但现在发现在运行应用程序时我收到以下错误:

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

我对整个React / Express事情有些新意,但是我一直在努力跟进教程和学习,如果有什么不合适的地方,请原谅。我希望看看是否有人可以解释究竟是什么导致了这个错误,并让我知道我可以遵循什么来解决这个错误。下面是一些示例代码,类似于我遇到的问题,如果它有任何帮助。

(作为参考,我正在关注Tyler McGinnis React路由器服务器渲染教程并尝试对其进行扩展以添加样式 - Link Here

预先感谢您提供有关可能导致此错误的原因的任何解释。

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 个答案:

答案 0 :(得分:3)

问题仅仅因为您删除了isomorphic-style-loader而消失了。请不要接受您自己的回答。这里的问题是您没有提供context,所以insertCss.apply(_context, styles)会抱怨,因为_contextundefined。要解决此问题,请按照下列步骤操作:

  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. ContextProviderbrowser.jsserver.js分别包裹在两者中。请记住在两个文件中都声明context

browser.js (在其他应用中,这将是根客户端代码,即client.jsindex.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

答案 1 :(得分:-1)

整夜检查代码并无休止地搜索Google。我找到了一个修复程序,我的代码的主要问题是webpack.config.babel.js设置。

我更改了sass ||的浏览器测试scss使用style-loader,而不是isomorphic-style-loader。我还从我的应用中移除了所有isomorphic-style-loader逻辑(即。withStylesinsertCss等。)

我不确定这是否是正确的方法,但与此同时,它似乎解决了我的问题并且没有返回任何错误或网络问题。