webpack是时下十分流行的编译和打包工具,它提供一种可扩展的loader的方式,简单的配置,即可以编译打包各种型的文件,包括js、css、image、font、html,以及各类预编译语言都不在话下。javascript
在上一节的【入门:十分钟自动化构建】中咱们讲解了如何用gulp去搭建一个工做流。咱们认识到gulp是一个流程管理工具,以单个任务为基础单元,组合成为一套完整的工做流,并且gulp还有不少的以gulp-*
格式命名的工做模块,用来处理各类资源文件,若是没有看过上一节内容的同窗,建议先回去看过,再往下阅读,由于本节内容是跟上一节的知识点紧密联系的。css
这一节咱们会讲解如何构建具有版本管理能力的项目,什么是版本管理能力?不是什么svn或者branch,咱们看这样一个场景来帮助理解:html
你用上次搭建的工做流开发了一个网站,而后上线。
次日打开发现有bug,心想尼玛赶忙趁着老板没发现修复一下。
改完代码,打包,发布,一鼓作气,完美。
然而十分钟之后老板让你去一趟办公室,打开页面跟你说有个bug。
内心一抽,一看!我勒个去,这坑爹的缓存啊。。。java
怎么去解决这个缓存?,或者说,怎么保证我上线一个新版本,能够完彻底全地替代旧版本?这就是版本管理。node
这问题有不少解决方法,包括手动打个戳啊什么的,像src="a.jpg?201608062315"
,这确实能够解决,可是若是一次更新的东西不少,你压根改不过来。react
gulp能帮咱们作这事吗?能够,麻烦,有兴趣的同窗能够自行搜索资料。有没有简单点的套路?有的,webpack自然支持这一功能。接下来咱们就介绍如何用webpack来搭建这么一套工做流。webpack
webpack的用法,咱们简单介绍一下。跟gulp同样,webpack也是写好配置文件才能开始工做。git
全局安装webpackgithub
npm install webpack -g
记得养成好习惯,也本地安装一下哦web
npm install webpack --save-dev
顺带咱们把接下来要用到的几个loader一块儿安装了:
npm install style-loader css-loader sass-loader swig-loader --save-dev
这里的*-loader
做用跟gulp-*
差很少,就是一些编译用的模块。
紧接着咱们在根目录下,新建一个webpack.config.js配置文件,咱们直接来看代码:
var path = require('path') module.exports = { entry: { Index: ['./src/js/index.js'] }, output: { path: path.resolve(__dirname, './dist/static'), publicPath: 'static/', filename: '[name].js' }, resolve: { extensions: ['', '.js', '.scss', '.swig'] }, module: { loaders: [ { test: /\.css$/, loader: 'style!css' }, { test: /\.scss$/, loader: 'style!css!sass' }, { test: /\.swig$/, loader: 'swig' } ] } }
这里大体分为四部分的内容:
entry
入口文件,也就是一切工做的起点,你能够将整个web应用都最终打包成一个js文件,那你只须要定义一个入口,而若是你但愿对多个页面独立开来,你须要定义多个入口,最终在不一样的页面引用不一样的js。一个entry对应生成一个bundle。
output
定义打包输出的配置:
dist/static
下的;<script src="index.js"></script>
=> <script src="static/index.js"></script>
;[name].min.js
,最终打包出来文件名就是index.min.js
。resolve
这个配置无关紧要,它是定义一些经常使用的文件拓展名,被定义了的文件格式,在引用的时候能够不加扩展名,好比我配置了.js
的拓展名以后,在开发中我能够直接require('./common')
,而不须要require('./common.js')
。
module
最重要的一个部分,咱们在这里定义针对各类类型文件的loader(加载器/编译器),能够看到咱们分别定义了css、scss、swig三种文件对应的loader,多个loader之间用!
隔开,'-loader'能够省略不写。注意,编译的优先次序是从右到左的,好比scss文件是先被sass-loader处理,而后再被css-loader处理,最后再被style-loader处理。
除了这几点,webpack还有个比较重要的配置项plugins
,这个咱们暂时不介绍,后面会在具体的应用场景里引入。除此以外,webpack的其余配置项你们能够去官网了解。
话很少说,咱们仍是取原来的gulp_base项目源码来作介绍。
- project
|- src // 源文件夹
| |- tpl
| | `- index.swig | |- sass | | `- index.scss | |- js | | `- index.js |- dist // 打包文件夹 `- package.json
webpack的模块化使咱们能够很方便地使用commonjs的规范来组织代码。有关commonjs的内容,你们能够自行查阅相关文章,这里不做深刻展开;或者关注我以后的文章,我将会针对模块化作一些讲解。不过这都是后话了。
在这里咱们只须要知道,咱们经过将页面划分红不少的小模块,每一个模块都是单独的js文件,咱们能够经过require的方式,去引入一个模块,进而组织成一个大的web应用。而webpack更甚,在webpack里,"一切资源皆模块",无论是图片仍是css,均可以在js文件中直接require,固然前提是你已经在webpack.config.js的module项里配置了这类文件的相关loader。
ok,准备就绪,咱们来试着编译一下
webpack
理论上,咱们若是把js文件放在一个叫作js的文件夹里,那几乎百分百肯定这货就是.js结尾的,扩展名显得有点多余。固然若是你硬是要在js文件夹里放个.jpg的文件我也拿你没办法是吧。
我们看看dist目录里面是否是有了个static文件夹?文件夹里是否是有个文件叫作index.js?是否是就成功了?没成功你找我。
好,咱们试着验证另一个配置,output.filename
,咱们改一下:
module.exports = { output: { path: path.resolve(__dirname, './dist/static'), publicPath: 'static/', filename: '[name].[chunkhash].js' }, ... }
命令行里webpack一下:
Hash: 408544aa45e4f298cb49 Version: webpack 1.13.1 Time: 49ms Asset Size Chunks Chunk Names Index.e167a63b2bcf28077701.js 1.53 kB 0 [emitted] Index [0] multi Index 28 bytes {0} [built] [1] ./src/js/index.js 21 bytes {0} [built]
打开dist/static目录一看,生成了一个Index.e167a63b2bcf28077701.js
文件,chunkhash
是由webpack对index.js这个文件进行 md5 编码以后获得的一个戳,这个戳是惟一的,拼接在文件名上,能够表示这个文件的惟一性,只要index.js里面的内容被修改一丁点,这个生成的戳就会不同。
这不正是咱们开头想要的版本管理吗?太美好了,没想到这么容易就解决了这个问题。
有个问题,若是我每修改一点,这个文件名就不同了,那我不就要在html中每一个引用了这个js的地方再修改一下文件名?这根本就没有减负嘛!
怎么去解决这个问题?webpack固然提供了解决方案,若是咱们用webpack帮咱们打包html,而且自动地去注入这些资源,那问题就解决了。
通常来讲webpack就只是处理入口文件,也就是从js去入手,这样的话怎么都轮不到html,那怎么样才能让webpack去处理一下html?答案就是咱们以前提过的,plugins!
咱们来修改一下配置:
var path = require('path') var HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: { Index: ['./src/js/index.js'] }, output: { path: path.resolve(__dirname, './dist/static'), publicPath: 'static/', filename: '[name].[chunkhash].js' }, resolve: { extensions: ['', '.js', '.scss', '.swig'] }, module: { loaders: [ { test: /\.css$/, loader: 'style!css' }, { test: /\.scss$/, loader: 'style!css!sass' }, { test: /\.swig$/, loader: 'swig' } ] }, plugins: [ new HtmlWebpackPlugin({ chunks: ['Index'], filename: '../index.html', // 留意这里,这里的路径是相对来path配置的 template: './src/tpl/index', inject: true }) ] }
请留意两点修改的地方,一个是var HtmlWebpackPlugin = require('html-webpack-plugin')
,一个是plugins
的配置内容。
webpack容许以插件的方式去扩展功能。html-webpack-plugin是webpack提供的一个简化html处理的插件,其实本意就是为了解决这个引用bundle的问题,顺便提供一些像压缩啊注入、变量啊什么的功能。具体其余功能你们能够去官网看文档。
使用前记得先install啊!
咱们再次编译一下,而后查看一下dist文件夹,是否是多了个index.html?并且index.js也被编译后自动注入了页面
编译前:
// index.swig
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> </head> <body> hello, world! </body> </html>
编译后:
// index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> </head> <body> hello, world! <script type="text/javascript" src="static/Index.e167a63b2bcf28077701.js"></script></body> </html>
done!
看着这html,有没有感受少了点什么?嗯,少了css,咱们来看看css怎么注入。
其实css更简单,咱们已经说过了:
在webpack里,一切资源皆模块。
咱们只须要在index.js中直接require就能够了:
// index.js require('../sass/index') console.log('index')
编译一下,打开index.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> </head> <body> hello, world! <script type="text/javascript" src="static/index.c04c40a250e85ea100ab.js"></script></body> </html>
没看到有引入css啊?你个骗纸!
别着急,试着双击index.html文件,在浏览器打开试试?是否是有样式了!神奇,样式是哪里来的?原来css都被打包进js里面了!
虽说目的是达到了,可是总以为有点很差,咱们仍是习惯外联css啦,能不能实现?能够!甩你一个plugin!extract-text-webpack-plugin:
var ExtractTextPlugin = require('extract-text-webpack-plugin') module.exports = { module: { loaders: [ { test: /\.css$/, loader: ExtractTextPlugin.extract('style', ['css']) }, { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', ['css', 'sass']) }, { test: /\.swig$/, loader: 'swig' } ] }, plugins: [ new ExtractTextPlugin('[name].[chunkhash].css'), ... ], ... }
看着就能明白吧,这里再也不细讲,反正也就是这么用而已。
编译一遍,看看index.html:
// index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> <link href="static/index.d4085060ebf342f7a5a1.css" rel="stylesheet"></head> <body> hello, world! <script type="text/javascript" src="static/index.d4085060ebf342f7a5a1.js"></script></body> </html>
完美!
到这里,咱们的打包工做算是介绍完了,下面咱们试着搭建开发环境。
咱们在使用gulp的时候,搭建开发环境是借助browser-sync,而对于webpack来讲,咱们有更好的选择:webpack-dev-server!
webpack-dev-server是webpack官网提供的一款本地开发服务器,能够为咱们提供一个适用于在webpack下进行开发的环境。
webpack-dev-server的用法其实很简单,基于webpack的配置,稍稍补充一些内容就能够了。webpack-dev-server有两种使用方式,cli和api,咱们分别作介绍。
在开始以前,咱们须要针对开发环境单首创建一份配置文件,有印象的同窗能够记得,咱们上一节讲过,打包与开发的配置文件有一个不一样点,打包是以产出而且优化为目的的,而开发则更侧重效率,因此咱们须要把一些压缩和多余的优化手段给去掉,减轻开发过程当中的编译负担。
咱们新建一份配置文件webpack.dev.config.js:
// webpack.dev.config.js var path = require('path') var HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: { index: ['./src/js/index.js'] }, output: { path: path.resolve(__dirname, './dist/static') filename: '[name].js' }, resolve: { extensions: ['', '.js', '.scss', '.swig'] }, module: { loaders: [ { test: /\.css$/, loader: 'style!css' }, { test: /\.scss$/, loader: 'style!css!sass' }, { test: /\.swig$/, loader: 'swig' } ] }, plugins: [ new HtmlWebpackPlugin({ chunks: ['index'], filename: '../index.html', template: './src/tpl/index', inject: true }) ] }
咱们把extract-text-webpack-plugin
去掉,再把为了不缓存而在文件名加戳的方式也去掉,这样就差很少了。
值得注意的是,咱们把path和publicPath两项都去掉了,这里是为了配合下面的cli方案使用,咱们接下去看就会明白。
cli
cli的方式要求咱们全局安装webpack-dev-server:
npm install webpack-dev-server -g
而后咱们直接在命令行里输入:
webpack-dev-server --config ./webpack.dev.config.js --display-error-details --devtool eval --content-base ./ --inline --progress --colors --host 0.0.0.0
天了噜,这也太长了。这长长的一串命令,都是针对webpack-dev-server初始化的配置,固然,webpack-dev-server的配置也是能够在webpack.dev.config.js里定义的,只是咱们这里主要展现cli的用法,因此就直接这样输入了,具体各个参数是什么功能,咱们能够没必要所有了解,由于这里并无特别关键的配置项,大部分都是对终端里输出形式的一些设置,咱们挑重点的来讲:
热更新的做用是使得咱们在修改并保存代码以后,在不刷新浏览器的状况下,自动更新浏览器对应部分的代码!注意,此时页面是不会刷新的,但变化马上就能够反映出来!这一项咱们并无设置,为何?由于这里有一点小问题,热更新的功能目前并不能对.html(或者静态模板)这样的文件起做用,也就是说,若是咱们修改的是html,它将会不起任何做用,除非咱们手动刷新,才能看到效果。这就有点捡芝麻丢西瓜了,因此咱们选择了放弃它。
固然,若是是像react这样的主要由js来构建的项目,那咱们能够毫无顾忌地使用热更新。
咱们在配置文件中去掉了publicPath的设置,其实你能够试着设置一下,你会发现publicPath是会影响到所有的资源包括index.html,使得咱们全部的文件都被放在服务器的static
目录下,此时只能经过/static
来访问到index.html,因此咱们选择去掉这两项设置,讲文件统一编译打包到根目录下。
其余配置项有兴趣的同窗能够到官网的文档里了解。
api
cli的方式虽然很方便很简单,可是若是要使用更多的定制化,api会更灵活一些。
注意,这一部份内容十分重要,由于咱们以后乃至下一节的文章里,都是基于api来配置工做流的,请务必掌握这一节的内容,若是你还有再深刻研究下去的想法的话,不要知足于cli的便捷方式而对这一部份内容粗略带过。
使用api的方式,意味着咱们须要本身来手动定义一个开发服务器,而且补充相关配置项,为了使配置与实现逻辑分开,咱们仍是使用原来那份配置文件webpack.dev.config.js,可是新建一份js文件server.js来编写这个服务器逻辑。
var WebpackDevServer = require('webpack-dev-server') var webpack = require('webpack') var config = require('./webpack.dev.config.js') var path = require('path') var compiler = webpack(config) var server = new WebpackDevServer(compiler, { stats: { colors: true, chunks: false } }) server.listen(8080, 'localhost', function() {})
ok,咱们先来试着跑一下:
node ./server.js
回车,成功啦!并且运行效果貌似跟cli的同样。
这里的内容实在没什么可讲的,咱们首先使用webpack传入配置内容config,获得一个编译的实例,以后咱们再建立一个webpackDevServer的实例(本地服务器),来跑这份编译实例,服务器监听8080端口,因此咱们在浏览器中输入http://localhost:8080就能够访问了,搞定!
到这里就ok了吗?其实尚未,咱们试着修改index.js,而后保存,看看浏览器。
浏览器没有反应,也就是说,这里尚未实现自动刷新浏览器的功能,咱们配置少了一些东西。
好,那咱们接着来。
webpackDevServer的官方文档里其实已经说得很清楚了:
Similar to the inline mode the user must make changes to the webpack configuration.
Three changes are needed:
- add an entry point to the webpack configuration:
webpack/hot/dev-server
.- add the
new webpack.HotModuleReplacementPlugin()
to the webpack configuration.- add
hot: true
to the webpack-dev-server configuration to enable HMR on the server.
这里就算看不懂意思也能猜获得吧~
webpack/hot/dev-server
new webpack.HotModuleReplacementPlugin()
hot: true
二话不说就开搞,咱们按照指示修改获得最终的server.js长这样:
var WebpackDevServer = require('webpack-dev-server') var webpack = require('webpack') var config = require('./webpack.dev.config.js') var path = require('path') for (var key in config.entry) { var entry = config.entry[key] entry.unshift('webpack-dev-server/client?http://localhost:8080', 'webpack/hot/dev-server') } config.plugins.push(new webpack.HotModuleReplacementPlugin()) var compiler = webpack(config) var server = new WebpackDevServer(compiler, { hot: true, stats: { colors: true, chunks: false } }) server.listen(8080, 'localhost', function() {})
都是挺简单的东西没啥看不懂的,咱就不细讲了,直接跑起来!
修改一下index.js,保存!呀,效果出来了,挺好;
修改一下index.scss,呀,效果出来了,倍儿棒;
修改一下index.swig,保存!呀,你咋就没反应了啊。。。
还记得咱们用cli来搭建的时候遇到的相似状况吗?WebpackDevServer的Hot Module Replacement是不支持html模板的,因此这里也同样,怎么解决?
仍是像原来,咱们把hot: true
的配置项去掉就行了,ok,运行一遍,修改,保存,done!
gulpfile.js文件咱们一看就明白,逻辑清晰井井有条,webpack的配置文件则须要细细咀嚼。
相比gulp,webpack构建工做流是否是会复杂些?固然,咱们只是对比来讲,事实上若是只是从操做来看,也并无多复杂。我描述一个感觉,看你们是否是有共鸣:
gulp是一套流程管理工具,专门管理一个个的任务,同时结合一些编译模块来处理各类资源文件;
webpack则是一个大而全的编译器,主要用于各类资源文件的编译和打包工做,由此为中心衍生出了一系列周边工具,好比开发工具,插件等等。
事实上,webpackDevServer编译的文件是存放在内存中的,操做起来速度很是的快,整个的项目编译完成几乎就是那么一个刷新的瞬间,而gulp则仍是硬盘上的操做,速度要慢几个量级,咱们没有感觉到明显的差别,那是由于项目还不够大。
有没有留意到,咱们这一节并无像上一节那样介绍到ftp工具?之因此没有做介绍,是由于webpack并无提供这样的插件或者模块去完成这样的工做,事实上也不该该有,由于webpack自己是一个资源处理器,与项目的上传部署没有太多关系。咱们须要借助其余的npm包,用一种毫无联系的方式去组合这两个功能,咱们可能须要编写一个ftp.js,不知你们是否还记得咱们上一节当中的npm run build && npm run upload
命令?差很少就是这样。这个ftp.js就跟webpack的关系至关微弱了,不像咱们写gulp那样,扩展一个功能的时候,只须要写多一个task。
这里概念很是抽象,我也不能完完整整地传达,不知道你们理解多少。老方法,我仍是举个栗子来讲明:
webpack就比如一套很好用的螺丝刀,基本全部的机器的维修工做都能应付。可是若是须要完整一点的工程,咱们可能还须要大锤啊电钻啊什么乱七八糟的。可是东西一过来可能无法整理到一块儿,只能拿个塑料袋粗略地装起来;
gulp比如一个工具箱,里面也有几件比较简单的螺丝刀,若是有其余的工具,来了就往里面放,妥妥的。
嗯,又是一个不怎么贴切的栗子,凑合着用吧憋唧唧歪歪的。
咱们先理解到这里,进一步的对比,咱们会在教程最后的章节里呈现一次比较细致的说明。
后话:咱们能够把webpack这把好用的螺丝刀放gulp这个工具箱里吗?答案固然是yes!帅(美)的人已经动手了,丑的还在卖萌。下一节【强化:构建易用高可扩展性的工做流】咱们将为你们讲解如何使用gulp结合webpack,各取所长地搭建一套简单易用、高可扩展性的工做流。
本次演示项目的git地址:webpack_base