UNPKG

@aniyajs/rotor

Version:

基于webpack5开发的一款专注于打包、运行的工具

309 lines (282 loc) 8.35 kB
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, };