Skip to content

Commit 4aedacb

Browse files
authored
Merge pull request #60 from sveltejs/no-virtual-plugin
Virtual FS without exposing separate plugin
2 parents ca1b71b + cc3b8f9 commit 4aedacb

7 files changed

+327
-160
lines changed

.eslintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"mocha"
1919
],
2020
"env": {
21-
"node": true
21+
"node": true,
22+
"es6": true
2223
}
2324
}

index.js

+29-31
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
const { basename, extname, posix, relative } = require('path');
1+
const { basename, extname, relative } = require('path');
22
const { compile, preprocess } = require('svelte');
33
const { getOptions } = require('loader-utils');
4-
const { statSync, utimesSync, writeFileSync } = require('fs');
5-
const { tmpdir } = require('os');
4+
const VirtualModules = require('./lib/virtual');
65

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

98
function makeHot(id, code, hotOptions) {
109
const options = JSON.stringify(hotOptions);
1110
const replacement = `
12-
1311
if (module.hot) {
14-
1512
const { configure, register, reload } = require('${posixify(hotApi)}');
1613
1714
module.hot.accept();
@@ -26,7 +23,6 @@ if (module.hot) {
2623
}
2724
}
2825
29-
3026
export default $2;
3127
`;
3228

@@ -80,7 +76,15 @@ function deprecatePreprocessOptions(options) {
8076
options.preprocess = options.preprocess || preprocessOptions;
8177
}
8278

79+
const virtualModuleInstances = new Map();
80+
8381
module.exports = function(source, map) {
82+
if (this._compiler && !virtualModuleInstances.has(this._compiler)) {
83+
virtualModuleInstances.set(this._compiler, new VirtualModules(this._compiler));
84+
}
85+
86+
const virtualModules = virtualModuleInstances.get(this._compiler);
87+
8488
this.cacheable();
8589

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

9296
options.filename = this.resourcePath;
93-
if (!options.format) {
94-
options.format = this.version === 1 ? options.format || 'cjs' : 'es';
95-
}
96-
if (!options.shared) {
97-
options.shared = options.format === 'es' && 'svelte/shared.js';
98-
}
99-
97+
if (!('format' in options)) options.format = 'es';
98+
if (!('shared' in options)) options.shared = options.format === 'es' && 'svelte/shared.js';
99+
if (!('name' in options)) options.name = capitalize(sanitize(options.filename));
100+
if (!('onwarn' in options)) options.onwarn = warning => this.emitWarning(new Error(warning));
100101
if (options.emitCss) options.css = false;
101102

102-
if (!options.name) options.name = capitalize(sanitize(options.filename));
103-
104-
if (!options.onwarn) options.onwarn = warning => this.emitWarning(new Error(warning));
105-
106103
deprecatePreprocessOptions(options);
107104
options.preprocess.filename = options.filename;
108105

109106
preprocess(source, options.preprocess).then(processed => {
110-
let { js, css, ast } = normalize(compile(processed.toString(), options));
111-
112-
if (options.emitCss && css.code) {
113-
const posixTmpdir = posixify(tmpdir());
114-
const tmpFile = posix.join(posixTmpdir, 'svelte-' + ast.hash + '.css');
115-
116-
css.code += '\n/*# sourceMappingURL=' + css.map.toUrl() + '*/';
117-
js.code = js.code + `\nrequire('${tmpFile}');\n`;
118-
119-
writeFileSync(tmpFile, css.code);
120-
const { atime, mtime } = statSync(tmpFile);
121-
utimesSync(tmpFile, new Date(atime.getTime() - 99999), new Date(mtime.getTime() - 99999));
122-
}
107+
let { js, css } = normalize(compile(processed.toString(), options));
123108

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

115+
if (options.emitCss && css.code) {
116+
const cssFilepath = options.filename.replace(
117+
/\.[^/.]+$/,
118+
`.svelte.css`
119+
);
120+
css.code += '\n/*# sourceMappingURL=' + css.map.toUrl() + '*/';
121+
js.code = js.code + `\nimport '${cssFilepath}';\n`;
122+
123+
if (virtualModules) {
124+
virtualModules.writeModule(cssFilepath, css.code);
125+
}
126+
}
127+
130128
callback(null, js.code, js.map);
131129
}, err => callback(err)).catch(err => {
132130
// wrap error to provide correct
133131
// context when logging to console
134132
callback(new Error(`${err.name}: ${err.toString()}`));
135133
});
136-
};
134+
};

lib/virtual-stats.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Used to cache a stats object for the virtual file.
3+
* Extracted from the `mock-fs` package.
4+
*
5+
* @author Tim Schaub http://tschaub.net/
6+
* @link https://github.com/tschaub/mock-fs/blob/master/lib/binding.js
7+
* @link https://github.com/tschaub/mock-fs/blob/master/license.md
8+
*/
9+
10+
/* eslint-disable no-restricted-syntax, no-prototype-builtins, no-continue */
11+
/* eslint-disable no-bitwise, no-underscore-dangle */
12+
13+
'use strict';
14+
15+
var constants = require('constants');
16+
17+
/**
18+
* Create a new stats object.
19+
* @param {Object} config Stats properties.
20+
* @constructor
21+
*/
22+
function VirtualStats(config) {
23+
for (var key in config) {
24+
if (!config.hasOwnProperty(key)) {
25+
continue;
26+
}
27+
this[key] = config[key];
28+
}
29+
}
30+
31+
/**
32+
* Check if mode indicates property.
33+
* @param {number} property Property to check.
34+
* @return {boolean} Property matches mode.
35+
*/
36+
VirtualStats.prototype._checkModeProperty = function(property) {
37+
return (this.mode & constants.S_IFMT) === property;
38+
};
39+
40+
/**
41+
* @return {Boolean} Is a directory.
42+
*/
43+
VirtualStats.prototype.isDirectory = function() {
44+
return this._checkModeProperty(constants.S_IFDIR);
45+
};
46+
47+
/**
48+
* @return {Boolean} Is a regular file.
49+
*/
50+
VirtualStats.prototype.isFile = function() {
51+
return this._checkModeProperty(constants.S_IFREG);
52+
};
53+
54+
/**
55+
* @return {Boolean} Is a block device.
56+
*/
57+
VirtualStats.prototype.isBlockDevice = function() {
58+
return this._checkModeProperty(constants.S_IFBLK);
59+
};
60+
61+
/**
62+
* @return {Boolean} Is a character device.
63+
*/
64+
VirtualStats.prototype.isCharacterDevice = function() {
65+
return this._checkModeProperty(constants.S_IFCHR);
66+
};
67+
68+
/**
69+
* @return {Boolean} Is a symbolic link.
70+
*/
71+
VirtualStats.prototype.isSymbolicLink = function() {
72+
return this._checkModeProperty(constants.S_IFLNK);
73+
};
74+
75+
/**
76+
* @return {Boolean} Is a named pipe.
77+
*/
78+
VirtualStats.prototype.isFIFO = function() {
79+
return this._checkModeProperty(constants.S_IFIFO);
80+
};
81+
82+
/**
83+
* @return {Boolean} Is a socket.
84+
*/
85+
VirtualStats.prototype.isSocket = function() {
86+
return this._checkModeProperty(constants.S_IFSOCK);
87+
};
88+
89+
module.exports = VirtualStats;

lib/virtual.js

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
var VirtualStats = require('./virtual-stats');
2+
3+
var inode = 45000000;
4+
5+
// Adapted from https://github.com/sysgears/webpack-virtual-modules
6+
// MIT Licensed https://github.com/sysgears/webpack-virtual-modules/blob/master/LICENSE
7+
8+
function VirtualModulesPlugin(compiler) {
9+
this.compiler = compiler;
10+
11+
if (!compiler.inputFileSystem._writeVirtualFile) {
12+
var originalPurge = compiler.inputFileSystem.purge;
13+
14+
compiler.inputFileSystem.purge = function() {
15+
originalPurge.call(this, arguments);
16+
if (this._virtualFiles) {
17+
Object.keys(this._virtualFiles).forEach(
18+
function(file) {
19+
var data = this._virtualFiles[file];
20+
setData(this._statStorage, file, [null, data.stats]);
21+
setData(this._readFileStorage, file, [null, data.contents]);
22+
}.bind(this)
23+
);
24+
}
25+
};
26+
27+
compiler.inputFileSystem._writeVirtualFile = function(file, stats, contents) {
28+
this._virtualFiles = this._virtualFiles || {};
29+
this._virtualFiles[file] = { stats: stats, contents: contents };
30+
setData(this._statStorage, file, [null, stats]);
31+
setData(this._readFileStorage, file, [null, contents]);
32+
};
33+
}
34+
35+
compiler.hooks.watchRun.tapAsync('VirtualModulesPlugin', (watcher, callback) => {
36+
this._watcher = watcher.compiler || watcher;
37+
callback();
38+
});
39+
}
40+
41+
VirtualModulesPlugin.prototype.writeModule = function(filePath, contents) {
42+
var len = contents ? contents.length : 0;
43+
var time = Date.now();
44+
45+
var stats = new VirtualStats({
46+
dev: 8675309,
47+
nlink: 0,
48+
uid: 1000,
49+
gid: 1000,
50+
rdev: 0,
51+
blksize: 4096,
52+
ino: inode++,
53+
mode: 33188,
54+
size: len,
55+
blocks: Math.floor(len / 4096),
56+
atime: time,
57+
mtime: time,
58+
ctime: time,
59+
birthtime: time
60+
});
61+
62+
this.compiler.inputFileSystem._writeVirtualFile(filePath, stats, contents);
63+
};
64+
65+
function setData(storage, key, value) {
66+
if (storage.data instanceof Map) {
67+
storage.data.set(key, value);
68+
} else {
69+
storage.data[key] = value;
70+
}
71+
}
72+
73+
module.exports = VirtualModulesPlugin;

0 commit comments

Comments
 (0)