现在,webpack可以说是最流行的模块加载器(module bundler)。一方面,它为前端静态资源的组织和管理提供了相对较完善的解决方案,另一方面,它也很大程度上改变了前端开发的工作流程。在应用了webpack的开发流程中,想要继续“自动刷新”的爽快体验,就可能得额外做一些事情。

webpack与自动刷新

本文并不打算介绍webpack,如果你还不清楚它是什么,推荐阅读下面几篇入门文章:

webpack要求静态资源在被真正拿来访问之前,都要先完成一次编译,即运行完成一次webpack命令。因此,自动刷新需要调整到适当的时间点。也就是说,修改了css等源码并保存后,应该先触发一次webpack编译,在编译完成后,再通知浏览器去刷新。

开发Express项目的问题

现在有这样的一个应用了webpack的Express项目,目录结构如下:

其中,client内是前端的静态资源文件,比如css、图片以及浏览器内使用的javascript。server内是后端的文件,比如express的routes、views以及其他用node执行的javascript。根目录的app.js,就是启动express的入口文件了。

开发的时候我们会怎样做呢?

先启动Express服务器,然后在浏览器中打开某个页面,接下来再编辑源文件。那么,问题就来了,比如我编辑.scss源文件,即使我只改了一小点,我也得在命令行里输入webpack等它编译完,然后再切到浏览器里按一下F5,才能看到修改后的效果。

再比如,我修改了routes里的.js文件想看看结果,我需要到命令行里重启一次Express服务器,然后同样切到浏览器里按一下F5。

这可真是太费事了。

所以,我们要让开发过程愉快起来。

改进目标

我们希望的Express&Webpack项目的开发过程是:

  • 如果修改的是client里的css文件(包括.scss等),保存后,浏览器不会整页刷新,新的样式效果直接更新到页面内。

  • 如果修改的是client里的javascript文件,保存后,浏览器会自动整页刷新,得到更新后的效果。

  • 如果修改的是server里的文件,保存后,服务器将自动重启,浏览器会在服务器重启完毕后自动刷新。

经过多次尝试,我最终得到了一个实现了以上这些目标的项目配置。接下来,本文将说明这个配置是如何做出来的。

从webpack-dev-server开始

首先,webpack已经想到了开发流程中的自动刷新,这就是webpack-dev-server。它是一个静态资源服务器,只用于开发环境。

一般来说,对于纯前端的项目(全部由静态html文件组成),简单地在项目根目录运行webpack-dev-server,然后打开html,修改任意关联的源文件并保存,webpack编译就会运行,并在运行完成后通知浏览器刷新。

和直接在命令行里运行webpack不同的是,webpack-dev-server会把编译后的静态文件全部保存在内存里,而不会写入到文件目录内。这样,少了那个每次都在变的webpack输出目录,会不会觉得更清爽呢?

如果在请求某个静态资源的时候,webpack编译还没有运行完毕,webpack-dev-server不会让这个请求失败,而是会一直阻塞它,直到webpack编译完毕。这个对应的效果是,如果你在不恰当的时候刷新了页面,不会看到错误,而是会在等待一段时间后重新看到正常的页面,就好像“网速很慢”。

webpack-dev-server的功能看上去就是我们需要的,但如何把它加入到包含后端服务器的Express项目里呢?

webpack-dev-middleware和webpack-hot-middleware

Express本质是一系列middleware的集合,因此,适合Express的webpack开发工具是webpack-dev-middlewarewebpack-hot-middleware

webpack-dev-middleware是一个处理静态资源的middleware。前面说的webpack-dev-server,实际上是一个小型Express服务器,它也是用webpack-dev-middleware来处理webpack编译后的输出。

webpack-hot-middleware是一个结合webpack-dev-middleware使用的middleware,它可以实现浏览器的无刷新更新(hot reload)。这也是webpack文档里常说的HMR(Hot Module Replacement)。

参考webpack-hot-middleware的文档示例,我们把这2个middleware添加到Express中。

webpack配置文件部分

首先,修改webpack的配置文件(为了方便查看,这里贴出了webpack.config.js的全部代码):

var webpack = require('webpack');
var path = require('path'); var publicPath = 'http://localhost:3000/';
var hotMiddlewareScript = 'webpack-hot-middleware/client?reload=true'; var devConfig = {
entry: {
page1: ['./client/page1', hotMiddlewareScript],
page2: ['./client/page2', hotMiddlewareScript]
},
output: {
filename: './[name]/bundle.js',
path: path.resolve('./public'),
publicPath: publicPath
},
devtool: 'source-map',
module: {
loaders: [{
test: /\.(png|jpg)$/,
loader: 'url?limit=8192&context=client&name=[path][name].[ext]'
}, {
test: /\.scss$/,
loader: 'style!css?sourceMap!resolve-url!sass?sourceMap'
}]
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
]
}; module.exports = devConfig;

这是一个包含多个entry的较复杂的例子。其中和webpack-hot-middleware有关的有两处。一是plugins的位置,增加3个插件,二是entry的位置,每一个entry后都增加一个hotMiddlewareScript

hotMiddlewareScript的值是webpack-hot-middleware/client?reload=true,其中?后的内容相当于为webpack-hot-middleware设置参数,这里reload=true的意思是,如果碰到不能hot reload的情况,就整页刷新。

在这个配置文件中,还有一个要点是publicPath不是/这样的值,而是http://localhost:3000/这样的绝对地址。这是因为,在使用?sourceMap的时候,style-loader会把css的引入做成这样:

这种blob的形式可能会使得css里的url()引用的图片失效,因此建议用带http的绝对地址(这也只有开发环境会用到)。有关这个问题的详情,你可以查看github上的issue

Express启动文件部分

接下来是Express启动文件内添加以下代码:

var webpack = require('webpack'),
webpackDevMiddleware = require('webpack-dev-middleware'),
webpackHotMiddleware = require('webpack-hot-middleware'),
webpackDevConfig = require('./webpack.config.js'); var compiler = webpack(webpackDevConfig); // attach to the compiler & the server
app.use(webpackDevMiddleware(compiler, { // public path should be the same with webpack config
publicPath: webpackDevConfig.output.publicPath,
noInfo: true,
stats: {
colors: true
}
}));
app.use(webpackHotMiddleware(compiler));

以上这段代码应该位于Express的routes代码之前。其中,webpack-dev-middleware配置的publicPath应该和webpack配置文件里的一致。

webpack-dev-middleware和webpack-hot-middleware的静态资源服务只用于开发环境。到了线上环境,应该使用express.static()

到此,client部分的目标就完成了。现在到网页里打开控制台,应该可以看到[HMR] connected的提示。这个项目中我只要求css使用HMR,如果你希望javascript也使用HMR,一个简单的做法是在entry文件内添加以下代码:

if(module.hot) {
module.hot.accept();
}

这样,与这个entry相关的所有.js文件都会使用hot reload的形式。关于这一点的更多详情,请参考hot module replacement

接下来是server部分。

reload和supervisor

server部分的自动刷新,会面临一个问题:自动刷新的消息通知依靠的是浏览器和服务器之间的web socket连接,但在server部分修改代码的话,一般都要重启服务器来使变更生效(比如修改routes),这就会断开web socket连接。

所以,这需要一个变通的策略:浏览器这边增加一个对web socket断开的处理,如果web socket断开,则开启一个稍长于服务器重启时间的定时任务(setTimeout),相当于等到服务器重启完毕后,再进行一次整页刷新。

reload是一个应用此策略的组件,它可以帮我们处理服务器重启时的浏览器刷新。

现在,还差一个监听server文件,如果有变更就重启服务器的组件。参考reload的推荐,我们选用supervisor

下面将reload和supervisor引入到Express项目内。

监听文件以重启服务器

通过以下代码安装supervisor(是的,必须-g):

npm install supervisor -g

然后,在package.json里设置新的scripts

"scripts": {
"start": "cross-env NODE_ENV=dev supervisor -i client app"
}

这里的主要变化是从node app改为supervisor -i client app。其中-i等于--ignore,这里表示忽略client,显然,我们可不希望在改前端代码的时候服务器也重启。

这里的cross-env也是一个npm组件,它可以处理windows和其他Unix系统在设置环境变量的写法上不一致的问题。

把会重启的服务器和浏览器关联起来

把Express启动文件最后的部分做这样的修改:

var reload = require('reload');
var http = require('http'); var server = http.createServer(app);
reload(server, app); server.listen(3000, function(){
console.log('App (dev) is now running on port 3000!');
});

Express启动文件的最后一般是app.listen()。参照reload的说明,需要这样用http再增加一层服务。

然后,再到Express的视图文件views里,在底部增加一个<script>

<% if (env !== "production") { %>
<script src="/reload/reload.js"></script>
<% } %>

所有的views都需要这样一段代码,因此最好借助模板引擎用include或extends的方式添加到公共位置。

这里的reload.js和前面webpack的开发环境bundle.js并不冲突,它们一个负责前端源文件变更后进行编译和刷新,另一个负责在服务器发生重启时触发延时刷新。

到此,server也完成了。现在,修改项目内的任意源文件,按下ctrl + s,浏览器里的页面都会对应地做一次“适当”的刷新。

完整示例

完整示例已经提交到github:express-webpack-full-live-reload-example

效果如下:

附加的可选方案

前面说的server部分,分为views和routes,如果只修改views,那么服务器并不需要重启,直接刷新浏览器就可以了。

针对这样的开发情景,可以把views文件的修改刷新变得更快。这时候我们不用reload和supervisor,改为用browsersync,在Express的启动文件内做如下修改:

var bs = require('browser-sync').create();
app.listen(3000, function(){
bs.init({
open: false,
ui: false,
notify: false,
proxy: 'localhost:3000',
files: ['./server/views/**'],
port: 8080
});
console.log('App (dev) is going to be running on port 8080 (by browsersync).');
});

然后,使用browsersync提供的新的访问地址就可以了。这样,修改views(html)的时候,由browsersync帮忙直接刷新,修改css和javascript的时候继续由webpack的middleware来执行编译和刷新。

转载:https://segmentfault.com/a/1190000004505747

express_webpack自动刷新的更多相关文章

  1. 浏览器自动刷新——基于Nodejs的Gulp LiveReload与VisualStudio完美结合。

    本文版权桂博客园和作者吴双共同所有,转载和爬虫请注明原文地址 http://www.cnblogs.com/tdws/p/6016055.html 写在前面 大家好我是博客园的蜗牛,博客园的蜗牛就是我 ...

  2. Sublime3和Chrome配置自动刷新网页【实测可用】

    SublimeText2下的LiveReload在SublimeText3下无法正常使用,本文整理SublimeText3安装LiveReload的方法.win7下实测可用! 安装成功后,就不需要再手 ...

  3. gulp之压缩合并MD5清空替换加前缀以及自动编译自动刷新浏览器大全

    gulp是基于流的前端构件化工具.目前比较火的前端构建化工具还是挺多的,grunt gulp fis3等等. 这个鬼东西有什么用?请参考https://www.zhihu.com/question/3 ...

  4. 更为简单的Ctrl+S自动刷新浏览器工具-LinrF5

    一款自动刷新浏览器的小工具,它通过监听用户的按键,如果在键盘按下 Ctrl+S ,则自动刷新浏览器,操作十分简单,前端开发必备神器,快速提升工作效率,支持IE.火狐以及最新版的chrome33.之前我 ...

  5. SQL实现类似于自动刷新数据的功能

    有时需要在SQL中,定时刷新某张表,比如说是要定时查询某张表的行数,通常做法就是手动的按F5去执行来刷新数据.但是如果这个定时查询历时较长,10分钟,或半小时,手动的话肯定是要崩溃了.貌似SQL没有像 ...

  6. Javascript实现页面加载完成后自动刷新一遍清除缓存文件

    我们有些时候在加载页面时,会出现缓存文件对当前文件的表现效果有干扰,如有些缓存的样式文件会是页面效果发生改变,这时我们希望页面在加载时能自动刷新一遍清楚缓存文件. 但是由于跳转页面肯定会用到BOM部分 ...

  7. Notepad++自动刷新文本

    现在的日志信息往往都是打印在硬盘上,而不是保存到线上,所以我们常常会使用notepad++来查看硬盘上的文本文件 这时往往会出现两个问题 (1)在notepad++长时间最小化后,再次打开会提示是否下 ...

  8. 简单配置webpack自动刷新浏览器

    文档地址  http://webpack.github.io/docs/usage.html 首先全局安装webpack(我这里使用的是淘宝的cnpm) cnpm install webpack 检查 ...

  9. gruntJs篇之connect+watch自动刷新

    grunt很强大,可以帮我我们解决很多繁琐的操作,虽然刚接触不久,但依然感受到其强大之处,这篇记录一下通过grunt.js实现事实刷新页面, 省去了编码 -> 保存 -> F5..F5.. ...

随机推荐

  1. ubuntu 修改hostname

    1.sudo gedit /etc/hostname 2. 修改成你的新名字,例如 SS1 3. 保存,退出 3. sudo gedit /etc/hosts 4修改成心的名字 SS1 5. 保存,退 ...

  2. TypeScript -- JavaScript的救赎

    TypeScript的设计目的应该是解决JavaScript的"痛点":弱类型和没有命名空间,导致很难模块化,不适合开发大型程序.另外它还提供了一些语法糖来帮助大家更方便地实践面向 ...

  3. RTT之shell

    两种shell的切换:如果打开了FINSH_USING_MSH而没有打开FINSH_USING_MSH_ONLY,finsh同时支持两种c-style模式与msh模式,但是默认进入c-style模式, ...

  4. python学习之可变不可变

    在python的数据类型中,整数.字符串.元组是不可变的:而列表.字典是可变的.所以不用C的思维来修改一个整数,它相当于重新定义了一个整数(原来的被覆盖掉了),名字和原来一样,但与前面的同名变量没有一 ...

  5. Murano Weekly Meeting 2015.09.01

    Meeting time: 2015.September.1st 1:00~2:00 Chairperson:  Nikolay Starodubtsev, from Mirantis Meeting ...

  6. stm32 输入捕获学习(二)

    (本文参考STM32  开发指南 V1.3   -- ALIENTEK 战舰 STM32 开发板库函数教程 ) 1. 实验设计 我们用 TIM5 的通道 1(PA0)来做输入捕获,捕获 PA0 上高电 ...

  7. Git 设置 Hook

    Git 设置 hook Hook 就是钩子,在需要的时候调用,根据每个钩子脚本(函数)的返回值决定下一步的操作. 在使用 Git 的过程中,有时候需要定制 Git 以便满足实际的需求. 需求 在一个项 ...

  8. anaular js loadding效果

    以前用的jquery的时候,用ajax实现,比较好弄,下面是angularjs的方式: //body下面增加div <div data-loading></div> //dir ...

  9. jsp smartupload学习

    smartupload 是jsp中用于上传文件的组件, 其特点如下: 1.使用简单.在JSP文件中仅仅书写三五行java代码就可以搞定文件的上传或下载,方便. 2.能全程控制上传.利用jspSmart ...

  10. 服务器断电后 redis重启后启动不起来

    服务器断电后 redis 重启后启动不起来 原因:db持久化失败 1. 先查询redis的进程 ps -ef|grep redis 2. 查询redis的缓存文件在哪 whereis dump.rdb ...