Skip to content

Instantly share code, notes, and snippets.

@ScriptedAlchemy
Last active December 9, 2019 00:40
Show Gist options
  • Select an option

  • Save ScriptedAlchemy/b46d20a46d29ab72397b1bd3331382ed to your computer and use it in GitHub Desktop.

Select an option

Save ScriptedAlchemy/b46d20a46d29ab72397b1bd3331382ed to your computer and use it in GitHub Desktop.
Module plugin
const path = require('path');
const VirtualStats = require('./virtual-stats');
class SyntheticPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
const moduleName = this.options.moduleName;
const ctime = SyntheticPlugin.statsDate();
let modulePath = this.options.path;
let contents;
if (typeof this.options.contents === 'string') {
contents = this.options.contents;
}
if (typeof this.options.contents === 'object') {
if (typeof this.options.contents.then !== 'function') {
contents = JSON.stringify(this.options.contents);
}
}
if (typeof this.options.contents === 'function') {
contents = this.options.contents();
}
if (typeof contents === 'string') {
contents = Promise.resolve(contents);
}
function resolverPlugin(request, cb) {
// populate the file system cache with the virtual module
const fs = (this && this.fileSystem) || compiler.inputFileSystem;
const join = (this && this.join) || path.join;
// webpack 1.x compatibility
if (typeof request === 'string') {
request = cb;
cb = null;
}
if (!modulePath) {
modulePath = join(compiler.context, moduleName);
}
const resolve = (data) => {
SyntheticPlugin.populateFilesystem({ fs, modulePath, contents: data, ctime });
};
const resolved = contents.then(resolve);
if (!cb) {
return;
}
resolved.then(() => cb());
}
const waitForResolvers = !compiler.resolvers.normal;
function addPlugin() {
const useModuleFactory = !compiler.resolvers.normal.plugin;
if (useModuleFactory) {
if (compiler.hooks) {
compiler.hooks.normalModuleFactory.tap('SyntheticPlugin', (nmf) => {
nmf.hooks.beforeResolve.tap('SyntheticPlugin', resolverPlugin);
});
} else {
compiler.plugin('normal-module-factory', (nmf) => {
nmf.plugin('before-resolve', resolverPlugin);
});
}
} else {
compiler.resolvers.normal.plugin('before-resolve', resolverPlugin);
}
}
if (waitForResolvers) {
compiler.plugin('after-resolvers', addPlugin);
} else {
addPlugin();
}
}
static populateFilesystem(options) {
const fs = options.fs;
const modulePath = options.modulePath;
const contents = options.contents;
const mapIsAvailable = typeof Map !== 'undefined';
const statStorageIsMap = mapIsAvailable && fs._statStorage.data instanceof Map;
const readFileStorageIsMap = mapIsAvailable && fs._readFileStorage.data instanceof Map;
if (readFileStorageIsMap) { // [email protected] or greater
if (fs._readFileStorage.data.has(modulePath)) {
return;
}
} else if (fs._readFileStorage.data[modulePath]) { // [email protected] or lower
return;
}
const stats = SyntheticPlugin.createStats(options);
if (statStorageIsMap) { // [email protected] or greater
fs._statStorage.data.set(modulePath, [null, stats]);
} else { // [email protected] or lower
fs._statStorage.data[modulePath] = [null, stats];
}
if (readFileStorageIsMap) { // [email protected] or greater
fs._readFileStorage.data.set(modulePath, [null, contents]);
} else { // [email protected] or lower
fs._readFileStorage.data[modulePath] = [null, contents];
}
}
static statsDate(inputDate) {
if (!inputDate) {
inputDate = new Date();
}
return inputDate.toString();
}
static createStats(options) {
if (!options) {
options = {};
}
if (!options.ctime) {
options.ctime = SyntheticPlugin.statsDate();
}
if (!options.mtime) {
options.mtime = SyntheticPlugin.statsDate();
}
if (!options.size) {
options.size = 0;
}
if (!options.size && options.contents) {
options.size = options.contents.length;
}
return new VirtualStats({
dev: 8675309,
nlink: 1,
uid: 501,
gid: 20,
rdev: 0,
blksize: 4096,
ino: 44700000,
mode: 33188,
size: options.size,
atime: options.mtime,
mtime: options.mtime,
ctime: options.ctime,
birthtime: options.ctime,
});
}
}
module.exports = SyntheticPlugin;
// some file in the app
var moduleFoo = require('someModule');
console.log(someModule.thing);
const constants = require('constants');
class VirtualStats {
/**
* Create a new stats object.
* @param {Object} config Stats properties.
* @constructor
*/
constructor(config) {
for (const key in config) {
if (!config.hasOwnProperty(key)) {
continue;
}
this[key] = config[key];
}
}
/**
* Check if mode indicates property.
* @param {number} property Property to check.
* @return {boolean} Property matches mode.
*/
_checkModeProperty(property) {
return ((this.mode & constants.S_IFMT) === property);
}
/**
* @return {Boolean} Is a directory.
*/
isDirectory() {
return this._checkModeProperty(constants.S_IFDIR);
}
/**
* @return {Boolean} Is a regular file.
*/
isFile() {
return this._checkModeProperty(constants.S_IFREG);
}
/**
* @return {Boolean} Is a block device.
*/
isBlockDevice() {
return this._checkModeProperty(constants.S_IFBLK);
}
/**
* @return {Boolean} Is a character device.
*/
isCharacterDevice() {
return this._checkModeProperty(constants.S_IFCHR);
}
/**
* @return {Boolean} Is a symbolic link.
*/
isSymbolicLink() {
return this._checkModeProperty(constants.S_IFLNK);
}
/**
* @return {Boolean} Is a named pipe.
*/
isFIFO() {
return this._checkModeProperty(constants.S_IFIFO);
}
/**
* @return {Boolean} Is a socket.
*/
isSocket() {
return this._checkModeProperty(constants.S_IFSOCK);
}
}
module.exports = VirtualStats;
var SynthericPlugin = require('magic-modules');
var vmds = new SyntheticPlugin({
'node_modules/someModule.js': 'module.exports = { thing: "test" };',
'node_modules/anotherModule.js': 'module.exports = { other: "thinges" };',
'pages/blog.js': 'module.exports = 'require("@myorg/blog")'
});
module.exports = {
// ...
plugins: [
vmds
]
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment