Open mowatermelon opened 6 years ago
简单的说,Grunt / Gulp 和 browserify / webpack 不是一回事。
1)Gulp / Grunt 可以理解为帮助前端自动化的工具,用于优化前端工作流程。比如自动刷新页面、combo、压缩css、js、编译less等等。二者的区别可以自行百度,个人推荐Gulp。
2)browserify / webpack 提供的是一个前端模块化的方案,和requirejs类似但又有不同。
requirejs是一种在线"编译" 模块的方案,相当于在页面上加载一个AMD 解释器,以便于览器能够识别 define、exports、module,而这些东西就是用于模块化的关键。
而browserify / webpack,则是一个预编译模块的方案。它是预编译的,不需要在浏览器中加载解释器。因此完全可以gulp+webpack 或者gulp+browserify这样的方式搭配使用,甚至,只使用webpack,由于其强大的插件支持,也能满足绝大多数需求了。
相关依赖
"dependencies": {
"gulp": "^3.9.1"
},
"devDependencies": {
"gulp": "^3.9.1",
"gulp-load-plugins": "^1.5.0",
"gulp-clean": "^0.3.2",
"gulp-concat": "^2.6.1",
"gulp-connect": "^5.0.0",
"gulp-cssmin": "^0.2.0",
"gulp-imagemin": "^3.3.0",
"gulp-less": "^3.3.2",
"gulp-uglify": "^3.0.0",
"open": "0.0.5"
}
gulp实战案例
gulpfile.js
var gulp =require('gulp');
var $ = require('gulp-load-plugins')();
var open = require('open');
var app ={
srcPath:'src/',
devPath:'build/',
proPath:'dist/'
}
gulp.task('lib',function(){
gulp.src('lib/**/*.min.js')
.pipe(gulp.dest(app.devPath+'vendor'))
.pipe(gulp.dest(app.proPath+'vendor'))
.pipe($.connect.reload());
});
gulp.task('html',function(){
gulp.src(app.srcPath+'**/*.html')
.pipe(gulp.dest(app.devPath))
.pipe(gulp.dest(app.proPath))
.pipe($.connect.reload());
});
gulp.task('json',function(){
gulp.src(app.srcPath+'data/**/*.json')
.pipe(gulp.dest(app.devPath+'data'))
.pipe(gulp.dest(app.proPath+'data'))
.pipe($.connect.reload());
});
gulp.task('mdcss',function(){
gulp.src('lib/mdui/src/mdui.less')
.pipe($.less())
.pipe(gulp.dest(app.devPath+'css'))
.pipe($.cssmin())
.pipe(gulp.dest(app.proPath+'css'))
.pipe($.connect.reload());
});
gulp.task('mdfont',function(){
gulp.src('lib/mdui/dist/fonts/**/*')
.pipe(gulp.dest(app.devPath+'fonts'))
.pipe(gulp.dest(app.proPath+'fonts'))
.pipe($.connect.reload());
});
gulp.task('mdicon',function(){
gulp.src('lib/mdui/dist/icons/**/*')
.pipe(gulp.dest(app.devPath+'fonts'))
.pipe(gulp.dest(app.proPath+'fonts'))
.pipe($.connect.reload());
});
gulp.task('less',function(){
gulp.src(app.srcPath+'style/index.less')
.pipe($.less())
.pipe(gulp.dest(app.devPath+'css'))
.pipe($.cssmin())
.pipe(gulp.dest(app.proPath+'css'))
.pipe($.connect.reload());
});
gulp.task('js',function(){
gulp.src(app.srcPath+'script/**/*.js')
.pipe($.concat('index.js'))
.pipe(gulp.dest(app.devPath+'js'))
.pipe($.uglify())
.pipe(gulp.dest(app.proPath+'js'))
.pipe($.connect.reload());
});
gulp.task('image',function(){
gulp.src(app.srcPath+'image/**/*')
.pipe(gulp.dest(app.devPath+'image'))
.pipe($.imagemin())
.pipe(gulp.dest(app.proPath+'image'))
.pipe($.connect.reload());
});
gulp.task('build',['image','js','mdcss','mdfont','mdicon','less','lib','html','json']);
gulp.task('serve',['build'],function(){
$.connect.server({
root:[app.devPath],
livereload:true,
port:1234
});
open('http://localhost:1234');
gulp.watch(app.srcPath+'script/**/*.js',['js']);
gulp.watch(app.srcPath+'lib/**/*.js',['lib']);
gulp.watch(app.srcPath+'**/*.html',['html']);
gulp.watch(app.srcPath+'data/**/*.json',['json']);
gulp.watch(app.srcPath+'lib/mdui/src/mdui.less',['mdcss']);
gulp.watch(app.srcPath+'lib/mdui/dist/fonts/**/*',['mdfont']);
gulp.watch(app.srcPath+'lib/mdui/dist/icons/**/*',['mdicon']);
gulp.watch(app.srcPath+'style/index.less',['less']);
gulp.watch(app.srcPath+'image/**/*',['image']);
});
gulp.task('default',['serve']);//输入gulp默认执行的命令
gulp.task('clean',function(){
gulp.src([app.devPath,app.proPath])
.pipe($.clean());
});
jQuery在使用grunt,bootstrap在使用grunt,百度UEditor在使用grunt
cnpm i grunt-cli -g
安装成功之后,在项目中新建Gruntfile.js
文件,注意文件首字母大写
基础案例
运行案例
安装相关依赖包
请注意按需引用
引用相关包
grunt.loadNpmTasks(NpmPackageName)
// 举个例子
grunt.loadNpmTasks('grunt-contrib-watch')
对引入包进行相关配置
NpmPackageName :{
prop1:prop1Value,
prop2:prop2Value
}
// 举个例子
watch:{
build:{
files:['src/*.js','src/*.css'],
tasks:['jshint','uglify'],
options:{spawn:false}
}
}
再执行使用
grunt.registerTask('default',[NpmPackageName1,NpmPackageName2,NpmPackageName3,....])
// 举个例子
grunt.registerTask('default',['jshint','uglify','watch'])
放上完整代码
关于build属性
那么这里是不是必须用“build”这一个名字?答案很明显,当然不是。之前之所以没直接说,是因为要先把插件的安装和配置讲明白,不变一次传输太多知识,现在一并和大家说了。
这里可以用任何字符串代替“build”(但要符合js语法规则)。甚至,你可以把“build”指向的内容分开来写。这样对多人协同开发很友好。好的设计就是这样:让用户尽情发挥他们的自由来干事,而不是去限制他们。
根据自己的需要和项目的特点来取舍吧
grunt是前端工程化的先驱,是yeoman重要的一环,跟gulp类似都是任务执行器,只是gulp提供了更自然基于流的方式连接不同的任务。作为任务执行器理论上可以做跟自动化相关的任何事情,只是在前端用的多些,而且现成的工具也比较多。
webpack更专注于前端的通用解决方案,定义了前端构建的标准模式,同时提供了良好的扩展机制,社区发现良好。
国内百度也有一套开源的解决方案,叫fis,个人感觉语法比较清晰,也很方便定制,通过生成资源表和提供的加载框架在做细粒度的优化上比webpack灵活些(毕竟是国内的,社区就没有webpack那么活跃了,主要是百度的工程师在维护)。
总体对于单纯前端构建来说,小项目差别不大,什么熟用什么吧,大项目用webpack和fis这种后期维护会省力很多
gulp和webpack两者功能有重复,但不是完全的替代关系,可以混用的.
这几样除了 Grunt,你都搞懂了就知道该在哪些场景用用什么最合适了。Grunt 适合那些非 Node 程序员或者从其他语言转过来的开发者。
这些工具我只能说各有特点,我在不同类型的项目中都用过。但是基于本能我会对那些过于“功能丰富”的抱有戒心,例如 Webpack。还有一些强迫症认为 npm scripts 就够了,增加对 gulp 的依赖没必要,这也不是绝对的。
因为稍微复杂一些的控制还是要写 Shell 或者其他 JS 程序来帮助你,否则更麻烦。不过,考虑到实际工作中要创建大量的实验项目,那么用 Gulp 更多,前端会和 Browserify 配合。
但是写 React-Native 的时候往往会再配合 Webpack(让 React Native 更多地利用 Node.JS 的资产),因为 Browserify 在这块还没人研究好。
最近写了好几个 Yeoman Generator 来加速实验项目的创建,生成的脚手架也是以 Gulp 为主。
还有一个最重要的,Gulp 是基于 Node Stream 创建的,Node Stream 是 Node.JS 中及其重要的设计模式,天生就具有良好的互操作性,这是 Webpack 比不了的(但同时也是很多不熟悉 Node Stream 的人不愿意用 Gulp 的原因)。
而 Webpack 的优势在于对于复杂的依赖树进行重新批分,以便于前端多页面的复用,这是唯一的优势。但是其自身的 Require 实现也是对目前 Node.JS 的模块系统的一种污染。
用过gulp,最近在琢磨webpack。
感觉两者理念不太同:gulp是将一系列常规的task按部就班的自动执行,比如scripts和less各自的compile-minify-transfer等等,它从使用者的角度出发,做的还是那些大家手动会去做的事,只不过自动化了,通过一个脚本来执行,grunt类似;
而webpack则把scripts和static files均视为module,通过loader(比如babel-loader\less-loader,分别用以把es6转为es5和less转为css)的处理,将之转化为项目所需的所谓的assets。
它从项目的角度出发,通过配置(webpack.config.js)实现,因此更加省心(理论上,因为不用考虑脚本的逻辑,当然,看起来可配置的项还是挺复杂的,不过这些都是死的吧,而且常用的就那几项)。
webpack吓我一大蹦的地方在于,一执行命令,就只需引入一个bundle.js,样式脚本啥啥啥的都有了,哭。
gulp 和 grunt 都有的扩展包
clean 清空文件 文件夹 (grunt-contrib-clean和 gulp-clean) concat 文件,文件夹合并 (grunt-contrib-concat和 gulp-concat)
uglify 压缩js(grunt-contrib-uglify和 gulp-uglify)
gulp独有
gulp-load-plugins 加载gulp的各种扩展包,不用像grunt那样需要自己一个一个写扩展包引用,这边可以直接通过这个配置插件,直接就加载对应扩展包,就可以进行使用了
var gulp =require('gulp');
var $ = require('gulp-load-plugins')();//引入配置包的时候注意要在最后写一个括号,表示直接执行
var app ={
srcPath:'src/',
devPath:'build/',
proPath:'dist/'
}
gulp.task('lib',function(){
gulp.src('lib/**/*.min.js')
.pipe(gulp.dest(app.devPath+'vendor'))
.pipe(gulp.dest(app.proPath+'vendor'))
.pipe($.connect.reload());//这样可以直接就通过$.NpmPackageName就可以调用了
});
cssmin 压缩css (gulp-cssmin)
imagemin 压缩图片 (gulp-imagemin)
less 函数css (gulp-less)
grunt独有
copy 复制文件,文件夹 (grunt-contrib-copy ),有点类似gulp中的gulp.dest,对重新编译之后的文件生成到制定文件夹
watch 监听文件变化 (grunt-contrib-watch )
// 引用相关包
grunt.loadNpmTasks('grunt-contrib-watch')
// 对引入包进行相关配置
watch:{
build:{
files:['src/*.js','src/*.css'],
tasks:['jshint','uglify'],
options:{spawn:false}
}
}
//再执行使用
grunt.registerTask('default',['watch'])
总结来说
webpack , browserify , rollup是一类,主攻javascript模块打包方案(方案+工具+插件),webpack更专注于前端的通用解决方案,定义了前端构建的标准模式,同时提供了良好的扩展机制,社区发现良好。
但是grunt , gulp , 主攻前端工具,结合插件,合并、压缩、编译 sass/less,browser 自动载入资源,grunt是前端工程化的先驱,是yeoman重要的一环,跟gulp类似都是任务执行器,只是gulp提供了更自然基于流的方式连接不同的任务。作为任务执行器理论上可以做跟自动化相关的任何事情,只是在前端用的多些,而且现成的工具也比较多。
https://blog.csdn.net/u014168594/article/details/77198729
webpack并不强制你使用某种模块化方案,而是通过兼容所有模块化方案让你无痛接入项目。有了webpack,你可以随意选择你喜欢的模块化方案,至于怎么处理模块之间的依赖关系及如何按需打包,webpack会帮你处理好的。
关于模块化的一些内容,可以看看我之前的文章:js的模块化进程
一切皆模块:
正如js文件可以是一个“模块(module)”一样,其他的(如css、image或html)文件也可视作模 块。因此,你可以require(‘myJSfile.js’)亦可以require(‘myCSSfile.css’)。这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重复利用等的目的。
按需加载: 传统的模块打包工具(module bundlers)最终将所有的模块编译生成一个庞大的bundle.js文件。但是在真实的app里边,“bundle.js”文件可能有10M到15M之大可能会导致应用一直处于加载中状态。因此Webpack使用许多特性来分割代码然后生成多个“bundle”文件,而且异步加载部分代码以实现按需加载。
把所有依赖打包成一个bundle.js文件,通过代码分割成单元片段并按需加载。
如图,entry.js是入口文件,调用了util1.js和util2.js,而util1.js又调用了util2.js。
打包后的bundle.js例子
/******/ ([
/* 0 */ //模块id
/***/ function(module, exports, __webpack_require__) {
__webpack_require__(1); //require资源文件id
__webpack_require__(2);
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
//util1.js文件
__webpack_require__(2);
var util1=1;
exports.util1=util1;
/***/ },
/* 2 */
/***/ function(module, exports) {
//util2.js文件
var util2=1;
exports.util2=util2;
/***/ }
...
...
/******/ ]);
id
为记号,通过函数把各个文件依赖封装达到分割效果,如上代码 id 为 0
表示 entry
模块需要的依赖, 1
表示 util1
模块需要的依赖_webpack_require__(1)
表示 util1.js
模块,__webpack_require__(2)
表示 util2.js 模块exports.util1=util1
模块化的体现,输出该模块承担了很多前端代码的自动化生成的工作。 搭配很多loader,可以完成不同的事情。 比如说把繁杂的项目中用到的js打包并压缩成你设定的一个或多个文件去发布到生产环境。 比如把react中引用的图片压缩并抽出来项目中引用到的。没引用到的,不会拿过来。 比如CSS Modules,单个组件开发时用单独的css定义,最终打包成一个css文件里,而没用到的css不会打包。而且通过设置,不用担心各个文件中的同名css打包到一个css里后重复。 less转css sass转css PostCSS转css es6写的js,通过babel转成浏览器可执行的es5 ......
原理,其实就是用程序代替人的工作。比如我们开发的时候经常是css写到一个或几个文件,随着迭代,里面肯定就产生很多垃圾不再用的代码,而用人去识别很累,如果用CSS Modules,把css也模块化,随着迭代,不再引用到的,就不会再打包进去,不用人来识别了。同理,浏览器是不识别sass这种的,程序来帮你转成浏览器可读的css。 不管用什么程序,其实都可以开发这样的一个解释器,其实就是一个翻译转化的过程。只不过当前前端的发展需要和node的便捷加上npm方便的分发,所以在node中,大量的工具出现了。
其实原理和功能,就是用程序代替人做那些事,由程序翻译成普通浏览器可执行的代码。
原理: 一切皆为模块,由于webpack并不支持除.js以外的文件,从而需要使用loader转换成webpack支持的模块,plugin用于扩展webpack的功能,在webpack构建生命周期的过程在合适的时机做了合适的事情。
先了解一下Webpack从构建到输出文件结果的过程: 1.解析配置参数,合并从shell传入和webpack.config.js文件的配置信息,输出最终的配置信息 2.注册配置中的插件,好让插件监听webpack构建生命周期中的事件节点,做出对应的反应 3.解析配置文件中entry入口文件,并找出每个文件依赖的文件,递归下去 4.在递归每个文件的过程中,根据文件类型和配置文件中loader找出相对应的loader对文件进行转换 5.递归结束之后得到每个文件最终的结果,根据entry配置生成代码chunk 6.输出所有chunk到文件系统
webpack.base.conf.js
var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}
webpack.dev.conf.js
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// cheap-module-eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
new FriendlyErrorsPlugin()
]
})
webpack.prod.conf.js
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = process.env.NODE_ENV === 'testing'
? require('../config/test.env')
: config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: process.env.NODE_ENV === 'testing'
? 'index.html'
: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
webpack.test.conf.js
// This is the webpack config used for unit tests.
var utils = require('./utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseConfig = require('./webpack.base.conf')
var webpackConfig = merge(baseConfig, {
// use inline sourcemap for karma-sourcemap-loader
module: {
rules: utils.styleLoaders()
},
devtool: '#inline-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/test.env')
})
]
})
// no need for app entry during tests
delete webpackConfig.entry
module.exports = webpackConfig
webpack-dev-server在webpack的watch基础上开启服务器。
webpack-dev-server是运行在内存中的开发服务器,支持高级webpack特性hot module replacement。这对于react vue这种组件化开发是很方便的。
使用webpack-dev-server命令开启服务器,配合HMR及可以实现代码更改浏览器局部刷新的能力。
Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.
当应用在运行期间hmr机制能够修改、添加、或者移除相应的模块,而不使整个页面刷新。 hmr机制适用于单页应用。
要实现hmr机制,需要配合webpack-dev-server服务器,这个服务器本身就实现了监察watch文件改动的能力,再开启HMR选项,就添加了watch模块变化的能力。这是HMR机制能生效的基础。
每次修改一个模块的时候,webpack会生成两部分,一个是manifest.json,另一部分是关于这次模块更新编译完成的chunks。manifest.json中存着的是chunk更改前后的hash值。
从编译器webpack的角度来讲提供了hmr的原材料。供后续使用。
模块发生变化时,webpack会生成之前讲过的两部分基础文件,但是何时将变化后的模块应用到app中去?这里就需要在应用代码中编写handler去接受到模块变化信息。但是不能在所有模块中编写handler吧?这里就用到了消息冒泡机制。
Scalable webpack configurations
在单个配置文件中维护配置,但是区分好条件分支。调用不同的npm命令时候设置不同的环境变量,然后在分支中匹配,返回我们需要的配置文件。
这样做的好处可以在一个文件中管理不同npm操作的逻辑,并且可以共用相同的配置。webpack-merge这个模块可以起到合并配置的作用。
Webpack与Gulp、Grunt没有什么可比性,它可以看作模块打包机,通过分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优点使得Webpack在很多场景下可以替代Gulp/Grunt类的工具。
他们的工作方式也有较大区别:
Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。
Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。