从零开始构建react应用(六)同构之样式直出
前言上文讲到通过同构服务端渲染,可以直出html结构,虽然讲解了样式,图片等静态资源在服务端引入问题的解决方案,但是并没有实际进行相关操作,这篇文章就讲解一下如何让样式像html一样直出。
加入样式文件目前我们的项目中还不存在任何样式文件,所以需要先写一个,就给组件App写一个样式文件吧。 安装依赖下面这些依赖都是后续会用到的,先安装一下,下面会详细讲解每个依赖的作用。 npm install postcss-loader postcss-import postcss-cssnext postcss-nested postcss-functions css-loader style-loader isomorphic-style-loader --save-dev 创建.pcss文件css文件的后缀是.css,less文件的后缀是.less,这里我选择使用PostCSS配合其插件来写样式,所以我就自己定义一个后缀.pcss好了。 // ./src/client/component/app/style.pcss .root { color: red; } 设定一个root类,样式就是简单的设置颜色为红色。然后在App组件里引用它。 // ./src/client/component/app/index.tsx ... import * as styles from './style.pcss'; ... public render() { return ( <div className={styles.root}>hello world</div> ); } ... 这个时候你会发现编辑器里是这样的: // ./@types/index.d.ts declare module '*.pcss' { const content: any; export = content; } 保存之后就不会看到编辑器报错了,但是terminal里webpack打包会提示出错,因为我们还没有加对应的loader。 配置.pcss文件的解析规则js都组件化了,css模块化也是很有必要的,不用再为避免取重复类名而烦恼。我们在base配置里新导出一个方法用以获取postcss的规则。 // ./src/webpack/base.ts ... export const getPostCssRule = (styleLoader) => ({ test: /.pcss$/,use: [ styleLoader,{ loader: 'css-loader',options: { camelCase: true,importLoaders: 1,localIdentName: '[path][name]---[local]---[hash:base64:5]',modules: true,},{ loader: 'postcss-loader',options: { plugins: () => [ require('postcss-import')({ path: path.join(baseDir,'./src/client/style'),}),require('postcss-cssnext'),require('postcss-nested'),require('postcss-functions')({ functions: { x2(v,u) { return v * 2 + (u ? u : 'px'); },],}); ... 我们可以从上面这个方法看到,要处理 // ./src/webpack/client.ts ... (clientDevConfig.module as webpack.NewModule).rules.push( ... getPostCssRule({ loader: 'style-loader',... ); ... // ./src/webpack/server.ts ... (clientDevConfig.module as webpack.NewModule).rules.push( ... getPostCssRule({ loader: 'isomorphic-style-loader',... ); ... 客户端和服务端处理样式文件需要使用到不同的styleLoader。 PostCSS简介PostCSS是一个使用js来转换css的工具,这个是官方介绍。其配合webpack使用的loader就是postcss-loader,但是只有单个postcss-loader其实没有什么用,需要配合其插件来实现强大的功能。
讲这么多,写代码举个栗子吧~ // ./src/client/style/variables.pcss :root { --fontSizeValue: 16; } // ./src/client/style/index.pcss @import 'variables.pcss'; body { margin: 0; font-size: x2(var(--fontSizeValue)); } 引入我们刚写的index.pcss // ./src/client/index.tsx ... import './style/index.pcss'; ... CSS Modules简介简单来说就是css模块化,不用再担心全局类名的问题。我们根据上述css-loader的options来看:
isomorphic-style-loader在客户端,使用style-loader,它会动态的往dom里插入style元素,而服务端由于缺少客户端的相关对象及API,所以需要isomorphic-style-loader,目前用到它只是为了避免报错哈哈,后续还有大作用,样式直出全靠它。 打包运行注意:打包运行之前不要忘了给tsconfig.client.json和tsconfig.server.json引入我们的自定义模块定义文件index.d.ts,不然webpack编译就会报找不到pcss这种模块啦。 // ./src/webpack/tsconfig.client(server).json ... "include": [ ... "../../@types/**/*",... ] ... 运行结果如下: 直出样式我们利用isomorphic-style-loader来实现服务端直出样式,原理的话根据官方介绍就是利用了react的context api来实现,在服务端渲染的过程中,利用注入的insertCss方法和高阶组件(hoc high-order component)来获取样式代码。 安装依赖npm install prop-types --save-dev 改写App组件根据其官方介绍,我们在不使用其整合完毕的isomorphic router的情况下,需要写一个Provider给App组件: // ./src/client/component/app/provider.tsx import * as React from 'react'; import * as PropTypes from 'prop-types'; class AppProvider extends React.PureComponent<any,any> { public static propTypes = { context: PropTypes.object,}; public static defaultProps = { context: { insertCss: () => '',}; public static childContextTypes = { insertCss: PropTypes.func.isRequired,}; public getChildContext() { return this.props.context; } public render() { return this.props.children || null; } } export default AppProvider; 将原App组件里的具体内容迁移到AppContent组件里去: // ./src/client/component/app/content.tsx import * as React from 'react'; import * as styles from './style.pcss'; /* tslint:disable-next-line no-submodule-imports */ import withStyles from 'isomorphic-style-loader/lib/withStyles'; @withStyles(styles) class AppContent extends React.PureComponent { public render() { return ( <div className={styles.root}>hello world</div> ); } } export default AppContent; 新的App组件: // ./src/client/component/app/index.tsx import * as React from 'react'; import AppProvider from './provider'; import AppContent from './content'; class App extends React.PureComponent { public render() { return ( <AppProvider> <AppContent /> </AppProvider> ); } } export default App;
|