gulpfile not compiling js and css on save

31 views Asked by At

I'm encountering an issue with my Gulpfile: when I save changes, the console indicates that both JS and CSS are successfully compiled. However, the updated results aren't reflected on the frontend until I manually restart the server using "yarn dev." Essentially, to see my code changes, I have to run "yarn dev" after each modification.

I've attached my Gulpfile for reference. I'm seeking insights into what might be causing this issue and how I can resolve it. Any help or suggestions would be greatly appreciated!

'use strict';

const packageFile = require('./package.json');
const version = `v${packageFile.version}`;
const errorsFail = process.argv.indexOf('--errors-fail') > -1;
// Sync is true by default; if --no-sync is passed, then sync is false.
const sync = process.argv.indexOf('--no-sync') === -1;

// Gulp-related tasks.

// Utilities
const del = require('del');
const gulp = require('gulp');

// Fractal
const fractal = require('./fractal.js');
const logger = fractal.cli.console;

// Images and icons
const imagemin = require('gulp-imagemin');
const svgSprite = require('gulp-svg-sprite');

// Misc
const cache = require('gulp-memory-cache');
const changed = require('gulp-changed');
const count = require('gulp-count');
const deleted = require('gulp-deleted');
const { exec, execSync } = require('child_process');
const gulpIf = require('gulp-if');
const path = require('path');
const rename = require('gulp-rename');
const replace = require('gulp-replace');
const sourcemaps = require('gulp-sourcemaps');

// JavaScript
const babel = require('gulp-babel');
const concat = require('gulp-concat');
const eslint = require('gulp-eslint');
const uglify = require('gulp-uglify');

// CSS
const autoprefixer = require('gulp-autoprefixer');
const cleanCSS = require('gulp-clean-css');
const criticalSplit = require('postcss-critical-split');
const glob = require('gulp-sass-glob');
const postCSS = require('gulp-postcss');
const sass = require('gulp-sass');
const sassLint = require('gulp-sass-lint');
const Fiber = require('fibers');

// Stubs server
const stubServer = require('./stubs/server');

// opt for primary implementation (dart sass)
sass.compiler = require('sass');

// Files to process.

// Dummy product data.
const productData = [
  'src/assets/js/product-data.js',
  'src/assets/js/product-data.json'
];

// Icons.
const componentIcons = [
  'src/assets/icons/**/*.svg'
];

// Flag Icons.
const flagIcons = [
  'src/assets/icons/flags/*.svg'
]

// Images.
const componentImages = [
  'src/assets/images/**/*'
];

// Videos.
const componentVideos = [
  'src/assets/videos/**/*'
];

// Fonts.
const componentFonts = [
  'src/assets/fonts/*'
];

// Pdfs.
const componentPdfs = [
  'src/assets/pdfs/*'
];

const staticAssets = [
  // This includes:
  // - CSS assets we don’t control.
  // - Dummy HTML for components w/iframe elements.
  'src/assets/static/**/*',
];

// BE component js files--ignores *.json!
const configJs = [
  'src/components/**/*.config.js'
];

// FE base js files for all components.
const baseJs = [
  'node_modules/ev-emitter/ev-emitter.js',
  'node_modules/object-fit-images/dist/ofi.js',
  'node_modules/headroom.js/dist/headroom.js',
  'src/assets/js/capture-metadata.js',
];

// FE component js files.
const componentJs = baseJs.concat([
  'node_modules/imagesloaded/imagesloaded.js',
  'node_modules/tiny-slider/dist/tiny-slider.js',
  'node_modules/intl-tel-input/build/js/intlTelInput.js',
  'node_modules/intl-tel-input/build/js/utils.js',
  'node_modules/promise-polyfill/dist/polyfill.js',
  'src/assets/js/capture-metadata.js',
  'src/assets/js/app.js',
  'src/assets/js/trade-app-api.js',
  'src/components/**/*.js',
  '!src/components/_backlog/**/*.js',
  // Deprecated components should not be in the build.
  '!src/components/04-deprecated/**/*',
  // Dealer profiles JS is handled separately.
  '!src/components/03-pages/dealer-profiles/**/*.js',
  '!src/components/**/*.config.js'
]);

// FE b2b-specific component js files.
const b2bJs = baseJs.concat([
  'src/assets/js/app.js',
  'src/components/01-elements/**/checkbox.js',
  'src/components/01-elements/**/default-modal.js',
  'src/components/01-elements/**/select-normal.js',
  'src/components/01-elements/**/shipping-dropdown.js',
  'src/components/01-elements/**/tooltip.js',
  'src/components/02-components/**/header-flyout.js',
  'src/components/02-components/**/mini-cart.js',
  'src/components/02-components/**/mini-search.js',
  'src/components/02-components/**/shopping-cart.js',
]);

const dealerProfilesJs = [
  'src/components/03-pages/dealer-profiles/**/*.js',
  '!src/components/03-pages/dealer-profiles/**/*.config.js',
];

const stylesheetSwitcherJs = [
  './node_modules/stylesheet-switcher/public/dist/sss.js',
  './node_modules/stylesheet-switcher/public/dist/sss.js.map'
];

const vendorJs = [
  './node_modules/fg-loadcss/dist/cssrelpreload.min.js',
];

// External sass to include.
const sassIncludePaths = [
  // Plugins.
  './node_modules/breakpoint-sass/stylesheets',
  './node_modules/susy/sass',
  // Third party styles.
  './node_modules/tiny-slider/dist',
  './node_modules/intl-tel-input/src/css',
];

// Sass.
const allScss = [
  'src/**/*.scss',
  '!src/components/04-deprecated/**/*.scss',
];

// Fractal theme files.
const themeScss = [
  './src/assets/scss/theme.scss'
];

const componentScss = [
  'src/assets/scss/*.scss',
  '!src/assets/scss/fractal/*.scss',
  `!${themeScss[0]}`
];

const componentGeneratedCss = componentScss
  .map(glob => glob.replace(/scss/g, 'css').replace(/src/g, 'tmp'));
componentGeneratedCss.push('!./tmp/assets/css/preview.css');
componentGeneratedCss.push('!./tmp/assets/css/*critical.css');

// Functions.

// Creates a string showing the current tag & commit and # of commits since.
function timestamp() {
  try {
    // Find out the latest tag in the local repo.
    const latestTag = execSync('git tag --sort version:refname | tail -1').toString().trim();
    // Get the name of the current branch.
    const currentBranch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
    // Get the number of commits since the latest tag ON THE CURRENT BRANCH.
    const postReleaseCommits = parseInt(execSync(`git rev-list ${latestTag}..${currentBranch} | wc -l`).toString().trim());
    // Get the short hash of the current commit.
    const currentCommitHash = execSync(`git rev-parse --short --verify ${currentBranch}`).toString().trim();
    // Create a string showing how many commits since last tag; this will either
    // be e.g. ` +23` if postReleaseCommits > 0, or en empty string.
    const postReleaseCount = postReleaseCommits ? ` +${postReleaseCommits}` : '';

    // Return it prepended by a newline--we will be appending this to various
    // files.
    return `\n/* ${latestTag}${postReleaseCount} (${currentCommitHash}) */\n`;
  } catch (error) {
    logger.error(error);
    logger.log(`Failed to create assets timestamp. Carrying on without it.`);
    return '';
  }
}

// Build the static site for deployment.
function build() {
  const builder = fractal.web.builder();

  builder.on('progress', (completed, total) => logger.update(`Exported ${completed} of ${total} items.`, 'info'));
  builder.on('error', err => logger.error(err.message));

  return builder.build().then(() => {
    logger.success('Fractal build completed!');
  });
}

// After build, tar and gzip www/assets to www/latest-assets.tgz.
function archive() {
  const commit = execSync('git rev-parse --short HEAD').toString().trim();
  return exec(`tar -czf tmp/${version}-assets.build-${commit}.tgz -C tmp assets`);
}

// Serve the dynamic site for local environments.
function serve() {
  const server = fractal.web.server({
    sync,
  });

  server.on('error', err => logger.error(err.message));

  return server.start().then(() => {
    logger.success(`Fractal server is now running at ${server.url}.`);
  });
}

function clean() {
  return del([
    'dist/',
    'tmp/assets/',
  ]);
}

function products() {
  return gulp.src(productData, { since: gulp.lastRun(products) })
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(gulpIf(errorsFail, eslint.failAfterError()))
    .pipe(gulp.dest('./tmp/assets/js'));
}

function icons() {
  return gulp.src([...componentIcons, ...flagIcons])
    // Copies individual icons over for rare situations when the `use` tag can't
    // be used, such as within CSS or `img` tags.
    .pipe(gulp.dest('./tmp/assets/icons/individual'))
    .pipe(svgSprite({
      mode: {
        symbol: {
          dest: '.',
          prefix: '.icon--%s',
          dimensions: '%s',
          sprite: 'icons.svg',
          // Creates an scss file of icon dimensions that is used by the app.
          render: {
            scss: true
          }
        }
      }
    }))
    .pipe(gulp.dest('tmp/assets/icons'));
}

function images() {
  return gulp.src(componentImages)
    // Delete images removed from src.
    .pipe(deleted({
      src: 'src/assets/images',
      dest: 'tmp/assets/images',
      patterns: [ '**/*' ],
    }))
    // Only process image files that have changed.
    .pipe(changed('tmp/assets/images'))
    .pipe(count('## image files processed', { logFiles: true }))
    .pipe(imagemin())
    .pipe(gulp.dest('tmp/assets/images'));
}

function videos() {
  return gulp.src(componentVideos)
    // Delete videos removed from src.
    .pipe(deleted({
      src: 'src/assets/videos',
      dest: 'tmp/assets/videos',
      patterns: [ '**/*' ],
    }))
    // Only process video files that have changed.
    .pipe(changed('tmp/assets/videos'))
    .pipe(count('## video files processed', { logFiles: true }))
    .pipe(gulp.dest('tmp/assets/videos'));
}

function fonts() {
  return gulp.src(componentFonts)
    // Delete fonts removed from src.
    .pipe(deleted({
      src: 'src/assets/fonts',
      dest: 'tmp/assets/fonts',
      patterns: [ '**/*' ],
    }))
    // Only process font files that have changed.
    .pipe(changed('tmp/assets/fonts'))
    .pipe(count('## font files processed', { logFiles: true }))
    .pipe(gulp.dest('tmp/assets/fonts'));
}

function pdfs() {
  return gulp.src(componentPdfs)
    // Delete pdfs removed from src.
    .pipe(deleted({
      src: 'src/assets/pdfs',
      dest: 'tmp/assets/pdfs',
      patterns: [ '**/*' ],
    }))
    // Only process pdf files that have changed.
    .pipe(changed('tmp/assets/pdfs'))
    .pipe(count('## pdf files processed', { logFiles: true }))
    .pipe(gulp.dest('tmp/assets/pdfs'));
}

function staticResources() {
  return gulp.src(staticAssets)
    // Delete html removed from src.
    .pipe(deleted({
      src: 'src/assets/static',
      dest: 'tmp/assets/static',
      patterns: [ '**/*' ],
    }))
    // Only process html files that have changed.
    .pipe(changed('tmp/assets/static'))
    .pipe(count('## static files processed', { logFiles: true }))
    .pipe(gulp.dest('tmp/assets/static'));
}

function configScripts() {
  // The BE scripts (e.g. `*.config.js` files) are only loaded during dev and
  // build gulp tasks, so we don't need to to minify or concatenate them or
  // create sourcemaps. On the other hand, linting them still is no bad idea.
  return gulp.src(configJs, { since: gulp.lastRun(configScripts) })
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(gulpIf(errorsFail, eslint.failAfterError()));
}

// Generic function to process front-end scripts.
function frontEndScripts(scripts, name) {
  return gulp.src(scripts, { since: cache.lastMtime(name) })
    .pipe(sourcemaps.init())
    .pipe(babel({
      presets: [['es2015', { modules: false }]],
    }))
    .pipe(uglify())
    .pipe(cache(name))
    .pipe(concat(name))
    .pipe(sourcemaps.write('./'))
    .pipe(replace(/\n$/, timestamp()))
    .pipe(gulp.dest('tmp/assets/js'));
}

function componentScripts() {
  return frontEndScripts(componentJs, 'app.js');
}

function dealerProfilesScripts() {
  return frontEndScripts(dealerProfilesJs, 'dealer-profiles.js');
}

function b2bScripts() {
  return frontEndScripts(b2bJs, 'app-b2b.js');
}

function jsLint() {
  return gulp.src(componentJs.concat(configJs))
    .pipe(gulpIf(isOwnedSrcFile, eslint()))
    .pipe(gulpIf(isOwnedSrcFile, eslint.format()))
    .pipe(gulpIf(isOwnedSrcFile, gulpIf(errorsFail, eslint.failAfterError())));
}

function isOwnedSrcFile(file) {
  return !!file.path.match(new RegExp(`^${path.resolve(__dirname, 'src')}`));
}

function themeScripts() {
  return gulp.src(stylesheetSwitcherJs)
    .pipe(gulp.dest('./tmp/assets/js'));
}

function vendorScripts() {
  return gulp.src(vendorJs)
    .pipe(gulp.dest('tmp/assets/js/vendor'));
}

function scssLint() {
  return gulp.src(allScss)
    .pipe(sassLint({
      rules: {
        'class-name-format': [
          2,
          {
            convention: 'hyphenatedbem',
          },
        ],
        'clean-import-paths': {
          'filename-extension': false
        },
        'declarations-before-nesting': 2,
        'empty-line-between-blocks': 2,
        'force-attribute-nesting': 0,
        'force-element-nesting': 0,
        'force-pseudo-nesting': 0,
        'indentation': [
          2,
          {
            size: 2,
          },
        ],
        'mixins-before-declarations': 2,
        'no-color-literals': 0,
        // We use CSS comments to indicate critical styles.
        // postcss-critical-split supports important comments (/*! critical */),
        // which would satisfy this `no-css-comments` rule, but there is a bug
        // with the implementation. Once that issue is fixed, we can remove the
        // following line.
        // See: https://github.com/mrnocreativity/postcss-critical-split/issues/17
        'no-css-comments': 0,
        'no-duplicate-properties': 2,
        'no-ids': 2,
        'no-important': 2,
        'no-mergeable-selectors': 2,
        'no-trailing-whitespace': 2,
        'no-qualifying-elements': 0,
        'no-trailing-zero': 0,
        'no-vendor-prefixes': 0,
        'one-declaration-per-line': 2,
        'property-sort-order': [
          2,
          {
            'order': 'smacss'
          }
        ],
        // The following rule is commented out until we have transitioned all
        // stylesheets to use the agreed-upon units. It’s too noisy when left
        // uncommented (even as a warning), but we want to keep them to
        // uncomment temprarily as we chip away at #1777.
        /* 'property-units': [
         *   1,
         *   {
         *     global: [
         *       'rem',
         *     ],
         *     'per-property': {
         *       border: [
         *         'px',
         *         'rem',
         *       ],
         *       'border-bottom': [
         *         'px',
         *         'rem',
         *       ],
         *       'border-radius': [
         *         'px',
         *         'rem',
         *       ],
         *       'letter-spacing': [
         *         'px',
         *       ],
         *     },
         *   },
         * ], */
        'pseudo-element': 0,
        'quotes': 2,
        'space-after-colon': 2,
        'space-after-comma': 2,
        'space-around-operator': 2,
        'space-before-brace': 2,
        'space-before-colon': 2,
        'trailing-semicolon': 2,
        'zero-unit': 2,
      }
    }))
    .pipe(sassLint.format())
    .pipe(gulpIf(errorsFail, sassLint.failOnError()));
}

function componentStyles() {
  let modified = this ? this.modified : null;
  if (modified) {
    modified = Array.isArray(modified) ? modified : [ modified ];
  }
  return gulp.src(componentScss)
    .pipe(changed('tmp/assets/css', {
      hasChanged: determineModifiedStatus(modified),
    }))
    .pipe(sourcemaps.init())
    .pipe(glob())
    .pipe(
      sass({
        // Fiber reduces the async overhead, speeding up compilation
        fiber: Fiber,
        outputStyle: 'expanded',
        includePaths: sassIncludePaths,
      }).on('error', function (error) {
        if (errorsFail) {
          throw error;
        } else {
          // sass.logError is expected to be passed directly to the `.on()`
          // method, where it would be bound with `this` and have access to the
          // `emit()` method. Since we’re calling the function explicitly, we
          // need to bind `this` to it ourselves:
          sass.logError.call(this, error);
        }
      })
    )
    .pipe(autoprefixer())
    .pipe(sourcemaps.write('./'))
    .pipe(replace(/\n$/, timestamp()))
    .pipe(gulp.dest('tmp/assets/css'))
    .pipe(count('## component stylesheets compiled (including sourcemaps)', { logFiles: true }));
}

function determineModifiedStatus(modified) {
  // Determines whether a top-level stylesheet should be compiled depending on
  // whether it imports any of the supplied component-level stylesheets.
  return function(stream, source, dest) {
    // If no modified files are supplied or the modified files include the
    // current source path, just add source to the stream and move on.
    if (!modified || modified.includes(source.path.replace(`${process.cwd()}/`, ''))) {
      return stream.push(source);
    }

    // Inspect the contents of the source file to determine whether it contains
    // any of the supplied submodules.
    const contents = source.contents.toString();
    const streamIt = modified.some(function (file) {
      let baseLayoutRegExp, categoryPath, categoryRegExp,
        componentPath, componentRegExp, everythingRegExp, globalRegExp;

      // If the modified file is not a component or scss asset file, ignore it.
      if (!file.match(/src\/components/) && !file.match(/src\/assets\/scss/)) {
        return false;
      }
      // Determine whether modified file is asset scss file.
      const globalMatch = file.match(/src\/assets\/scss\/((?:abstracts|base|layout)\/)/);
      const fileIsGlobal = !!globalMatch;

      if (fileIsGlobal) {
        globalRegExp = new RegExp(globalMatch[1]);
      } else {
        // Match an _everything_ import.
        // e.g. source file has:
        //   @import '../../components/**/*.scss';
        everythingRegExp = /\/components\/\*\*\/\*\.scss/;

        // Match whether the submodule’s category is imported wholesale.
        // e.g. submodule is `element` and source file has:
        //   @import '../../components/01-elements/**/*.scss';
        categoryPath = file.match(/(components\/\d{2}-[^/]+)/)[1];
        categoryRegExp = new RegExp(`${categoryPath}/\\*\\*/\\*`, 'gm');

        // Match whether the submodule’s top-level component is imported.
        // e.g. submodule is (or belongs to) `01-elements/form-items` and
        // source file has:
        //   @import '../../components/01-elements/form-items/**/*.scss';
        componentPath = file.match(/(components\/\d{2}-[^/]+\/[^/]+)/)[1]
        componentRegExp = new RegExp(componentPath, 'gm');
      }

      // Return whether any of the matches is truthy.
      return (
        (
          // File is global file and stylesheet imports it.
          fileIsGlobal && contents.match(globalRegExp)
        ) || (
          // Stylesheet imports _everything_.
          contents.match(everythingRegExp) ||
          // Stylesheet imports submodule’s category.
          contents.match(categoryRegExp) ||
          // Stylesheet imports submodule’s top-level component.
          contents.match(componentRegExp)
        )
      );
    });

    if (streamIt) {
      return stream.push(source);
    }
  }
}

function themeStyles() {
  return gulp.src(themeScss)
    .pipe(sass({
      fiber: Fiber,
      outputStyle: 'compressed',
    }).on('error', sass.logError))
    .pipe(autoprefixer())
    .pipe(cleanCSS())
    .pipe(gulp.dest('tmp/assets/css'));
}

function criticalCss() {
  return gulp.src(componentGeneratedCss)
    // TODO: Use changed (and transformPath) to split only changed stylesheets.
    .pipe(postCSS([ criticalSplit({ output: 'critical' }) ]))
    .pipe(rename(path => path.basename += '-critical'))
    .pipe(cleanCSS())
    .pipe(gulp.dest('tmp/assets/css'));
}

function nonCriticalCss() {
  return gulp.src(componentGeneratedCss)
    // TODO: Use changed (and transformPath) to split only changed stylesheets.
    .pipe(postCSS([ criticalSplit({ output: 'rest' }) ]))
    .pipe(rename(path => path.basename += '-noncritical'))
    .pipe(cleanCSS())
    .pipe(gulp.dest('tmp/assets/css'));
}

function optimizeSourceCss() {
  return gulp.src(componentGeneratedCss)
    .pipe(cleanCSS())
    .pipe(gulp.dest('tmp/assets/css'));
}

function initWatch(done) {
  gulp.watch('src/assets/icons', icons);
  gulp.watch('src/assets/images', images);
  gulp.watch('src/assets/videos', videos);
  gulp.watch('src/assets/fonts', fonts);
  gulp.watch('src/assets/pdfs', pdfs);
  gulp.watch('src/assets/static', staticResources);
  gulp.watch(productData, products);
  gulp.watch(configJs, configScripts);
  gulp.watch(componentJs, gulp.series(componentScripts, jsLint))
    .on('unlink', (filePath) => {
      // Remove deleted files from the cache.
      cache.forget('app.js', path.resolve(filePath));
    });
  gulp.watch(b2bJs, b2bScripts)
    .on('unlink', (filePath) => {
      // Remove deleted files from the cache.
      cache.forget('app-b2b.js', path.resolve(filePath));
    });
  gulp.watch(dealerProfilesJs, dealerProfilesScripts)
    .on('unlink', (filePath) => {
      // Remove deleted files from the cache.
      cache.forget('dealer-profiles.js', path.resolve(filePath));
    });
  gulp.watch(vendorJs, vendorScripts);
  gulp.watch(stylesheetSwitcherJs, themeScripts);
  // Instantiate a watcher and bind file paths of modified files to the
  // componentStyles function.
  const scssWatcher = gulp.watch('src/**/*.scss');
  scssWatcher.on('change', function(filepath, stats) {
    return gulp.series([
      gulp.parallel([
        componentStyles.bind({
          modified: filepath,
        }), themeStyles,
      ]),
      gulp.parallel([
        criticalCss, nonCriticalCss,
      ]),
      optimizeSourceCss,
      scssLint
    ])();
  });
  done();
}

function stubs(done) {
  const port = 4000;
  stubServer.listen(port, () => {
    logger.success(`Stubs server is listening on port: ${port}.`);
  });
  done();
}

function copyToDist() {
  return gulp.src(['tmp/**/*'])
    .pipe(gulp.dest('dist/'));
}

const compile = gulp.series(
  icons,
  gulp.parallel([
    images, videos, fonts, pdfs, staticResources, products, configScripts,
    componentScripts, dealerProfilesScripts, b2bScripts, vendorScripts,
    themeScripts, componentStyles, themeStyles,
  ]),
  gulp.parallel([
    criticalCss, nonCriticalCss,
  ]),
  optimizeSourceCss
);

// Abriged version of compile stream, focused exclusively on the assets that
// are actually used in production B2C and B2B websites. This excludes any
// assets that are used exclusively within the pattern library.
const compileProdAssets = gulp.series(
  icons,
  gulp.parallel([
    fonts, products, componentScripts, dealerProfilesScripts, b2bScripts,
    vendorScripts, componentStyles, themeStyles,
  ]),
  gulp.parallel([
    criticalCss, nonCriticalCss,
  ]),
  optimizeSourceCss
);

gulp.task('archive', archive);
gulp.task('assets', gulp.series(clean, compileProdAssets, copyToDist));
gulp.task('build', gulp.series(clean, compile, archive, build));
gulp.task('compile', compile);
gulp.task('dev', gulp.series(compile, serve, initWatch));
gulp.task('lint', gulp.parallel(scssLint, jsLint));
gulp.task('preview', serve);
gulp.task('start', gulp.series(compile, serve));
gulp.task('stubs', stubs);

0

There are 0 answers