isomorphic-style-loader探究

isomorphic-style-loader基础配置

{
    test: /.iso.scss/,
    use:[
        {
            loader: 'isomorphic-style-loader'
        },
        {
            loader: 'css-loader',
            options: {} // **自定义option
        }
    ]
},

也就是isomorphic-style-loader一定接收的是css-loader处理后的文件。

作用:

isomorphic-style-loader是webpack的loader,将css文件可以转化为style标签插入到html的header头中,实现server render的critical css的插入,提升页面的体验.

你肯定想起了之前很火的style-loader不是也是这种作用么?是的。isomorphic-style-loader是React版本,支持server side render. 其他的实现原理是一样的.

使用demo case github传送门

基础组件App

/* App.scss */
.root { padding: 10px }
.title { color: red }
/* App.js */
import React, { Component } from 'react'
import withStyles from 'isomorphic-style-loader/withStyles'
import s from './App.iso.scss'

@withStyles(s)
class App extends Component {
    render() {
        return (

Hello, world!

); } } export default App;

因为isomorphic-style-loader是将css文件转为js的方式进行加载的,天然支持了CSS Modules.

实现ssr,自然,首先在服务端,渲染出带有style标签的html

import express from 'express'
import React from 'react'
import ReactDOM from 'react-dom'
import StyleContext from 'isomorphic-style-loader/StyleContext'
import App from './App.js'

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

// Server-side rendering of the React app
server.get('*', (req, res, next) => {
  const css = new Set() // CSS for all rendered React components
  const insertCss = (...styles) => styles.forEach(style => css.add(style._getCss()))
  const body = ReactDOM.renderToString(
    
      
    
  )
  const html = `
    
      
        
        
      
      
        
${body}
` res.status(200).send(html) }) // xxx

它就会输出如下的html片段


  
    My Application
    
    
  
  
    

Hello, World!

然后在client端进行渲染hydrate

import React from 'react'
import ReactDOM from 'react-dom'
import StyleContext from 'isomorphic-style-loader/StyleContext'
import App from './App.js'

const insertCss = (...styles) => {
  const removeCss = styles.map(style => style._insertCss())
  return () => removeCss.forEach(dispose => dispose())
}

ReactDOM.hydrate(
  
    
  ,
  document.getElementById('root')
)

这样执行的结果是在client端每个引入的css文件会都以style标签的形式进行单独插入。

原理分析

我们一定好奇,上述代码中引用的style._getCss(),style._insertCss是哪里提供的,为什么这样使用.
somorphic-style-loader的源码就是你想要的答案

isomorphic-style-loader提供了5个文件.


isomorphic1.png

其中index.js毫无以为是webpack配置中的loader主文件,我们留在最后再进行分析,下面我们一次分析其他几个文件

StyleContext.js
import React from 'react'

const StyleContext = React.createContext({
  insertCss: null,
})

export default StyleContext

作用很简单,提供了一个Context,它的默认值是一个有一个insertCss方法的object.
看到这里,就很容易理解上文中引用的StyleContext.Provider,为什么要提供一个insertCss方法了.

withStyle.js
import React from 'react'
import hoistStatics from 'hoist-non-react-statics'

import StyleContext from './StyleContext'

function withStyles(...styles) {
  return function wrapWithStyles(ComposedComponent) {
    class WithStyles extends React.PureComponent {
      constructor(props, context) {
        super(props, context)
        this.removeCss = context.insertCss(...styles)
      }

      componentWillUnmount() {
        if (this.removeCss) {
          setTimeout(this.removeCss, 0)
        }
      }

      render() {
        return 
      }
    }

    const displayName = ComposedComponent.displayName || ComposedComponent.name || 'Component'

    WithStyles.displayName = `WithStyles(${displayName})`
    WithStyles.contextType = StyleContext
    WithStyles.ComposedComponent = ComposedComponent

    return hoistStatics(WithStyles, ComposedComponent)
  }
}

export default withStyles

可以看到WithStyle就是一个HOC,在child子组件的基础上,增加了context以及在constructor中就执行了context的insertCss方法.
不熟悉的同学,可以看一下react中的Context[图片上传失败...(image-ce3341-1573790756740)]

insertCss.js

这个文件就不详细分析了,就是做了一件事儿,根据传入的styles数组,插入到html的header标签中

useStyle.js

这个文件干的其实和withStyle.js一样的事儿,支持是支持react hook的函数式写法

index.js --- 重要
import { stringifyRequest } from 'loader-utils'

module.exports = function loader() {}
module.exports.pitch = function pitch(request) {
  if (this.cacheable) {
    this.cacheable()
  }

  const insertCss = require.resolve('./insertCss.js')
  return `
    var refs = 0;
    var css = require(${stringifyRequest(this, `!!${request}`)});
    var insertCss = require(${stringifyRequest(this, `!${insertCss}`)});
    var content = typeof css === 'string' ? [[module.id, css, '']] : css;

    exports = module.exports = css.locals || {};
    exports._getContent = function() { return content; };
    exports._getCss = function() { return '' + css; };
    exports._insertCss = function(options) { return insertCss(content, options) };

    // Hot Module Replacement
    // https://webpack.github.io/docs/hot-module-replacement
    // Only activated in browser context
    if (module.hot && typeof window !== 'undefined' && window.document) {
      var removeCss = function() {};
      module.hot.accept(${stringifyRequest(this, `!!${request}`)}, function() {
        css = require(${stringifyRequest(this, `!!${request}`)});
        content = typeof css === 'string' ? [[module.id, css, '']] : css;
        removeCss = insertCss(content, { replace: true });
      });
      module.hot.dispose(function() { removeCss(); });
    }
  `
}
  1. stringifyRequest方法,作用是将引用的绝对路径转化为相对路径.
    我本地项目的一个路径,request原始值为/Users/zhifeizhang/pinduoduo_proj/cart2/cart/node_modules/css-loader/index.js??ref--6-rules-1!/Users/zhifeizhang/pinduoduo_proj/cart2/cart/node_modules/sass-loader/lib/loader.js??ref--6-rules-3!/Users/zhifeizhang/pinduoduo_proj/cart2/cart/cart/promotion/page.scss 转化后变为 "!!../../node_modules/css-loader/index.js??ref--6-rules-1!../../node_modules/sass-loader/lib/loader.js??ref--6-rules-3!./page.scss"
  2. pitch方法. 我们知道webpack的loader的执行顺序,默认从右到左,但是在loader中有pitch方法的时候,其实是从左向右一次执行pitch方法的,也就是会先执行isomorphic-style-loader.另外,当pitch方法有返回值的时候,后面的loader是不会执行的。

那么,css-loader是怎么起作用的呢?

var css = require(${stringifyRequest(this, `!!${request}`)});

这个转化后的值,实际类似的是这样的, ``"!!../../node_modules/css-loader/index.js??ref--6-rules-1!../../node_modules/sass-loader/lib/loader.js??ref--6-rules-3!./page.scss"`也就是说通过这种方式直接调用的css-loader下的index.js文件进行编译的xxx.css

  1. _getCss以及_insertCss
    这里的_getCss就是将样式css进行require进来,然后我们再回头看看server端StyleContext传入的value insertCss方法,执行的就是对每一个style执行_getCss(),进行收集css set,然后在server端html里进行输出style,这样就理清楚了。

    _insertCss,同样这个方法其实执行的是style标签进行插入。这个方法,我们可以看到在client端指定的就是
const insertCss = (...styles) => {
  const removeCss = styles.map(style => style._insertCss())
  return () => removeCss.forEach(dispose => dispose())
}

至于返回值removeCss,可以看到是在componentWillUnmount中会执行,进行删除对应的style标签

参考材料

loader-utils
isomorphic-style-loader
webpack loader从上手到理解系列
webpack的pitching loader

你可能感兴趣的:(isomorphic-style-loader探究)