书接上回,在上回的笔记中,已经完成了react的一个最简单的实践,但是这个实践还有很多问题,这次我们来解决一下这些问题。
在这回我们主要解决项目在开发时的热更新问题和css的加载问题,顺便把这个项目目录改一改,让他改的像个正经的项目,而不是用于验证可行性的玩具。
开发中的热更新
在做react的开发的时候,大家肯定非常熟悉webpack-hot-server给我们提供的热更新,他启动了一个node服务,在本地的某个端口可以访问到我们开发的页面,并提供一个监控,在我们的文件发生变动之后,自动打包编译并展示新的内容在相应的端口上。
但是换到我们做SSR的时候,这一套就不能使用了,我们需要用新的一套解决方案。
解决方案分客户端和服务端,毕竟我们其实是开启了两个服务,只不过服务端的热更新我们是用nodemon来实现的
那么nodemon又是个什么东西呢?
nodemon就是一个用来替代node的包,不需要任何的配置(当然可以配置),就可以监控项目文件的变化,并在文件发生改变的时候,自动的重启服务。
是不是感觉特别智能,使用前首先要下载,因为这个东西在所有的项目中都可以用,所以推荐直接全局安装
npm install nodemon -g
然后把通常启动命令由
node [你需要启动的服务入口]
改成
nodemon [你需要启动的服务入口]
然后你会发现当我们改变客户端的时候,这个服务依旧被重启了,但是并没有发生变化,这是为什么呢?
因为我们之前走的方式首先服务端读取react的组件,然后输出字符串组装成页面返回给浏览器,然后浏览器读取对打包出来的main.js然后渲染,虽然在重启后我们已经更新了对应的react组件输出的字符串,但是我们并没有改变mian.js这个东西,他仍然会渲染之前的内容。
下面我们是否应该搞客户端的热更新呢?并不是,我们要把nodemon监控的范围圈定在我们的服务端,避开客户端,不然我们每次修改客户端,迎来的都不是热更新,而是服务的重启,虽然并不耽误热更新效果,但是随着项目的变大,重启的时候是越来越长的,我们肯定不能接受改一个css整个项目直接重启了。
当然随着项目的变大,nodemon效率也会越来越低,到时候我们再更换真正的服务端热更新,优化这个东西,最好是在有需求的时候再搞。
好了,说一下nodemon的配置,他的配置其实也很简单,在项目的根目录下新建一个配置文件
mkdir nodemon.json
在文件里写入
{
"ignore": ["app/"]
}
就可以忽略app文件夹内的所有文件了,后续我们重新整理项目的时候这里也要做相应的改变。
上面的都折腾完了,我们可以开始折腾我们的客户端热更新了,目前常用的解决方案大概是webpack-dev-middleware+webpack-hot-middleware来搞定客户端的热更新
首先我们下载两个依赖
npm install webpack-dev-middleware webpack-hot-middleware --save-dev
然后在我们的server.js文件中做出一些改变
我们要引入一些东西
import webpack from 'webpack';
import webpackConfig from './webpack.config'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'
其中weboack.config就是我们的webpack配置文件
然后添加
let compiler = webpack(webpackConfig)
server.use(webpackDevMiddleware(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
}))
server.use(webpackHotMiddleware(compiler));
这里注意到我们使用了webpackConfig.output.publicPath。但是我们好像并没有设置这个地址,这个地址可以理解为内存中的打包文件的访问入口,因为热更新的时候文件并不会真是输出在文件夹内,而是存放在内存中。然后我们去更改我们的webpack配置文件。
output: {
filename: '[name].js',
path: path.join(__dirname, 'dist'),
publicPath: '/dist'
}
然后更改我们的模板文件template.js文件中的js文件地址,更改为/dist/main.js。
好了,现在运行
npm run start
来看看效果吧,更爱hello.js文件的内容看看会不会更新。
下面我们来搞一搞css的loader,这个就不多说了,网上教程一大把。
emmm,直到开始配置css-loader之前我都是这么以为的。然而事实我头疼了两天,一直在报错,始终无法运行css-loader。一直说css-loader找不到@babel/preset-env。
然后我下载这个依赖,依旧如此。
我还没有放弃,把@babel/preset-env下载的全局,还是没有用。最后参考了别人的依赖目录,发现了一个问题,没有人的css-loader的版本是大于2.0的,然后去官网看了看,发现2.0版本更改了很多依赖。
2.0版本他对于babel的依赖全面转到了新的@babel/core中,我懒得重新配一套babel了,就直接把css-loader降级,完美解决。
简单说一下一个最简单的less的实例吧。
首先我们在app文件夹内建立一个home.less的文件,然后填写一些内容比如
.hello{
font-size:20px;
}
在我们引入这个文件 并给div一个类名
import Hello from './hello.js'
import ReactDOM from 'react-dom'
import React from 'react'
import './home.less';
ReactDOM.hydrate(<Hello />, document.getElementById('root'))
import React from 'react'
export default class Hello extends React.Component {
render () {
return <div onClick={this.click} className='hello'>hello worssdsdwldsas</div>
}
}
less的引入最好是在最外层,这样就不用在每个模块都引入一次样式文件了,并且因为我们的服务端渲染的字符串是直接读取模块信息的,但是目前并没有给他对应的css解析能力,只有js的解析能力。
如果我们把less文件引入在hello模块中,大家可以试着看一下效果。
我们再去webpack里面配置一下css-loader
配置前我们还是要去下载对应的依赖
npm install less less-loader css-loaser
我们只需要在webpack的rule中添加以下代码
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
},
我是不打算在项目中写css和sass,所以我只做了less的解析。
好了这些都搞完了,我们的css应该可以实时的更新了,但是现在的更新是css都写在mian.js里面的,但是在实际的项目中,我们通常不会这么做,因为浏览器的规则规定,js必须全部下载完才会执行,而js通常会放到整个页面的最后面,所以在项目巨大的时候,这样的状态会让页面出现光秃秃的html的情况,所以我们要把他们区分出来。让js归js,css归css。但是这样并不影响我们开发,那么我们既要让开发和生产分离了。
开发生产分离
刚刚写热更新的我们先写js,分离的时候我们反过来,先搞css,因为css相对于js是更看重分离的。
既然搞分离,那么我们最早要搞的其实应该是webpack。
那么webpack应该怎么分离呢,首先看我们之前的packjson.json里的内容,scripts里的内容就是我们平时执行的脚本,我们分离出来start和build。
"scripts": {
"start": "nodemon index.js",
"build": "webpack",
},
这是我们之前的启动脚本,启动服务端时候回自动启动webpack的热更新,但是这个功能在生产环境并没有用,所以我们要使用环境变量区分出来生产环境还开发环境。
这里我们使用的是cross-env,如果不使用这个,在mac环境下,和linux环境下也没什么问题,但是win下可能就会无法识别环境变量,我们并不能要求所有人都统一开发环境,所以最好做一下兼容。
npm i cross-env --save-dev
然后将启动脚本修改为
"start": "cross-env NODE_ENV=development nodemon index.js",
在我们所有的文件中都可以通过
process.env.NODE_ENV
来访问这个变量,对应的,我们也可以设置一个脚本启动生产环境。
"startprod": "cross-env NODE_ENV=project node index.js",
这里用node启动,因为我们有其他的东西守护进程。
说完了服务端,我们再来看客户端,客户端在开发环境下是不需要我们自己去启动的,我们需要的是生产环境的打包
之前的脚本是
"build": "webpack",
他只执行了webpack,并没有指定文件,所以他回去搜索默认的文件webpack.config.js,来启动webpack。但是我们并不想用他,并且我们要把webpack也分离出来。
因为生产环境的webpack和开发环境的webpack其实在很多地方都是相同,我们需要把相同的东西分离出来,这样我们在改两者都可以用到的东西的时候只需要改一处,不会出现改了一个忘了另一个的问题,不然这种问题一旦爆发,就是爆发在生产环境了。
既然分离webpack,那就感觉单独建立一个文件夹用来放置设置项的东西,所以我们新建一个config的文件,并在文件夹里新建三个文件。
mkdir config
touch webpack.base.js webpack.prod.js webpack.dev.js
说道分离,我们就要确认那些东西是通用,那些是特有的,目前似乎除了css都是通用的,所以我们分离
var path = require('path');
const WebpackBase = {
mode: 'production',
entry: {
main: './app/index.js'
},
output: {
filename: '[name].js',
path: path.join(__dirname, '../dist'),
publicPath: '/dist'
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.jsx?$/,
loaders: ['babel-loader'],
},
]
},
}
module.exports = WebpackBase;
对于css来说,我们的开发可以不用变,生产环境用extract-text-webpack-plugin来分离css
npm i extract-text-webpack-plugin --save-dev
然后在webpack.prod.js中引入
var ExtractTextPlugin = require('extract-text-webpack-plugin');
并把这个文件中的less规则替换为
{
test:/\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader',
'less-loader'
]
})
},
然后在这一个文件的plugins中添加一个插件
new ExtractTextPlugin('main.css')
这个mian.css就是你输出的css文件了,好了上面就是关于css配置改,下面我们接着说我们的webpack分离后的文件应该是什么样子。
因为我们webpack分离了一下,在使用的时候我们还要合并他们
npm install webpack-merge --save-dev
所以对应的两个文件分别为
var base = require('./webpack.base');
var merge = require('webpack-merge');
const webpackConfig = merge(base,{
module: {
rules: [
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
},
]
}
})
module.exports = webpackConfig
var path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var base = require('./webpack.base');
var merge = require('webpack-merge');
const webpackConfig = merge(base,{
module: {
rules: [
{
test:/\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader',
'less-loader'
]
})
},
]
},
plugins:[
require('autoprefixer'),
new ExtractTextPlugin('main.css')
]
})
module.exports = webpackConfig
说道这里我们webpack的分离就写完了,但是记得把我们服务加载的webpack地址更改到相应的地址。还有脚本,不要拿忘记我们为什么分离他。
最后的脚本改成
"build": "cross-env NODE_ENV=project webpack --config config/webpack.prod.js",
设置他的NODE_ENV变量为project,并使用config文件夹下的webpack.prod.js作为配置文件启动webpack打包。
笔记第二章到此位置,这一章里面我们实现了开发阶段的热更新,方便了我们开发,下一章我们就要研究研究react服务端渲染的路由问题了。
哦对了,还有一些小修改我并没有在这里展示出来,因为不太重要,或者是我在写的时候忘记了,可以对照着我github看,里面每一章都会有对应的分支。