Skip to content

Commit

Permalink
feat(cli h5): 支持onPageScroll和onReachBottom api
Browse files Browse the repository at this point in the history
  • Loading branch information
Littly committed Mar 27, 2019
1 parent 3f6a7a7 commit 2a8224e
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 23 deletions.
98 changes: 81 additions & 17 deletions packages/taro-cli/src/h5.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const minimatch = require('minimatch')
const Util = require('./util')
const npmProcess = require('./util/npm')
const CONFIG = require('./config')
const { source: toAst, getObjKey, obj: objToAst } = require('./util/ast_convert')
const { source: toAst, obj: objToAst, toVariable: toVar } = require('./util/ast_convert')

const addLeadingSlash = path => path.charAt(0) === '/' ? path : '/' + path
const removeLeadingSlash = path => path.replace(/^\.?\//, '')
Expand Down Expand Up @@ -78,6 +78,7 @@ if (projectConfig.hasOwnProperty(DEVICE_RATIO)) {
let pages = []
let tabBar
let tabbarPos
// let appConfig = {}

const FILE_TYPE = {
ENTRY: 'ENTRY',
Expand All @@ -86,7 +87,7 @@ const FILE_TYPE = {
NORMAL: 'NORMAL'
}

const isUnderSubPackages = (parentPath) => (parentPath.isObjectProperty() && /subPackages|subpackages/i.test(getObjKey(parentPath.node.key)))
const isUnderSubPackages = (parentPath) => (parentPath.isObjectProperty() && /subPackages|subpackages/i.test(toVar(parentPath.node.key)))

function createRoute ({ absPagename, relPagename, isIndex, chunkName = '' }) {
const chunkNameComment = chunkName ? `/* webpackChunkName: "${chunkName}" */` : ''
Expand Down Expand Up @@ -204,7 +205,7 @@ function processEntry (code, filePath) {
exit (astPath) {
const node = astPath.node
const key = node.key
const keyName = getObjKey(key)
const keyName = toVar(key)
let funcBody

const isRender = keyName === 'render'
Expand Down Expand Up @@ -356,14 +357,14 @@ function processEntry (code, filePath) {
const node = astPath.node
const key = node.key
const value = node.value
const keyName = getObjKey(key)
const keyName = toVar(key)
if (keyName === 'pages' && t.isArrayExpression(value)) {
const subPackageParent = astPath.findParent(isUnderSubPackages)
let root = ''
if (subPackageParent) {
/* 在subPackages属性下,说明是分包页面,需要处理root属性 */
const rootNode = astPath.parent.properties.find(v => {
return getObjKey(v.key) === 'root'
return toVar(v.key) === 'root'
})
root = rootNode ? rootNode.value.value : ''
}
Expand Down Expand Up @@ -424,12 +425,14 @@ function processEntry (code, filePath) {
enter (astPath) {
const node = astPath.node
const key = node.key
const value = node.value
const keyName = getObjKey(key)
const keyName = toVar(key)

if (keyName === 'state') hasState = true
if (keyName !== 'config' || !t.isObjectExpression(value)) return
astPath.traverse(classPropertyVisitor)
if (keyName === 'state') {
hasState = true
} else if (keyName === 'config') {
// appConfig = toVar(node.value)
astPath.traverse(classPropertyVisitor)
}
}
},
ImportDeclaration: {
Expand Down Expand Up @@ -539,7 +542,7 @@ function processEntry (code, filePath) {
exit (astPath) {
const node = astPath.node
const key = node.key
const keyName = getObjKey(key)
const keyName = toVar(key)
if (keyName === 'constructor') {
hasConstructor = true
} else if (keyName === 'componentWillMount') {
Expand Down Expand Up @@ -638,6 +641,10 @@ function processOthers (code, filePath, fileType) {
let isPage = fileType === FILE_TYPE.PAGE
let hasComponentDidMount = false
let hasComponentDidShow = false
let hasComponentDidHide = false
let hasOnPageScroll = false
let hasOnReachBottom = false
let pageConfig = {}

ast = babel.transformFromAst(ast, '', {
plugins: [
Expand Down Expand Up @@ -698,22 +705,77 @@ function processOthers (code, filePath, fileType) {
'method', t.identifier('componentDidShow'), [],
t.blockStatement([]), false, false))
}
if (!hasComponentDidHide) {
astPath.pushContainer('body', t.classMethod(
'method', t.identifier('componentDidHide'), [],
t.blockStatement([]), false, false))
}
}
},
ClassMethod: {
exit (astPath) {
const node = astPath.node
const key = node.key
const keyName = toVar(key)
if (hasOnReachBottom) {
if (keyName === 'componentDidShow') {
node.body.body.unshift(
toAst(`
this._offReachBottom = Taro.onReachBottom({
callback: this.onReachBottom,
ctx: this,
onReachBottomDistance: ${JSON.stringify(pageConfig.onReachBottomDistance)}
})
`)
)
} else if (keyName === 'componentDidHide') {
node.body.body.unshift(
toAst('this._offReachBottom()')
)
}
}
if (hasOnPageScroll) {
if (keyName === 'componentDidShow') {
node.body.body.unshift(
toAst('this._offPageScroll = Taro.onPageScroll({ callback: this.onPageScroll, ctx: this })')
)
} else if (keyName === 'componentDidHide') {
node.body.body.unshift(
toAst('this._offPageScroll()')
)
}
}
}
}
}

traverse(ast, {
ClassExpression: ClassDeclarationOrExpression,
ClassDeclaration: ClassDeclarationOrExpression,
ClassProperty: isPage ? {
enter (astPath) {
const node = astPath.node
const key = toVar(node.key)
if (key === 'config') {
pageConfig = toVar(node.value)
}
}
} : {},
ClassMethod: isPage ? {
exit (astPath) {
const node = astPath.node
const key = node.key
const keyName = getObjKey(key)
const keyName = toVar(key)
if (keyName === 'componentDidMount') {
hasComponentDidMount = true
} else if (keyName === 'componentDidShow') {
hasComponentDidShow = true
} else if (keyName === 'componentDidHide') {
hasComponentDidHide = true
} else if (keyName === 'onPageScroll') {
hasOnPageScroll = true
} else if (keyName === 'onReachBottom') {
hasOnReachBottom = true
}
}
} : {},
Expand Down Expand Up @@ -869,9 +931,13 @@ function processFiles (filePath) {
// 脚本文件 处理一下
const fileType = classifyFiles(filePath)
const content = file.toString()
const transformResult = fileType === FILE_TYPE.ENTRY
? processEntry(content, filePath)
: processOthers(content, filePath, fileType)
let transformResult
if (fileType === FILE_TYPE.ENTRY) {
pages = []
transformResult = processEntry(content, filePath)
} else {
transformResult = processOthers(content, filePath, fileType)
}
const jsCode = transformResult.code
fs.ensureDirSync(distDirname)
fs.writeFileSync(distPath, Buffer.from(jsCode))
Expand All @@ -893,13 +959,11 @@ function watchFiles () {
})
watcher
.on('add', filePath => {
pages = []
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.CREATE, '添加文件', relativePath)
processFiles(filePath)
})
.on('change', filePath => {
pages = []
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.MODIFY, '文件变动', relativePath)
processFiles(filePath)
Expand Down
24 changes: 19 additions & 5 deletions packages/taro-cli/src/util/ast_convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,30 @@ function convertSourceStringToAstExpression (str, opts = {}) {
return template(str, Object.assign({}, babylonConfig, opts))()
}

const getObjKey = (node) => {
if (t.isIdentifier(node)) {
return node.name
} else {
const convertAstExpressionToVariable = (node) => {
if (t.isObjectExpression(node)) {
const obj = {}
const properties = node.properties
properties.forEach(property => {
const key = convertAstExpressionToVariable(property.key)
const value = convertAstExpressionToVariable(property.value)
obj[key] = value
})
return obj
} else if (t.isArrayExpression(node)) {
return node.elements.map(convertAstExpressionToVariable)
} else if (t.isLiteral(node)) {
return node.value
} else if (t.isIdentifier(node)) {
const name = node.name
return name === 'undefined'
? undefined
: name
}
}

exports.obj = convertObjectToAstExpression
exports.array = convertArrayToAstExpression
exports.source = convertSourceStringToAstExpression
exports.getObjKey = getObjKey
exports.generateMinimalEscapeCode = generateMinimalEscapeCode
exports.toVariable = convertAstExpressionToVariable
2 changes: 2 additions & 0 deletions packages/taro-h5/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export * from './system'
export * from './others'
export * from './navigationBar'
export * from './imageUtils'

export * from './privateApis'
2 changes: 2 additions & 0 deletions packages/taro-h5/src/api/privateApis/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './pageScroll'
export * from './reachBottom'
21 changes: 21 additions & 0 deletions packages/taro-h5/src/api/privateApis/pageScroll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createCallbackManager, createScroller } from '../utils'

export const onPageScroll = (opt) => {
const callbackManager = createCallbackManager()
const scroller = createScroller(opt.ctx)
const onScroll = () => {
callbackManager.trigger({
scrollTop: scroller.getPos()
})
}

callbackManager.add(opt)
scroller.listen(onScroll)

return () => {
callbackManager.remove(opt)
if (callbackManager.count() === 0) {
scroller.unlisten(onScroll)
}
}
}
40 changes: 40 additions & 0 deletions packages/taro-h5/src/api/privateApis/reachBottom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createCallbackManager, createScroller } from '../utils'

/**
* @typedef {Object} ReachBottomParam
* @property {Function} callback
* @property {*} ctx
* @property {Number|undefined} onReachBottomDistance
*/

/**
* @param {ReachBottomParam} opt
*/
export const onReachBottom = (opt) => {
const callbackManager = createCallbackManager()
const scroller = createScroller(opt.ctx)
const distance = typeof opt.onReachBottomDistance === 'number'
? opt.onReachBottomDistance
: 50

let canTrigger = true

const onScroll = () => {
if (scroller.isReachBottom(distance)) {
canTrigger && callbackManager.trigger()
canTrigger = false
} else {
canTrigger = true
}
}

callbackManager.add(opt)
scroller.listen(onScroll)

return () => {
callbackManager.remove(opt)
if (callbackManager.count() === 0) {
scroller.unlisten(onScroll)
}
}
}
55 changes: 54 additions & 1 deletion packages/taro-h5/src/api/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// import { findDOMNode } from 'nervjs'

function shouleBeObject (target) {
if (target && typeof target === 'object') return { res: true }
return {
Expand Down Expand Up @@ -77,6 +79,55 @@ const isValidColor = (color) => {
return VALID_COLOR_REG.test(color)
}

const createCallbackManager = () => {
const callbacks = []

const add = (opt) => {
callbacks.push(opt)
}

const remove = (opt) => {
const pos = callbacks.findIndex(({ callback }) => {
return callback === opt.callback
})
if (pos > -1) {
callbacks.splice(pos, 1)
}
}

const count = () => callbacks.length
const trigger = (...args) => {
callbacks.forEach(({ callback, ctx }) => {
callback.call(ctx, ...args)
})
}

return {
add,
remove,
count,
trigger
}
}

const createScroller = inst => {
// const dom = findDOMNode(inst)
const el = document.querySelector('.taro-tabbar__panel') || document.body

const listen = callback => {
el.addEventListener('scroll', callback)
}
const unlisten = callback => {
el.removeEventListener('scroll', callback)
}
const getPos = () => el.scrollTop
const isReachBottom = (distance = 0) => {
return el.scrollHeight - el.scrollTop - el.clientHeight < distance
}

return { listen, unlisten, getPos, isReachBottom }
}

export {
shouleBeObject,
getParameterError,
Expand All @@ -88,5 +139,7 @@ export {
temporarilyNotSupport,
permanentlyNotSupport,
isValidColor,
isFunction
isFunction,
createCallbackManager,
createScroller
}

0 comments on commit 2a8224e

Please sign in to comment.