UNPKG

mya-command-release

Version:

mya release command.

260 lines (223 loc) 8.3 kB
const _ = fis.util; const fs = require('fs'); const path = require('path'); const portfinder = require('portfinder'); const app = require('express')(); const rHmr = /"(?:[^\\"\r\n\f]|\\[\s\S])*"|'(?:[^\\'\n\r\f]|\\[\s\S])*'|(<\/head>|<!--hmr-->)/ig; let hmrServer, hmrPort, io; let curResMap = {}; const cwd = process.cwd(); const serverRoot = (function() { var key = 'FIS_SERVER_DOCUMENT_ROOT'; var serverInfo = getServerInfo(); if (process.env && process.env[key]) { var path = process.env[key]; if (fis.util.exists(path) && !fis.util.isDir(path)) { fis.log.error('invalid environment variable [' + key + '] of document root [' + path + ']'); } return path; } else if (serverInfo['root'] && fis.util.is(serverInfo['root'], 'String')) { return serverInfo['root']; } else { return fis.project.getTempPath('www'); } })(); let destPath = ''; const defaultHostname = (function() { var ip = false; var net = require('os').networkInterfaces(); /* 获取fis-config.js中配置的默认host规则,可以是字符串或正则,字符串中 * 可以代表任意数字。 使用示例:fis.config.set('livereload-iprule', '192.168.*.*') 适用场景:机器上安装虚拟机后,会有很多虚拟网卡,默认获取的ip在局域网中访问不到,手机调试时加载不了。 */ var iprule = fis.config.get('livereload-iprule'); if (iprule && typeof(iprule) === 'string') { iprule = new RegExp('^' + iprule.replace(/\.|\*/g, function(v) { return v === '.' ? '\\\.' : '\\\d+' }) + '$'); } else if (!(iprule instanceof RegExp)) { iprule = /^\d+(?:\.\d+){3}$/; } Object.keys(net).every(function(key) { var detail = net[key]; Object.keys(detail).every(function(i) { var address = String(detail[i].address).trim(); if (address && iprule.test(address)) { ip = address; } return !ip; // 找到了,则跳出循环 }); return !ip; // 找到了,则跳出循环 }); return ip || '127.0.0.1'; })(); function getServerInfo() { var conf = path.join(fis.project.getTempPath('server'), 'conf.json'); if (fis.util.isFile(conf)) { return fis.util.readJSON(conf); } return {}; } function normalizePath(dest, root) { if (dest === 'preview') { return serverRoot; } return path.resolve(cwd, dest); } function getAvailablePort(options) { return new Promise((resolve, reject) => { portfinder.getPort(options, (err, port) => { if (err) { fis.log.warn('The port %s for hmr is already in use!', options.basePort); return reject(err); } return resolve(port); }); }); } function makeHmrServer(obj) { return new Promise((resolve, reject) => { if (hmrServer) { // 对比新旧map,如果 map 发生变化,发送消息更新 map if (JSON.stringify(curResMap) !== JSON.stringify(obj.map)) { curResMap = Object.assign({}, obj.map); io.emit('update:map', {map: curResMap}); } return resolve({hmrServer, port: hmrPort, io, inited: true}); } hmrServer = require('http').Server(app); io = require('socket.io')(hmrServer); const basePort = fis.media().get('hmr.port', 8232); getAvailablePort({ port: basePort }).then(port => { hmrPort = port; hmrServer.listen(port); process.stdout.write('\n Hmr server start at '.bold.yellow + port + '\n'); app.get('/hmr.runtime.js', (req, res) => { const conf = { hostname: defaultHostname, port }; let script = fs.readFileSync(__dirname + '/../vendor/hmr.runtime.js', 'utf-8'); script = script.replace(/{{(\w+?)}}/g, (match, key) => { return conf[key]; }); res.writeHead(200, { // 'Content-Length': script.length, 'Content-Type': 'text/javascript', 'Connection': 'close' }); res.end(script); }); ['mya.hot.js', 'vue-hot-reload-api.js'].forEach(file => { app.get(`/${file}`, (req, res) => { let script = fs.readFileSync(__dirname + `/../vendor/${file}`, 'utf-8'); res.writeHead(200, { 'Content-Type': 'text/javascript', 'Connection': 'close' }); res.end(script); }); }); io.on('connection', socket => { curResMap = obj.map; socket.emit('establish', { map: curResMap }); }); resolve({hmrServer, port, io}); }).catch((err, port) => { if (err) { reject(err); } }); }); } function injectHmrRuntime(file, port) { var content = file.getContent(); if (!file.isHtmlLike || typeof content !== 'string') { return; } rHmr.lastIndex = 0; content = content.replace(rHmr, function(all, token) { if (token) { var hostname = fis.get('hmr.hostname', defaultHostname); all = ` <script charset="utf-8" src="http://${hostname}:${port}/socket.io/socket.io.js"></script> <script charset="utf-8" src="http://${hostname}:${port}/hmr.runtime.js"></script> <script charset="utf-8" src="http://${hostname}:${port}/vue-hot-reload-api.js"></script> <script charset="utf-8" src="http://${hostname}:${port}/mya.hot.js"></script>${token} `; } return all; }); file.setContent(content); } function isPkgFile(file) { return Object.keys(curResMap.pkg).some(pkg => { return curResMap.pkg[pkg].uri === file.url }); } function handleHmrFile(file, io) { let content = file.getContent(); if (typeof content !== 'string') { return ; } if (isPkgFile(file)) { return ; } if (file.extras.skipHmr) { return ; } if (file.isJsLike) { if (file.extras.hmr && file.extras.hmr.forceReload) { io.emit('update:reload'); return ; } content = content.replace(/\b__M\.define\b/g, '__M.hot.update'); // 增量文件暂不支持 sourceMap content = content.replace(/\/\/# sourceMappingURL=\S+\.map/, ''); // 写入hot文件到目标目录 // release 目录按照 deploy dest / release dest / tempPath 目录 优先级 _.write(path.join(destPath, file.release.replace('.js', '.hot.js')), content, 'utf-8'); io.emit('update:js', { uri: `${ file.url.replace('.js', '') }.hot.js?t=${ new Date().getTime() }`, ext: file.ext, moduleId: file.id, noReload: file.extras.hmr && file.extras.hmr.noReload }); return ; } if (file.isCssLike) { // 写入hot文件到目标目录 // fs.writeFileSync(path.join(root, file.release.replace('.css', '.hot.css')), content, 'utf-8'); io.emit('update:css', { // uri: `${ file.url.replace('.css', '') }.hot.css?t=${ new Date().getTime() }`, ext: file.ext, moduleId: file.id, content: content.trim() }); return ; } } function handleHmr(obj, next) { const isHMR = obj.options.hot; if (!isHMR) { next(null, obj); } destPath = normalizePath(obj.options.dest, fis.project.getProjectPath()); makeHmrServer(obj) .then(({hmrServer, port, io, inited}) => { console.log('\n[hmr] hmr start'); _.toArray(obj.modified).forEach(file => { injectHmrRuntime(file, port); inited && handleHmrFile(file, io); }); console.log('[hmr] hmr end'); next(null, obj); }) .catch(err => { console.log(err); io.emit('update:reload'); next(null, obj); }); } exports.handleHmr = handleHmr;