Skip to content

Virtual FS without exposing separate plugin #60

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

Merged
merged 9 commits into from
May 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"mocha"
],
"env": {
"node": true
"node": true,
"es6": true
}
}
60 changes: 29 additions & 31 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
const { basename, extname, posix, relative } = require('path');
const { basename, extname, relative } = require('path');
const { compile, preprocess } = require('svelte');
const { getOptions } = require('loader-utils');
const { statSync, utimesSync, writeFileSync } = require('fs');
const { tmpdir } = require('os');
const VirtualModules = require('./lib/virtual');

const hotApi = require.resolve('./lib/hot-api.js');

function makeHot(id, code, hotOptions) {
const options = JSON.stringify(hotOptions);
const replacement = `

if (module.hot) {

const { configure, register, reload } = require('${posixify(hotApi)}');

module.hot.accept();
Expand All @@ -26,7 +23,6 @@ if (module.hot) {
}
}


export default $2;
`;

Expand Down Expand Up @@ -80,7 +76,15 @@ function deprecatePreprocessOptions(options) {
options.preprocess = options.preprocess || preprocessOptions;
}

const virtualModuleInstances = new Map();

module.exports = function(source, map) {
if (this._compiler && !virtualModuleInstances.has(this._compiler)) {
virtualModuleInstances.set(this._compiler, new VirtualModules(this._compiler));
}

const virtualModules = virtualModuleInstances.get(this._compiler);

this.cacheable();

const options = Object.assign({}, this.options, getOptions(this));
Expand All @@ -90,47 +94,41 @@ module.exports = function(source, map) {
const isProduction = this.minimize || process.env.NODE_ENV === 'production';

options.filename = this.resourcePath;
if (!options.format) {
options.format = this.version === 1 ? options.format || 'cjs' : 'es';
}
if (!options.shared) {
options.shared = options.format === 'es' && 'svelte/shared.js';
}

if (!('format' in options)) options.format = 'es';
if (!('shared' in options)) options.shared = options.format === 'es' && 'svelte/shared.js';
if (!('name' in options)) options.name = capitalize(sanitize(options.filename));
if (!('onwarn' in options)) options.onwarn = warning => this.emitWarning(new Error(warning));
if (options.emitCss) options.css = false;

if (!options.name) options.name = capitalize(sanitize(options.filename));

if (!options.onwarn) options.onwarn = warning => this.emitWarning(new Error(warning));

deprecatePreprocessOptions(options);
options.preprocess.filename = options.filename;

preprocess(source, options.preprocess).then(processed => {
let { js, css, ast } = normalize(compile(processed.toString(), options));

if (options.emitCss && css.code) {
const posixTmpdir = posixify(tmpdir());
const tmpFile = posix.join(posixTmpdir, 'svelte-' + ast.hash + '.css');

css.code += '\n/*# sourceMappingURL=' + css.map.toUrl() + '*/';
js.code = js.code + `\nrequire('${tmpFile}');\n`;

writeFileSync(tmpFile, css.code);
const { atime, mtime } = statSync(tmpFile);
utimesSync(tmpFile, new Date(atime.getTime() - 99999), new Date(mtime.getTime() - 99999));
}
let { js, css } = normalize(compile(processed.toString(), options));

if (options.hotReload && !isProduction && !isServer) {
const hotOptions = Object.assign({}, options.hotOptions);
const id = JSON.stringify(relative(process.cwd(), options.filename));
js.code = makeHot(id, js.code, hotOptions);
}

if (options.emitCss && css.code) {
const cssFilepath = options.filename.replace(
/\.[^/.]+$/,
`.svelte.css`
);
css.code += '\n/*# sourceMappingURL=' + css.map.toUrl() + '*/';
js.code = js.code + `\nimport '${cssFilepath}';\n`;

if (virtualModules) {
virtualModules.writeModule(cssFilepath, css.code);
}
}

callback(null, js.code, js.map);
}, err => callback(err)).catch(err => {
// wrap error to provide correct
// context when logging to console
callback(new Error(`${err.name}: ${err.toString()}`));
});
};
};
89 changes: 89 additions & 0 deletions lib/virtual-stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Used to cache a stats object for the virtual file.
* Extracted from the `mock-fs` package.
*
* @author Tim Schaub http://tschaub.net/
* @link https://github.com/tschaub/mock-fs/blob/master/lib/binding.js
* @link https://github.com/tschaub/mock-fs/blob/master/license.md
*/

/* eslint-disable no-restricted-syntax, no-prototype-builtins, no-continue */
/* eslint-disable no-bitwise, no-underscore-dangle */

'use strict';

var constants = require('constants');

/**
* Create a new stats object.
* @param {Object} config Stats properties.
* @constructor
*/
function VirtualStats(config) {
for (var 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.
*/
VirtualStats.prototype._checkModeProperty = function(property) {
return (this.mode & constants.S_IFMT) === property;
};

/**
* @return {Boolean} Is a directory.
*/
VirtualStats.prototype.isDirectory = function() {
return this._checkModeProperty(constants.S_IFDIR);
};

/**
* @return {Boolean} Is a regular file.
*/
VirtualStats.prototype.isFile = function() {
return this._checkModeProperty(constants.S_IFREG);
};

/**
* @return {Boolean} Is a block device.
*/
VirtualStats.prototype.isBlockDevice = function() {
return this._checkModeProperty(constants.S_IFBLK);
};

/**
* @return {Boolean} Is a character device.
*/
VirtualStats.prototype.isCharacterDevice = function() {
return this._checkModeProperty(constants.S_IFCHR);
};

/**
* @return {Boolean} Is a symbolic link.
*/
VirtualStats.prototype.isSymbolicLink = function() {
return this._checkModeProperty(constants.S_IFLNK);
};

/**
* @return {Boolean} Is a named pipe.
*/
VirtualStats.prototype.isFIFO = function() {
return this._checkModeProperty(constants.S_IFIFO);
};

/**
* @return {Boolean} Is a socket.
*/
VirtualStats.prototype.isSocket = function() {
return this._checkModeProperty(constants.S_IFSOCK);
};

module.exports = VirtualStats;
73 changes: 73 additions & 0 deletions lib/virtual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
var VirtualStats = require('./virtual-stats');

var inode = 45000000;

// Adapted from https://github.com/sysgears/webpack-virtual-modules
// MIT Licensed https://github.com/sysgears/webpack-virtual-modules/blob/master/LICENSE

function VirtualModulesPlugin(compiler) {
this.compiler = compiler;

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

compiler.inputFileSystem.purge = function() {
originalPurge.call(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)
);
}
};

compiler.inputFileSystem._writeVirtualFile = function(file, stats, contents) {
this._virtualFiles = this._virtualFiles || {};
this._virtualFiles[file] = { stats: stats, contents: contents };
setData(this._statStorage, file, [null, stats]);
setData(this._readFileStorage, file, [null, contents]);
};
}

compiler.hooks.watchRun.tapAsync('VirtualModulesPlugin', (watcher, callback) => {
this._watcher = watcher.compiler || watcher;
callback();
});
}

VirtualModulesPlugin.prototype.writeModule = function(filePath, contents) {
var len = contents ? contents.length : 0;
var time = Date.now();

var stats = new VirtualStats({
dev: 8675309,
nlink: 0,
uid: 1000,
gid: 1000,
rdev: 0,
blksize: 4096,
ino: inode++,
mode: 33188,
size: len,
blocks: Math.floor(len / 4096),
atime: time,
mtime: time,
ctime: time,
birthtime: time
});

this.compiler.inputFileSystem._writeVirtualFile(filePath, stats, contents);
};

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

module.exports = VirtualModulesPlugin;
Loading