如何使用 Webpack 将样式加载到 React 服务器端并返回到样式加载器客户端?

How to load styles onto React server-side using Webpack and go back to style-loader client-side?

我的目标是将 CSS 渲染成类似于服务器端的样式标签,或者与 style-loader 插件在客户端的渲染方式完全相同。我知道这是不可能的,因为 style-loader 直接写入 DOM 而 DOM 不存在于 Node.js.

目前我正在使用 ExtractTextPlugin,但如果我没有将所有 CSS 编译成一个大文件,它会在页面加载时丢失一些样式,除非我是 运行 服务器上的 Webpack 而不是直接编译它。

我得到了渲染页面的代码:

server.jsx

const renderedContent = renderToString(
    <Provider store={store} history={history}>
        <RoutingContext {...renderProps} />
    </Provider>
)

const finalState = store.getState()
const renderedPage = renderFullPage(renderedContent, finalState)

完全渲染-page.jsx

module.exports = function renderFullPage(renderedContent = undefined, state = {}) {
    return '<!DOCTYPE html>' + renderToStaticMarkup(
        <html lang="en">
        <head>
            <meta charSet="utf-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            {typeof __production !== 'undefined' && __production === true && <link id="css" rel="stylesheet" href="/main.css" />}
        </head>
        <body>
            <div id="root"><div dangerouslySetInnerHTML={{__html: renderedContent}}></div></div>
            <script dangerouslySetInnerHTML={{__html: 'window.__INITIAL_STATE__ =' + JSON.stringify(state)}} />
            <script src="/bundle.js"></script>
        </body>
        </html>
    )
}

当我在我的 React 模块中需要它们时,我正在全局导入样式,如下所示:

import './../../assets/styl/flex-video'

而且我想改变我加载 CSS 的方式,将所有 CSS 放入一个 var 我可以循环并以相同的方式输出 <style> 标签 style-loader 是这样吗:

{typeof __production !== 'undefined' && __production === true && cssFiles.map((styles) => {
    <style>{styles}</style>
})}

这在 Webpack 中可行吗?是否有像 isomorphic-style-loader 这样的插件可以做到这一点?如果是这样,我将如何修改我的代码?

这是您想要使用 isomorphic-style-loader 的示例。

首先,您创建一个样式助手来自动将样式推送到 DOM 或将它们加载到稍后将在服务器上呈现的 var。

这是我创建的样式助手示例,经过修改后可以接受一组样式文件(CSS、Sass、LESS、Stylus):

utilities/style-helper.jsx

import React, { Component } from 'react'
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'

const css = []

const collectOrRender = function(styles) {
    let renderedCollection = []

    for (let i = 0, l = styles.length; i < l; i++) {
        const stylesFile = styles[i]

        if (canUseDOM) {
            renderedCollection[stylesFile._insertCss()]
        }

        css.push(stylesFile._getCss())
    }

    return renderedCollection
}

export function styleHelper(ComposedComponent, styles) {
    return class Styles extends Component {
        componentWillMount() {
            this.styleRemovers = collectOrRender(styles)
        }

        componentWillUnmount() {
            setTimeout(() => {
                for (let i = 0, l = this.styleRemovers.length; i < l; i++) {
                    let styleRemover = this.styleRemovers[i]
                    typeof styleRemover === 'function' && styleRemover()
                }
            }, 0)
        }

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

export function renderStyles() {
    return css.join('')
}

然后你必须获取你的 Style Helper 并让它在加载时包装你的模块。

此示例展示了如何在使用 react-redux 中的 connect() 时执行此操作。

我的-module.jsx

// Utilities
import { styleHelper } from 'utilities/style-helper'

const styles = [
    require('normalize.css'),
    require('styl/global'),
    require('styl/site'),
    require('styl/bubble')
]

class myModule extends Component {
    render() { return (
        <div />
    )}
}

export default connect(
    state => ({ menuIsOpen: state.collapsibleMenu.menuIsOpen })
)(styleHelper(myModule, styles))

处理客户端上的加载。现在在服务器上,您将所有这些样式存储到一个数组中。您需要将该数组拉入您的 HTML 页面并将其放置在一些 <style> 标签之间。

这是我如何做到的示例:

完全渲染-page.jsx

import React from 'react'
import { renderToStaticMarkup } from 'react-dom/server'

// Utilities
import { renderStyles } from './style-helper'

module.exports = function renderFullPage(renderedContent = undefined, state = {}) {
    return '<!DOCTYPE html>' + renderToStaticMarkup(
        <html lang="en">
        <head>

            {/* Critical Styles */}
            <style dangerouslySetInnerHTML={{__html: renderStyles()}} />
        </head>
        <body>
            <div id="root" dangerouslySetInnerHTML={{__html: renderedContent}}></div>
            <script dangerouslySetInnerHTML={{__html: 'window.__INITIAL_STATE__ =' + JSON.stringify(state)}} />
            <script src="/bundle.js"></script>
        </body>
        </html>
    )
}

注意 style 标签如何从 Style Helper 获取样式并将它们放入 <style> 标签中。当反应加载时,它会弄清楚要做什么,但现在服务器将只需要CSS来正确显示页面。