前端自动化构建是当下的热门,我记得2014年的时候,前端的自动化构建,大多是用在javascript的合并、压缩、语法检查、coffeescript,Sass,LESS转换上,构建工具也有很多,比如ant,grunt,gulp等,二次封装的工具也有很多,比如百度的FIS,国外的Yeoman。2016年以后,随着es6,es7,Node的兴起,前端又发生了翻天覆的变化,特别是移动端的H5最为明显,以前切个图,在PC上预览测试就可以发布的时代,在移动端就不灵验了,在手机端预览至少要搭建一个http服务器,比如http://192.168.0.2/index.html。在手机端输入网址不方全,通常会将网址做成一个二维码,然后用手机扫一下就可以打开预览。我们每改一下样式,就在手机上点一下刷新或电脑上按一下F5,这在最初的时候,也不觉得有什么问题,因为拿到我手上的静态页,通常由切片的同事做好了兼容性测试,需要一边刷新浏览器,一边改样式的机会不多。随着我们尝试用Less,stylus,这样的css工具,一方面,需要用到gulp这样的工具在后台自动监听我们的样式改动,另一方面,手动刷新的时候,gulp的脚本未必转换完了。这时候迫切需要浏览器自动刷新。
总的来说,需求就两点,一是需要一个http服务器,来供手机访问静态资源,另一个是监听代码的改动并自动刷新浏览器。要满足这两个需求的第三方工具,应当不难找,事实上像fis,yeoman,vuecli这样的工具应当都可以做到。可是我觉得它们都太复杂了,虽然我只用到其中一点点功能,但是我不得不仔细的通读他们的文档,找到自己需要的功能。有时候,官方说三分钟入门,可是我花三十分钟了还没有入门。或许我几天之后,我好不容易入门了,结果周围同事又给我推荐另一个工具,说比我手上这好一千倍,于是我又去学另一个工具。如此,很容易陷入不同工具之间的学习。更要命的是,转了一圈回来,其实我用的那一点功能,用哪一个工具都差不多,既不像A同学说的那么差劲,也不像B同学说的那么好。
2015年6月份的时候,我们的项目开始用express+React.js做服务端渲染,Redux做状态管理。我们搭建了一套开发框架,以前那些自动化的工具,都不能完全满足我的需求了。js和css的改动越来越频繁,而且node不像php代码那样,改动之后,服务器会自动更新,它需要手动重启node进程,另一方面,自动刷新浏览器,会导致redux的action日志看不到。这个时候,浏览器的自动刷新已经满足不了我们的需求。我们需要浏览器在不刷新的情况下,局部更新我改过的代码。这也就是“热替换”(HMR)这个概念的来由。很多新同事搞不清什么是自动刷新,什么是热替换。热替换听起来,有点像是ajax的效果,不过,ajax是点击某个动作或触发某个事件之后由js脚本触发,而“热替换"是在我们改动了代码的时候触发(也就是CTRL+S保存的时候). 自动刷新就是指不用手动去按F5.
要实现自动刷新和静态服务器,最简的就是用webpack-dev-server . 如果之前用过webpack,那么webpack-dev-server则很容易接受,如果还没有用过webpack,那么我觉得很有必要去看看。网上有很多介绍webpack及webpack-dev-server的文章,我觉得要这实现这个需求,只要两步就可以:
1.安装webpack-dev-server
2. 运行webpack-dev-server --inline --hot
webpack.config.js的配置:
var path = require("path"); var webpack = require('webpack'); //var ExtractTextPlugin = require("extract-text-webpack-plugin"); var node_modules_dir = path.resolve(__dirname, 'node_modules'); module.exports = { entry:[ //'webpack-dev-server/client?http://localhost:8081', //'webpack/hot/dev-server', './src/app.jsx','./src/app.css' ], module: { loaders: [{ test: /.es6|jsx$/, exclude: [node_modules_dir], loaders: ['react-hot','babel-loader'], }, { test:/.css$/, loaders:['style', 'css'] }] }, output: { path: path.resolve(__dirname, "dist"), publicPath:"/assets/", filename: 'bundle.js' }, resolve: { extensions: ['', '.js','.es6','.jsx'] }, plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }), //new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ] }
通过命令行的方式执行webpack-dev-server , 重点要说的是--hot这个参数,它就是启动热替换用的。它会自动给plugins插入new webpack.HotModuleReplacementPlugin() 因此不需要人为的再配置这个插件了。所以我在webpack.config.js中注释掉了这一行。 然后entry中也不需要加webpack/hot/dev-server和webpack-dev-server/client,记住:命令行方式运行webpack-dev-server会自动替你完成这些工作。不要人云亦云的去添加这些注释的内容,我看到甚至还有用express配合webpack-hot-middleware,webpack-dev-middleware 之类的用法,那更加复杂了,对于只想要实现自动刷新和热替换来说,命令行方式运行,配合 --hot --inline 这两个参数是最简单有效的做法。其它复杂的配置和用法,有它的特殊的适用场景,比如同时需要两个服务器,一个充当数据接口,一个充当静态文件服务器, 我们希望webpack-dev-server通过node API的方式运行。这个有点复杂,用到这么复杂的情形的,一般都是能自己搞定这个配置的。有这方面需求的,参考demo8的代码。
填坑记
extract-text-webpack-plugin这个插件,它可以将模块中的样式部分提出来,单独打包成文件,但是它只适用于生产模式。开始我也没有注意,在开发模式下也用了,还觉得很爽,直到有一次,我发现更改样式的时候,浏览器居然自动刷新了,而我期望是热替换。这时我才理解,只适用于生产模式,不是说它在开发模式下,就不生效了,而是它配合 --hot参数的时候,不会有热替换的效果。
简单的一行小字,没有细看,结果走了很多弯路。俗话说,方向反了,停止就是进步。 对于普通的js模块来说,热替换需要自己写loader插件,如果是react,vue则官方会提供热替换的loader,比如react的 react-hot-loader 。如果没有用这些可以支持热替换的插件,那么默认的就是浏览器的自动刷新效果。css的话,只要加上css-loader,style-loader就可以了。最后再补一刀,output:中要配publicPath
output: { path: path.resolve(__dirname, "dist"), publicPath:"/assets/", filename: 'bundle.js' },
在html中也要写publickPath的地址:
/assets/这个虚拟目录中的文件实际上是保存在内存中的,这样就为模块的热替换提供了可能。如果写成实际的产出目录,是怎么也不会看到热替换的效果的,甚至连自动刷新都不会出现.
源码地址:https://github.com/bjtqti/how_to_use_webpack/tree/master/demo18
验证是否成功开启HMR
首先看控制台的提示:
出现这个HMR说明配置方面是对的,但是光有这个还不行,如果模块没有热替换的loader,那么就会触发失败,失败的结果就是导致浏览器刷新。这也是为什么有时候明明出现这个热替换的标志了,还是会出现浏览器刷新。如果添加了支持热替换的loader,那么当我们保存更改的时候,浏览器只会默默的更新变化区域,而不会产生刷新。同时,控制台的log也会累积,不会被清空。
手机预览
辛苦做出来的H5页面,在电脑上模拟显示的效果,都未必可靠,最后都需要放到手机上进行真机检测。通过webpack-dev-server生成的http服务器,可以实现通过手机访问。比如电脑的ip是192.168.1.122, 默认情况下,那么通过http://191.168.1.122:8080就可以在手机上打开我们的H5页面。如果打不开,请检查两个地方:
1. webpack-dev-server 添加 --host 0.0.0.0 参数
2. 保证电脑和手机处于同一个无线网络,即电脑可以ping通手机。
打包发布
在手机上检测完好之后,就需要把代码进行合并,压缩,添加hash标记,小图片,字体文件转base64,提取共公代码等等. 一方面可以简单的使用
webpack -p 也可自行配置webpack.config.js,演示:
plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }), new ExtractTextPlugin('css/[name].css'), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false }, output: { comments: false }, sourceMap: false }), new webpack.optimize.CommonsChunkPlugin('vendor','js/vendor.js'), new webpack.optimize.OccurenceOrderPlugin(true), new webpack.NoErrorsPlugin() ]
到此,整个构建工作就完成了。
小结
关于webpack及webpack-der-server的教程很多,但是很多文章发布的较早,不一定适用你现在用的版本。遇到问题的时候,我建议先看看错误提示,然后查看官方文档。先从简单的用法开始去动手尝试,一定要尝试,不要觉得很简单,只在脑海里想当然的运行一遍就以为学会了,俗话说,实践出真知,同样的问题,不同的环境(windows/mac/node版本...),会有不同的情况,只有自己一一去试过了,遇到问题心中有数,才不会慌。最后,要把学到的内容结合实际的项目去运用看看,有哪些功能对目前的开发有帮助,哪些功能还不是很了解,需要更深入的学习。遇到实在有自己找不到解决办法的问题,再去网上查,个人觉得stockoverflow还算比较靠谱。最后,要记得把自己遇到的坑整理成章,分享是一种美德。