blob: 03b9a803cf29a3ad7eb1c38b91634233d311dc4b [file] [log] [blame]
import blocker from 'weex-transformer/lib/blocker'
import templater from 'weex-templater'
import styler from 'weex-styler'
import scripter from 'weex-scripter'
import md5 from 'md5'
import { transformerVersion } from './config'
import {
MODULE_EXPORTS_REG,
REQUIRE_REG,
FUNC_START_REG,
FUNC_END_REG,
getNameByPath,
checkFileExist,
depHasRequired,
stringifyFunction,
appendToWarn
} from './util'
export function parseWeex (loader, params, source, map, deps, elementName) {
return new Promise(
// separate source into <element>s, <template>, <style>s and <script>s
separateBlocks(source, deps || []))
// pre-parse non-javascript parts
.then(preParseBlocks(loader, params, map))
// join blocks together and parse as javascript finally
.then(parseBlocks(loader, params, map, elementName))
}
function separateBlocks (source, deps) {
return (resolve, reject) => {
blocker.format(source, (err, ret) => {
if (err) {
reject(err)
}
else {
ret.deps = deps
resolve(ret)
}
})
}
}
function preParseBlocks (loader, params, map) {
return (blocks) => {
const { deps, elements, template, styles, scripts, config, data } = blocks
const promises = [
Promise.resolve(),
Promise.resolve(),
Promise.resolve(),
Promise.resolve(scripts),
Promise.resolve(deps),
Promise.resolve(config),
Promise.resolve(data)
]
let content
// pre-parse sub elements
if (elements) {
const elPromises = []
Object.keys(elements).forEach(key => {
const el = elements[key]
// record original positions of each <element>
map.setElementPosition(el.name, el.line, el.column)
elPromises.push(parseWeex(loader, params, el.content, map, deps, el.name))
})
promises[0] = Promise.all(elPromises)
}
// pre-parse template
if (template) {
content = template.content
promises[1] = parseTemplate(loader, params, content, deps)
}
// pre-parse styles
if (styles) {
content = styles.reduce((pre, cur) => {
return pre + '\n' + cur.content
}, '')
promises[2] = parseStyle(loader, params, content)
}
return Promise.all(promises)
}
}
function parseBlocks (loader, params, map, elementName) {
return (results) => {
const elements = results[0] || []
const template = results[1]
const style = results[2]
const scripts = results[3]
const deps = results[4] || []
const configResult = results[5]
const dataResult = results[6]
let content = ''
let config = {}
let data
const mapOffset = { basic: 0, subs: [] }
if (scripts) {
// record original and generated position of each <script>
// the generated content is begin with empty string
// so later the template, styles and elements will be appended/prepended
// and mapOffset.basic will record lines of prepended *required* content
content += scripts.reduce((prev, next, i) => {
// length of previous content
const line = prev.split(/\r?\n/g).length + 1
const column = 1
const oriLine = next.line
const oriColumn = next.column
mapOffset.subs.push({
original: { line: oriLine, column: oriColumn },
generated: { line, column },
// length of next content
length: next.content.split(/\r?\n/g).length
})
return prev + '\n;' + next.content
}, '')
}
let requireContent = ''
if (deps.length) {
requireContent += deps.map(dep =>
depHasRequired(content, dep) ? 'require("' + dep + '");' : ''
).join('\n')
if (requireContent) {
// length of implicitly requires
mapOffset.basic = requireContent.split(/\r?\n/g).length
content = requireContent + '\n' + content
}
}
if (template) {
// append template content, not impact sourcemap
content += '\n;module.exports.template = module.exports.template || {}' +
'\n;Object.assign(module.exports.template, ' + template + ')'
}
if (style) {
// append style content, not impact sourcemap
content += '\n;module.exports.style = module.exports.style || {}' +
'\n;Object.assign(module.exports.style, ' + style + ')'
}
// prepare entry config
if (configResult) {
config = new Function('return ' + configResult.content.replace(/\n/g, ''))()
}
config.transformerVersion = transformerVersion
config = JSON.stringify(config, null, 2)
// prepare entry data
if (dataResult) {
data = new Function('return ' + dataResult.content.replace(/\n/g, ''))()
data = JSON.stringify(data, null, 2)
}
return parseScript(loader, params, content, { config, data, elementName, elements, map, mapOffset })
}
}
export function parseTemplate (loader, params, source, deps) {
return new Promise((resolve, reject) => {
templater.parse(source, (err, obj) => {
if (err) {
reject(err)
}
else {
appendToWarn(loader, obj.log)
// push valid obj.deps to deps
if (deps && obj.deps) {
obj.deps.map(
dep => checkFileExist(dep, params.resourcePath)
).forEach(dep => {
if (dep) {
deps.push(dep)
}
})
}
// parse json to string and treat function specially
let target = JSON.stringify(obj.jsonTemplate, stringifyFunction, ' ')
target = target.replace(FUNC_START_REG, '').replace(FUNC_END_REG, '')
resolve(target)
}
})
})
}
export function parseStyle (loader, params, source) {
return new Promise((resolve, reject) => {
styler.parse(source, (err, obj) => {
if (err) {
reject(err)
}
else {
appendToWarn(loader, obj.log)
resolve(JSON.stringify(obj.jsonStyle, null, 2))
}
})
})
}
export function parseScript (loader, params, source, env) {
const { config, data, elementName, elements, map, mapOffset } = env
// the entry component has a special resource query and not a sub element tag
const isEntry = params.resourceQuery.entry === true && !elementName
// resolve component name
const name = isEntry
? md5(source)
: (elementName || params.resourceQuery.name || getNameByPath(params.resourcePath))
// join with elements deps
// 2 more lines between each element and the end
map && map.start()
const prefix = (elements || []).reduce((prev, next, index) => {
const prevLength = prev.split(/\r?\n/g).length
const nextLength = next.split(/\r?\n/g).length
// record generated positions of each <element>
map && map.addElement(name, index, prevLength, nextLength)
return prev + next + ';\n\n'
}, '')
// fix data option from an object to a function
let target = scripter.fix(source)
// wrap with __weex_define__(name, [], (r, e, m) {...})
// 1 more line at start, 1 more line at end
target = target
.replace(MODULE_EXPORTS_REG, '__weex_module__.exports')
.replace(REQUIRE_REG, '__weex_require__($1$2$1)')
target = ';__weex_define__("@weex-component/' + name + '", [], ' +
'function(__weex_require__, __weex_exports__, __weex_module__)' +
'{\n' + target + '\n})'
// record mapOffset into sourcemap
if (mapOffset) {
// length of generated prefix (elements) and basic (implicitly requires)
const preLines = prefix.split(/\r?\n/g).length + mapOffset.basic
mapOffset.subs.forEach(info => {
map.addScript(elementName || name, info, preLines)
})
}
map && map.end()
// append __weex_bootstrap__ for entry component
// not impact sourcemap
if (isEntry) {
target += '\n;__weex_bootstrap__("@weex-component/' + name + '", ' +
String(config) + ',' +
String(data) + ')'
}
return Promise.resolve(prefix + target)
}