mya-command-release
Version:
mya release command.
260 lines (223 loc) • 8.3 kB
JavaScript
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;