UNPKG

systemjs-tools

Version:
495 lines (385 loc) 16.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.passiveInit = exports.init = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _chokidar = require('chokidar'); var _chokidar2 = _interopRequireDefault(_chokidar); var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _socket = require('socket.io'); var _socket2 = _interopRequireDefault(_socket); var _spdy = require('spdy'); var _spdy2 = _interopRequireDefault(_spdy); var _handlers = require('./handlers'); var _config = require('./config'); var _systemjsBuilder = require('systemjs-builder'); var _systemjsBuilder2 = _interopRequireDefault(_systemjsBuilder); var _Rx = require('rxjs/Rx'); var _Rx2 = _interopRequireDefault(_Rx); var _fs = require('mz/fs'); var _fs2 = _interopRequireDefault(_fs); var _deepmerge = require('deepmerge'); var _deepmerge2 = _interopRequireDefault(_deepmerge); var _bluebird = require('bluebird'); var _bluebird2 = _interopRequireDefault(_bluebird); var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } var Subject = _Rx2.default.Subject; var init = function init() { var configOverrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; /** * passive initialisation of systemjs-tools */ var _getConfig = (0, _config.getConfig)((0, _config.conform)(configOverrides)), config = _getConfig.config, valid = _getConfig.valid, errors = _getConfig.errors; if (!valid) { var _console; (_console = console).error.apply(_console, _toConsumableArray(errors)); process.exit(1); } var tools = { config: config, _: { cache: { version: require(_path2.default.join(__dirname, '../package.json')).version, bundle: {}, sfx: {} }, // Builder instance (so we can share the cache) builder: new _systemjsBuilder2.default(_path2.default.join(config.directories.root, config.directories.baseURL)), // TODO: Make an event middleware system events: new Subject(), // Construct for pipelining build operations (parallel is slower) promiseContext: {}, then: function then(context, f) { var nextPromise = (tools._.promiseContext[context] || _bluebird2.default.resolve()).then(f); tools._.promiseContext[context] = nextPromise.catch(function () {}); return nextPromise; } } }; var _ = tools._; // f: given (req, res) -> give us more information about the request tools.analyse = _handlers.analyse; // functions to centralize logging _.log = function () { for (var _len = arguments.length, messages = Array(_len), _key = 0; _key < _len; _key++) { messages[_key] = arguments[_key]; } return _.events.next({ type: 'log', message: messages.join(' ') }); }; _.warn = function (message) { return _.events.next({ type: 'warning', message: message }); }; _.error = function (message, error) { return _.events.next({ type: 'error', message: message, error: error }); }; _.fatal = function (message, error) { _.events.next({ type: 'fatal', message: message, error: error }); _.exit(); }; // f: load the internal cache from disk _.loadCache = function () { try { var cache = JSON.parse(_fs2.default.readFileSync(_path2.default.join(config.directories.root, config.cache), 'utf8')); if (cache.version == _.cache.version) { // Since we can't persist promise values, we construct them on load Object.values(cache.bundle).forEach(function (bundleCache) { bundleCache.bundlePromise = _bluebird2.default.resolve(bundleCache.bundle); }); _.cache = cache; if (_.cache.builder) _.builder.setCache(_.cache.builder); _.log('using cache found at ' + config.cache); } else { _.log('resetting cache :: cache@' + cache.version + ' <*> systemjs-tools@' + _.cache.version); } } catch (error) { _.warn('couldn\'t find a valid cache at ' + config.cache + '. Starting fresh :)'); } }; // request that the cache be persisted _.persistCache = function () { _.events.next({ type: 'persist-cache' }); }; _.invalidate = function (_ref) { var absolutePath = _ref.absolutePath; _.builder.invalidate(_path2.default.relative(config.directories.baseURL, absolutePath)); var normalized = _path2.default.normalize(_path2.default.relative(_path2.default.join(config.directories.root, config.directories.baseURL), absolutePath)); var rebundle = []; Object.values(_.cache.bundle).forEach(function (bundleCache) { bundleCache.bundle && bundleCache.bundle.modules.forEach(function (module) { if (_path2.default.normalize(module) == normalized) { rebundle.push([bundleCache.expression, bundleCache.options]); bundleCache.valid = false; } }); }); if (!config.lazy) rebundle.forEach(function (_ref2) { var _ref3 = _slicedToArray(_ref2, 2), expression = _ref3[0], options = _ref3[1]; return _.bundle(expression, options); }); }; // f: bundle the expression _.bundle = function (expression) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var updateLastAccessed = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; options = (0, _deepmerge2.default)(config.builder.options, options); var cacheName = expression + '#' + JSON.stringify(options); if (!_.cache.bundle[cacheName]) { // console.log('fresh') _.cache.bundle[cacheName] = { expression: expression, options: options, valid: false, bundling: false, bundle: null, bundlePromise: _bluebird2.default.resolve(), lastAccessed: Date.now() }; } var cache = _.cache.bundle[cacheName]; if (cache.valid && !cache.bundling) { // the cache is valid, so we do nothing _.log('serving cached bundle for ' + expression); } else if (cache.bundling) { // the cache is invalid but the expression is already being bundled, so we do nothing _.log('hooking into queued bundle request for ' + expression); } else { // the cache is invalid and no build process is happening... so LETS DO DIS // we are bundling cache.bundling = true; cache.bundlePromise = _.then('build', function () { _.log('bundling ' + expression + '...'); _.log('options ' + JSON.stringify(options, null, true)); cache.valid = true; // we are bundling, re-declared in-case it was switched in the previous tick cache.bundling = true; var start = new Date().getTime(); return _.builder.bundle(expression, options).then(function (bundle) { _.persistCache(); _.log('finished bundling ' + expression + '; took ' + (new Date().getTime() - start) + ' ms'); cache.bundling = false; cache.bundle = bundle; return bundle; }).catch(function (err) { _.error('failed to bundle ' + expression, err); }); }); _.log('bundle request for ' + expression + ' queued...'); } if (updateLastAccessed) cache.lastAccessed = Date.now(); return cache.bundlePromise; }; // f: start a development/production http2 server tools.serve = function () { var configOverride = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var _merge = (0, _deepmerge2.default)(config, { serve: configOverride }), _merge$serve = _merge.serve, server = _merge$serve.server, port = _merge$serve.port, keys = _merge$serve.keys, handler = _merge$serve.handler, dir = _merge$serve.dir, entries = _merge.entries; server = server || _spdy2.default.createServer(keys, handler(tools)); server.listen(port, function (error) { error ? _.error('Couldn\'t start server on port ' + port, error) : _.log('serving ' + dir + ' at https://localhost:' + port); }); tools.developmentChannel(); // in next tick so we get nicer log ordering if (!config.lazy) process.nextTick(function () { if (entries.length > 0) { _.log('preemptively bundling app entries'); entries.forEach(function (entry) { return _.bundle(entry); }); } }); return { server: server }; }; // f: setup socket handler to proxy all system events to browser tools.developmentChannel = function () { var channelConfigOverride = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var _merge2 = (0, _deepmerge2.default)(config.channel, channelConfigOverride), port = _merge2.port, keys = _merge2.keys; var server = _spdy2.default.createServer(keys, function (req, res) { return res.end('systemjs-tools development channel'); }); server.listen(port, function (error) { // log if failed if (error) _.error('Couldn\'t start dev channel on port ' + port, error); // else start socket server else { _.log('dev channel started at https://localhost:' + port); var socketServer = (0, _socket2.default)(server); socketServer.on('connect', function (socket) { socket.on('identification', function () { return _.log('client connected to development channel'); }); // listen to system events _.events // filter for events the browser cares about .filter(function (_ref4) { var type = _ref4.type; return ['hmr'].indexOf(type) >= 0; }) // and send them to browser .subscribe(function (event) { return socket.emit('*', event); }); }); } }); }; // f: notify system that a file has changed _.fileChanged = function (absolutePath) { _.log('file-changed: ' + _path2.default.relative(config.directories.root, absolutePath)); _.events.next({ type: 'file-changed', absolutePath: absolutePath, relativePath: _path2.default.relative(config.directories.root, absolutePath), url: _path2.default.relative(config.directories.baseURL, absolutePath) }); }; // f: watch file system and notify systemjs-tools when file changes _.watchFileSystem = function () { var _ref5; var chokidarOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _.watcher = _chokidar2.default.watch(config.directories.root, (0, _deepmerge2.default)({ cwd: process.cwd(), ignored: (_ref5 = ["**/jspm_packages", "**/node_modules", "**/icons", _path2.default.join(config.directories.root, config.cache)]).concat.apply(_ref5, _toConsumableArray(config.directories.ignored || [])), ignoreInitial: true }, chokidarOptions)); // Watch for changes (used to invalidate builder and hmr) _.watcher.on('all', function (event, changePath) { return _.fileChanged(changePath); }); }; // f: bust files that have changed since last time systemjs-tools ran _.bustOldBuilderEntries = function () { if (_.cache.builder) { _.then('build', function () { return _bluebird2.default.all(Object.values(_.cache.builder.trace).map(function (t) { if (t.path) { (function () { var file = _path2.default.join(config.directories.root, config.directories.baseURL, t.path); _fs2.default.stat(file).then(function (stats) { if (stats.mtime.getTime() > t.timestamp) _.fileChanged(file); return _bluebird2.default.resolve(); }).catch(function (err) { _.log('Can\'t open ' + _path2.default.relative(config.directories.root, file) + ', assuming it has been deleted'); _.fileChanged(file); return _bluebird2.default.resolve(); }); })(); } })); }); } }; /** * active initialisation of systemjs-tools */ // print system messages _.events.filter(config.log).subscribe(function (_ref6) { var type = _ref6.type, message = _ref6.message, error = _ref6.error, relativePath = _ref6.relativePath; switch (type) { case 'log': console.log('::', message); break; case 'warning': console.warn('::', message); break; case 'error': console.error(':: error ::', message, '\n\n', error, '\n'); break; case 'fatal': console.error(':: fatal error ::', message, '\n\n', error, '\n'); break; case 'file-changed': _.log('file changed ::', relativePath); } }); tools.handlers = (0, _handlers.makeHandlers)(tools); // Config entry to support systemjs-hmr _.builder.config({ meta: { '@hot': { build: false } } }); // Load config files config.builder.configFiles.forEach(function (file) { try { _.builder.loadConfigSync(_path2.default.join(config.directories.root, file)); } catch (error) { _.fatal('Couldn\'t load config ' + file, error); } }); // Load cache _.loadCache(); // Watch for changes in the file system if (config.watch) _.watchFileSystem(); _.bustOldBuilderEntries(); _.events.fileChanged = _.events.filter(function (_ref7) { var type = _ref7.type; return type == 'file-changed'; }); // Invalidate builder on file change _.events.fileChanged.subscribe(function (event) { _.invalidate(event); }); // persist cache on file change _.events.fileChanged.subscribe(function () { return _.persistCache(); }); // Generate hmr events from file changes _.events.fileChanged.map(function (event) { return _extends({}, event, { type: 'hmr', entries: config.entries }); }).subscribe(_.events); // persist cache if persist-cache message received _.events.filter(function (_ref8) { var type = _ref8.type; return type == 'persist-cache'; }).debounceTime(1000).subscribe(function () { _.log('persisting cache'); _.cache.builder = _.builder.getCache(); var cache = _extends({}, _.cache, { bundle: {} }); Object.keys(_.cache.bundle).forEach(function (cacheName) { cache.bundle[cacheName] = _lodash2.default.omit(_.cache.bundle[cacheName], ['bundlePromise']); }); return _fs2.default.writeFile(_path2.default.join(config.directories.root, config.cache), JSON.stringify(cache), 'utf8').catch(function (err) { return _.error('failed to persist cache', err); }); }); return tools; }; var passiveInit = function passiveInit() { var configOverrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return init((0, _deepmerge2.default)({ watch: false }, configOverrides)); }; exports.init = init; exports.passiveInit = passiveInit;