应用层打包工具——webpack

一开始学webpack其实有点逼不得已,作为一个奉行保守主义的人,对于新东西都会有一种观望的心态,这不是重点,我不会告诉你其实是因为我懒,真的是因为Gulp在简单的项目上已经足够好用。

Gulp本身只是一个框架,通过安装各种插件,可以实现从监听-打包-合并-压缩-编译一系列流程,管道的概念在使用的过程中跟它的前身Grunt比起来简直舒服太多。(其实我也没怎么用过grunt……真的不是我懒,是因为出生太晚,我开始接触这类工具时Gulp已经流行起来了)

然而,在开始实践组件化开发之后,发现一个有点邪乎的事情:gulp很难处理较为复杂的依赖关系,各组件之间相互引入,写配置文件时会写得万念俱灰。经常哭得把我家狗狗吓出房间……

即便是一个最简单的项目,也需要做出如下的路径配置:

image_1ba4d13c41k95suss3vtn0spj9.png-13kB

并且,React、Vue、Angular2,这三大MVVM框架都将webpack作为官方推荐的打包工具,虽然还是有点怀念Gulp,但不得不追随神的指引——去跳Webpack的坑。

webpack最早只是作为一个打包工具出现,也就是只操作js代码,但由于它智能寻找依赖的特点,很快插件数量就直追而上,就功能而言,现在基本能够完全取代Gulp,加上基于Express框架开发的webpack-dev-server,让它有了比Gulp家的browser-sync更赞的热模块替换功能,browser-sync在监听到代码修改后会重载代码并刷新页面,而Webpack的热模块替换可以做到在不刷新页面的情况下替换代码。

你没有听错,是在不刷新页面的情况下替换代码

上面说的Webpack可以智能查找依赖,是指:无论有多少个组件导入,它会自己寻找到对应的组件并打包在一起,不需要使用者操心。

如果是Gulp,则需要像上面那样配置一大串路径并且挨个注入监听才行。(怎么注入监听,下面会有例子)

Gulp最得意的莫过于管道式的工作方式,但webpack轻而易举地就做到了,并且书写起来更简洁明了,例如一个最普通的sass打包函数,gulp首先要导入sass编译模块,配置好输入路径和输出路径,还需要将这个函数注入到监听的服务里:

var sass = require('sass');

gulp.task('sass', function() {
    return gulp.src('./src/*.scss')
        .pipe(sass())
        .pipe(gulp.dest("dist/"));
});

其实这很清晰明了,但有个前提,就是你需要知道哪些CSS是你的应用真正用到的,如果项目庞大,那就不得不硬着头皮去维护一个无聊的依赖文件列表,或是干脆包含一整个目录的文件。

举个栗子说清楚一些:假设你在开发时需要用到一张图片,如果开发完成后你忘记把这张图片从开发目录中删除,那恭喜你,即便这张图片没有被任何代码引用,也照样会成功打包进入到生产环境中。

这样一来,很有可能引入一些冗余的代码或者一些静态文件,而且只能寄托命运让你在某一天醍醐灌顶突然发现这张图片并没有被引用。

造成这种隐患的原因是:Gulp是根据路径打包的,不管这个文件有没有用到,只要它的位置在Gulp的处理序列内,它就会处理!而Webpack的巧妙在于,被用到的它才打包,没用到的则是视而不见!这就是按需打包的概念。

此外,还有一处事关体验的地方,Gulp一旦检测到报错信息,就会退出,也就是你写css代码的时候多打了个分号,尽管眼睛看见了,然而身体的反射神经迟钝,手不听使唤按了保存,世界就毁灭了!你只能叹息一声,乖乖把分号删掉,然后回到终端(控制台)重启Gulp服务。

猜猜是谁已经做过无数次这种事……

而Webpack会显示报错信息,但是不会宕机,等你修正了错误之后,它会重新编译。有追求的工具就应该这样!!!

上面列出了Gulp处理sass的代码量,相对来说,webpack就简单多了:

{
  test: /\.scss$/,
  use: 'style!css!sass'
}

// 不过这么写的话,css最终会被打包到js文件里,如果想将css单独打包出来,还需要安装插件,最终写成下面这样

const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    module: {
        ...
        rules: [
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    fallbackLoader: 'style-loader',
                    loader: 'css-loader!sass-loader'
                })
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin('./[name].css')
    ]
};

悲催,这好像比Gulp的代码量更多……

不!!以上看到的都是表面现象!!

不信来份真的比较一下!

Gulp先来!

var gulp        = require('gulp');
var browserSync = require('browser-sync').create();
var sass        = require('gulp-sass');
var prefix      = require('gulp-autoprefixer');
var cssmin      = require('gulp-clean-css');
var htmlmin     = require('gulp-htmlmin');
var imagemin    = require('gulp-imagemin');
var jsmin       = require('gulp-uglify');
var rename      = require('gulp-rename');
var reload      = browserSync.reload;

var Asset = {       // 配置监听路径
    html: './src/html/*.html',
    js: './src/js/*.js',
    sass: './src/sass/*.scss',
    img: './src/images/*'
}

// 启动静态服务器
gulp.task('server', function() {
    browserSync.init({
        server: "."
    });
});

gulp.task('html', function() {      // html代码的处理模块
  gulp.src(Asset.html)
    .pipe(htmlmin({collapseWhitespace: true}))
    .pipe(gulp.dest('dist/'));
});

gulp.task('img', function() {       // 图片的处理模块,负责压缩图片什么的
    return gulp.src(Asset.img)
        .pipe(imagemin({optimizationLevel: 7}))
        .pipe(gulp.dest('dist/images/'));
});

gulp.task('js', function() {        // js的处理模块
  return gulp.src(Asset.js)
    .pipe(jsmin())
    .pipe(rename({suffix: '.min'}))
    .pipe(gulp.dest('dist/'));
});

gulp.task('sass', function() {      // sass处理模块
    return gulp.src(Asset.sass)
        .pipe(sass())
        .pipe(prefix())
        .pipe(rename({suffix: '.min'}))
        .pipe(cssmin())
        .pipe(gulp.dest("dist/"))
        .pipe(reload({stream: true}));
});
gulp.task('watch', function() {     // 监听服务
    gulp.watch(Asset.sass, ['sass'], reload);
    gulp.watch(Asset.js, ['js'], reload);
    gulp.watch(Asset.html, ['html'], reload);
    gulp.watch(Asset.img, ['img'], reload);
    gulp.watch("*").on('change', reload);
    gulp.watch("./dist/**/*.*").on('change', reload);
});

// 启动
gulp.task('default', ['sass', 'img', 'html', 'js', 'server', 'watch']);

那些中括号里面的东东就是注入

image_1ba4ggm2nct1glb1biotec15cv9.png-43kB

这里说的注入跟Angular的依赖注入概念基本一样!注入这个概念理解起来可能比较困难,换一个词,应该叫反转,概念是这样的:一般的函数形参个数都……

呃……不跑题哈~

然后是Webpack选手

const path = require('path');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    context: __dirname + '/src',
    entry: {
        index: './js/index.js'
    },
    output: {
        filename: '[name].bundle.js',
        path: __dirname + '/dist'
    },
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    fallbackLoader: 'style-loader',
                    loader: 'css-loader!sass-loader'
                })
            },

            {
              test: /\.html$/,
              use: 'html-withimg-loader'
            },
            {
              test: /\.(png|jpg)$/,
              use: 'url-loader?limit=8192&name=images/[name].[ext]'
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin('./[name].css'),
        new HtmlWebpackPlugin({
            template: './html/index.html',
            filename: './index.html',
            chunks: ['index']
        })
    ]
};

以上两份配置文件完成的是差不多的工作,代码量……

呃……其实也就是半斤八两,别天真了,成熟一点:少写几行代码并不能体现出什么优越性……

那么从这里开始,态度要转弯了……

上面说了,文件较多的时候,配置Gulp的各种路径会万念俱灰,那么……配置Webpack的时候会比万念俱灰更加万念俱灰,跟webpack相比,Gulp倒是显得挺傻瓜。其实,单纯比较代码量的话,Webpack配置文件的代码量确实是少些,然而Webpack太过于博大精深,要理解它需要付出不少努力。

学了Webpack是不是就能召唤神龙?

No!!经由Webpack打包出来的代码可读性很差,尽管很少人会闲着没事去读打包后的代码,但前端近几年迭代太快,有人诟病就会有人创新,号称下一代打包工具的Rollup已经在崛起,它进一步升级了“按需打包”的概念,只是各类插件尚不成熟,因此就目前来看,将Webpack用作应用层的打包还是最合适的。

等我跳了Rollup的坑再来跟各位介绍~


夜鹰
742 声望7 粉丝

优秀,是一种习惯……