全称是Hot Module ReplaceMent(HMR),理解成热模块替换或者模块热替换均可以吧,和.net中的热插拔一个意思,就是在运行中对程序的模块进行更新。这个功能主要是用于开发过程当中,对生产环境没有任何帮助(这一点区别.net热插拔)。效果上就是界面的无刷新更新。
HMR基于WDS,style-loader能够经过它来实现无刷新更新样式。可是对于JavaScript模块就须要作一点额外的处理,怎么处理继续往下看。由于HMR是用于开发环境的,因此咱们修改下配置,作两份准备。一个用于生产,一个用于开发。
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); const PATHS = { app: path.join(__dirname, 'app'), build: path.join(__dirname, 'build'), }; const commonConfig={ entry: { app: PATHS.app, }, output: { path: PATHS.build, filename: '[name].js', }, plugins: [ new HtmlWebpackPlugin({ title: 'Webpack demo', }), ], } function developmentConfig(){ const config ={ devServer:{ //使能历史记录api historyApiFallback:true, hotOnly:true,//关闭热替换 注释掉这行就行 stats:'errors-only', host:process.env.Host, port:process.env.PORT, overlay:{ errors:true, warnings:true, } }, plugins: [ new webpack.HotModuleReplacementPlugin(), ], }; return Object.assign( {}, commonConfig, config, { plugins: commonConfig.plugins.concat(config.plugins), } ); } module.exports = function(env){ console.log("env",env); if(env=='development'){ return developmentConfig(); } return commonConfig; };



plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), ],
import component from './component'; let demoComponent=component(); document.body.appendChild(demoComponent); //HMR 接口 if(module.hot){ module.hot.accept('./component',()=>{ const nextComponent=component(); document.body.replaceChild(nextComponent,demoComponent); demoComponent=nextComponent; }) }
并修改component.js:
export default function () { var element = document.createElement('h1'); element.innerHTML = 'Hello webpack'; return element; }
这个时候页面更新了。每次改动页面上都会增长一个带有hot-update.js ,相似于下面这样:
webpackHotUpdate(0,{ /***/ "./app/component.js": /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony default export */ __webpack_exports__["default"] = function () { var element = document.createElement('h1'); element.innerHTML = 'Hello web '; element.className='box'; return element; }; /***/ }) })
经过webpackHotUpdate对相应模块进行更新。0表示模块的id,"./app/component.js"表示模块对应的name。结构是webpack(id,{key:function(){}})。function外带了一个括号,不知道有什么做用。webpackHotUpdate的定义是这样的:
this["webpackHotUpdate"] = function webpackHotUpdateCallback(chunkId, moreModules) { // eslint-disable-line no-unused-vars
hotAddUpdateChunk(chunkId, moreModules); if(parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules); } ;
小结:从结构来看,一个是id,一个是对应修改的模块。但实际执行更新的是hotApply方法。热更新整个机制仍是有点复杂,效果上像MVVM的那种绑定。有兴趣的能够深刻研究下。不建议在生产使用HMR,会让总体文件变大,并且对生成没有什么帮助,在下一节会讲样式的加载,style-loader就是用到了HMR。但对于js模块还要写额外的代码,这让人有点不爽。
demo:http://files.cnblogs.com/files/stoneniqiu/webpack-ch3.zip
参考:
【webpack】-- 自动刷新与解析
2017-02-26 23:53 by stoneniqiu, 298 阅读, 0 评论, 收藏, 编辑
前端须要频繁的修改js和样式,且须要根据浏览器的页面效果不断的作调整;并且每每咱们的开发目录和本地发布目录不是同一个,修改以后须要发布一下;另一点就是并非全部的效果均可以直接双击页面就能看到,咱们经常须要在本地用nginx建一个站点来观察(本身电脑上ok了才放到测试环境去)。因此若是要用手工刷新浏览器和手动(或点击)发布,还要启动站点,确实是个不小的体力活。而这三点webpack能够帮咱们作到。
webpack-dev-server
1.安装
npm install webpack-dev-server --save-dev
先经过npm将其安装到开发目录。安装完成以后会在node_modules/bin下找到。
2.npm启动
而后修改package.json:(基于上一节)
"scripts": { "start": "webpack-dev-server --env development", "build": "webpack --env production" }
如今就能够经过npm run start 或者 npm start来启动了。
启动以后,能够看到Project is running at http://localhost:8080 上面。打开页面
说明WDS已经帮咱们自动建了一个站点.咱们修改component.js ,cmd中会出现编译,页面会自动刷新。
3.直接启动
官网介绍能够直接经过下面的命令启动WDS。
webpack-dev-server --env development
但会出现webpack-dev-server --env development 不是内部命令的提示,这种问题都是环境变量的问题,将你开发的bin目录设置到环境变量中便可,好比个人目录是‘E:\Html5\node_modules\.bin’,就加上分号写在后面。
C:\Users\Administrator.9BBOFZPACSCXLG2\AppData\Roaming\npm;C:\Program Files (x86)\Microsoft VS Code\bin;E:\Html5\node_modules\.bin
4.8080端口占用
若是默认的8080端口占用,WDS会换一个。好比用nginx先发布一个。
server{ listen 8080; location / { root E:/Html5/build; index index.html index.htm; } }
再启动WDS:
端口切到了8081。也能够手动配置端口:
devServer:{ //... port: 9000 }
nodemon 自动启动
WDS是监视开发文件的,webpack.config.js改变不会引发自动启动。因此咱们须要nodemon去作这件事情。
npm install nodemon --save-dev
先安装在开发目录,而后修改package.json:
"scripts": { "start": "nodemon --watch webpack.config.js --exec \"webpack-dev-server --env development\"", "build": "webpack --env production" },
等于让nodemon去监视webpack.config.js,变化了就去启动它。
这样就你可让你的双手专心的开发了。
代理
不过有一点疑问,就是WDS这个站点的替代性,由于咱们本身部署的nginx有一些api的代理。若是挂在WDS的这个默认站点上天然是没法访问的。换句话说能否给WDS配置一个刷新路径。若是文件改变去刷新指定的地址,或者让我去配个代理。既然它自己是一个http服务器,确定也有代理的功能。搜了下果真有:https://github.com/webpack/webpack-dev-server/tree/master/examples/proxy-advanced
module.exports = { context: __dirname, entry: "./app.js", devServer: { proxy: { "/api": { target: "http://jsonplaceholder.typicode.com/", changeOrigin: true, pathRewrite: { "^/api": "" }, bypass: function(req) { if(req.url === "/api/nope") { return "/bypass.html"; } } } } } }
即将api这个字段替换成http://jsonplaceholder.typicode.com/,并将其从原地址中删掉,这样就能够本身实现代理了。皆大欢喜!WDS是经过 http-proxy-middleware 来实现代理。更多参考:http://webpack.github.io/docs/webpack-dev-server.html#bypass-the-proxy;https://github.com/chimurai/http-proxy-middleware#options
but,这种刷新是怎么实现的呢?由于页面上没有嵌入什么别的js,去翻原码 web-dev-server/server.js中有这么一段:
Server.prototype._watch = function(path) { const watcher = chokidar.watch(path).on("change", function() { this.sockWrite(this.sockets, "content-changed"); }.bind(this)) this.contentBaseWatchers.push(watcher); }
用chokidar来监视文件变化,server的内部维护的有一个socket集合:
Server.prototype.sockWrite = function(sockets, type, data) { sockets.forEach(function(sock) { sock.write(JSON.stringify({ type: type, data: data })); }); }
sock是一个sockjs对象。https://github.com/sockjs/sockjs-client,从http://localhost:8080/webpack-dev-server/页面来看,sockjs是用来通讯记录日志的。
var onSocketMsg = { hot: function() { hot = true; log("info", "[WDS] Hot Module Replacement enabled."); }, invalid: function() { log("info", "[WDS] App updated. Recompiling..."); sendMsg("Invalid"); }, hash: function(hash) { currentHash = hash; }, ... }
咱们在看app.js,其中有一个OnSocketMsg 对象。

ok的时候触发一个reloadApp
function reloadApp() { if(hot) { log("info", "[WDS] App hot update..."); var hotEmitter = __webpack_require__("./node_modules/webpack/hot/emitter.js"); hotEmitter.emit("webpackHotUpdate", currentHash); if(typeof self !== "undefined") { // broadcast update to window self.postMessage("webpackHotUpdate" + currentHash, "*"); } } else { log("info", "[WDS] App updated. Reloading..."); self.location.reload(); } }
也就是说WDS先检测文件是否变化,而后经过sockjs通知到客户端,这样就实现了刷新。以前WebSocket的第三方只用过socket.io,看起来sockjs也蛮好用的。没必要外带一个js,在主js里面就能够写了。
小结:效率提升的一方面是将一些机械的重复性流程或动做自动化起来。WDS和nodemon就是两个为你干活的小弟。
【webpack】-- 样式加载
2017-03-12 09:08 by stoneniqiu, 11 阅读, 0 评论, 收藏, 编辑
一,样式打包
1.安装css-loader,style-loader
npm install css-loader style-loader --save-dev
2.修改webpack.config.js
module:{ rules:[{ test:/\.css$/, use: ['style-loader', 'css-loader'], }] },
3.添加样式
body { background: cornsilk; }
而后在index.js中引入
import './main.css';
再运行npm start,在http://localhost:8080/中打开
这时候页面出现了背景色,并且发现样式写入了header中,这个时候你改变颜色,界面也会无刷新的更新,这正是上一节HMR的效果。
样式也是经过webpackHotUpdate方法进行更新。
2、加载less
再看一下如何加载less,先安装less-loader
npm install less less-loader --save-dev
再修改配置文件:
module:{ rules:[{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'], }] },
而后创建一个less文件。less.less
@base: #f938ab; .box-shadow(@style, @c) when (iscolor(@c)) { -webkit-box-shadow: @style @c; box-shadow: @style @c; } .box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) { .box-shadow(@style, rgba(0, 0, 0, @alpha)); } .box { color: saturate(@base, 5%); border-color: lighten(@base, 30%); div { .box-shadow(0 0 5px, 30%) } } body { background: cornsilk; }
修改index.js
import './less.less'; import component from './component'; var ele=document.createElement("div"); ele.innerHTML="this is an box"; ele.className="box"; document.body.appendChild(ele); let demoComponent=component(); document.body.appendChild(demoComponent);
获得效果:
能够看见编译成功,要注意的是,再使用less的时候import只能是less文件,这个时候再import main.css会报错。这一节对less就作一个简单的演示,其余样式预处理器同理,下面的内容仍是继续基于css。
3、理解css做用域和css 模块
通常来讲css的做用域都是全局的,咱们常在母版页里面添加了多个样式文件,后面的样式文件会覆盖前面的样式文件,经常给咱们的调试带来麻烦。而CSS Modules经过import引入了本地做用域。这样可以避免命名空间冲突。webpack的css-loader是支持CSS Modules的,怎么理解呢,先看几个例子。咱们先在配置中开启(先关掉HMR):
module:{ rules:[{ test:/\.css$/, use: ['style-loader', { loader: 'css-loader', options: { modules: true,//让css-loader支持Css Modules。 }, },],
而后定义一个新的样式(main.css):
body { background: cornsilk; } .redButton { background: red;color:yellow; }
给component加一个样式,先引入main.css。
import styles from './main.css'; export default function () { var element = document.createElement('h1'); element.className=styles.redButton; element.innerHTML = 'Hello webpack'; return element; }
这个时候咱们看到界面已经变化了。
再看右边生成的样式,咱们的样式名称已经发生了改变。回顾整个过程至关于main.css中的每个类名成了一个模块,在js中能够像获取模块同样的获取。可是你可能想,为毛我不能直接给元素赋值,干吗要import呢。这是个好问题,咱们再新增一个样式
不一样样式文件的同名类
other.css
.redButton { background:rebeccapurple;color:snow; }
它也有一个.redbutton的类(但效果是紫色的),而后在index.js中建立一个div元素并给它添加redbutton样式。
import './main.css'; import styles from './other.css'; import component from './component'; var ele=document.createElement("div"); ele.innerHTML="this is an other button"; ele.className=styles.redButton; document.body.appendChild(ele); let demoComponent=component(); document.body.appendChild(demoComponent);
再看效果
上面这个图说明了两问题,一个是咱们在index.js中引入了2个样式文件,在index页面就输出了两个style,这让人有点不爽,但咱们后面再解决。另一个就是虽然两个样式文件中都有redButton这个类,可是这二者仍是保持独立的。这样就避免了命名空间的相互干扰。若是你这个时候直接赋值
element.className="redButton";
这样是获取不到样式的。直接对元素的样式默认是全局的。
全局样式
若是想让某个样式是全局的。能够经过:global来包住。
other.css
:global(.redButton) { background:rebeccapurple;color:snow; border: 1px solid red; }
main.css
:global(.redButton) { background: red;color:yellow; }
这个时候redbutton这两个样式就会合并。须要直接经过样式名来获取。
element.className="redButton";
组合样式
咱们再修改other.css,建立一个shadowButton 样式,内部经过composes组合redbutton类。
.redButton { background:rebeccapurple;color:snow; border: 1px solid red; } .shadowButton{ composes:redButton; box-shadow: 0 0 15px black; }
修改index.js:
var ele=document.createElement("div"); ele.innerHTML="this is an shadowButton button"; console.log(styles); ele.className=styles.shadowButton; document.body.appendChild(ele);
看一下是什么效果:
日志打印出来的是styles对象,它包含了两个类名。能够看见shadowButton是由两个类名组合而成的。div的class和下面的对应。
4、输出样式文件
npm install extract-text-webpack-plugin --save-dev
先安装extracttextplugin这个插件,而后再webpack.config.js中进行配置:
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const extractTxtplugin = new ExtractTextPlugin({ filename: '[name].[contenthash:8].css', }); const commonConfig={ entry: { app: PATHS.app, }, output: { path: PATHS.build, filename: '[name].js', }, module:{ rules:[{ test:/\.css$/, use:extractTxtplugin.extract({ use:'css-loader', fallback: 'style-loader', }) }]}, plugins: [ new HtmlWebpackPlugin({ title: 'Webpack demo', }), extractTxtplugin ], }
一开始看到这个配置,让人有点懵。首先看fileName,表示最后输出的文件按照这个格式'[name].[contenthash:8].css',name默认是对应的文件夹名称(这里是app),contenthash会返回特定内容的hash值,而:8表示取前8位。固然你也能够按照其余的格式写,好比直接命名:
new ExtractTextPlugin('style.css')
而ExtractTextPlugin.extract自己是一个loader。fallback:'style-loader'的意思但有css没有被提取(外部的css)的时候就用style-loader来处理。注意到如今咱们的index.js以下:

import './main.css'; import styles from './other.css'; import component from './component'; var ele=document.createElement("div"); ele.innerHTML="this is an box"; ele.className=styles.shadowButton; document.body.appendChild(ele); let demoComponent=component(); document.body.appendChild(demoComponent); //HMR 接口 if(module.hot){ module.hot.accept('./component',()=>{ const nextComponent=component(); document.body.replaceChild(nextComponent,demoComponent); demoComponent=nextComponent; }) }
引入了两个css文件。
这个时候咱们执行 npm run build
再看文件夹获得一个样式文件。(若是不想看到日志能够直接npm build)
可是咱们在第三部分使用了CSS Modules,发现other.css的样式没有打包进来。因此,咱们的webpack.config.js还要修改:
module:{ rules:[{ test:/\.css$/, use:extractTxtplugin.extract({ use:[ { loader: 'css-loader', options: { modules: true, }, }], fallback: 'style-loader', }) }]},
再次build。
发现两个样式打包成了一个文件。只要内容发生了变化,样式的名称就会变化。更多配置能够移步https://www.npmjs.com/package/extract-text-webpack-plugin
参考:
https://www.npmjs.com/package/css-loader#local-scope
https://survivejs.com/webpack/styling/loading/