前端项目构建实战

使用 Gulp 构建完整的前端项目


📋 目录


项目结构

推荐的项目结构

project/
├── src/                          # 源文件
│   ├── assets/                   # 静态资源
│   │   ├── fonts/               # 字体文件
│   │   └── icons/               # 图标
│   ├── images/                  # 图片
│   │   └── sprites/             # 雪碧图
│   ├── js/                      # JavaScript
│   │   ├── components/          # 组件
│   │   ├── utils/               # 工具函数
│   │   ├── app.js               # 入口文件
│   │   └── main.js              # 主逻辑
│   ├── scss/                    # Sass 样式
│   │   ├── base/                # 基础样式
│   │   ├── components/          # 组件样式
│   │   ├── layout/              # 布局样式
│   │   ├── pages/               # 页面样式
│   │   ├── utils/               # 工具样式
│   │   ├── vendors/             # 第三方样式
│   │   └── app.scss             # 主样式文件
│   └── templates/               # 模板文件
│       ├── layouts/             # 布局模板
│       ├── partials/            # 局部模板
│       └── pages/               # 页面模板
├── dist/                         # 构建输出(自动生成)
├── public/                       # 静态文件(直接复制)
├── gulpfile.js                  # Gulp 配置
├── gulpfile.config.js           # Gulp 配置文件
├── package.json                 # 项目配置
└── README.md                    # 项目说明

初始化项目

1. 创建项目目录

mkdir my-project
cd my-project

2. 初始化 npm

npm init -y

3. 安装 Gulp 和插件

# 安装 Gulp
npm install --save-dev gulp gulp-cli
 
# 安装核心插件
npm install --save-dev \
  gulp-uglify \
  gulp-babel @babel/core @babel/preset-env \
  gulp-sass sass \
  gulp-postcss autoprefixer cssnano \
  gulp-clean-css \
  gulp-htmlmin \
  gulp-imagemin \
  gulp-concat \
  gulp-rename \
  gulp-sourcemaps \
  browser-sync \
  del \
  gulp-plumber \
  gulp-notify \
  gulp-size \
  gulp-changed

4. 创建配置文件

// gulpfile.config.js
module.exports = {
  paths: {
    src: {
      js: 'src/js/**/*.js',
      scss: 'src/scss/**/*.scss',
      html: 'src/*.html',
      images: 'src/images/**/*',
      fonts: 'src/assets/fonts/**/*'
    },
    dest: {
      js: 'dist/js',
      css: 'dist/css',
      html: 'dist',
      images: 'dist/images',
      fonts: 'dist/fonts'
    }
  },
  options: {
    uglify: {
      compress: {
        drop_console: true
      }
    },
    babel: {
      presets: ['@babel/preset-env']
    },
    sass: {
      outputStyle: 'expanded'
    },
    postcss: [
      require('autoprefixer')(),
      require('cssnano')()
    ],
    htmlmin: {
      collapseWhitespace: true,
      removeComments: true,
      minifyCSS: true,
      minifyJS: true
    },
    imagemin: {
      quality: 85
    }
  }
};

配置开发环境

开发环境特点

  • 快速构建
  • 生成 Source Map
  • 保留未压缩代码
  • 热重载
  • 错误提示

开发环境配置

// gulpfile.js
const { src, dest, watch, series, parallel } = require('gulp');
const browserSync = require('browser-sync').create();
const config = require('./gulpfile.config.js');
 
// JavaScript 处理(开发环境)
function scriptsDev() {
  const babel = require('gulp-babel');
  const sourcemaps = require('gulp-sourcemaps');
  const plumber = require('gulp-plumber');
  const notify = require('gulp-notify');
 
  return src(config.paths.src.js, { sourcemaps: true })
    .pipe(plumber({
      errorHandler: notify.onError('Error: <%= error.message %>')
    }))
    .pipe(sourcemaps.init())
    .pipe(babel(config.options.babel))
    .pipe(sourcemaps.write('.'))
    .pipe(dest(config.paths.dest.js))
    .pipe(browserSync.stream());
}
 
// CSS 处理(开发环境)
function stylesDev() {
  const sass = require('gulp-sass')(require('sass'));
  const sourcemaps = require('gulp-sourcemaps');
  const postcss = require('gulp-postcss');
  const plumber = require('gulp-plumber');
  const notify = require('gulp-notify');
 
  return src(config.paths.src.scss, { sourcemaps: true })
    .pipe(plumber({
      errorHandler: notify.onError('Error: <%= error.message %>')
    }))
    .pipe(sourcemaps.init())
    .pipe(sass(config.options.sass))
    .pipe(postcss([require('autoprefixer')()]))
    .pipe(sourcemaps.write('.'))
    .pipe(dest(config.paths.dest.css))
    .pipe(browserSync.stream());
}
 
// HTML 处理(开发环境)
function htmlDev() {
  const fileInclude = require('gulp-file-include');
  const plumber = require('gulp-plumber');
 
  return src(config.paths.src.html)
    .pipe(plumber())
    .pipe(fileInclude({
      prefix: '@@',
      basepath: '@file'
    }))
    .pipe(dest(config.paths.dest.html))
    .pipe(browserSync.stream());
}
 
// 图片处理(开发环境)
function imagesDev() {
  return src(config.paths.src.images)
    .pipe(dest(config.paths.dest.images));
}
 
// 启动开发服务器
function server() {
  browserSync.init({
    server: {
      baseDir: './dist'
    },
    port: 3000,
    open: true,
    notify: false
  });
}
 
// 文件监听
function watchFiles() {
  watch(config.paths.src.js, scriptsDev);
  watch(config.paths.src.scss, stylesDev);
  watch(config.paths.src.html, htmlDev);
  watch(config.paths.src.images, imagesDev);
}
 
// 开发任务
exports.dev = series(
  parallel(scriptsDev, stylesDev, htmlDev, imagesDev),
  parallel(server, watchFiles)
);

配置生产环境

生产环境特点

  • 代码压缩
  • 图片优化
  • 文件哈希
  • 资源优化
  • 性能优化

生产环境配置

// JavaScript 处理(生产环境)
function scriptsProd() {
  const babel = require('gulp-babel');
  const uglify = require('gulp-uglify');
  const concat = require('gulp-concat');
  const rename = require('gulp-rename');
  const sourcemaps = require('gulp-sourcemaps');
  const size = require('gulp-size');
 
  return src(config.paths.src.js)
    .pipe(sourcemaps.init())
    .pipe(babel(config.options.babel))
    .pipe(concat('app.js'))
    .pipe(dest(config.paths.dest.js))
    .pipe(uglify(config.options.uglify))
    .pipe(rename({ suffix: '.min' }))
    .pipe(sourcemaps.write('.'))
    .pipe(size({ showFiles: true, gzip: true }))
    .pipe(dest(config.paths.dest.js));
}
 
// CSS 处理(生产环境)
function stylesProd() {
  const sass = require('gulp-sass')(require('sass'));
  const postcss = require('gulp-postcss');
  const cleanCSS = require('gulp-clean-css');
  const concat = require('gulp-concat');
  const rename = require('gulp-rename');
  const sourcemaps = require('gulp-sourcemaps');
  const size = require('gulp-size');
 
  return src(config.paths.src.scss)
    .pipe(sourcemaps.init())
    .pipe(sass(config.options.sass))
    .pipe(postcss(config.options.postcss))
    .pipe(concat('app.css'))
    .pipe(dest(config.paths.dest.css))
    .pipe(cleanCSS())
    .pipe(rename({ suffix: '.min' }))
    .pipe(sourcemaps.write('.'))
    .pipe(size({ showFiles: true, gzip: true }))
    .pipe(dest(config.paths.dest.css));
}
 
// HTML 处理(生产环境)
function htmlProd() {
  const htmlmin = require('gulp-htmlmin');
  const fileInclude = require('gulp-file-include');
 
  return src(config.paths.src.html)
    .pipe(fileInclude({
      prefix: '@@',
      basepath: '@file'
    }))
    .pipe(htmlmin(config.options.htmlmin))
    .pipe(dest(config.paths.dest.html));
}
 
// 图片处理(生产环境)
function imagesProd() {
  const imagemin = require('gulp-imagemin');
 
  return src(config.paths.src.images)
    .pipe(imagemin([
      imagemin.gifsicle({ interlaced: true }),
      imagemin.mozjpeg({ quality: 85, progressive: true }),
      imagemin.optipng({ optimizationLevel: 5 }),
      imagemin.svgo({
        plugins: [
          { removeViewBox: true },
          { cleanupIDs: false }
        ]
      })
    ]))
    .pipe(dest(config.paths.dest.images));
}
 
// 字体处理
function fonts() {
  return src(config.paths.src.fonts)
    .pipe(dest(config.paths.dest.fonts));
}
 
// 清理构建目录
function clean() {
  const del = require('del');
  return del(['dist/**/*']);
}
 
// 构建任务
exports.build = series(
  clean,
  parallel(scriptsProd, stylesProd, htmlProd, imagesProd, fonts)
);

优化构建性能

1. 增量构建

const changed = require('gulp-changed');
 
function images() {
  return src('src/images/**/*')
    .pipe(changed('dist/images'))  // 只处理修改过的图片
    .pipe(imagemin())
    .pipe(dest('dist/images'));
}

2. 缓存处理结果

const cache = require('gulp-cache');
 
function images() {
  return src('src/images/**/*')
    .pipe(cache(imagemin([
      imagemin.jpegtran({ progressive: true }),
      imagemin.optipng({ optimizationLevel: 5 })
    ])))
    .pipe(dest('dist/images'));
}

3. 并行执行任务

// 串行(慢)
exports.build = series(scripts, styles, html, images);
 
// 并行(快)
exports.build = parallel(scripts, styles, html, images);
 
// 混合(推荐)
exports.build = series(
  clean,
  parallel(scripts, styles, html, images)
);

4. 使用 gulp.lastRun

const { lastRun } = require('gulp');
 
function scripts() {
  return src('src/js/**/*.js', {
    since: lastRun(scripts)  // 只返回自上次运行后修改的文件
  })
    .pipe(babel())
    .pipe(dest('dist/js'));
}

5. 多核构建

const os = require('os');
const cluster = require('cluster');
 
function parallelBuild() {
  const cpuCount = os.cpus().length;
  
  if (cluster.isMaster) {
    // 创建工作进程
    for (let i = 0; i < cpuCount; i++) {
      cluster.fork();
    }
  } else {
    // 工作进程执行任务
    return parallel(scripts, styles, images);
  }
}

完整配置示例

package.json

{
  "name": "gulp-project",
  "version": "1.0.0",
  "description": "Gulp 前端项目构建",
  "scripts": {
    "dev": "gulp dev",
    "build": "gulp build",
    "clean": "gulp clean",
    "serve": "gulp serve"
  },
  "devDependencies": {
    "@babel/core": "^7.15.0",
    "@babel/preset-env": "^7.15.0",
    "autoprefixer": "^10.3.0",
    "browser-sync": "^2.27.0",
    "cssnano": "^5.0.0",
    "del": "^6.0.0",
    "gulp": "^4.0.2",
    "gulp-babel": "^8.0.0",
    "gulp-clean-css": "^4.3.0",
    "gulp-concat": "^2.6.1",
    "gulp-file-include": "^2.3.0",
    "gulp-htmlmin": "^5.0.1",
    "gulp-if": "^3.0.0",
    "gulp-imagemin": "^8.0.0",
    "gulp-notify": "^4.0.0",
    "gulp-plumber": "^1.2.1",
    "gulp-postcss": "^9.0.0",
    "gulp-rename": "^2.0.0",
    "gulp-sass": "^5.0.0",
    "gulp-size": "^4.0.1",
    "gulp-sourcemaps": "^3.0.0",
    "gulp-uglify": "^3.0.2",
    "sass": "^1.38.0"
  }
}

完整 gulpfile.js

const { src, dest, watch, series, parallel, lastRun } = require('gulp');
const browserSync = require('browser-sync').create();
const config = require('./gulpfile.config.js');
 
// 判断是否为生产环境
const isProduction = process.env.NODE_ENV === 'production';
 
// 清理构建目录
function clean() {
  const del = require('del');
  return del(['dist/**/*']);
}
 
// JavaScript 处理
function scripts() {
  const babel = require('gulp-babel');
  const uglify = require('gulp-uglify');
  const concat = require('gulp-concat');
  const rename = require('gulp-rename');
  const sourcemaps = require('gulp-sourcemaps');
  const plumber = require('gulp-plumber');
  const size = require('gulp-size');
 
  return src(config.paths.src.js, { 
    sourcemaps: !isProduction,
    since: lastRun(scripts)
  })
    .pipe(plumber({
      errorHandler: require('gulp-notify').onError('Error: <%= error.message %>')
    }))
    .pipe(sourcemaps.init())
    .pipe(babel(config.options.babel))
    .pipe(concat('app.js'))
    .pipe(dest(config.paths.dest.js))
    .pipe(gulpif(isProduction, uglify(config.options.uglify)))
    .pipe(gulpif(isProduction, rename({ suffix: '.min' })))
    .pipe(sourcemaps.write('.'))
    .pipe(gulpif(isProduction, size({ showFiles: true, gzip: true })))
    .pipe(dest(config.paths.dest.js))
    .pipe(gulpif(!isProduction, browserSync.stream()));
}
 
// CSS 处理
function styles() {
  const sass = require('gulp-sass')(require('sass'));
  const postcss = require('gulp-postcss');
  const cleanCSS = require('gulp-clean-css');
  const concat = require('gulp-concat');
  const rename = require('gulp-rename');
  const sourcemaps = require('gulp-sourcemaps');
  const plumber = require('gulp-plumber');
  const size = require('gulp-size');
 
  return src(config.paths.src.scss, { 
    sourcemaps: !isProduction,
    since: lastRun(styles)
  })
    .pipe(plumber({
      errorHandler: require('gulp-notify').onError('Error: <%= error.message %>')
    }))
    .pipe(sourcemaps.init())
    .pipe(sass(config.options.sass))
    .pipe(postcss(gulpif(isProduction, config.options.postcss : [require('autoprefixer')()])))
    .pipe(concat('app.css'))
    .pipe(dest(config.paths.dest.css))
    .pipe(gulpif(isProduction, cleanCSS()))
    .pipe(gulpif(isProduction, rename({ suffix: '.min' })))
    .pipe(sourcemaps.write('.'))
    .pipe(gulpif(isProduction, size({ showFiles: true, gzip: true })))
    .pipe(dest(config.paths.dest.css))
    .pipe(gulpif(!isProduction, browserSync.stream()));
}
 
// HTML 处理
function html() {
  const htmlmin = require('gulp-htmlmin');
  const fileInclude = require('gulp-file-include');
 
  return src(config.paths.src.html)
    .pipe(fileInclude({
      prefix: '@@',
      basepath: '@file'
    }))
    .pipe(gulpif(isProduction, htmlmin(config.options.htmlmin)))
    .pipe(dest(config.paths.dest.html))
    .pipe(gulpif(!isProduction, browserSync.stream()));
}
 
// 图片处理
function images() {
  const imagemin = require('gulp-imagemin');
  const changed = require('gulp-changed');
 
  return src(config.paths.src.images, {
    since: lastRun(images)
  })
    .pipe(changed(config.paths.dest.images))
    .pipe(gulpif(isProduction, imagemin([
      imagemin.gifsicle({ interlaced: true }),
      imagemin.mozjpeg({ quality: 85, progressive: true }),
      imagemin.optipng({ optimizationLevel: 5 }),
      imagemin.svgo({
        plugins: [
          { removeViewBox: true },
          { cleanupIDs: false }
        ]
      })
    ])))
    .pipe(dest(config.paths.dest.images));
}
 
// 字体处理
function fonts() {
  return src(config.paths.src.fonts)
    .pipe(dest(config.paths.dest.fonts));
}
 
// 开发服务器
function server() {
  browserSync.init({
    server: {
      baseDir: './dist'
    },
    port: 3000,
    open: true,
    notify: false
  });
}
 
// 文件监听
function watchFiles() {
  watch(config.paths.src.js, scripts);
  watch(config.paths.src.scss, styles);
  watch(config.paths.src.html, html);
  watch(config.paths.src.images, images);
  watch(config.paths.src.fonts, fonts);
}
 
// 任务
exports.clean = clean;
exports.scripts = scripts;
exports.styles = styles;
exports.html = html;
exports.images = images;
exports.fonts = fonts;
exports.watch = watchFiles;
 
// 开发任务
exports.dev = series(
  clean,
  parallel(scripts, styles, html, images, fonts),
  parallel(server, watchFiles)
);
 
// 生产构建
exports.build = series(
  clean,
  parallel(scripts, styles, html, images, fonts)
);
 
// 默认任务
exports.default = isProduction ? exports.build : exports.dev;

📚 相关链接


标签: gulp 实战 前端构建 工程化