diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..effaf658 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/index.js b/index.js index 320a12ce..a70962d4 100644 --- a/index.js +++ b/index.js @@ -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()}`)); }); }; diff --git a/lib/virtual-stats.js b/lib/virtual-stats.js index 4e1b1dee..af634711 100644 --- a/lib/virtual-stats.js +++ b/lib/virtual-stats.js @@ -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. @@ -12,7 +14,7 @@ 'use strict'; -var constants = require('constants'); +const constants = require('constants'); /** * Create a new stats object. @@ -20,7 +22,7 @@ var constants = require('constants'); * @constructor */ function VirtualStats(config) { - for (var key in config) { + for (const key in config) { if (!config.hasOwnProperty(key)) { continue; } diff --git a/lib/virtual.js b/lib/virtual.js index 307e2c27..e9d75541 100644 --- a/lib/virtual.js +++ b/lib/virtual.js @@ -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 @@ -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--; + } }; } @@ -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"); } }