@aniyajs/rotor
Version:
基于webpack5开发的一款专注于打包、运行的工具
309 lines (282 loc) • 8.35 kB
JavaScript
const detect = require("detect-port-alt");
const isRoot = require("is-root");
const prompts = require("prompts");
const chalk = require("chalk");
const url = require("url");
const address = require("address");
const { clearConsole } = require("./common");
const forkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const formatWebpackMessages = require("./formatWebpackMessages");
const isInteractive = process.stdout.isTTY;
/**
* Details of the current application
*
* @param {*} appName
* @param {*} appVersion
* @param {*} urls
* @param {*} useYarn
* @param {*} ms
*/
function printInstructions(appName, appVersion, urls, useYarn) {
console.log();
console.log();
console.log(
`App ${chalk.blue(appName)} ${chalk.gray(
"v" + appVersion,
)} is start, now accessible in browser.`,
);
console.log();
console.log(
` ${chalk.gray("--")} Local: ${chalk.cyan(
urls.localUrlForTerminal,
)}`,
);
console.log(
` ${chalk.gray("--")} On your network: ${chalk.cyan(
urls.lanUrlForTerminal,
)}`,
);
console.log();
console.log("Please note that the development version is not optimized.");
console.log(
`To create a production version, use ${chalk.cyan(
(useYarn ? "yarn" : "npm run") + " build",
)}.`,
);
console.log();
console.log();
}
/**
* 选择可使用的端口
*
* @param {*} host
* @param {*} defaultPort
* @returns
*/
function choosePort(host, defaultPort) {
return detect(defaultPort, host).then(
(_port) =>
new Promise((resolve, reject) => {
if (_port == defaultPort) {
return resolve(_port);
}
const message =
process.platform !== "win32" && defaultPort < 1024 && !isRoot()
? "在低于1024的端口上运行服务器需要管理员权限"
: `${defaultPort}端口已被占用`;
if (isInteractive) {
const questions = {
type: "confirm",
name: "shouldChangePort",
message:
chalk.yellow(message) +
"\n\n你想要在另一个端口上运行应用程序吗?",
initial: true,
};
prompts(questions).then((answer) => {
if (answer.shouldChangePort === undefined) {
// prompts 被中断(如进程退出),静默 resolve null 让上层跳过
resolve(null);
} else if (answer.shouldChangePort) {
resolve(_port);
} else {
reject("👻 请自行退出当前项目")
}
});
} else {
reject(message);
}
}),
(err) => {
throw new Error(
chalk.red(`在${chalk.bold(host)}上找不到开放的端口。`) +
"\n" +
("网络错误信息:" + err.message || err) +
"\n",
);
},
);
}
/**
* 准备好的url配置
*
* @param {string} protocol
* @param {string} host
* @param {number} port
* @param {string} [pathname="/"]
* @param {boolean} [isHash=false]
* @return {object}
*/
function prepareUrls(protocol, host, port, pathname = "/", isHash = false) {
const formatUrl = (hostname) =>
url.format({
protocol,
hostname,
port,
pathname,
});
const prettyPrintUrl = (hostname) =>
url.format({
protocol,
hostname,
port,
pathname,
});
const isUnspecifiedHost = host === "0.0.0.0" || host === "::" || host === "localhost";
let prettyHost, lanUrlForConfig, lanUrlForTerminal;
if (isUnspecifiedHost) {
prettyHost = "localhost";
try {
// 返回IPv4地址。
lanUrlForConfig = address.ip();
if (lanUrlForConfig) {
// 检查该地址是否为私有ip地址。
if (
/^10[.]|^172[.](1[6-9]|2[0-9]|3[0-1])[.]|^192[.]168[.]/.test(
lanUrlForConfig,
)
) {
// 该地址是私有的,并且格式化以便以后使用。
lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig);
} else {
// 地址不私密
lanUrlForTerminal = undefined;
}
}
} catch (_error) {
// ignore
}
} else {
prettyHost = host;
}
const localUrlForTerminal = prettyPrintUrl(prettyHost);
const localUrlForBrowser = formatUrl(prettyHost);
return {
lanUrlForConfig,
lanUrlForTerminal: isHash ? `${lanUrlForTerminal}/#/` : lanUrlForTerminal,
localUrlForTerminal: isHash ? `${localUrlForTerminal}/#/` : localUrlForTerminal,
localUrlForBrowser: isHash ? `${localUrlForBrowser}/#/` : localUrlForBrowser,
};
}
/**
* 创建一个自定义消息的webpack编译器
*
* @param {*} {
* webpack,
* latestConfig,
* useTypeScript,
* appName,
* appVersion,
* urls,
* useYarn,
* }
* @return {*}
*/
function createCompiler({
webpack,
latestConfig,
useTypeScript,
appName,
appVersion,
urls,
useYarn,
}) {
let compiler;
try {
compiler = webpack(latestConfig);
} catch (error) {
console.log(chalk.red("Failed to compile."));
console.log();
console.log(error.message || error);
console.log();
process.exit(1);
}
// 观察中的 compilation 无效时执行
// 比如更改了 package.json 文件
compiler.hooks.invalid.tap("invalid", () => {
if (isInteractive) {
clearConsole();
}
console.log("Compiling...");
});
let isFirstCompile = true;
let tsMessagesPromise = Promise.resolve();
if (useTypeScript && latestConfig.tslint) {
forkTsCheckerWebpackPlugin
.getCompilerHooks(compiler)
.waiting.tap("awaitingTypeScriptCheck", () => {
console.log(chalk.yellow("文件发送成功,正在等待类型检查结果…"));
});
}
compiler.hooks.done.tap("done", (stats) => {
if (isInteractive) {
clearConsole();
}
//因为有ForkTsCheckerWebpackPlugin,所以这里我们不需要自定义错误消息
//如果你的应用是js ?
const statsData = stats.toJson({
all: false,
warnings: true,
errors: true,
});
const messages = formatWebpackMessages(statsData);
const isSuccessful = !messages.errors.length && !messages.warnings.length;
if (isSuccessful) {
console.log(chalk.green("Compiled successfully!"));
}
if (isSuccessful && (isInteractive || isFirstCompile)) {
printInstructions(appName, appVersion, urls, useYarn, statsData.time);
}
isFirstCompile = false;
// 如果errors存在,只显示错误
if (messages.errors.length) {
// 只保留第一个错误其他的通常是指示性的
// 同样的问题,但混淆了读者。
if (messages.errors.length > 1) {
messages.errors.length = 1;
}
console.log(chalk.red("Failed to compile.\n"));
return;
}
// 如果没有发现错误,显示警告。
if (messages.warnings.length) {
console.log(chalk.yellow("Compiled with warnings.\n"));
// 展示一些 ESLint 提示.
console.log(
"\nSearch for the " +
chalk.underline(chalk.yellow("keywords")) +
" to learn more about each warning.",
);
console.log(
"To ignore, add " +
chalk.cyan("// eslint-disable-next-line") +
" to the line before.\n",
);
}
});
// You can safely remove this after ejecting.
// We only use this block for testing of @aniyajs/rotor itself:
const isSmokeTest = process.argv.some(
(arg) => arg.indexOf("--smoke-test") > -1,
);
if (isSmokeTest) {
compiler.hooks.failed.tap("smokeTest", async () => {
await tsMessagesPromise;
process.exit(1);
});
compiler.hooks.done.tap("smokeTest", async (stats) => {
await tsMessagesPromise;
if (stats.hasErrors() || stats.hasWarnings()) {
process.exit(1);
} else {
process.exit(0);
}
});
}
return compiler;
}
module.exports = {
choosePort,
prepareUrls,
createCompiler,
};