bia
Version:
a tool for download git repository
510 lines (476 loc) • 15.1 kB
JavaScript
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,
}