Webpack: Trigger a file watch recompile on an unchanged file

974 Views Asked by At

I'm developing a Webpack 5 plugin and I need to manually trigger a recompile on a watched file without modifying it (mainly to do some niche HMR stuff).

I figure the easiest way is to convince the compiler that the file has changed. I don't want to actually change the file with fs. I've looked into the webpack source code - spoofing webpack's NodeWatchFileSystem looks to be very hacky. Triggering a recompile another way is beyond me.

2

There are 2 best solutions below

0
On BEST ANSWER

Tracing backwards all the way to the file watchers, when a file is saved, the file watcher emits the "change" event and the functions that follow consume this event and trigger the recompilation for the changed file.

As we require a "change" event and a recompilation, but for an unchanged file, accessing these consumer functions (setFileTime(...)) & simulating the consumption of a "change" event would be a cleaner solution like below, worked great for me!

//unchanged file that needs to be recompiled
let filePath = "/Users/my-project/src/HelloWorld.js" 

// Webpack v5.x
compiler.watchFileSystem.watcher.fileWatchers
  .get(filePath)
  .directoryWatcher.setFileTime(
    filePath,
    new Date().getTime(),
    false,
    false,
    "change"
  );

// Webpack v4.x
compiler.watchFileSystem.watcher.fileWatchers
  .find((watcher) => watcher.path === filePath)
  .directoryWatcher.setFileTime(
    filePath,
    new Date().getTime(),
    false,
    "change"
  );

1
On

The simplest way to recompile watched files (without them actually changing) is to update their timestamps and add them to the modified files on the webpack compiler. Then, invalidating watching, making sure to clear the watcher first.

This will trigger a new build on just those files, running all loaders, and updating HMR, etc.

const time = Date.now();
const timestamps = new Map<string, FileSystemInfoEntry>();
const modifiedFiles = new Set<string>();

// Make an entry for each file path that should recompile
paths.forEach(path => {
  timestamps.set(path, {
    safeTime: time,
    timestamp: time,
    // accuracy: 0,
  });
  modifiedFiles.add(path);
});

compiler.fileTimestamps = timestamps;
compiler.contextTimestamps = timestamps;
compiler.modifiedFiles = modifiedFiles;

// Trigger recompile
compiler.watching.watcher = null; // Otherwise watcher will pull in fresh file data, undoing the compiler modifications above
compiler.watching.invalidate(() => { // Triggers the recompile
  console.log('Recompile finished');
});