冲突 server/client 呈现和 Webpack 的本地 css 模块

Conflicting server/client rendering and Webpack’s local css modules

我正在使用 Fluxible 帮助在新项目上创建一个同构应用程序,并且进展顺利。到目前为止我喜欢它。不过,我 运行 遇到了减速带,想知道如何克服它。

到目前为止,这是我的 Header 组件:

import React from 'react'
import Nav from '../Nav/Nav'

import classNames from 'classnames'
if (process.env.BROWSER) var styles = require('./Header.css')

class Header extends React.Component {
    render() {

        // Header classes
        var theClasses = process.env.BROWSER ? classNames({
            [styles.Header]: true
        }) : ''

        return (
            <header className={theClasses}>
                <Nav selected={this.props.selected} links={this.props.links} />
            </header>
        )
    }
}

export default Header

您会看到我正在使用 process.env.BROWSER 来检测我使用的是哪个 ENV。如果我们在客户端,我需要 CSS。如果我们在服务器上,我会跳过它。效果很好。

问题稍后出现在文件中,我根据 Header.css 文件的内容构建 theClasses object,然后在 Header 像这样:

<header className={theClasses}>
    <Nav selected={this.props.selected} links={this.props.links} />
</header>

问题是因为我没有在服务器上加载 css,theClasses 最终为空,为客户端呈现的内容最终与服务器上的内容不同. React 显示此警告:

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:

(client) n28"><header class="Header--Header_ip_OK

(server) n28"><header class="" data-reactid=".2ei

你会推荐什么来解决这个问题?


更新 2015 年 9 月 24 日

最初的问题是我无法CSS在服务器端编译,所以我开始像这样检查浏览器:

if (process.env.BROWSER) var styles = require('./Application.css')

如果我删除 if (process.env.BROWSER) 位,我会收到此错误:

SyntaxError: src/components/Application/Application.css: Unexpected token (2:0)
  1 |
> 2 | @import 'styles/index.css';
    | ^
  3 |

在下面的简单 CSS 文件中:

@import 'styles/index.css';

.Application {
    box-shadow: 0 0 0 1px var(--medium-gray);
    box-sizing: border-box;
    lost-center: 1080px 32px;
}

我用 Fluxible Yo Generator which provides two Webpack config files here: https://github.com/yahoo/generator-fluxible/tree/master/app/templates

开始了这个项目

我用几个装载机更新了我的:

var webpack = require('webpack');
var path = require('path');

module.exports = {
    resolve: {
        extensions: ['', '.js', '.jsx']
    },
    entry: [
        'webpack-dev-server/client?http://localhost:3000',
        'webpack/hot/only-dev-server',
        './client.js'
    ],
    output: {
        path: path.resolve('./build/js'),
        publicPath: '/public/js/',
        filename: 'main.js'
    },
    module: {
        loaders: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                loaders: [
                    require.resolve('react-hot-loader'),
                    require.resolve('babel-loader')
                ]
            }, {
                test: /\.css$/,
                loader: 'style-loader!css-loader?modules&localIdentName=[name]__[local]_[hash:base64:5]!postcss-loader'
            }, {
                test: /\.(png|jpg|svg)$/,
                loader: 'url?limit=25000'
            }, {
                test: /\.json$/,
                loader: 'json-loader'
            }
        ]
    },
    postcss: function () {
        return [
            require('lost'),
            require('postcss-import')({
                path: ['./src/'],
                onImport: function (files) {
                    files.forEach(this.addDependency);
                }.bind(this)
            }),
            require('postcss-mixins'),
            require('postcss-custom-properties'),
            require('autoprefixer')({
                browsers: ['last 3 versions']
            })
        ];
    },
    node: {
        setImmediate: false
    },
    plugins: [
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify(process.env.NODE_ENV),
                BROWSER: JSON.stringify(true)
            }
        })
    ],
    devtool: 'eval'
};

这就是我所在的位置……不确定如何获得 CSS 已编译的服务器端。感谢我能得到的任何帮助。

您需要确保服务器生成与客户端相同的标记,这意味着您也需要在服务器上使用 CSS 模块。

我们在我当前的项目中也通过使用 Webpack 来编译我们的 Node 代码来实现这一点。 James Long 写了一篇关于如何设置的非常好的指南,分布在三篇博文中:

Backend Apps with Webpack (Part I)

Backend Apps with Webpack (Part II)

Backend Apps with Webpack (Part III)

您当然应该使用 类 渲染您的服务器端 HTML,否则您将错过享受同构应用程序特权的时间。这意味着您仍然需要等待 JS 加载才能应用 CSS 样式,这违背了构建 HTML 服务器端的某些目的。这也意味着您不能“cut the mustard”并为旧浏览器提供关闭 Javascript 的应用程序。

问题是为什么不在服务器上渲染 CSS 类?这也让我困惑了一段时间,但我猜你没有两个 Webpack 入口点?一个用于客户端,一个用于服务器。

如果我的假设是正确的,那么请查看同样使用 Fluxible 的沙箱存储库 here where I'm doing multiple builds for the Node server entrypoint and the Browser entrypoint

不过,我暂时对这一切持保留态度,因为它只是一个供个人使用的测试项目。我还在本地 css 中使用了这种方法,它在服务器和客户端上都构建了它,而且效果很好。

编辑: 我看到您正在使用 ES6,所以我假设您确实在构建服务器端?如果是这样,您不包含 CSS 的原因是什么?

你可以试试css-modules-require-hook

webpack.config.js

{
  test: /\.css$/,
  loader: 'style-loader!css-loader?modules&localIdentName=[name]__[local]___[hash:base64:5]'
}

server.js

require('css-modules-require-hook')({
  // This path should match the localIdentName in your webpack css-loader config.
  generateScopedName: '[name]__[local]___[hash:base64:5]'
})

所以你必须有两个 Webpack 配置,第一个用于编译浏览器包,第二个用于编译服务器包。

使用标准配置,您有 css-loader?...:

{
    test: /\.css$/,
    loader: 'style-loader!css-loader?modules&localIdentName=[name]__[local]_[hash:base64:5]'
}

您会得到两个不同的捆绑包。

在服务器上。所有 类 都在 css.locals 对象中:

let css = require('styles.scss');
css.locals == { fooClass: 'some_foo_class' }

在浏览器上没有 .locals:

let css = require('styles.scss');
css == { fooClass: 'some_foo_class' }

所以你需要在服务器版本中去掉 .locals,这样它应该像在浏览器中一样。

你可以在 webpack 服务器配置中使用 css-loader/locals?... 来做到这一点:

{
    test: /\.scss$/i,
    loader: "css/locals?modules&importLoaders=1&-minimize&localIdentName=[local]_[hash:3]!sass"
}

此处有更多详细信息https://github.com/webpack/css-loader/issues/59#issuecomment-109793167