webpack 配置react脚手架(二):热更新
下面继续配置 webpack dev server hot module replacement:
首先配置dev-server 安装 npm i webpack-dev-server -D
const isDev = process.env.NODE_ENV === 'development' const config = {
entry:{},
output:{},
plugins:{}
} if(isDev){
config.devServer = {
host: '0.0.0.0', //设置 0.0.0.0 的host 可以访问 localhost,127.0.0.1,本季ip来访问
contentBase: path.join(__dirname, '../dist'), //因为,devserver是服务打包后的文件,所以和output的path 保持一致即可
port: '8888',
hot: true,
overlay: {
errors: true //server有任何的错误,则在网页中 蒙层加提示
}
}
} module.exports = config;
修改json文件: "dev:client": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.client.js",
其中 cross-env 是兼容了win mac linux的NODE_ENV,也是一个安装包: npm i cross-env -D
然后 npm run dev:cient 即可以启动服务 localhost:8888;
发现 app.js是无法获取到的,其路径为: http://localhost:8888/public/app.js 可以看出是多了一层 public;
根据server的配置项:contentBase 是把dev-server放在了dist目录下,开启的服务器。则 locahost:8888 相当于 dist目录,而之前output配置的输出文件前面是有路径 public的,所以 dev-server也需要增加这个配置:
if(isDev){
config.devServer = {
host: '0.0.0.0', //设置 0.0.0.0 的host 可以访问 localhost,127.0.0.1,本季ip来访问
contentBase: path.join(__dirname, '../dist'), //因为,devserver是服务打包后的文件,所以和output的path 保持一致即可
port: '8888',
hot: true,
overlay: {
errors: true //server有任何的错误,则在网页中 蒙层加提示
},
publicPath: '/public/', //增加公共路径,对应着output的 publicPath
historyApiFallback: {
index: '/public/index.html' //这里是给本地服务器增加功能:因为是单页面应用,如果刷新页面,或者访问不到路由,则跳转到首页
},
}
}
注意:一定要把打包生成的dist目录删除掉,在执行 npm run dev:client 这时因为,服务器会先检测本地磁盘是否有dist目录,如果有就会调取这里面的文件!!
===================华丽的分割线
接下来配置 热更新 hot,
Q:webpack-dev-server 已经是热加载,为何还要在 react 项目还要安装 react-hot-loader 呢?
A:其实这两者的更新是有区别的,webpack-dev-server 的热加载是开发人员修改了代码,代码经过打包,重新刷新了整个页面。而 react-hot-loader 不会刷新整个页面,它只替换了修改的代码,做到了页面的局部刷新。但它需要依赖 webpack 的 HotModuleReplacement 热加载插件 (参考文章: react使用react-hot-loader实现局部热更新)
首先在 .babelrc 文件中增加 对react hot更新的配置:
{
"presets": [
["es2015", { "loose": true }],
"react"
],
"plugins": [ "react-hot-loader/babel"] //使用babel的情况下,添加 react-hot-loader,支持react 热更新
}
安装包: npm i react-loader@next -D //教程中这里是最新的版本,尚未正式版,开发的时候注意版本
修改app.js 入口文件:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.jsx' ReactDOM.hydrate(<App />,document.getElementById('root')); if (module.hot) {
module.hot.accept('./App.jsx', () => {
const NextApp = require('./App.jsx').default
ReactDOM.hydrate(<NextApp />,document.getElementById('root'))
})
} // module.hot 监听到 app.jsx发生变化之后,重新获取 app.jsx 为NextApp 然后重新渲染;
修改package.js文件:
const webpack = require('webpack'); //因为用到了webpack下的包 HotModuleReplacementPlugin
const config ={ }
if(isDev){
config.entry=[
'react-hot-loader/patch', //入口文件中要把 hot 打包进去
path.join(__dirname,'../client/app.js')
],
config.devServer = {
host: '0.0.0.0',
contentBase: path.join(__dirname, '../dist'),
port: '8888',
hot: true, //打开这里
overlay: {
errors: true
},
publicPath: '/public/',
historyApiFallback: {
index: '/public/index.html'
}
}
config.plugins.push(new webpack.HotModuleReplacementPlugin) //增加了这里
}
最后还要返回来在 app.js 入口文件中配置:
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './App.jsx' ReactDOM.hydrate(<App />,document.getElementById('root')); const root = document.getElementById('root');
const render = Component => {
ReactDOM.hydrate(
<AppContainer>
<Component/>
</AppContainer>,
root
)
} render(App);
if (module.hot) {
module.hot.accept('./App.jsx', () => {
const NextApp = require('./App.jsx').default
render(NextApp);
})
}
这样才能热更新!
=================================服务端更新配置
上面书写了客户端的热更新,并且热更新的文件都存在内存中,所以服务端不能再从 dist文件夹下获取依赖的 js和 html文件,因此,服务端的js文件也需要区分是否是dev模式:
const express = require('express')
const ReactSSR = require('react-dom/server');
const fs = require('fs')
const path = require('path')
const app = express(); const isDev = process.env.NODE_ENV === 'development'; //在这里定义
if(!isDev){
const serverEntry = require('../dist/server-entry').default;//引入的是服务端的配置打包后的js文件
const template = fs.readFileSync(path.join(__dirname, '../dist/index.html'), 'utf8')//同步引入客户端打包生成的 html 文件,如果不使用 utf8 则是buffer文件
app.use('/public', express.static(path.join(__dirname, '../dist'))); //给静态文件指定返回内容,这里给piblic文件夹下的内容返回的是静态文件的dist文件夹
app.get('*', function (req, res) {
const appString = ReactSSR.renderToString(serverEntry);
res.send(template.replace('<!--app-->',appString)) //用返回的js文件替换掉模板中的<app>,然后发送的是模板文件
})
}else{
//util 文件夹下的 dev.static.js
const devStatic = require('./util/dev.static.js');
devStatic(app); //之所以这里把 app 传递进去,是因为app是 express(),我们可以在新建的文件中继续使用 app.get、app.send 等函数
} app.listen(3333, function () {
console.log('server is listening on 3333')
})
根据以上代码可知,把原来的从dist目录下获取文件的代码放在了 不是 dev模式下了,而dev模式下我们放在了 util/dev.static.js 文件下。
根据if else可以看出,在文件 dev.static.js 文件中我们要做的事情是:把静态文件js和模版从内存中提取出来,交给app.get请求 然后 send 出去。
接下来编辑 dev.static.js 文件,首先安装 npm i axios -S
步骤一: 获取内存中的模板html文件
const axios = require('axios');// 在浏览器端和服务器端都可以使用 axios /*在这里从内存中获取模版html,因为每次dev-server启动的是本地的服务,url是固定的;
这样可以根据 dev-server 实时的拿到最新的 模板文件
*/
const getTemplate = () => {
return new Promise((resolve,reject)=>{
axios.get('http://localhost:8888/public/index.html')
.then(res => {
resolve(res.data); //返回的内容放在了 data中
})
.catch(reject)
})
} module.exports = function (app) {
app.get("*",function(req,res){ })
}
步骤二:获取。server-entry.js等bundle文件
const axios = require('axios');
//从内存中获取 js等bundle文件,启动webpack,通过webpack打包的结果,获取bundle文件。
const webpack = require('webpack');
//通过 config.server.js 文件 获取 输出文件路径等信息
const serverConfig = require('../../build/webpack.config.server.js'); const getTemplate = () => {
return new Promise((resolve,reject)=>{
axios.get('http://localhost:8888/public/index.html')
.then(res => {
resolve(res.data);
})
.catch(reject)
})
} // 通过webpack的watch方法,监听配置文件中的 entry 入口文件(及其依赖的文件)是否发生变化,一旦变化,就会重新打包(类似于热更新)
const serverCompiler = webpack(serverConfig);
serverCompiler.watch({},(err,status)=>{//status 在终端上显示的信息
if(err) throw;
let stats = status.toJson();
stats.error.forEach(err => console.log(err));
stats.waring.forEach(warn => console.warn(warn)); const bundlePath = path.join(
serverConfig.output.path,
serverConfig.output.filename
);// 获取输出文件的路径
}) module.exports = function (app) {
app.get("*",function(req,res){ })
}
获取到 生成的 文件名字之后需要在 内存中读取 文件:
要使用 memory-fs,所以要安装 npm i memory-fs -D;
const axios = require('axios');
const path = require('path');
const webpack = require('webpack');
const serverConfig = require('../../build/webpack.config.server.js');
// 要使用 memory-fs,所以要安装 npm i memory-fs -D;
// 在内存中读写文件,这样就可以从内存中读取 获取到的文件
const MemoryFs = require('memory-fs'); //最后要把得到的js文件,渲染到dom上去,所以要用到
const ReactDomServer = require('react-dom/server'); const getTemplate = () => {
return new Promise((resolve,reject)=>{
axios.get('http://localhost:8888/public/index.html')
.then(res => {
resolve(res.data);
})
.catch(reject)
})
} //通过module的 constructor 构造方法去创建一个新的 module
const Module = module.constructior let serverBundle;
const mfs = new MemoryFs;//new 一个 对象;
const serverCompiler = webpack(serverConfig);
serverCompiler.outputFileSystem = mfs; //webpack 提供的配置项,其输出通过mfs内存读写,这里如果写错名字就会写到硬盘中
serverCompiler.watch({},(err,status)=>{
if(err) throw;
let stats = status.toJson();
stats.error.forEach(err => console.log(err));
stats.waring.forEach(warn => console.warn(warn)); const bundlePath = path.join(
serverConfig.output.path,
serverConfig.output.filename
);// 获取输出文件的路径
//通过 mfs 读取文件的路径,就可以得到文件,是 string 类型的文件,无法直接使用
const bundle = mfs.readFileSync(bundlePath,'utf-8'); //需要传入 编码格式
const m = new Module();
//动态编译成一个文件,需要给这个文件指定文件名字,否则无法在缓存中进行缓存,下次则拿不到该文件
m._compile(bundle,'server-entry.js');//使用module的_compile方法将String的文件,生成一个新的 模块,转换成了真正可以读取的文件
/*为了在后面的 app.get方法中使用。将生成的文件赋值给全局变量;
此外,因为是在 watch中执行的,每次依赖的文件更新,输出的文件也会更新*/
serverBundle = m.exports.default;
}) module.exports = function (app) {
app.get("*",function(req,res){
getTemplate().then(template => {
const content = ReactDomServer.renderToString(serverBundle);
res.send(template.replace('<!--app-->',content))
})
})
}
最后在package.json 中定义命令:
{
"script":{
"dev:server":"cross-env NODE_ENV = development node server/sever.js"
} }
启动客户端和服务器端:npm run dev:client npm run dev:server;
发现无论是js还是html都返回的一样,所以就想之前 对静态文件的 public中做的区分,但是由于这个是在内存中,所以不同:
安装: npm i http-proxy-middleware -D 做代理的中间件
const axios = require('axios');
const path = require('path');
const webpack = require('webpack');
const serverConfig = require('../../build/webpack.config.server.js');
const MemoryFs = require('memory-fs');
const ReactDomServer = require('react-dom/server');
//引入中间件
const proxy = require('http-proxy-middleware'); const getTemplate = () => {
return new Promise((resolve,reject)=>{
axios.get('http://localhost:8888/public/index.html')
.then(res => {
resolve(res.data);
})
.catch(reject)
})
} const Module = module.constructior let serverBundle;
const mfs = new MemoryFs;
const serverCompiler = webpack(serverConfig);
serverCompiler.outputFileSystem = mfs;
serverCompiler.watch({},(err,status)=>{
if(err) throw;
let stats = status.toJson();
stats.error.forEach(err => console.log(err));
stats.waring.forEach(warn => console.warn(warn)); const bundlePath = path.join(
serverConfig.output.path,
serverConfig.output.filename
);
const bundle = mfs.readFileSync(bundlePath,'utf-8');
const m = new Module();
m._compile(bundle,'server-entry.js');
serverBundle = m.exports.default;
}) module.exports = function (app) {
//服务器端端口是 3333;客户端的端口是 8888;
//这里做的代理是,访问当前3333端口的public文件时,代理去请求客户端的 8888端口文件
app.use('/public',proxy({
target:'http://localhost:8888'
})) app.get("*",function(req,res){
getTemplate().then(template => {
const content = ReactDomServer.renderToString(serverBundle);
res.send(template.replace('<!--app-->',content))
})
})
}
webpack 配置react脚手架(二):热更新的更多相关文章
- webpack 配置react脚手架(六):api
1 访问网址 https://cnodejs.org/api 可以调取api 2.//该body-parser 可以将请求的body数据,转变成 json 格式数据://express-session ...
- webpack 配置react脚手架(五):mobx
1. 配置项.使用mobx,因为语法时es6-next,所以先配置 .babelrc 文件 { "presets": [ ["es2015", { " ...
- webpack 配置react脚手架(三):eslint 及优化
首先谨记 eslint的官网: http://eslint.cn/ 1 安装eslint npm i eslint -D 2.在根目录下新建文件 .eslintrc { "extends ...
- webpack 配置react脚手架
1 react 基本js文件: import React from 'react'; import ReactDOM from 'react-dom'; import App from './App. ...
- webpack 配置react脚手架(四):路由配置
1. 由于 react-router 是集成了 react-router-dom 和 react-router-native的一起的,所以这里要使用的是 react-router-dom, 2. 安装 ...
- 解放F5——React开启模块热更新
解放F5--React开启模块热更新 在一个正在开发的应用中,刷新页面将会降低你的生产效率:你必须得等待页面加载完毕. 一个大的应用可能会花很多秒钟才能刷新完页面.使用 HMR(模块热替换) 可以避免 ...
- [webpack] 配置react+es6开发环境
写在前面 每次开新项目都要重新安装需要的包,简单记录一下. 以下仅包含最简单的功能: 编译react 编译es6 打包src中入口文件index.js至dist webpack配置react+es6开 ...
- 基于webpack的react脚手架
一.前言:react的cli开发模式太过于简单,好多东西都要自己配置 二.这里有个简单的配置,可以直接上手开发(不熟悉webpack和npm的绕路),已经完成的配置如下 1:默认ejs模板 2:编译l ...
- webpack配置React开发环境(上)
Webpack 是一个前端资源加载/打包工具,我们部门的一条主要技术栈就是Webpack+React+ES6+node,虽然之前自己做个人项目也接触好多次Webpack,但是自己并没有研读总结过Web ...
随机推荐
- 微信小程序 之页面跳转
wxml: <view><button bindtap="abc" >跳转</button></view> js: abc: (e) ...
- JS Maximum call stack size exceeded
一.问题描述 Maximum call stack size exceeded 翻译为:超过最大调用堆栈大小 二.效果截图 三.问题解决方案 出现该问题,说明程序出现了死循环了.所以要去检查出错的程 ...
- AndFix Bug 热修复框架原理及源码解析
作为阿里巴巴开源的 Android 应用热修复工具——AndFix,帮助 Anroid 开发者修复应用的线上问题.Andfix 是 “Android hot-fix” 的缩写. 1.什么是AndFix ...
- ACL 实验
一.环境准备 1. 软件:GNS3 2. 路由:c7200 二.实验操作 实验要求: 1. 掌握标准 ACL.扩展 ACL 的配置方法. 2. 掌握命名 ACL 的配置方法. 3. 掌握访问控制列表配 ...
- 016 Android 图片选择器(在选中和未选中的过程中,切换展示图片)
1.目标效果 在选中和未选中的过程中,切换展示图片 2.实现方法 (1)在app--->res--->drawable 右击drawable文件夹右键,new ---->drawab ...
- JWT知识整理
JSON Web Token:(https://jwt.io/) JSON Web Token(JWT)是一个开放式标准(RFC 7519),它定义了一种紧凑(Compact)且自包含(Self-co ...
- Java基础笔试练习(四)
1.编译Java Application 源程序文件将产生相应的字节码文件,这些字节码文件的扩展名为( ). A.java B.class C.html D.exe 答案: B 解析: Java源程序 ...
- leetcode 2019.10.29 首次破百
刷题首次破百,记录一下自己成长的历程. 仍在路上,会慢慢变强的~
- C# ObservableCollection两个字段排序的情况
相对于System.Linq的OrderBy及OrderByDescending方法,调用后产生IOrderedEnumberable对象,这个对象为排序后的返回值,但原对象未发生变化. 试想,有这种 ...
- 基于MFC的网页ActiveX控件开发全程实录2(js向ActiveX控件传递参数)
原文转自 https://blog.csdn.net/qianbin3200896/article/details/81452822 1.ActiveX控件部分(JS到ActiveX控件)继续上一篇博 ...