美文网首页
isomorphic-style-loader探究

isomorphic-style-loader探究

作者: 前端大飞 | 来源:发表于2019-11-15 12:06 被阅读0次

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的插入,提升页面的体验.</br>
你肯定想起了之前很火的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 (<div className={s.root}>
            <h1 className={s.title}>Hello, world!</h1>
        </div>);
    }
}

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(
    <StyleContext.Provider value={{ insertCss }}>
      <App />
    </StyleContext.Provider>
  )
  const html = `<!doctype html>
    <html>
      <head>
        <script src="client.js" defer></script>
        <style>${[...css].join('')}</style>
      </head>
      <body>
        <div id="root">${body}</div>
      </body>
    </html>`
  res.status(200).send(html)
})

// xxx

它就会输出如下的html片段

<html>
  <head>
    <title>My Application</title>
    <script async src="/client.js"></script>
    <style type="text/css">
      .App_root_Hi8 { padding: 10px }
      .App_title_e9Q { color: red }
    </style>
  </head>
  <body>
    <div id="root">
      <div class="App_root_Hi8">
        <h1 class="App_title_e9Q">Hello, World!</h1>
      </div>
    </div>
  </body>
</html>

然后在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(
  <StyleContext.Provider value={{ insertCss }}>
    <App />
  </StyleContext.Provider>,
  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 <ComposedComponent {...this.props} />
      }
    }

    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,这样就理清楚了。</br>
    _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探究

    isomorphic-style-loader基础配置 也就是isomorphic-style-loader一定接...

  • NSRunLoop

    深入理解RunLoop RunLoop深度探究(一) RunLoop深度探究(二) RunLoop深度探究(三) ...

  • 《骆驼祥子》名著导读(1)

    导读目标: 1.探究主人公的主要经历; 2.分析人物形象; 3.探究人物命运的根源。 4.探究小说主题。 一、探究...

  • isomorphic-style-loader在前后端渲染样式同

    前言 在笔者的上一篇文章(基于react的前后端渲染实例讲解)中,对react下前后端渲染的流程进行了一个介绍。对...

  • “课程的逻辑”观后感二十一

    课程的逻辑这本书强调学生的自主探究,那么什么是自主探究,什么是探究性学习?根据书中所说,探究性学习是这样一...

  • 探究学习与理科教学—课程的逻辑第9章学习笔记

    本章旨在探讨“探究学习”的来龙去脉,梳理“探究学习”理论的发展背景、主要内涵及其教育价值。 一、探究学...

  • 探究

    光明不是光明 黑暗不是黑暗 只有跨出的每一步试探 才能找出答案 黑夜的眼睛从不说谎 坚定的步伐从不迷惘 黎明放出曙...

  • 探究

    凌晨两点多了,一夜兴奋,起初还有点困,外面施工,舍友进入打鼾时分,失眠在所难免。 其实眼里是冒星星了。 这时候的舍...

  • 探究

    就像在炒一道菜,材料已经准备好,但还差一味调料,我就非要等着把调料弄齐,再下锅做菜。 好多时候,我的这种性格因子在...

  • 探究

    三十七岁,拒绝称自己是中年,心底已绝望

网友评论

      本文标题:isomorphic-style-loader探究

      本文链接:https://www.haomeiwen.com/subject/jbwjictx.html