UNPKG

bia

Version:

a tool for download git repository

510 lines (476 loc) 15.1 kB
const path = require('path') const fse = require('fs-extra') const cheerio = require('cheerio') const request = require('request') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const t = require('@babel/types') const template = require('@babel/template').default const generator = require('@babel/generator').default const CONFIG = require('../../config/cheerio.json') /** * 获取文件路径 * @param {string} fileDir 文件路径 */ const getCheerioRes = (fileDir, category) => { return new Promise((resolve, reject) => { try { const htmlDom = fse.readFileSync(fileDir, 'utf8') const $ = cheerio.load(htmlDom, { decodeEntities: false, }) // get css dep let cssDep = [] $('style,link[type="text/css"]').map((index, item) => { const $dom = $(item) if (/LINK/i.test($dom.prop('tagName'))) { let href = $dom.attr('href') if (CONFIG[category].css.indexOf(href.replace(/^https:/i, '').replace(/^https:/i, '')) > -1) { // base依赖不做打包 } else { cssDep.push({ type: 'link', value: $dom.attr('href'), }) } } else { cssDep.push({ type: 'inline', value: $dom.html() + '\n' }) } }) $('style,link[type="text/css"]').remove() // get js dep let headJsDep = [] let bodyJsDep = [] $('script').map((index, item) => { const $dom = $(item) // link if ($dom.parents('head').length > 0) { // head let src = $dom.attr('src') || '' if (!!src) { if (CONFIG[category].head.indexOf(src.replace(/^https:/i, '').replace(/^https:/i, '')) > -1) { // base依赖不做打包 } else { headJsDep.push({ type: 'link', value: $dom.attr('src'), }) } } else { headJsDep.push({ type: 'inline', value: $dom.html() + '\n', }) } } else { // body let src = $dom.attr('src') || '' if (!!src) { if (CONFIG[category].body.indexOf(src.replace(/^https:/i, '').replace(/^https:/i, '')) > -1) { // base依赖不做打包 } else { bodyJsDep.push({ type: 'link', value: $dom.attr('src'), }) } } else { bodyJsDep.push({ type: 'inline', value: $dom.html() + '\n', }) } } }) $('script').remove() // get html string let htmlString = '' htmlString = $('body').html() $('body').html('') // get head info let info = { title: $('title').text(), keywords: $('meta[name=keywords]').attr('content'), description: $('meta[name=description]').attr('content'), } $('title').text('') $('meta[name=keywords]').attr('content', '') $('meta[name=description]').attr('content', '') // get result const result = { head: { info, cssDep, // css dep type: 'link' | 'inline' jsDep: headJsDep, // js dep type: 'link' | 'inline' }, body: { dom: htmlString, // dom jsDep: bodyJsDep, // js dep type: 'link' | 'inline' }, tpl: $.html().replace(/\n(\n)*( )*(\n)*\n/g, ''), // html tpl } resolve(result) } catch (err) { reject(err) } }) } /** * 格式化链接 * @params {string} url url链接 * @params {string} name query的name * @params {string} value query的value */ const rParseURL = (url, name, value) => { let _url = url.indexOf('?', url.indexOf('?') + 1) < 0 ? url : url.slice(0, url.indexOf('?', url.indexOf('?') + 1)) let _regx = /(\S{1,})\?([^?]*)(#|$)/ let _searchRegx = new RegExp('(^|&)' + name + '(=[^&^#^=]*)?(&|#|$)') let _regxResult = _url.match(_regx) let _search = _regxResult ? _regxResult[2] : '' let _name = name + '=' + value let _newSearch = !_searchRegx.test(_search) ? (_name + (!_search ? '' : '&') + _search) : _search.replace(_searchRegx, '$1' + _name + '$3') return !_regxResult ? _url.replace(/([^#]*)([\S]*)/, '$1?' + _newSearch + '$2') : (_regxResult[1] + '?' + _newSearch) } /** * 获取远程文件内容 * @param {string} src 文件路径 */ const requestFile = (src) => { return new Promise((resolve, reject) => { src = /^http(s)?:/.test(src) ? src : 'https:' + src let ver = new Date().getTime() request(rParseURL(src, 'ver', ver), (_err, _res, _body) => { if (_res.statusCode == 200) { let _result = '' try { _result = JSON.parse(_body) } catch (e) { _result = _body } resolve(_result) } else { reject(_err) } }) }) } /** * 组合资源 * @params {array} list 资源列表,区分外链和内联 * [{type: '', value: ''}] */ const combineResource = (list) => { return new Promise((resolve, reject) => { let resString = '' let resPromiseList = list.map((item) => { if (item.type == 'link') { return requestFile(item.value) } else { return Promise.resolve(item.value) } }) Promise.all(resPromiseList).then(list => { resolve(list) }) }) } /** * ast 语法分析js * @params {object} info * info.title {string} 标题 * info.keywords {string} 关键词 * info.description {string} 描述 */ const parseInfo = (info) => { let jsString = ` document.title = "${info.title}"; document.querySelector('meta[name=keywords]').content = "${info.keywords}"; document.querySelector('meta[name=description]').content = "${info.description}"; ` let createJs = ` ;(function() { var stat = document.createElement('script'); var jsString = ""; stat.innerHTML = jsString; $('head').append(stat); })() ` const baseAst = parser.parse(createJs, { sourceType: 'module', }) traverse(baseAst, { VariableDeclarator(path) { if (!path.node.init.value) { path.node.init.value = jsString } } }) const output = generator(baseAst, { jsescOption: { minimal: true, } }).code return output } /** * ast 语法分析css * @params {string} cssString css字符 */ const parseCss = (cssString) => { let createCss = ` ;(function() { var tag = document.createElement('style'); tag.innerHTML = ""; document.head.appendChild(tag); })() ` const baseAst = parser.parse(createCss, { sourceType: 'module', }) traverse(baseAst, { AssignmentExpression(path) { path.node.right.value = cssString } }) const output = generator(baseAst, { jsescOption: { minimal: true, } }).code return output } /** * ast 语法分析js * @params {string} jsString js字符 */ const parseJs = (jsString) => { let createJs = ` ;(function() { var stat = document.createElement('script'); var jsString = ""; stat.innerHTML = jsString; $('head').append(stat); })() ` const baseAst = parser.parse(createJs, { sourceType: 'module', }) traverse(baseAst, { VariableDeclarator(path) { if (!path.node.init.value) { path.node.init.value = jsString } } }) const output = generator(baseAst, { jsescOption: { minimal: true, } }).code return output } /** * ast 语法分析html * @params {string} htmlString html字符 */ const parseHtml = (htmlString) => { let createJs = ` ;(function() { var dom = ""; $('body').prepend(dom); })() ` const baseAst = parser.parse(createJs, { sourceType: 'module', }) traverse(baseAst, { VariableDeclarator(path) { if (!path.node.init.value) { path.node.init.value = htmlString } } }) const output = generator(baseAst, { jsescOption: { minimal: true, } }).code return output } /** * 生成js bundle *@params {object} result 页面结构对象 */ const createJsBundle = (result) => { return new Promise((resolve, reject) => { // ast parseInfo const headInfoString = parseInfo(result.info) // ast headCss const headCssString = parseCss(result.headCssString) // ast headJs const headJsString = parseJs(result.headJsString, true) // ast bodyJs const bodyJsString = result.bodyJsString // ast html const bodyHtmlString = parseHtml(result.dom) const jsBundle = ` ${headInfoString} ${headCssString} ${headJsString} ${bodyHtmlString} ${bodyJsString} ` resolve({ tpl: result.tpl, jsBundle, }) }) } /** * 上传js bundle 文件 * @params {object} result * result.jsBundle {string} jsBundle内容 */ const uploadJsBundle = (result) => { return new Promise((resolve, reject) => { //// todo resolve({ ...result, jsBundleLink: './jsBundle.js', }) }) } /** * 更新最终的tpl */ const updateTpl = (result, category) => { return new Promise((resolve, reject) => { const $ = cheerio.load(result.tpl, { decodeEntities: false, }) // css let cssLinkList = CONFIG[category].css.map((item) => { return `<link rel="stylesheet" type="text/css" href="${item}" />` }) $('head').append('\r\n' + cssLinkList.join('\r\n') + '\r\n') // js head let jsHeadLinkList = CONFIG[category].head.map((item) => { return `<script type="text/javascript" src="${item}"></script>` }) $('head').append('\r\n' + jsHeadLinkList.join('\r\n') + '\r\n') // js body let jsBodyLinkList = CONFIG[category].body.map((item) => { return `<script type="text/javascript" src="${item}"></script>` }) jsBodyLinkList.push(`<script type="text/javascript" src="${result.jsBundleLink}"></script>`) $('body').append('\r\n' + jsBodyLinkList.join('\r\n') + '\r\n') resolve({ tpl: $.html(), jsBundle: result.jsBundle, jsBundleLink: result.jsBundleLink, }) }) } /** * 格式化cheerio的数据 *@params {object} result 页面结构对象 */ const formatCheerioRes = (result, category) => { return new Promise((resolve, reject) => { let headCssString = '' let headJsString = '' let bodyJsString = '' combineResource(result.head.cssDep).then((list) => { // 处理head的css headCssString = list.join('\r\n') return combineResource(result.head.jsDep) }).then(list => { headJsString = list.join(';\r\n') return combineResource(result.body.jsDep) }).then(list => { bodyJsString = list.join(';\r\n') const newResult = { info: result.head.info, tpl: result.tpl, dom: result.body.dom, headCssString, headJsString, bodyJsString, } return createJsBundle(newResult) }).then((result) => { return uploadJsBundle(result) }).then((result) => { return updateTpl(result, category) }).then((result) => { resolve(result) }) }) } /** * 添加tabbar * @params {object} result 页面结构对象 * @params {boolean} tabbarFlag tabbar标记 */ const addGlobalTabbar = (result, tabbarFlag) => { return new Promise((resolve, reject) => { if (!tabbarFlag) { resolve(result) } const $ = cheerio.load(result.tpl, { decodeEntities: false, }) // tabbar css const tabbarCss = fse.readFileSync(path.resolve(__dirname, './tabbar/index.css')) $('head').append(`<style>${tabbarCss}</style>\r\n`) // tabbar dom+js const tabbarHtml = fse.readFileSync(path.resolve(__dirname, './tabbar/index.html')) const tabbarJs = fse.readFileSync(path.resolve(__dirname, './tabbar/index.js')) $('body').prepend(`\r\n` + tabbarHtml + `\r\n` + `<script>${tabbarJs}</script>`) resolve({ tpl: $.html(), jsBundle: result.jsBundle, jsBundleLink: result.jsBundleLink, }) }) } /** * 生成文件 * @param {string} dist 生成文件路径 * @param {data} data 数据 */ const outputFile = (dist, data) => { return new Promise((resolve, reject) => { try { fse.outputFileSync(dist, data) resolve() } catch (err) { reject(err) } }) } /** * 生成json * @param {string} dist 生成文件路径 * @param {data} data 数据 */ const outputJson = (dist, data) => { return new Promise((resolve, reject) => { try { fse.outputFileSync(dist, JSON.stringify(data, null, 4)) resolve() } catch (err) { reject(err) } }) } module.exports = { getCheerioRes, formatCheerioRes, addGlobalTabbar, outputFile, outputJson, }