博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
webpack 配置react脚手架(二):热更新
阅读量:6216 次
发布时间:2019-06-21

本文共 10239 字,大约阅读时间需要 34 分钟。

下面继续配置 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 热加载插件 (参考文章: )

首先在 .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(
,document.getElementById('root'));if (module.hot) { module.hot.accept('./App.jsx', () => { const NextApp = require('./App.jsx').default ReactDOM.hydrate(
,document.getElementById('root')) })}// module.hot 监听到 app.jsx发生变化之后,重新获取 app.jsx 为NextApp 然后重新渲染;

修改package.js文件:

const webpack = require('webpack'); //因为用到了webpack下的包  HotModuleReplacementPluginconst 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(
,document.getElementById('root'));const root = document.getElementById('root'); const render = Component => { ReactDOM.hydrate(
, 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('
',appString)) //用返回的js文件替换掉模板中的
,然后发送的是模板文件 }) }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 构造方法去创建一个新的 moduleconst Module = module.constructiorlet 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('
',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.constructiorlet 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('
',content)) }) })}

 

转载于:https://www.cnblogs.com/xiaozhumaopao/p/10992743.html

你可能感兴趣的文章
Android IOS WebRTC 音视频开发总结(十四)-- sip和xmpp异同
查看>>
iOS NSNotification传递带参数的通知
查看>>
python之路--管道, 事件, 信号量, 进程池
查看>>
POJ 3667 & HDU 3308 & HDU 3397 线段树的区间合并
查看>>
Mysql 常用语句
查看>>
Java解惑(三) puzzle 24--35
查看>>
断舍离~~~
查看>>
查询结果多个合并一个GROUP_CONCAT(EmployeeName)
查看>>
selenium获取弹窗提示
查看>>
JQuery 的Ajax的使用
查看>>
5. Web vulnerability scanners (网页漏洞扫描器 20个)
查看>>
Mysql存储过程(四)——异常处理
查看>>
aptana 报错:Java heap space
查看>>
POJ 1840 Eqs
查看>>
10.9h5日记
查看>>
canvas图片与img图片的相互转换
查看>>
正则表达式基本语法
查看>>
Asp.net 2.0 用 FileUpload 控件实现多文件上传 用户控件(示例代码下载)
查看>>
老生常谈分布式锁
查看>>
pandas.DataFrame.pivot
查看>>