const insertCss = (...styles:any) => { const removeCss = styles.map((style:any) => style._insertCss()); return () => removeCss.forEach((dispose:any) => dispose()); }; ReactDOM.hydrate( <StyleContext.Provider value={{ insertCss }}> <AppError> <Component /> </AppError> </StyleContext.Provider>, elRoot );
这一部分主要是引入了 StyleContext 初始化根部的context,并且定义好一个 insertCss 方法,在组件 withStyle 中触发。
部分 isomorphic-style-loader 源码
... function WithStyles(props, context) { var _this; _this = _React$PureComponent.call(this, props, context) || this; _this.removeCss = context.insertCss.apply(context, styles); return _this; } var _proto = WithStyles.prototype; _proto.componentWillUnmount = function componentWillUnmount() { if (this.removeCss) { setTimeout(this.removeCss, 0); } }; _proto.render = function render() { return React.createElement(ComposedComponent, this.props); }; ...
可以看到 context 中的 insert 方法就是根组件中的 定义好的 insert 方法,并且在 componentWillUnmount 这个销毁的生命周期中把之前 style 清除掉。而 insert 方法主要是为了给当前的 style 定义好id并且嵌入,这里就不展开说明了,有兴趣的可以看一下源码。
服务端中获取定义好的css
const css = new Set(); // CSS for all rendered React components const insertCss = (...styles :any) => { return styles.forEach((style:any) => css.add(style._getCss())); }; const extractor = new ChunkExtractor({ statsFile: this.statsFile }); const jsx = extractor.collectChunks( <StyleContext.Provider value={{ insertCss }}> <Provider store={serverStore}> <StaticRouter location={url} context={routerContext}> <AppRoutes context={defaultContext} initialData={data} /> </StaticRouter> </Provider> </StyleContext.Provider> ); const html = ReactDOMServer.renderToString(jsx); const cssString = Array.from(css).join(''); ...
其中 cssString 就是我们最后获取到的 css 内容,我们可以像 html 替换一样把 css 嵌入到 html 中。
let ret = ` <!DOCTYPE html> <html lang="en"> <head> ... <style>${extra.cssString}</style> </head> <body> <div>${html}</div> ... </body> </html> `;
那这样就大功告成啦!!!!
我来说一下在做这个的时候遇到的坑
1.不能使用分离 css 的插件 mini-css-extract-plugin ,因为分离 css 和把 css 放置到 style 中会有冲突,引入github大神的一句话
With isomorphic-style-loader the idea was to always include css into js files but render into dom only critical css and also make this solution universal (works the same on client and server side). If you want to extract css into separate files you probably need to find another way how to generate critical css rather than use isomorphic-style-loader.
2.很多文章说到在 service 端的打包中不需要打包 css,那是因为他们使用的是style-loader 的情况,我们如果使用 isomorphic-style-loader, 我们也需要把 css 打包一下,因为我们在服务端中毕竟要触发 withStyle。
总结
因为代码太多了,所以只是展示了整个 SSR 流程的思想,详细代码可以查看。还有希望大牛们指导一下我的错误,万分感谢!!