UNPKG

@ali-i18n-fe/dada-component

Version:
391 lines (332 loc) 11.1 kB
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"); }