Skip to content

Update VirtualModulesPlugin from upstream #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
42 changes: 42 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
svelte-loader is licensed under the MIT license:
Copyright (c) 2020 svelte-loader contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

lib/virtual.js and lib/virtual-stats.js also contain code licensed under the
MIT license:
Copyright (c) 2017 SysGears

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,6 @@ module.exports = function(source, map) {
}, err => callback(err)).catch(err => {
// wrap error to provide correct
// context when logging to console
callback(new Error(`${err.name}: ${err.toString()}`));
callback(new Error(`${err.name || 'Error'}: ${err.toString()}`));
});
};
6 changes: 4 additions & 2 deletions lib/virtual-stats.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Adapted from https://github.com/sysgears/webpack-virtual-modules

/**
* Used to cache a stats object for the virtual file.
* Extracted from the `mock-fs` package.
Expand All @@ -12,15 +14,15 @@

'use strict';

var constants = require('constants');
const constants = require('constants');

/**
* Create a new stats object.
* @param {Object} config Stats properties.
* @constructor
*/
function VirtualStats(config) {
for (var key in config) {
for (const key in config) {
if (!config.hasOwnProperty(key)) {
continue;
}
Expand Down
215 changes: 173 additions & 42 deletions lib/virtual.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,56 @@
var VirtualStats = require('./virtual-stats');
// Adapted from https://github.com/sysgears/webpack-virtual-modules

var inode = 45000000;
const VirtualStats = require('./virtual-stats');
const path = require('path');

// Adapted from https://github.com/sysgears/webpack-virtual-modules
// MIT Licensed https://github.com/sysgears/webpack-virtual-modules/blob/master/LICENSE
let inode = 45000000;

function createWebpackData(result) {
return (function(storage) {
// In Webpack v5, this variable is a "Backend", and has the data stored in a field
// _data. In V4, the `_` prefix isn't present.
if (storage._data) {
const curLevelIdx = storage._currentLevel;
const curLevel = storage._levels[curLevelIdx];
return {
result: this.result,
level: curLevel
};
}
// Webpack 4
return [null, result];
}).bind({ result: result });
}

function getModulePath(filePath, compiler) {
return path.isAbsolute(filePath) ? filePath : path.join(compiler.context, filePath);
}

function createVirtualStats(len) {
const time = new Date();
const timeMs = time.getTime();

return new VirtualStats({
dev: 8675309,
ino: inode++,
mode: 16877,
nlink: 0,
uid: 1000,
gid: 1000,
rdev: 0,
size: len,
blksize: 4096,
blocks: Math.floor(len / 4096),
atimeMs: timeMs,
mtimeMs: timeMs,
ctimeMs: timeMs,
birthtimeMs: timeMs,
atime: time,
mtime: time,
ctime: time,
birthtime: time
});
}

/**
* @param {Compiler} compiler - the webpack compiler
Expand All @@ -12,28 +59,53 @@ function VirtualModulesPlugin(compiler) {
this.compiler = compiler;

if (!compiler.inputFileSystem._writeVirtualFile) {
var originalPurge = compiler.inputFileSystem.purge;
const originalPurge = compiler.inputFileSystem.purge;

compiler.inputFileSystem.purge = function() {
if (originalPurge) {
originalPurge.call(this, arguments);
originalPurge.apply(this, arguments);
}
if (this._virtualFiles) {
Object.keys(this._virtualFiles).forEach(
function(file) {
var data = this._virtualFiles[file];
setData(this._statStorage, file, [null, data.stats]);
setData(this._readFileStorage, file, [null, data.contents]);
}.bind(this)
);
Object.keys(this._virtualFiles).forEach(function(file) {
const data = this._virtualFiles[file];
this._writeVirtualFile(file, data.stats, data.contents);
}.bind(this));
}
};

compiler.inputFileSystem._writeVirtualFile = function(file, stats, contents) {
const statStorage = getStatStorage(this);
const fileStorage = getFileStorage(this);
const readDirStorage = getReadDirBackend(this);
this._virtualFiles = this._virtualFiles || {};
this._virtualFiles[file] = { stats: stats, contents: contents };
setData(this._statStorage, file, [null, stats]);
setData(this._readFileStorage, file, [null, contents]);
setData(statStorage, file, createWebpackData(stats));
setData(fileStorage, file, createWebpackData(contents));
const segments = file.split(/[\\/]/);
let count = segments.length - 1;
const minCount = segments[0] ? 1 : 0;
// create directories for each segment (to prevent files not being in a directory)
while (count > minCount) {
const dir = segments.slice(0, count).join(path.sep) || path.sep;
try {
compiler.inputFileSystem.readdirSync(dir);
} catch (e) {
const dirStats = createVirtualStats(stats.size);
setData(readDirStorage, dir, createWebpackData([]));
setData(statStorage, dir, createWebpackData(dirStats));
}
let dirData = getData(getReadDirBackend(this), dir);
// Webpack v4 returns an array, webpack v5 returns an object
dirData = dirData[1] || dirData.result;
const filename = segments[count];
if (dirData.indexOf(filename) < 0) {
const files = dirData.concat([filename]).sort();
setData(getReadDirBackend(this), dir, createWebpackData(files));
} else {
break;
}
count--;
}
};
}

Expand All @@ -50,39 +122,98 @@ function VirtualModulesPlugin(compiler) {
}

VirtualModulesPlugin.prototype.writeModule = function(filePath, contents) {
var len = contents ? contents.length : 0;
var time = new Date();
var timeMs = time.getTime();
const len = contents ? contents.length : 0;
const stats = createVirtualStats(len);
const modulePath = getModulePath(filePath, this.compiler);

var stats = new VirtualStats({
dev: 8675309,
ino: inode++,
mode: 33188,
nlink: 0,
uid: 1000,
gid: 1000,
rdev: 0,
size: len,
blksize: 4096,
blocks: Math.floor(len / 4096),
atimeMs: timeMs,
mtimeMs: timeMs,
ctimeMs: timeMs,
birthtimeMs: timeMs,
atime: time,
mtime: time,
ctime: time,
birthtime: time
});
// When using the WatchIgnorePlugin (https://github.com/webpack/webpack/blob/52184b897f40c75560b3630e43ca642fcac7e2cf/lib/WatchIgnorePlugin.js),
// the original watchFileSystem is stored in `wfs`. The following "unwraps" the ignoring
// wrappers, giving us access to the "real" watchFileSystem.
let finalWatchFileSystem = this._watcher && this._watcher.watchFileSystem;

while (finalWatchFileSystem && finalWatchFileSystem.wfs) {
finalWatchFileSystem = finalWatchFileSystem.wfs;
}
this.compiler.inputFileSystem._writeVirtualFile(filePath, stats, contents);
if (finalWatchFileSystem &&
(finalWatchFileSystem.watcher.fileWatchers.size ||
finalWatchFileSystem.watcher.fileWatchers.length)
) {
const fileWatchers = finalWatchFileSystem.watcher.fileWatchers instanceof Map ?
Array.from(finalWatchFileSystem.watcher.fileWatchers.values()) :
finalWatchFileSystem.watcher.fileWatchers;
fileWatchers.forEach(function(fileWatcher) {
if (fileWatcher.path === modulePath) {
delete fileWatcher.directoryWatcher._cachedTimeInfoEntries;
fileWatcher.directoryWatcher.setFileTime(
filePath,
stats.birthtime,
false,
false,
null
);
fileWatcher.emit("change", stats.birthtime, null);
}
});
}
};

function setData(storage, key, value) {
if (storage.data instanceof Map) {
storage.data.set(key, value);
function getStorageData(storage) {
return storage._data /* webpack 5 */ || storage.data /* webpack 4 */;
}

function getData(storage, key) {
const storageData = getStorageData(storage);
if (storageData instanceof Map) {
return storageData.get(key);
} else {
return storageData.data[key];
}
}

function setData(storage, key, valueFactory) {
const storageData = getStorageData(storage);
const value = valueFactory(storage);

if (storageData instanceof Map) {
storageData.set(key, value);
} else {
storageData.data[key] = value;
}
}

function getStatStorage(fileSystem) {
if (fileSystem._statStorage) {
// Webpack v4
return fileSystem._statStorage;
} else if (fileSystem._statBackend) {
// webpack v5
return fileSystem._statBackend;
} else {
// Unknown version?
throw new Error("Couldn't find a stat storage");
}
}

function getFileStorage(fileSystem) {
if (fileSystem._readFileStorage) {
// Webpack v4
return fileSystem._readFileStorage;
} else if (fileSystem._readFileBackend) {
// Webpack v5
return fileSystem._readFileBackend;
} else {
throw new Error("Couldn't find a readFileStorage");
}
}

function getReadDirBackend(fileSystem) {
if (fileSystem._readdirBackend) {
return fileSystem._readdirBackend;
} else if (fileSystem._readdirStorage) {
return fileSystem._readdirStorage;
} else {
storage.data[key] = value;
throw new Error("Couldn't find a readDirStorage from Webpack Internals");
}
}

Expand Down