create-skeleton
Version:
a nice tool to make skeleton screen
258 lines (225 loc) • 6.71 kB
JavaScript
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
};
;