UNPKG

create-skeleton

Version:
258 lines (225 loc) 6.71 kB
"use strict"; const path = require("path"); const https = require("https"); const fs = require("fs"); const commander = require("commander"); const cheerio = require("cheerio"); const puppeteer = require("puppeteer"); const semver = require("semver"); const chalk = require("chalk"); const packageJson = require("../package.json"); const evaluatePage = require("./evaluatePage"); const { Spinner, stringify, log, getType } = require("./utils"); const originalDirectory = process.cwd(); function init() { let skeletonUrl; const program = new commander.Command(packageJson.name).version(packageJson.version).arguments("<create-skeleton-url>").description("input skeleton screen url").action(url => { skeletonUrl = url; }).allowUnknownOption().on("--help", () => { console.log(`如果未配置文件: npx ${chalk.cyan(program.name())} ${chalk.green("https://baidu.com")}`); console.log(`如果已配置文件: npx ${chalk.cyan(program.name())}`); }).parse(process.argv); const skeletonConfigPath = path.resolve(originalDirectory, `${packageJson.name}.config.js`); const existSkeletonConfig = fs.existsSync(skeletonConfigPath); if (typeof skeletonUrl === "undefined") { if (!existSkeletonConfig) { console.error("Please specify the skeleton url:"); console.log(` ${chalk.cyan(program.name())} ${chalk.green("<create-skeleton-url>")}`); console.log(); console.log("For example:"); console.log(` ${chalk.cyan(program.name())} ${chalk.green("https://baidu.com")}`); console.log(); console.log(`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`); process.exit(1); } } checkForLatestVersion().catch(() => { try { return execSync(`npm view ${packageJson.name} version`).toString().trim(); } catch (e) { return null; } }).then(latest => { if (latest && semver.lt(packageJson.version, latest)) { console.log(); console.error(chalk.yellow(`You are running \`${packageJson.name}\` ${packageJson.version}, which is behind the latest release (${latest}).\n\n` + `We no longer support global installation of ${packageJson.name}.`)); console.log(); console.log("Please remove any global installs with one of the following commands:\n" + `- npm uninstall -g ${packageJson.name}\n` + `- yarn global remove ${packageJson.name}`); console.log(); console.log("The latest instructions for creating a skeleton can be found here:\n" + packageJson.homepage); console.log(); process.exit(1); } else { let skeletonConfig = {}; if (existSkeletonConfig) { skeletonConfig = require(skeletonConfigPath); } createSkeleton({ url: skeletonUrl, devtools: true, headless: true, ...skeletonConfig }); } }); } function checkForLatestVersion(packageName) { return new Promise((resolve, reject) => { https.get(`https://registry.npmjs.org/-/package/${packageName || packageJson.name}/dist-tags`, res => { if (res.statusCode === 200) { let body = ""; res.on("data", data => body += data); res.on("end", () => { resolve(JSON.parse(body || "{}").latest); }); } else { reject(); } }).on("error", () => { reject(); }); }); } async function puppet(device = "iPhone 6", launchOption) { const currentDevice = puppeteer.devices[device]; const browser = await puppeteer.launch(launchOption); async function openPage(url, extraHTTPHeaders) { const page = await browser.newPage(); try { await page.emulate(currentDevice); if (extraHTTPHeaders && getType(extraHTTPHeaders) === "object") { await page.setExtraHTTPHeaders(new Map(Object.entries(extraHTTPHeaders))); } await page.goto(url, { timeout: 2 * 60 * 1000, waitUntil: "networkidle0" }); } catch (e) { console.log("\n"); log.error(e.message); } return page; } return { browser, openPage }; } async function createSkeleton({ url = "", output: { filePath = "", injectSelector = "" } = {}, device, headless = false, devtools = true, extraHTTPHeaders, background = "#ecf0f2", animation = "", rootNode = "", header = "", beforeDraw, includeNode, launchOption = {}, writeSkeleton }) { const spinner = Spinner("magentaBright"); spinner.text = "正在启动浏览器..."; const { browser, openPage } = await puppet(device, { headless, devtools, ...launchOption }); spinner.text = `正在打开页面:${url}...`; const page = await openPage(url, extraHTTPHeaders); spinner.text = "正在生成骨架屏..."; const pageEvaluateArgs = stringify({ beforeDraw, header, rootNode, animation, background, includeNode, devtools, headless }); const content = await new Promise(resolve => { let currentPage; browser.on("targetchanged", async () => { const targets = await browser.targets(); const currentTarget = targets[targets.length - 1]; currentPage = await currentTarget.page(); resolve(await page.evaluate(evaluatePage, pageEvaluateArgs)); }); page.evaluate(evaluatePage, pageEvaluateArgs).then(value => { if (!currentPage) { resolve(value); } }); }); if (writeSkeleton) { writeSkeleton(content); } else { writeSkeletonToFile({ filePath, content, injectSelector }); } spinner.clear().succeed(`skeleton screen has created and output to ${filePath || path.join(process.cwd(), `/${packageJson.name}.html`)}`); if (headless) { await browser.close(); process.exit(0); } } function writeSkeletonToFile({ filePath, content, injectSelector }) { const data = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>example</title> <style> @keyframes ant-skeleton-loading { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } } </style> </head> <body> <div id="app">${content}</div> </body> </html> `; if (!filePath) { fs.writeFileSync(`${process.cwd()}/${packageJson.name}.html`, data); return; } const fileHTML = fs.readFileSync(filePath); let $ = cheerio.load(fileHTML, { decodeEntities: false }); $(injectSelector).html(content); fs.writeFileSync(filePath, $.html("html")); } module.exports = { init, createSkeleton };