针对需求相似的项目,有一套开发模板,平时开发时就在上面做修改,下面对这套模板做一下简单的总结。以下是使用的框架或工具。
babel
做js
编译器。eslint
做代码检查工具。vue全家桶
做视图、状态管理、路由。webpack
打包。phantomJS
实现预渲染。- 一套通信框架实现
android
和js
通信。
项目是内嵌在App
中的H5页面。在生成html
资源包后放到设备特定目录下,由App
去读取。其中babel
、eslint
、android
与js
的通信不详述,主要记录一下其他三个方面。
# vue全家桶。
vue
全家桶的使用就是根据官方文档搭建,抽象出业务需要的公共组件来供不同项目使用。引入events
,在每个业务初始化的时候实现事件总线,在事件总线上绑定多个常用的事件。
import events from 'events'
import _ from 'lodash'
const eventBus = {}
_.mixin(eventBus, events.EventEmitter.prototype)
eventBus.on(eventName, () => {
// event function
})
eventBus.emit(eventName, ...arr)
# webpack打包
- 将压缩好的
vue.min.js
、vue-router.min.js
、vuex.min.js
通过concat-files
合并成一个文件,再通过html-webpack-include-assets-plugin
插入到目标html
文件中。
const concat = require('concat-files')
concat([
'/path/to/vue.min.js',
'/path/to/vue-router.min.js',
'/path/to/vuex.min.js'
], '/path/to/vue.eco.js', (err) => {
if (err) {
throw err
}
console.log('done')
})
// webpack config
const HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin')
...
plugins [
new HtmlWebpackIncludeAssetsPlugin({
assets: [
'/path/to/vue.eco.js'
],
append: false
})
]
if-loader
区分不同环境下的代码html-webpack-plugin
生成多个html。CommonsChunkPlugin
做公共模块拆分。express
、webpack-dev-middleware
、webpack-hot-middleware
实现 dev-server 和 hot-reload。
# phantomJS预渲染
在未使用预渲染的时候,进入页面会有一段的白屏时间,此时在加载 js
和渲染 dom
。为了让用户体验更好,提出了预渲染方案。
预渲染方案的功能实际上是在进入页面的时候,使用已经渲染好的静态界面做为白屏阶段的一个补充 (也就是说这个阶段的界面是不能进行交互的,因为js代码并没有被加载)。在vue渲染好了真正的界面的时候,它会在render
阶段用自己渲染好的界面替换之前的静态界面。
界面用webview
载入,在触发webview
的onfinish
事件后,切换显示webview
的内容。为了更快呈现UI,就要让webview
尽快触发onFinish
事件,移除页面内的js
内容,改为setTimeout
逐个注入。同时提供静态化的内容,提前提供渲染好的页面。
利用nodejs
操控 phantom
,打开页面,抓取页面内容并写入。
const phantom = require('phantom')
const cheerio = require('cheerio')
const fs = require('fs-sync')
const minify = require('html-minifier').minify
(async function() {
const instance = await phantom.create()
const page = await instance.createPage()
const status = page.open(url)
let htmlContent
page.evaluate(function() {
return document.querySelector('xxx').outerHtml
}).then((html) => {
htmlContent = html
})
let $ = cheerio.load(htmlContent)
$('.container').attr('id', 'static')
const staticContent = minify($.html(), {
removeAttributeQuotes: true,
removeComments: true,
collapseWhitespace: true
})
const originalHtml = fs.read('xxx')
$ = cheerio.load('originalHtml')
$('#app').remove()
$(staticContent).appendTo('body')
const scriptSrcArray = []
const $script = $('script')
for (let i = 0; i < $script.length; i++) {
const src = $('script').eq(i).attr('src')
if (src) {
scriptSrcArray.push(src)
}
}
const scriptSrcArrayStr = JSON.stringify(scriptSrcArray)
$('script').remove()
$(`<script>
(function(){
document.querySelector('#static').attr('id', 'app')
function appendScript(head, src, callback) {
const script = document.createElement('script')
script.src = src
script.charset = 'utf-8'
script.async = true
const timeout = setTimeout(completed, 120000)
script.onload = completed
script.onerror = onScriptError
function completed() {
script.onerror = script.onload = null
clearTimeout(timeout)
callback()
}
function onScriptError() {
script.onerror = script.onload = null
clearTimeout(timeout)
console.log('onScriptError')
}
head.appendChild(script)
}
function appendRecursive(head, scriptSrcArray, i) {
if (i < scriptSrcArray.length) {
appendScript(head, scriptSrcArray[i], function() {
appendRecursive(head, scriptSrcArray, i + 1)
})
}
}
setTimeout(function() {
appendRecursive(
document.getElementsByTagName('head')[0],
JSON.parse('${scriptSrcArrayStr}')
)
})
})
})()</script>`.replace(/([\r\n]|\s{3,})/g, '')).appendTo('body')
const preRendered = $.html()
fs.write(xxx, preRendered)
page.close()
```