Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jpachecox/setup-gulp/llms.txt

Use this file to discover all available pages before exploring further.

gulpfile.js is the central configuration file for the entire Setup Gulp build pipeline. Every source glob, output destination, task definition, and plugin option lives here. Understanding its structure lets you adapt the project to any asset layout, extend it with custom tasks, and fine-tune how aggressively files are optimised before they reach the browser.

The paths object

The paths object is the single authoritative place for all source globs and output directories. Updating a value here propagates to every task that references it — you never need to hunt through individual task definitions.
gulpfile.js
const paths = {
  sassComponents: './src/scss/components/*.scss',
  sassSections: './src/scss/sections/*.scss',
  sass: './src/scss/*.scss',
  css: './src/css/*.css',
  jsComponents: './src/js/components/*.ts',
  jsSections: './src/js/sections/*.ts',
  js: './src/js/*.ts',
  font: './src/fonts/**/*',
  img: './src/img/**/*.+(png|jpg|jpeg|gif|svg)',
  dest: './assets',
  root: './index.html'
}
KeyDefault globWhat to change it to
sassComponents./src/scss/components/*.scssAny directory that holds per-component stylesheet partials
sassSections./src/scss/sections/*.scssAny directory that holds per-section stylesheet partials
sass./src/scss/*.scssThe root SCSS entry files that import partials
css./src/css/*.cssThe intermediate compiled CSS folder — rarely changed
jsComponents./src/js/components/*.tsPer-component TypeScript source files
jsSections./src/js/sections/*.tsPer-section TypeScript source files
js./src/js/*.tsRoot-level TypeScript entry files
font./src/fonts/**/*Any font format directory; the **/* glob picks up all sub-folders
img./src/img/**/*.+(png|jpg|jpeg|gif|svg)Extend the extension list (e.g. add webp) or point to a different source folder
dest./assetsChange to a Shopify theme directory such as ../my-theme/assets to deploy directly
root./index.htmlThe HTML entry point watched for full-page reloads by BrowserSync

BrowserSync configuration

BrowserSync is initialised inside the serve task and handles live reloading during development.
gulpfile.js
gulp.task('serve', (done) => {
  browserSync.init({
    server: { baseDir: './' },
  })
  gulp.watch(paths.root).on('change', browserSync.reload)
  done()
})
  • baseDir: './' — serves the project from the repository root so index.html is reachable at http://localhost:3000.
  • paths.root watch — triggers a full page reload whenever index.html changes.
  • CSS streaming — Sass and CSS tasks pipe through browserSync.stream() at the end of the pipeline, injecting updated styles directly into the browser without a full reload.
If you are working against an existing server — such as the Shopify CLI development server — replace the server option with a proxy:
gulpfile.js
browserSync.init({
  proxy: 'localhost:9292' // for Shopify CLI local dev
})
This tells BrowserSync to forward all requests to the existing server while still injecting its live-reload script, giving you hot CSS updates on top of whatever server is already running.

The watch task

The watch task registers a file watcher for every source glob in paths. Each watcher wraps its corresponding task in gulp.series so file events are processed sequentially — this prevents race conditions when a rapid sequence of saves would otherwise queue overlapping compilations.
gulpfile.js
gulp.task('watch', () => {
  gulp.watch(paths.font, gulp.series('fonts'))
  gulp.watch(paths.img, gulp.series('images'))
  gulp.watch(paths.jsComponents, gulp.series('jsComponents'))
  gulp.watch(paths.jsSections, gulp.series('jsSections'))
  gulp.watch(paths.js, gulp.series('js'))
  gulp.watch(paths.sassComponents, gulp.series('sassComponents'))
  gulp.watch(paths.sassSections, gulp.series('sassSections'))
  gulp.watch(paths.sass, gulp.series('sass'))
  gulp.watch(paths.css, gulp.series('minifyCss'))
  browserSync.stream()
})
Notice that font and image watchers also use gulp.series — the same pattern as every other watcher — because the copy and optimisation operations are fast enough that sequential processing is fine and avoids partial writes to paths.dest. Run the watcher with:
yarn start

The build task

The build task runs all ten compilation and optimisation tasks in parallel using gulp.parallel. Running them concurrently cuts total build time significantly compared to a sequential run.
gulpfile.js
gulp.task(
  'build',
  gulp.parallel(
    'cleanAssets',
    'fonts',
    'images',
    'jsComponents',
    'jsSections',
    'js',
    'sassComponents',
    'sassSections',
    'sass',
    'minifyCss'
  )
)
cleanAssets is included in the parallel group rather than run before the others. This means cleaning and compilation start at the same time. If you need to guarantee the output directory is fully empty before any new file is written — for example, to avoid stale files surviving a rename — swap gulp.parallel for a gulp.series('cleanAssets', gulp.parallel(...)) pattern. Run a full build with:
yarn build

Error handling

The handleErrors function is passed as the errorHandler option to plumber in every task. When a task fails, it fires a desktop notification and beeps the system speaker before exiting.
gulpfile.js
function handleErrors() {
  const args = Array.prototype.slice.call(arguments)

  notify
    .onError({
      title: 'Task Failed [<%= error.message %>',
      message: 'See console.',
    })
    .apply(this, args)

  gutil.beep()

  process.exit(1)
}
notify.onError is provided by gulp-notify and triggers an OS-level notification (macOS Notification Centre, Linux notify-send, etc.) with the error message interpolated into the title string. gutil.beep() comes from gulp-util, which is a deprecated package that has not been maintained since 2018. It still works at runtime because no replacement for beep() has been widely adopted, but you can safely remove the gutil.beep() call and the gulp-util import if desktop audio feedback is not needed.
gulp-util is deprecated. All of its utilities except beep() have direct replacements in the Gulp ecosystem — fancy-log for logging, plugin-error for error construction, and so on. If you upgrade or audit dependencies, replace gulp-util imports with their individual successors and remove the gutil.beep() call.

Image optimisation settings

The images task pipes every matched file through gulp-imagemin with per-format plugins. Each plugin exposes options you can tune to balance file size against processing time and visual quality.
gulpfile.js
gulp.task('images', () =>
  gulp.src(paths.img)
    .pipe(plumber({ errorHandler: handleErrors }))
    .pipe(imagemin([
      gifsicle({ interlaced: true }),
      mozjpeg({ quality: 75, progressive: true }),
      optipng({ optimizationLevel: 5 }),
      svgo({
        plugins: [
          { name: 'removeViewBox', active: true },
          { name: 'cleanupIDs', active: false },
        ],
      }),
    ]))
    .pipe(gulp.dest(paths.dest))
)
mozjpeg
  • quality (0–100) — lower values produce smaller files with more visible compression artefacts. 75 is a good default for web; drop to 60 for thumbnails.
  • progressive: true — encodes the JPEG in progressive scans so the browser can render a blurry preview before the full image loads.
optipng
  • optimizationLevel (0–7) — higher levels apply more aggressive deflate strategies. Level 5 balances speed and size well; use 7 in a CI pipeline where build time is less important.
gifsicle
  • interlaced: true — encodes the GIF so browsers can display a coarse version while the rest of the file downloads, matching the progressive behaviour of JPEG.
svgo
  • removeViewBox: true — strips the viewBox attribute from SVGs whose dimensions are set on the element itself. Disable this if your SVGs need to scale responsively.
  • cleanupIDs: false — leave internal IDs intact. Enabling this can break SVGs that use <use> references or CSS selectors targeting specific IDs.

Adding a new task

Adding a task involves three steps: defining the task function, registering a watcher so it runs on file changes during development, and including it in the build task so it runs during production builds.
gulpfile.js
// 1. Define the task
gulp.task('myTask', () =>
  gulp.src('./src/custom/**/*.js')
    .pipe(plumber({ errorHandler: handleErrors }))
    // ...transforms...
    .pipe(gulp.dest(paths.dest))
)

// 2. Add to watch
gulp.watch('./src/custom/**/*.js', gulp.series('myTask'))

// 3. Add to build
gulp.task('build', gulp.parallel(
  // ...existing tasks...
  'myTask'
))
Place step 1 anywhere in the file before the watch and build task definitions. The plumber call ensures the watcher keeps running even if the task throws an error — without it, a syntax mistake in a source file would kill the entire process.

Browserslist

The browserslist field in package.json controls which browsers Autoprefixer and Babel’s @babel/preset-env target when adding vendor prefixes and transpiling modern JavaScript syntax.
package.json
"browserslist": ["last 2 versions"]
"last 2 versions" targets the two most recent releases of every major browser, which covers the vast majority of users while keeping the CSS and JS output lean. You can be more specific when your audience requires it:
package.json
"browserslist": [
  "last 2 Chrome versions",
  "last 2 Firefox versions",
  "last 2 Safari versions",
  "> 1%"
]
Run npx browserslist in the project root at any time to see the exact list of browsers the current query resolves to.
gulpfile.js is written with ES module syntax (import/export). The "type": "module" field must remain present in package.json — removing it will cause Node.js to treat the file as CommonJS and throw a syntax error on the first import statement.

Build docs developers (and LLMs) love