# 正文

从我们开始搭建应用骨架到这里,这一路我们发现了很多问题,也解决了很多问题,这个收获是巨大的。但总感觉少点什么,期间完全没有涉及到 css ,一个完整的项目怎么能缺少 css 呢?

所以本节开始,我们来把 css 融合到我们的应用骨架里,达到可以给组件添加样式,美化页面的目的。

不就是支持 css 嘛,配置几个 loader 就完事了。

真的这么简单吗?

接下来,我们一步一步的实现应用骨架对 css 的支持。

# 安装所需 loader

npm i sass-loader style-loader postcss-loader css-loader autoprefixer

使用sass预编译,使用postcss-loader + autoprefixer 为选择器增加浏览器前缀

# 浏览器端 - loader 配置

加入 css 的相关loader配置,开发环境中使css打包进 jsstyle-loader会帮助我们将 css内联在页面内。

// webpack/webpack.dev.config.js
//webpack 配置文件
const path = require('path')
const resolvePath = (pathstr) => path.resolve(__dirname, pathstr);

module.exports = {
    mode: 'development',
    entry: resolvePath('../src/client/app/index.js'),//入口文件
    output: {
        filename: 'index.js',
        path: resolvePath('../dist/static')
    },
    module: {
        rules: [{
                test: /\.jsx?$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                + test: /\.(sa|sc|c)ss$/,
                + use: [
                    {
                        loader: "css-loader",
                    },
                    {
                        loader: "postcss-loader"
                    },
                    {
                        loader: "sass-loader"
                    }
            },
                {
                        test: /\.(png|jpg|gif)$/,
                        use: [{
                            loader: 'file-loader',
                            options: {
                                name: 'img/[name].[ext]'//配置图片的输出路径和名称
                            }
                }]
            ]
            },
        ]
    }
}

配置autoprefixer完成 css 前缀转换

在项目根目录创建postcss.config.js文件进行配置,也可以直接和 loader 写在一起,看个人习惯。

module.exports = {
    plugins: [require('autoprefixer')]  // 引用该插件
}

上面我们就完成了,针对浏览器端的 css 配置。

# 添加css测试代码

layout组件添加css,作为全局样式

// ./src/client/app/layout.css

body {
  background-color: #f4f5f5;
}

.layout-box {
  max-width: 750px;
  margin: 0 auto;
  text-align: center;
  background: #fff;
}

.layout-box h1 {
  margin-top: 20px;
  margin-top: 20px;
}

index组件添加css,作为业务css

// ./src/client/pages/index/index.scss

.page-index-box{
    width: 750px;
}
Index`组件导入 `css
// ./src/client/pages/index/index.js
//....
import './index.scss';
//....

结果运行npm run dev时,服务端代码打包失败

image-20210214214921254

由于组件会在双端构建,我们在组件内导入了 css,而服务端webpack配置文件没有配置相关的 css loader,所以服务端的代码打包失败了。

# 服务端处理

  • 暴力破解法

既然服务端无法处理css模块,而我们也不能给服务端配置添加相关的 css loader,否则css也会被打包进js

所以需要采取其他方法,这里有个取巧的方式,我们可以在服务端代码构建前干掉这行代码。

  • 如何删除这行导入?

方法有很多种,要么是借助工具,要么是自己写插件。

在这里我们自定义一个 babel plugin 来搞定。

  • 如何写 babel plugin

我们先增加一个目录,babel 下存放 pluginpreset

image-20210214214937167

创建一个 js 文件为插件文件,插件的名称为no-require-css

// ./webpack/babel/plugin/no-require-css.js

/**
 * 删除代码中导入的 css
 */
module.exports = function ({ types: babelTypes }) {
    return {
        name: "no-require-css",
        visitor: {
            ImportDeclaration(path, state) {
                let importFile = path.node.source.value;
                if(importFile.indexOf('.scss')>-1){
                    //如果引入了 css文件,则删除此节点
                    path.remove();
                }
            }
        }
    };
};
  • 配置插件

.babelrc内配置这个插件

{
    "env": {
        "node":{
            "presets": [
                [
                    "@babel/preset-env",
                    {
                        "targets":{
                            "node":"current"
                        }
                    }
                ],
                "@babel/preset-react"
            ],
            "plugins": [
                "@babel/plugin-proposal-class-properties",
                + "./webpack/babel/plugin/no-require-css"
            ]
        }
  • 查看结果

服务可以正常启动,css 已经内联到了head 内。

image-20210214214957102

# 页面抖动问题

我们已经实现了css的渲染,但是有些勉强,效果不够好,当页面刷新或者第一次进入的时候,页面会抖动。

image-20210214215012251

因为第一次进入是服务端直出的 html 结构,没有 css

css 是在客户端js 代码执行后动态插入到 head 内的,所以会出现抖动。

  • 如何解决这个问题呢?

我们可以采用传统的方式来解决,将所有的css模块 打包成一个文件,然后在服务端直出的时候带上它,作为资源文件加载。

例如:

<link rel="stylesheet" type="text/css" href="//s1.bigerfe.com/zz-static/css/styles.04403cf0.css">
  • 具体如何实现?

可能你会说,我们这里路过来问题有点太多了吧,这一个接一个的。

不用怕,慢慢来,不怕问题多,就怕没问题。

来吧,一起搞定他!

# css 合并

webpack4里需要使用mini-css-extract-plugin插件来将 css 进行合并。

看下具体配置

style-loader替换为

//...
  {
    loader: MiniCssExtractPlugin.loader,
  }
//...

设置plugin

  //...
  plugins: [
        new MiniCssExtractPlugin({
        filename: '[name].css' //设置名称
    })]

上面的[name]为资源的名称,会使用当前配置的entry的名称,所以我们调整下entry定义,增加入口main

//...
 entry:{
        main: resolvePath('../src/client/app/index.js'), //入口文件
    }
//...

同时将jsbundle 的名称改为占位

  output: {
        filename: '[name].js',
        path: resolvePath('../dist/static')
    }

# 服务端调整

经过上面的配置,我们已经将所有的 css打包到一个文件内。

image-20210214215029876

在服务端只需将main.css作为 link 直出即可。

// ./src/server/middlewares/react-ssr.js

//...
    ctx.body=`<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>${tdk.title}</title>
    <meta name="keywords" content="${tdk.keywords}" />
    <meta name="description" content="${tdk.description}" />
    + <link rel="stylesheet" type="text/css" href="/main.css" />
</head>

//...

启动服务,页面已正常显示。

看下具体效果

image-20210214215052352

# 小结

本节我们主要实现react ssr中的 css 的支持和处理。

客户端的处理,配置和我们以往单页开发中的配置没什么区别,主要是服务端方面的处理。

我们采用的方式比较直接,服务端构建前将导入的css模块代码移除,然后客户端配置将 css 提取到一个文件内,然后将 css作为link直出到浏览器端,解决了页面的抖动问题。

本节代码已上传

github.com/Bigerfe/koa… (opens new window)

阅读全文