Grunt构建工具实战踩坑指南从入门到放弃再到重新认识

Mc.园园 前端 阅读 2,854
赞 17 收藏
二维码
手机扫码查看
反馈

Grunt、Gulp、Webpack,我为什么还是怀念那个年代

最近新项目都用Vite了,回头看看Grunt这些老工具,突然有种想聊聊的想法。其实我比较喜欢用Gulp,但这几年Grunt给我踩了不少坑,也让我学会了很多东西。现在前端构建工具迭代太快,回头看这些”古老”的构建工具,反而能看清一些本质问题。

Grunt构建工具实战踩坑指南从入门到放弃再到重新认识

Grunt vs Gulp vs Webpack:谁更灵活?谁更省事?

先说结论吧,我当年项目里主要用Grunt,后来转向Gulp,现在基本都是Webpack/Vite了。但这几个工具各有特点,有些场景下Grunt还是很管用的。

先看Grunt的配置,这是我最熟悉的:

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */n'
      },
      build: {
        src: 'src/<%= pkg.name %>.js',
        dest: 'build/<%= pkg.name %>.min.js'
      }
    },
    
    sass: {
      dist: {
        files: {
          'css/main.css': 'scss/main.scss'
        }
      }
    },
    
    watch: {
      css: {
        files: 'scss/**/*.scss',
        tasks: ['sass']
      },
      scripts: {
        files: 'js/**/*.js',
        tasks: ['uglify']
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', ['sass', 'uglify']);
};

Grunt的问题就是配置太冗长了,而且它是基于文件的,每次都要读写硬盘,速度比较慢。但我当年选择Grunt主要是因为插件生态很丰富,遇到什么需求基本都能找到对应的grunt插件。现在想想其实这也是个陷阱,太多插件反而让人依赖。

再说Gulp,这是我后来转向的选择:

const { src, dest, watch, series } = require('gulp');
const uglify = require('gulp-uglify');
const sass = require('gulp-sass')(require('sass'));
const concat = require('gulp-concat');

function jsTask() {
  return src('src/*.js')
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(dest('dist/'));
}

function sassTask() {
  return src('scss/*.scss')
    .pipe(sass())
    .pipe(dest('css/'));
}

function watchTask() {
  watch(['scss/*.scss'], sassTask);
  watch(['src/*.js'], jsTask);
}

exports.default = series(sassTask, jsTask);
exports.watch = watchTask;

Gulp最大的优势是基于流的操作,速度快很多。代码也更直观,看着像写正常的JavaScript代码。这里的stream处理方式比Grunt的文件IO要高效得多。我踩过好几次坑就是在Grunt的文件处理上,特别是大项目里,每次build都很慢。

Webpack就更不用说了,现在项目的标配:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      },
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      }
    ]
  },
  devServer: {
    contentBase: './dist',
  }
};

性能差距比我想象的大

当年做性能测试的时候,发现差距还挺明显的。一个小项目(大概十几个JS文件,几个SCSS文件),Grunt完整build需要15-20秒,Gulp只要3-5秒,Webpack(配置好的情况下)基本上2秒内完成。这不是理论数据,而是我真正在项目里测出来的。

Grunt慢的原因主要是它的工作原理:读取文件 -> 处理 -> 写入文件 -> 下一个任务。这样反复的磁盘I/O确实拖慢了速度。而Gulp的流式处理就聪明多了,数据在内存里流转,减少了磁盘操作。

这里注意我踩过好几次坑的地方:Grunt的并发处理能力很差,如果你有多个并行任务,很容易卡住。Gulp在这方面就做得很好,可以轻松配置并行任务:

const { parallel } = require('gulp');

exports.build = parallel(jsTask, sassTask, imageTask);

我的选型逻辑

说说我现在的选型思路。如果是新项目,我肯定选Webpack或者Vite,这套生态已经非常成熟了。但如果是个老项目,或者只需要简单的文件处理,我会考虑要不要用Gulp。

Grunt现在基本不考虑了,除非是维护特别老的项目。但有个场景我还是会想到Grunt:当你需要处理大量静态资源文件,而且这些文件处理逻辑相对固定的时候,Grunt的声明式配置反而更清晰。比如批量压缩图片、处理各种静态文件,Grunt的配置虽然冗长,但是不容易出错。

我曾经在一个项目里遇到过这样的需求:需要把不同目录下的图片按规则移动到指定位置,然后压缩。这种纯文件操作的任务,用Grunt反而更简单直接:

grunt.initConfig({
  copy: {
    main: {
      files: [
        {expand: true, cwd: 'src/images/', src: ['**'], dest: 'dist/images/'}
      ]
    }
  },
  imagemin: {
    dynamic: {
      files: [{
        expand: true,
        cwd: 'dist/images/',
        src: ['**/*.{png,jpg,gif}'],
        dest: 'dist/images/'
      }]
    }
  }
});

但总的来说,Gulp的灵活性更好。你可以写任意的JavaScript代码来处理构建逻辑,这在复杂项目里很有用。Webpack虽然学习成本高,但模块打包能力确实是目前最强的。

踩坑提醒:这几点一定注意

使用这些工具时有几个常见坑需要注意。Grunt的watch任务容易内存泄漏,长时间运行会导致系统变慢。我当时用了grunt-contrib-watch,后来改成了更轻量的chokidar来监听文件变化。

Gulp的一个问题是错误处理,如果某个task出错了,默认会停止整个构建流程。这在开发环境下不太友好,我当时用了gulp-plumber这个插件来处理错误:

const plumber = require('gulp-plumber');

function sassTask() {
  return src('scss/*.scss')
    .pipe(plumber()) // 错误不会中断流程
    .pipe(sass())
    .pipe(dest('css/'));
}

Webpack的坑就更多了,配置复杂是公认的,但一旦配好了维护起来反而简单。Grunt的配置文件越来越庞大时,管理和维护就成了问题,特别是多人协作的项目。

以上是我个人对这几个构建工具的完整对比,有更优的实现方式欢迎评论区交流。这个话题其实还可以深入很多,比如构建缓存、增量编译等高级特性,后续可能会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论