@ali-i18n-fe/dada-component
Version:
391 lines (332 loc) • 11.1 kB
JavaScript
const {
shellSyncExec,
wait,
getCurrentPathConfig,
hasPreviewAtPath,
} = require("./utils");
const { getRouteByPath } = require("./webpack/utils");
const { getDefaultPublicPath } = require("./utils/def");
const { promiseAllLimit } = require("./utils");
const glob = require("glob");
const axios = require("axios");
const chunk = require("lodash/chunk");
const set = require("lodash/set");
const { PREVIEW_PATH } = require("./constants");
const { isDef } = require("./utils/def");
const dataUrl = require("datauri");
const path = require("path");
const fs = require("fs");
const lscUtils = require("@ali-i18n-fe/lsc-utils");
const log = lscUtils.log;
const baseCSS = `*{box-sizing:border-box}body,html{margin:0;font-family:system-ui,sans-serif;font-size:12px;width:100%;}`;
const getHtmlData = ({
baseCSS,
html: ehtml,
css,
script,
beforeScript,
styles,
webfont,
}) => {
const fontCSS = webfont ? getWebfontCSS(webfont) : "";
// fix url(//at.alicdn.com/t/font_
css = css.replace(/url\(\/\//g, "url(http://");
ehtml = ehtml ? ehtml.replace(/"\/\//g, '"http://') : ehtml;
const html = `<!DOCTYPE html>
<head>
<meta charset="utf-8"><style>${baseCSS}${fontCSS}${css}</style>
${styles}
<style>html,body{background: unset !important;}#preview-root>div{margin:0 auto;}#preview-root{width: max-content}</style>
</head>
<body>
<div id="preview-root"></div>
${beforeScript}
${ehtml ? ehtml : ""}
</body>
<script>${script}</script>
<script >
if (Component) {
DocsRender(Component, document.querySelector('#preview-root'));
}else{
document.getElementById('preview-root').innerText='Not Found Component';
}
</script>
`;
return html;
};
const getWebfontCSS = (fontpath) => {
const { content } = new dataUrl(fontpath);
const [name, ext] = fontpath.split("/").slice(-1)[0].split(".");
const css = `@font-face {
font-family: '${name}';
font-style: normal;
font-weight: 400;
src: url(${content});
}`;
return css;
};
module.exports = {
currentPath: process.cwd(),
async load(options = {}) {
this.debug = !!options.debug;
const files = glob.sync("**/index.js", {
cwd: path.resolve(this.currentPath, "dist/docs"),
realpath: true,
});
if (!files.length) {
throw new Error("请先执行 dada-comp build 编译");
}
try {
// await this.getSnapShots(files);
await promiseAllLimit(
files.map((file) => () => this.getSnapShot(file)),
40
);
// 兼容CMS组件预览图地址
const previewPath = path.resolve(PREVIEW_PATH);
const targetPath = path.resolve(this.currentPath, "dist/preview.png");
fs.existsSync(previewPath) &&
shellSyncExec(`cp ${previewPath} ${targetPath}`, { canIgnore: true });
this.writePackage();
} catch (e) {
log.error("[DADA-COMP] 截图失败:" + e.toString());
} finally {
this.closeBrowser();
}
},
get currentConfig() {
return getCurrentPathConfig();
},
getReactString() {
const { version: ReactVersion } = JSON.parse(
fs.readFileSync(
path.resolve(this.currentPath, "node_modules/react/package.json"),
"utf8"
)
);
const script = `<script src='https://unpkg.alipay.com/react@${ReactVersion}/umd/react.production.min.js'></script><script src='https://unpkg.alipay.com/react-dom@${ReactVersion}/umd/react-dom.production.min.js'></script><script src="//g.alicdn.com/code/lib/moment.js/2.24.0/moment.min.js"></script<script src="https://unpkg.alipay.com/@alife/mtop-mock-hook/dist/index.js"></script><script>MtopMockHook();</script>`;
return script;
},
getDocsString(filePath) {
const { libraryName } = this.currentConfig;
const publicPath = getDefaultPublicPath();
const route =
publicPath +
getRouteByPath(
path.relative(path.resolve(process.cwd(), "dist/docs"), filePath)
);
return `
${fs.readFileSync(path.resolve(this.currentPath, "dist/index.js"), "utf8")}
var Route = "${route}";
var COMP_LIBRARY = window['${libraryName}'];
var ComponentArr = COMP_LIBRARY.displayName ? [COMP_LIBRARY] : Object.values(COMP_LIBRARY);
var Component = ComponentArr.find(component => component.__docsPath === Route);
${fs.readFileSync(path.resolve(filePath), "utf8")}
var DocsRenders = window['${libraryName}'];
var DocsRender = typeof DocsRenders === "function" ? DocsRenders : DocsRenders[Object.keys(DocsRenders)[0]];
`;
},
getSnapshotHtml(filePath) {
const script = this.getDocsString(filePath);
const { extendTemplate } = this.currentConfig;
const cssPath = path.resolve(this.currentPath, "dist/index.css");
let docsCss = "";
if (fs.existsSync(cssPath)) {
docsCss = fs.readFileSync(cssPath, "utf8");
}
return getHtmlData({
beforeScript: this.getReactString(),
body: "",
baseCSS,
styles: "",
html: extendTemplate,
script,
css: docsCss,
});
},
async getSnapShots(filePaths) {
filePaths = filePaths.filter((filePath) => {
const previewPath = getJsPreviewPath(filePath);
if (!!hasPreviewAtPath(previewPath)) {
log.success("已存在预览图,使用用户自定预览图!");
return false;
}
return true;
});
const collection = chunk(filePaths, 5);
return Promise.all(
collection.map(async (files) => {
const htmls = files.map((file) => {
const html = this.getSnapshotHtml(file);
const previewPath = getJsPreviewPath(file);
return { html, previewPath };
});
const resultMap = await this.getImageFromAPI(htmls);
resultMap.forEach(({ buffer, previewPath }) => {
fs.writeFileSync(previewPath, buffer);
log.success(`组件预览生成成功:${previewPath}`);
if (previewPath === hasPreviewAtPath()) {
// 单库时,需要拷贝根目录图片至dist
shellSyncExec(`mv ${previewPath} ${PREVIEW_PATH}`);
}
});
})
);
},
async getImage(html) {
// 截图服务POST字节不能超过2M
const isLarge = JSON.stringify({ html }).length >= 1024 * 1024 * 1.98;
if (isLarge) {
console.log(
`LSC-COMP 组件包大小超出2M,采用本地Puppeteer进行截图!`.magenta
);
}
const getImageFn =
isLarge || this.debug ? this.getImageByLocal : this.getImageFromAPI;
return getImageFn.call(this, html);
},
getSnapShot(filePath) {
return new Promise(async (resolve, reject) => {
const previewPath = getJsPreviewPath(filePath);
if (!!hasPreviewAtPath(previewPath)) {
log.success("已存在预览图,使用用户自定预览图!");
resolve(previewPath);
return;
}
try {
const html = this.getSnapshotHtml(filePath);
const result = await this.getImage(html);
fs.writeFileSync(previewPath, result);
log.success(`组件预览生成成功:${previewPath}`);
resolve(previewPath);
} catch (e) {
reject(e);
}
}).then((previewPath) => {
if (previewPath === hasPreviewAtPath()) {
// 单库时,需要拷贝根目录图片至dist
shellSyncExec(`mv ${previewPath} ${PREVIEW_PATH}`);
}
});
},
async getImageByLocal(data) {
try {
require("puppeteer");
} catch (e) {
log.info("未安装Puppeteer,正在为您安装!");
await require("./installEnv").load();
}
const browser = await this.getBrowser();
const page = await browser.newPage();
const { snapshot = {} } = this.currentConfig;
await page.setViewport(Object.assign(page.viewport(), snapshot));
await page.setRequestInterception(true);
const tempUrl = "http://lsc-comp/";
page.on("request", (request) => {
if (request.url() === tempUrl || request.url() === tempUrl + "/") {
request.respond({
body: data,
});
} else if (request.url().includes(tempUrl)) {
request.respond({
body: "{data:'mock'}",
});
} else {
request.continue();
}
return;
});
await page.goto(tempUrl, { waitUntil: "networkidle0" });
// await page.goto('http://www.teste.org/');
// await page.setContent(data, { waitUntil: "networkidle0" });
const frames = page.frames().filter((frame) => frame !== page.mainFrame());
if (frames.length) {
await wait(10000);
}
const componentBoundingBox = await (
await page.$("body>#preview-root")
).boundingBox();
console.log("component", componentBoundingBox);
await page.setViewport({
width: Math.floor(componentBoundingBox.width),
height: Math.floor(componentBoundingBox.height),
});
return await page.screenshot({
type: "png",
clip: componentBoundingBox,
omitBackground: true,
});
},
async getBrowser() {
if (!this.browserPromise) {
this.browserPromise = require("puppeteer").launch({
headless: !this.debug,
args: ["--no-sandbox", "--disable-web-security"],
});
}
return this.browserPromise;
},
async closeBrowser() {
const browserPromise = this.browserPromise;
if (!this.debug) {
browserPromise && (await (await browserPromise).close());
}
},
async getImageFromAPI(html) {
if (Array.isArray(html)) {
const { data: res } = await axios.post(
"http://intl-faas.alibaba-inc.com/snapshots",
{
inputs: html,
}
);
return res.map(({ data }, index) => ({
...html[index],
buffer: Buffer.from(data),
}));
}
fs.writeFileSync(
path.resolve(__dirname, "log.json"),
JSON.stringify({ html }),
"utf8"
);
const { data: res } = await axios.post(
"http://intl-faas.alibaba-inc.com/snapshot",
{
html,
},
{
responseType: "arraybuffer",
}
);
return Buffer.from(res);
},
writePackage() {
if (!isDef()) {
return;
}
if (!fs.existsSync(PREVIEW_PATH)) {
return;
}
const { getPackageConfig } = require("./utils/getPackageJson");
const pkgJSON = getPackageConfig();
const pkgPath = path.resolve(process.cwd(), "./package.json");
const screenshot = `${getDefaultPublicPath()}docs/preview.png`;
set(pkgJSON, "componentConfig.screenshot", screenshot);
set(pkgJSON, "screenshot", screenshot);
fs.writeFileSync(pkgPath, JSON.stringify(pkgJSON, null, 2), "utf8");
},
};
/**
* 获取JS相对的Preview路径
* @param jsPath
* @returns {*|void|Promise<void>|Promise<any>}
*/
function getJsPreviewPath(
jsPath = path.resolve(process.cwd(), "dist/docs/index.js")
) {
if (jsPath === path.resolve(process.cwd(), "dist/docs/index.js")) {
jsPath = path.resolve(process.cwd(), "index.js");
}
return path.resolve(jsPath, "..", "preview.png");
}