feeles-ide
Version:
The hackable and serializable IDE to make learning material
580 lines (477 loc) • 19.2 kB
JavaScript
'use strict';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var fs = require('fs');
var path = require('path');
var mime = require('mime');
var unorm = require('unorm');
var _require = require('es6-promisify'),
promisify = _require.promisify;
var readFile = promisify(fs.readFile);
var stat = promisify(fs.stat);
var toPOSIX = function toPOSIX(str) {
return path.sep !== '/' ? str.split(path.sep).join('/') : str;
};
var MountFile =
/*#__PURE__*/
function () {
function MountFile(mountName, mountDir, timestamp) {
(0, _classCallCheck2.default)(this, MountFile);
// Copy props
this.mountName = mountName;
this.mountDir = mountDir;
this.timestamp = timestamp;
}
(0, _createClass2.default)(MountFile, [{
key: "serialize",
value: function () {
var _serialize = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee() {
var mountName, absolutePath, text, replaced, composed;
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
// Convert unicode
mountName = unorm.nfc(this.mountName);
absolutePath = path.resolve(this.mountDir, mountName);
_context.next = 4;
return readFile(absolutePath, 'utf8');
case 4:
text = _context.sent;
replaced = replaceEnvironmentVars(text);
composed = new Buffer(replaced).toString('base64');
return _context.abrupt("return", {
name: toPOSIX(mountName),
type: mime.getType(absolutePath),
lastModified: Date.parse(this.timestamp),
composed: composed,
options: {
isTrashed: false
},
credits: []
});
case 8:
case "end":
return _context.stop();
}
}
}, _callee, this);
}));
function serialize() {
return _serialize.apply(this, arguments);
}
return serialize;
}()
}, {
key: "serialized",
get: function get() {
if (!this._serialized) {
// Serialize
this._serialized = this.serialize();
}
return this._serialized;
}
}]);
return MountFile;
}();
module.exports =
/*#__PURE__*/
function () {
function FeelesWebpackPlugin(params) {
(0, _classCallCheck2.default)(this, FeelesWebpackPlugin);
params = Object.assign({
paths: ['mount'],
output: 'index.json',
ignore: /[]/,
// eslint-disable-line
debug: false
}, params);
this.output = params.output;
this.ignore = params.ignore;
this.debug = params.debug;
this.cache = new Map(); // { [mountName]: [<MountFile>] }
this.mountDirs = params.paths.map(function (s) {
return path.resolve(s);
});
this.priorityOrders = new Map(); // { [mountDir]: [order] }
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = this.mountDirs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var mountDir = _step.value;
this.priorityOrders.set(mountDir, this.priorityOrders.size);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
(0, _createClass2.default)(FeelesWebpackPlugin, [{
key: "apply",
value: function apply(compiler) {
var _this = this;
// コンパイル開始
compiler.plugin('compilation', function (compilation, params) {
var pushDirFiles = function pushDirFiles(dirPath) {
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = fs.readdirSync(dirPath)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var name = _step2.value;
var targetPath = path.resolve(dirPath, name);
var stats = fs.statSync(targetPath);
if (stats.isFile() && !_this.ignore.test(targetPath)) {
// 次の emit の compilation.fileDependencies に含める
params.compilationDependencies.add(targetPath);
}
if (stats.isDirectory()) {
pushDirFiles(targetPath);
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
}; // すべての mountDir を再帰的に探索する
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = _this.mountDirs[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var mountDir = _step3.value;
pushDirFiles(mountDir);
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
});
compiler.plugin('emit',
/*#__PURE__*/
function () {
var _ref = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee2(compilation, callback) {
var mountNameDir, _iteratorNormalCompletion4, _didIteratorError4, _iteratorError4, _iterator4, _step4, absolutePath, _iteratorNormalCompletion6, _didIteratorError6, _iteratorError6, _iterator6, _step6, mountDir, mountName, nextMountDir, entry, changed, _iteratorNormalCompletion5, _didIteratorError5, _iteratorError5, _iterator5, _step5, _step5$value, _mountName, _mountDir, _absolutePath, timestamp, p, add, files, json;
return _regenerator.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
// compilation.fileDependencies をもとにプロジェクト全体をシリアライズ
// { [mountName]: [mountDir] }
mountNameDir = new Map();
_iteratorNormalCompletion4 = true;
_didIteratorError4 = false;
_iteratorError4 = undefined;
_context2.prev = 4;
_iterator4 = compilation.fileDependencies[Symbol.iterator]();
case 6:
if (_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done) {
_context2.next = 32;
break;
}
absolutePath = _step4.value;
if (!_this.ignore.test(absolutePath)) {
_context2.next = 10;
break;
}
return _context2.abrupt("continue", 29);
case 10:
// ignore
// mountName を切り出す
_iteratorNormalCompletion6 = true;
_didIteratorError6 = false;
_iteratorError6 = undefined;
_context2.prev = 13;
for (_iterator6 = _this.mountDirs[Symbol.iterator](); !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
mountDir = _step6.value;
if (absolutePath.startsWith(mountDir + path.sep)) {
mountName = path.relative(mountDir, absolutePath); // すでにある候補もふくめて最も優先順位の高いパスを mountNameDir に set
nextMountDir = _this.maxPriority(mountDir, mountNameDir.get(mountName));
mountNameDir.set(mountName, nextMountDir);
}
}
_context2.next = 21;
break;
case 17:
_context2.prev = 17;
_context2.t0 = _context2["catch"](13);
_didIteratorError6 = true;
_iteratorError6 = _context2.t0;
case 21:
_context2.prev = 21;
_context2.prev = 22;
if (!_iteratorNormalCompletion6 && _iterator6.return != null) {
_iterator6.return();
}
case 24:
_context2.prev = 24;
if (!_didIteratorError6) {
_context2.next = 27;
break;
}
throw _iteratorError6;
case 27:
return _context2.finish(24);
case 28:
return _context2.finish(21);
case 29:
_iteratorNormalCompletion4 = true;
_context2.next = 6;
break;
case 32:
_context2.next = 38;
break;
case 34:
_context2.prev = 34;
_context2.t1 = _context2["catch"](4);
_didIteratorError4 = true;
_iteratorError4 = _context2.t1;
case 38:
_context2.prev = 38;
_context2.prev = 39;
if (!_iteratorNormalCompletion4 && _iterator4.return != null) {
_iterator4.return();
}
case 41:
_context2.prev = 41;
if (!_didIteratorError4) {
_context2.next = 44;
break;
}
throw _iteratorError4;
case 44:
return _context2.finish(41);
case 45:
return _context2.finish(38);
case 46:
entry = []; // プロジェクトのファイル全体
changed = false; // 変更があったかどうか
_iteratorNormalCompletion5 = true;
_didIteratorError5 = false;
_iteratorError5 = undefined;
_context2.prev = 51;
_iterator5 = mountNameDir.entries()[Symbol.iterator]();
case 53:
if (_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done) {
_context2.next = 81;
break;
}
_step5$value = (0, _slicedToArray2.default)(_step5.value, 2), _mountName = _step5$value[0], _mountDir = _step5$value[1];
// webpack が提供する timestamp を取得
_absolutePath = path.resolve(_mountDir, _mountName);
_context2.t2 = compilation.fileTimestamps[_absolutePath];
if (_context2.t2) {
_context2.next = 63;
break;
}
_context2.t3 = Date;
_context2.next = 61;
return stat(_absolutePath);
case 61:
_context2.t4 = _context2.sent.mtime;
_context2.t2 = _context2.t3.parse.call(_context2.t3, _context2.t4);
case 63:
timestamp = _context2.t2;
if (!_this.cache.has(_mountName)) {
_context2.next = 73;
break;
}
p = _this.cache.get(_mountName);
if (!(_mountDir === p.mountDir && timestamp <= p.timestamp)) {
_context2.next = 71;
break;
}
// 前回のビルドと同じ. そのまま
entry.push(p.serialized);
return _context2.abrupt("continue", 78);
case 71:
// 場所かタイムスタンプが異なるのでキャッシュを削除
_this.cache.delete(_mountName);
if (_this.debug) {
console.log('📦 Feeles/mod:', _mountName, _mountDir);
}
case 73:
// キャッシュとは異なるので新しく作成
add = new MountFile(_mountName, _mountDir, timestamp);
entry.push(add.serialized);
_this.cache.set(_mountName, add); // フラグを立てる
changed = true;
if (_this.debug) {
console.log('📦 Feeles/add:', _mountName, _mountDir);
}
case 78:
_iteratorNormalCompletion5 = true;
_context2.next = 53;
break;
case 81:
_context2.next = 87;
break;
case 83:
_context2.prev = 83;
_context2.t5 = _context2["catch"](51);
_didIteratorError5 = true;
_iteratorError5 = _context2.t5;
case 87:
_context2.prev = 87;
_context2.prev = 88;
if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
_iterator5.return();
}
case 90:
_context2.prev = 90;
if (!_didIteratorError5) {
_context2.next = 93;
break;
}
throw _iteratorError5;
case 93:
return _context2.finish(90);
case 94:
return _context2.finish(87);
case 95:
if (!changed) {
_context2.next = 102;
break;
}
_context2.next = 98;
return Promise.all(entry);
case 98:
files = _context2.sent;
console.log("\uD83D\uDCE6 Feeles:".concat(entry.length, " files mounted\tin ").concat(_this.mountDirs.join()));
json = JSON.stringify(files);
compilation.assets[_this.output] = {
source: function source() {
return json;
},
size: function size() {
return json.length;
}
};
case 102:
callback();
case 103:
case "end":
return _context2.stop();
}
}
}, _callee2, this, [[4, 34, 38, 46], [13, 17, 21, 29], [22,, 24, 28], [39,, 41, 45], [51, 83, 87, 95], [88,, 90, 94]]);
}));
return function (_x, _x2) {
return _ref.apply(this, arguments);
};
}());
compiler.plugin('after-emit', function (compilation, callback) {
// 次の emit の compilation.fileDependencies に含める
var _iteratorNormalCompletion7 = true;
var _didIteratorError7 = false;
var _iteratorError7 = undefined;
try {
for (var _iterator7 = _this.mountDirs[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
var mountDir = _step7.value;
compilation.contextDependencies.add(mountDir);
}
} catch (err) {
_didIteratorError7 = true;
_iteratorError7 = err;
} finally {
try {
if (!_iteratorNormalCompletion7 && _iterator7.return != null) {
_iterator7.return();
}
} finally {
if (_didIteratorError7) {
throw _iteratorError7;
}
}
}
callback();
});
}
}, {
key: "maxPriority",
value: function maxPriority(a, b) {
if (!this.priorityOrders.has(a)) return b;
if (!this.priorityOrders.has(b)) return a;
return this.priorityOrders.get(a) < this.priorityOrders.get(b) ? a : b;
}
}]);
return FeelesWebpackPlugin;
}();
/**
* Feeles で使える環境変数. これらの文字を "文字列としてリプレイス" する (import のロード先にも使える)
* process.env.__FEELES_COMMON_REGISTER__: @hackforplay/common の register.js
* process.env.__FEELES_COMMON_INDEX__: @hackforplay/common の index.js
* process.env.__FEELES_NODE_ENV__: process.env.NODE_ENV
*/
var environmentVars = function (vars) {
return function () {
return vars = vars || Object.keys(process.env).filter(function (s) {
return s.startsWith('__FEELES_');
});
};
}();
function replaceEnvironmentVars() {
var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var _iteratorNormalCompletion8 = true;
var _didIteratorError8 = false;
var _iteratorError8 = undefined;
try {
for (var _iterator8 = environmentVars()[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {
var key = _step8.value;
text = text.split(key).join(process.env[key]);
}
} catch (err) {
_didIteratorError8 = true;
_iteratorError8 = err;
} finally {
try {
if (!_iteratorNormalCompletion8 && _iterator8.return != null) {
_iterator8.return();
}
} finally {
if (_didIteratorError8) {
throw _iteratorError8;
}
}
}
return text;
}