` 元素。
77 |
78 | ## 参数
79 |
80 | 一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,`v-bind` 指令可以用于响应式地更新 HTML attribute:
81 |
82 | ```html
83 | ...
84 | ```
85 |
86 | 在这里 href 是参数,告知 `v-bind` 指令将该元素的 `href` attribute 与表达式 `url` 的值绑定。
87 |
88 | 另一个例子是 `v-on` 指令,它用于监听 DOM 事件:
89 |
90 | ```html
91 | ...
92 | ```
93 | > 监听事件的 `v-bind` 简写形式为: @click="doSomething"
94 |
95 | ## 动态参数
96 |
97 | ```html
98 |
101 | ...
102 | ```
103 |
104 | ## 修饰符
105 |
106 | 修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,`.prevent` 修饰符告诉 `v-on` 指令对于触发的事件调用 `event.preventDefault()`:
107 |
108 | ```html
109 |
110 | ```
111 | 你会看到当复选框被点击时,不会被勾选。
112 |
113 | ## 缩写
114 |
115 | `v-bind` 缩写
116 |
117 | ```html
118 |
119 | ...
120 |
121 |
122 | ...
123 | ```
124 |
125 | `v-on` 缩写
126 |
127 | ```html
128 |
129 | ...
130 |
131 |
132 | ...
133 | ```
134 |
--------------------------------------------------------------------------------
/doc/lone-guide/structure.md:
--------------------------------------------------------------------------------
1 | # 项目结构与运行原理
2 |
3 | ## 通信模型
4 |
5 | 
6 |
--------------------------------------------------------------------------------
/doc/lone-guide/structure/messenger-model.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/berwin/lone/cc2e77c6ce36c69af7b3b3031b26be454cff3b0e/doc/lone-guide/structure/messenger-model.gif
--------------------------------------------------------------------------------
/example/basic/app.config.js:
--------------------------------------------------------------------------------
1 | Lone.ui({
2 | entry: {
3 | logic: './app.main.js',
4 | page: './app.page.js'
5 | },
6 | routes: [
7 | { path: '/', component: 'test' },
8 | { path: '/official', component: 'official' },
9 | { path: '/lifecycle', component: 'lifecycle' },
10 | { path: '/query', component: 'query' }
11 | ]
12 | })
13 |
--------------------------------------------------------------------------------
/example/basic/app.main.js:
--------------------------------------------------------------------------------
1 | importScripts('../../dist/lone.logic.js')
2 |
3 | Lone.logic('test', {
4 | data: () => ({
5 | n: 0,
6 | list: [1, 2, 3, 4, 5],
7 | object: {
8 | title: 'How to do lists in Vue',
9 | author: 'Jane Doe',
10 | publishedAt: '2016-04-10'
11 | }
12 | }),
13 | methods: {
14 | navigateAndIncrement () {
15 | const increment = () => this.n++
16 | if (this.$route.path === '/') {
17 | this.$router.push('/foo', increment)
18 | } else {
19 | this.$router.push('/', increment)
20 | }
21 | },
22 | navigatorTo (url) {
23 | this.navigateTo({
24 | url,
25 | success () {
26 | console.log('navigateTo Success!')
27 | },
28 | fail () {
29 | console.log('navigateTo Fail!')
30 | },
31 | complete () {
32 | console.log('navigateTo Complete!')
33 | }
34 | })
35 | },
36 | test (data) {
37 | console.log('event trigger!', data)
38 | }
39 | },
40 | created () {
41 | console.log('app.main.js: created~~~')
42 | },
43 | onLoad (query) {
44 | console.log('app.main.js: onLoad~~~, query:', query)
45 | },
46 | mounted () {
47 | const vm = this
48 | setTimeout(() => {
49 | vm.setData({
50 | n: 2,
51 | list: [...vm.data.list, 6]
52 | })
53 | }, 1000)
54 | console.log('app.main.js: mounted~~~')
55 | },
56 | onReady () {
57 | console.log('app.main.js: onReady~~~')
58 | },
59 | onUnload () {
60 | console.log('app.main.js: onUnload~~~')
61 | },
62 | onShow () {
63 | console.log('app.main.js: onShow~~~')
64 | },
65 | onHide () {
66 | console.log('app.main.js: onHide~~~')
67 | }
68 | })
69 |
70 | Lone.logic('ad', {
71 | props: {
72 | title: String,
73 | n: {
74 | type: Number,
75 | required: true,
76 | default: -1,
77 | validator: function (value) {
78 | return value >= 0
79 | }
80 | },
81 | list: {
82 | type: Array,
83 | observer (newData, oldData) {
84 | console.log('ad->props-observer:', newData, oldData)
85 | }
86 | }
87 | },
88 | created () {
89 | this.$emit('enlarge-text', 123)
90 | }
91 | })
92 |
93 | Lone.logic('v-model', {
94 | data: () => (
95 | { n: 0, message: 'default text', checked: true, checkedNames: ['Jack'], picked: 'One' }
96 | ),
97 | methods: {
98 | showModel () {
99 | console.log(this.data)
100 | }
101 | },
102 | mounted () {
103 | setTimeout(_ => {
104 | this.setData({
105 | n: 2
106 | })
107 | }, 1000)
108 | }
109 | })
110 |
111 | Lone.logic('alert')
112 | Lone.logic('base-layout')
113 | Lone.logic('handle-error', {
114 | data () {
115 | console.log(window)
116 | },
117 | handleError () {
118 | console.log(window)
119 | }
120 | })
121 |
122 | Lone.logic('lifecycle', {
123 | beforeCreate () {
124 | console.log('lifecycle: beforeCreate')
125 | },
126 | created () {
127 | console.log('lifecycle: created')
128 | },
129 | onReady () {
130 | console.log('lifecycle: onReady')
131 | },
132 | mounted () {
133 | console.log('lifecycle: mounted')
134 | },
135 | onLoad () {
136 | console.log('lifecycle: onLoad')
137 | },
138 | onShow () {
139 | console.log('lifecycle: onShow')
140 | },
141 | onHide () {
142 | console.log('lifecycle: onHide')
143 | },
144 | beforeMount () {
145 | console.log('lifecycle: beforeMount')
146 | },
147 | beforeUpdate () {
148 | console.log('lifecycle: beforeUpdate')
149 | },
150 | updated () {
151 | console.log('lifecycle: updated')
152 | },
153 | beforeDestroy () {
154 | console.log('lifecycle: beforeDestroy')
155 | },
156 | onUnload () {
157 | console.log('lifecycle: onUnload ')
158 | },
159 | destroyed () {
160 | console.log('lifecycle: destroyed ')
161 | },
162 | data: () => ({ items: [], a: 0 }),
163 | methods: {
164 | addItem () {
165 | this.data.items.push(this.data.a)
166 | this.setData({
167 | items: this.data.items,
168 | a: this.data.a + 1
169 | })
170 | },
171 | dontChange () {
172 | this.setData({
173 | items: this.data.items,
174 | a: this.data.a
175 | })
176 | },
177 | back () {
178 | this.navigateBack()
179 | }
180 | }
181 | })
182 |
183 | Lone.logic('destory', {
184 | handleDestroyed () {
185 | this.$destroy()
186 | },
187 | beforeDestroy () {
188 | console.log('lifecycle: beforeDestroy')
189 | },
190 | destroyed () {
191 | console.log('lifecycle: destroyed ')
192 | }
193 | })
194 |
195 | Lone.logic('query', {
196 | data () {
197 | return {
198 | query: ''
199 | }
200 | },
201 | created () {
202 | this.setData({
203 | query: '11'
204 | })
205 | },
206 | onLoad (query) {
207 | this.setData({
208 | query: query ? JSON.stringify(query) : ''
209 | })
210 | },
211 | back () {
212 | this.navigateBack()
213 | },
214 | navigatorTo (url) {
215 | this.navigateTo({
216 | url
217 | })
218 | }
219 | })
220 |
--------------------------------------------------------------------------------
/example/basic/app.page.js:
--------------------------------------------------------------------------------
1 | Lone.page({
2 | components: [
3 | {
4 | name: 'test',
5 | template: `
6 |
= 0) {
123 | rest = html.slice(textEnd)
124 | while (
125 | !endTag.test(rest) &&
126 | !startTagOpen.test(rest) &&
127 | !comment.test(rest) &&
128 | !conditionalComment.test(rest)
129 | ) {
130 | // < in plain text, be forgiving and treat it as text
131 | next = rest.indexOf('<', 1)
132 | if (next < 0) break
133 | textEnd += next
134 | rest = html.slice(textEnd)
135 | }
136 | text = html.substring(0, textEnd)
137 | advance(textEnd)
138 | }
139 |
140 | // html 中没有标签的情况
141 | if (textEnd < 0) {
142 | text = html
143 | html = ''
144 | }
145 |
146 | if (options.chars && text) {
147 | options.chars(text)
148 | }
149 | } else {
150 | // lastTag 为 script,style,textarea的情况
151 | let endTagLength = 0
152 | const stackedTag = lastTag.toLowerCase()
153 | const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(' + stackedTag + '[^>]*>)', 'i'))
154 | const rest = html.replace(reStackedTag, function (all, text, endTag) {
155 | endTagLength = endTag.length
156 | if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
157 | text = text
158 | .replace(//g, '$1')
159 | .replace(//g, '$1')
160 | }
161 | if (shouldIgnoreFirstNewline(stackedTag, text)) {
162 | text = text.slice(1)
163 | }
164 | if (options.chars) {
165 | options.chars(text)
166 | }
167 | return ''
168 | })
169 | index += html.length - rest.length
170 | html = rest
171 | parseEndTag(stackedTag, index - endTagLength, index)
172 | }
173 |
174 | if (html === last) {
175 | options.chars && options.chars(html)
176 | if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) {
177 | options.warn(`Mal-formatted tag at end of template: "${html}"`)
178 | }
179 | break
180 | }
181 | }
182 |
183 | // Clean up any remaining tags
184 | parseEndTag()
185 |
186 | function advance (n) {
187 | index += n
188 | html = html.substring(n)
189 | }
190 |
191 | function parseStartTag () {
192 | const start = html.match(startTagOpen)
193 | if (start) {
194 | const match = {
195 | tagName: start[1],
196 | attrs: [],
197 | start: index
198 | }
199 | advance(start[0].length)
200 | let end, attr
201 | while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
202 | advance(attr[0].length)
203 | match.attrs.push(attr)
204 | }
205 | if (end) {
206 | match.unarySlash = end[1]
207 | advance(end[0].length)
208 | match.end = index
209 | return match
210 | }
211 | }
212 | }
213 |
214 | function handleStartTag (match) {
215 | const tagName = match.tagName
216 | const unarySlash = match.unarySlash
217 |
218 | if (expectHTML) {
219 | if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
220 | parseEndTag(lastTag)
221 | }
222 | if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
223 | parseEndTag(tagName)
224 | }
225 | }
226 |
227 | const unary = isUnaryTag(tagName) || !!unarySlash
228 |
229 | const l = match.attrs.length
230 | const attrs = new Array(l)
231 | for (let i = 0; i < l; i++) {
232 | const args = match.attrs[i]
233 | // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
234 | if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
235 | if (args[3] === '') { delete args[3] }
236 | if (args[4] === '') { delete args[4] }
237 | if (args[5] === '') { delete args[5] }
238 | }
239 | const value = args[3] || args[4] || args[5] || ''
240 | attrs[i] = {
241 | name: args[1],
242 | value: decodeAttr(
243 | value,
244 | options.shouldDecodeNewlines
245 | )
246 | }
247 | }
248 |
249 | // 如果当前标签的开始是没有 “/” 的,那表示当前标签有子集
250 | // 当进入子标签时将当前标签数据推入到 stack 中
251 | // lastTag 是 parentDOM
252 | if (!unary) {
253 | stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })
254 | lastTag = tagName
255 | }
256 |
257 | if (options.start) {
258 | options.start(tagName, attrs, unary, match.start, match.end)
259 | }
260 | }
261 |
262 | function parseEndTag (tagName, start, end) {
263 | let pos, lowerCasedTagName
264 | if (start == null) start = index
265 | if (end == null) end = index
266 |
267 | if (tagName) {
268 | lowerCasedTagName = tagName.toLowerCase()
269 | }
270 |
271 | // Find the closest opened tag of the same type
272 | if (tagName) {
273 | for (pos = stack.length - 1; pos >= 0; pos--) {
274 | if (stack[pos].lowerCasedTag === lowerCasedTagName) {
275 | break
276 | }
277 | }
278 | } else {
279 | // If no tag name is provided, clean shop
280 | pos = 0
281 | }
282 |
283 | if (pos >= 0) {
284 | // Close all the open elements, up the stack
285 | for (let i = stack.length - 1; i >= pos; i--) {
286 | if (process.env.NODE_ENV !== 'production' &&
287 | (i > pos || !tagName) &&
288 | options.warn
289 | ) {
290 | options.warn(
291 | `tag <${stack[i].tag}> has no matching end tag.`
292 | )
293 | }
294 | if (options.end) {
295 | options.end(stack[i].tag, start, end)
296 | }
297 | }
298 |
299 | // Remove the open elements from the stack
300 | stack.length = pos
301 | lastTag = pos && stack[pos - 1].tag
302 | } else if (lowerCasedTagName === 'br') {
303 | if (options.start) {
304 | options.start(tagName, [], true, start, end)
305 | }
306 | } else if (lowerCasedTagName === 'p') {
307 | if (options.start) {
308 | options.start(tagName, [], false, start, end)
309 | }
310 | if (options.end) {
311 | options.end(tagName, start, end)
312 | }
313 | }
314 | }
315 | }
316 |
--------------------------------------------------------------------------------
/packages/lone-compiler-core/parser/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import he from 'he'
4 | import { parseHTML } from './html-parser'
5 | import { parseText } from './text-parser'
6 | import { parseFilters } from './filter-parser'
7 | import { cached, no, camelize } from 'lone-util'
8 | import { genAssignmentCode } from '../directives/model'
9 | import { isIE, isEdge } from 'lone-util/env'
10 |
11 | import {
12 | addProp,
13 | addAttr,
14 | baseWarn,
15 | addHandler,
16 | addDirective,
17 | getBindingAttr,
18 | getAndRemoveAttr,
19 | pluckModuleFunction
20 | } from '../helpers'
21 |
22 | export const onRE = /^@|^v-on:/
23 | export const dirRE = /^v-|^@|^:/
24 | export const forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/
25 | export const forIteratorRE = /\((\{[^}]*\}|[^,]*),([^,]*)(?:,([^,]*))?\)/
26 |
27 | const argRE = /:(.*)$/
28 | const bindRE = /^:|^v-bind:/
29 | const modifierRE = /\.[^.]+/g
30 |
31 | const decodeHTMLCached = cached(he.decode)
32 |
33 | // configurable state
34 | export let warn
35 | let delimiters
36 | let transforms
37 | let preTransforms
38 | let postTransforms
39 | let platformIsPreTag
40 | let platformMustUseProp
41 | let platformGetTagNamespace
42 |
43 | export function createASTElement (tag, attrs, parent) {
44 | return {
45 | type: 1,
46 | tag,
47 | attrsList: attrs,
48 | attrsMap: makeAttrsMap(attrs),
49 | parent,
50 | children: []
51 | }
52 | }
53 |
54 | /**
55 | * Convert HTML string to AST.
56 | */
57 | export function parse (template, options) {
58 | warn = options.warn || baseWarn
59 |
60 | platformIsPreTag = options.isPreTag || no
61 | platformMustUseProp = options.mustUseProp || no
62 | platformGetTagNamespace = options.getTagNamespace || no
63 |
64 | transforms = pluckModuleFunction(options.modules, 'transformNode')
65 | preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
66 | postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
67 |
68 | delimiters = options.delimiters
69 |
70 | const stack = []
71 | const preserveWhitespace = options.preserveWhitespace !== false
72 | let root
73 | let currentParent
74 | let inVPre = false
75 | let inPre = false
76 | let warned = false
77 |
78 | function warnOnce (msg) {
79 | if (!warned) {
80 | warned = true
81 | warn(msg)
82 | }
83 | }
84 |
85 | function endPre (element) {
86 | // check pre state
87 | if (element.pre) {
88 | inVPre = false
89 | }
90 | if (platformIsPreTag(element.tag)) {
91 | inPre = false
92 | }
93 | }
94 |
95 | parseHTML(template, {
96 | warn,
97 | expectHTML: options.expectHTML,
98 | isUnaryTag: options.isUnaryTag,
99 | canBeLeftOpenTag: options.canBeLeftOpenTag,
100 | shouldDecodeNewlines: options.shouldDecodeNewlines,
101 | shouldKeepComment: options.comments,
102 | start (tag, attrs, unary) {
103 | // check namespace.
104 | // inherit parent ns if there is one
105 | const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
106 |
107 | // handle IE svg bug
108 | /* istanbul ignore if */
109 | if (isIE && ns === 'svg') {
110 | attrs = guardIESVGBug(attrs)
111 | }
112 |
113 | let element = createASTElement(tag, attrs, currentParent)
114 | if (ns) {
115 | element.ns = ns
116 | }
117 |
118 | // isForbiddenTag:判断element是不是 style 或者 script 标签
119 | if (isForbiddenTag(element)) {
120 | element.forbidden = true
121 | process.env.NODE_ENV !== 'production' && warn(
122 | 'Templates should only be responsible for mapping the state to the ' +
123 | 'UI. Avoid placing tags with side-effects in your templates, such as ' +
124 | `<${tag}>` + ', as they will not be parsed.'
125 | )
126 | }
127 |
128 | // apply pre-transforms
129 | for (let i = 0; i < preTransforms.length; i++) {
130 | element = preTransforms[i](element, options) || element
131 | }
132 |
133 | if (!inVPre) {
134 | processPre(element)
135 | if (element.pre) {
136 | inVPre = true
137 | }
138 | }
139 |
140 | if (platformIsPreTag(element.tag)) {
141 | inPre = true
142 | }
143 | if (inVPre) {
144 | processRawAttrs(element)
145 | } else if (!element.processed) {
146 | // structural directives
147 | processFor(element)
148 | processIf(element)
149 | processOnce(element)
150 | // element-scope stuff
151 | processElement(element, options)
152 | }
153 |
154 | function checkRootConstraints (el) {
155 | if (process.env.NODE_ENV !== 'production') {
156 | if (el.tag === 'slot' || el.tag === 'template') {
157 | warnOnce(
158 | `Cannot use <${el.tag}> as component root element because it may ` +
159 | 'contain multiple nodes.'
160 | )
161 | }
162 | // eslint-disable-next-line no-prototype-builtins
163 | if (el.attrsMap.hasOwnProperty('v-for')) {
164 | warnOnce(
165 | 'Cannot use v-for on stateful component root element because ' +
166 | 'it renders multiple elements.'
167 | )
168 | }
169 | }
170 | }
171 |
172 | // tree management
173 | if (!root) { // 首次解析到标签开始
174 | root = element // 将第一个解析到的标签设置为root
175 | checkRootConstraints(root)
176 | } else if (!stack.length) {
177 | // 能进到这里面,说明root元素不唯一,也就是有两个或多个root元素
178 | // allow root elements with v-if, v-else-if and v-else
179 | if (root.if && (element.elseif || element.else)) {
180 | checkRootConstraints(element)
181 | addIfCondition(root, {
182 | exp: element.elseif,
183 | block: element
184 | })
185 | } else if (process.env.NODE_ENV !== 'production') {
186 | warnOnce(
187 | 'Component template should contain exactly one root element. ' +
188 | 'If you are using v-if on multiple elements, ' +
189 | 'use v-else-if to chain them instead.'
190 | )
191 | }
192 | }
193 | // 如果当前 标签不是 style 和 script
194 | // 将当前标签 push 到 父标签的 children 中
195 | // 将当前标签的 parent 设置为父标签(stack 中的最后一个)
196 | if (currentParent && !element.forbidden) {
197 | if (element.elseif || element.else) {
198 | processIfConditions(element, currentParent)
199 | } else if (element.slotScope) { // scoped slot
200 | currentParent.plain = false
201 | const name = element.slotTarget || '"default"'
202 | ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
203 | } else {
204 | currentParent.children.push(element)
205 | element.parent = currentParent
206 | }
207 | }
208 |
209 | // 如果不是自闭和标签将当前标签推入stack
210 | // stack 记录DOM深度用
211 | if (!unary) {
212 | currentParent = element
213 | stack.push(element)
214 | } else {
215 | endPre(element)
216 | }
217 |
218 | // apply post-transforms
219 | for (let i = 0; i < postTransforms.length; i++) {
220 | postTransforms[i](element, options)
221 | }
222 | },
223 |
224 | end () {
225 | // remove trailing whitespace
226 | const element = stack[stack.length - 1]
227 | const lastNode = element.children[element.children.length - 1]
228 | if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
229 | element.children.pop()
230 | }
231 | // pop stack
232 | stack.length -= 1
233 | currentParent = stack[stack.length - 1]
234 | endPre(element)
235 | },
236 |
237 | chars (text) {
238 | if (!currentParent) {
239 | if (process.env.NODE_ENV !== 'production') {
240 | if (text === template) {
241 | warnOnce(
242 | 'Component template requires a root element, rather than just text.'
243 | )
244 | } else if ((text = text.trim())) {
245 | warnOnce(
246 | `text "${text}" outside root element will be ignored.`
247 | )
248 | }
249 | }
250 | return
251 | }
252 | // IE textarea placeholder bug
253 | /* istanbul ignore if */
254 | if (isIE &&
255 | currentParent.tag === 'textarea' &&
256 | currentParent.attrsMap.placeholder === text
257 | ) {
258 | return
259 | }
260 | const children = currentParent.children
261 | text = inPre || text.trim()
262 | ? isTextTag(currentParent) ? text : decodeHTMLCached(text)
263 | // only preserve whitespace if its not right after a starting tag
264 | // children.length 可以判断出当前是否解析到开始标签后面,开始标签后面children肯定是0,因为还没解析到children呢
265 | // 所以只有开始标签后面的没有空格例如
,这中间的空格就没有了
266 | : preserveWhitespace && children.length ? ' ' : ''
267 | if (text) {
268 | let expression
269 | if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) {
270 | children.push({
271 | type: 2,
272 | expression,
273 | text
274 | })
275 | } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
276 | children.push({
277 | type: 3,
278 | text
279 | })
280 | }
281 | }
282 | },
283 | comment (text) {
284 | currentParent.children.push({
285 | type: 3,
286 | text,
287 | isComment: true
288 | })
289 | }
290 | })
291 | return root
292 | }
293 |
294 | function processPre (el) {
295 | if (getAndRemoveAttr(el, 'v-pre') != null) {
296 | el.pre = true
297 | }
298 | }
299 |
300 | function processRawAttrs (el) {
301 | const l = el.attrsList.length
302 | if (l) {
303 | const attrs = el.attrs = new Array(l)
304 | for (let i = 0; i < l; i++) {
305 | attrs[i] = {
306 | name: el.attrsList[i].name,
307 | value: JSON.stringify(el.attrsList[i].value)
308 | }
309 | }
310 | } else if (!el.pre) {
311 | // non root node in pre blocks with no attributes
312 | el.plain = true
313 | }
314 | }
315 |
316 | export function processElement (element, options) {
317 | processKey(element)
318 |
319 | // determine whether this is a plain element after
320 | // removing structural attributes
321 | element.plain = !element.key && !element.attrsList.length
322 |
323 | processRef(element)
324 | processSlot(element)
325 | processComponent(element)
326 | for (let i = 0; i < transforms.length; i++) {
327 | element = transforms[i](element, options) || element
328 | }
329 | processAttrs(element)
330 | }
331 |
332 | function processKey (el) {
333 | const exp = getBindingAttr(el, 'key')
334 | if (exp) {
335 | if (process.env.NODE_ENV !== 'production' && el.tag === 'template') {
336 | warn(' cannot be keyed. Place the key on real elements instead.')
337 | }
338 | el.key = exp
339 | }
340 | }
341 |
342 | function processRef (el) {
343 | const ref = getBindingAttr(el, 'ref')
344 | if (ref) {
345 | el.ref = ref
346 | el.refInFor = checkInFor(el)
347 | }
348 | }
349 |
350 | export function processFor (el) {
351 | let exp
352 | if ((exp = getAndRemoveAttr(el, 'v-for'))) {
353 | const inMatch = exp.match(forAliasRE)
354 | if (!inMatch) {
355 | process.env.NODE_ENV !== 'production' && warn(
356 | `Invalid v-for expression: ${exp}`
357 | )
358 | return
359 | }
360 | el.for = inMatch[2].trim()
361 | const alias = inMatch[1].trim()
362 | const iteratorMatch = alias.match(forIteratorRE)
363 | if (iteratorMatch) {
364 | el.alias = iteratorMatch[1].trim()
365 | el.iterator1 = iteratorMatch[2].trim()
366 | if (iteratorMatch[3]) {
367 | el.iterator2 = iteratorMatch[3].trim()
368 | }
369 | } else {
370 | el.alias = alias
371 | }
372 | }
373 | }
374 |
375 | function processIf (el) {
376 | const exp = getAndRemoveAttr(el, 'v-if')
377 | if (exp) {
378 | el.if = exp
379 | addIfCondition(el, {
380 | exp: exp,
381 | block: el
382 | })
383 | } else {
384 | if (getAndRemoveAttr(el, 'v-else') != null) {
385 | el.else = true
386 | }
387 | const elseif = getAndRemoveAttr(el, 'v-else-if')
388 | if (elseif) {
389 | el.elseif = elseif
390 | }
391 | }
392 | }
393 |
394 | function processIfConditions (el, parent) {
395 | const prev = findPrevElement(parent.children)
396 | if (prev && prev.if) {
397 | addIfCondition(prev, {
398 | exp: el.elseif,
399 | block: el
400 | })
401 | } else if (process.env.NODE_ENV !== 'production') {
402 | warn(
403 | `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` +
404 | `used on element <${el.tag}> without corresponding v-if.`
405 | )
406 | }
407 | }
408 |
409 | function findPrevElement (children) {
410 | let i = children.length
411 | while (i--) {
412 | if (children[i].type === 1) {
413 | return children[i]
414 | } else {
415 | if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {
416 | warn(
417 | `text "${children[i].text.trim()}" between v-if and v-else(-if) ` +
418 | 'will be ignored.'
419 | )
420 | }
421 | children.pop()
422 | }
423 | }
424 | }
425 |
426 | export function addIfCondition (el, condition) {
427 | if (!el.ifConditions) {
428 | el.ifConditions = []
429 | }
430 | el.ifConditions.push(condition)
431 | }
432 |
433 | function processOnce (el) {
434 | const once = getAndRemoveAttr(el, 'v-once')
435 | if (once != null) {
436 | el.once = true
437 | }
438 | }
439 |
440 | function processSlot (el) {
441 | if (el.tag === 'slot') {
442 | el.slotName = getBindingAttr(el, 'name')
443 | if (process.env.NODE_ENV !== 'production' && el.key) {
444 | warn(
445 | '`key` does not work on because slots are abstract outlets ' +
446 | 'and can possibly expand into multiple elements. ' +
447 | 'Use the key on a wrapping element instead.'
448 | )
449 | }
450 | } else {
451 | let slotScope
452 | if (el.tag === 'template') {
453 | slotScope = getAndRemoveAttr(el, 'scope')
454 | /* istanbul ignore if */
455 | if (process.env.NODE_ENV !== 'production' && slotScope) {
456 | warn(
457 | 'the "scope" attribute for scoped slots have been deprecated and ' +
458 | 'replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ' +
459 | 'can also be used on plain elements in addition to to ' +
460 | 'denote scoped slots.',
461 | true
462 | )
463 | }
464 | el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
465 | } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
466 | el.slotScope = slotScope
467 | }
468 | const slotTarget = getBindingAttr(el, 'slot')
469 | if (slotTarget) {
470 | el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
471 | }
472 | }
473 | }
474 |
475 | function processComponent (el) {
476 | let binding
477 | if ((binding = getBindingAttr(el, 'is'))) {
478 | el.component = binding
479 | }
480 | if (getAndRemoveAttr(el, 'inline-template') != null) {
481 | el.inlineTemplate = true
482 | }
483 | }
484 |
485 | function processAttrs (el) {
486 | const list = el.attrsList
487 | let i, l, name, rawName, value, modifiers, isProp
488 | for (i = 0, l = list.length; i < l; i++) {
489 | name = rawName = list[i].name
490 | value = list[i].value
491 | if (dirRE.test(name)) {
492 | // mark element as dynamic
493 | el.hasBindings = true
494 | // modifiers
495 | modifiers = parseModifiers(name)
496 | if (modifiers) {
497 | name = name.replace(modifierRE, '')
498 | }
499 | if (bindRE.test(name)) { // v-bind
500 | name = name.replace(bindRE, '')
501 | value = parseFilters(value)
502 | isProp = false
503 | if (modifiers) {
504 | if (modifiers.prop) {
505 | isProp = true
506 | name = camelize(name)
507 | if (name === 'innerHtml') name = 'innerHTML'
508 | }
509 | if (modifiers.camel) {
510 | name = camelize(name)
511 | }
512 | if (modifiers.sync) {
513 | addHandler(
514 | el,
515 | `update:${camelize(name)}`,
516 | genAssignmentCode(value, '$event')
517 | )
518 | }
519 | }
520 | if (isProp || (
521 | !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
522 | )) {
523 | addProp(el, name, value)
524 | } else {
525 | addAttr(el, name, value)
526 | }
527 | } else if (onRE.test(name)) { // v-on
528 | name = name.replace(onRE, '')
529 | addHandler(el, name, value, modifiers, false, warn)
530 | } else { // normal directives
531 | name = name.replace(dirRE, '')
532 | // parse arg
533 | const argMatch = name.match(argRE)
534 | const arg = argMatch && argMatch[1]
535 | if (arg) {
536 | name = name.slice(0, -(arg.length + 1))
537 | }
538 | addDirective(el, name, rawName, value, arg, modifiers)
539 | if (process.env.NODE_ENV !== 'production' && name === 'model') {
540 | checkForAliasModel(el, value)
541 | }
542 | }
543 | } else {
544 | // literal attribute
545 | if (process.env.NODE_ENV !== 'production') {
546 | const expression = parseText(value, delimiters)
547 | if (expression) {
548 | warn(
549 | `${name}="${value}": ` +
550 | 'Interpolation inside attributes has been removed. ' +
551 | 'Use v-bind or the colon shorthand instead. For example, ' +
552 | 'instead of , use
.'
553 | )
554 | }
555 | }
556 | addAttr(el, name, JSON.stringify(value))
557 | }
558 | }
559 | }
560 |
561 | function checkInFor (el) {
562 | let parent = el
563 | while (parent) {
564 | if (parent.for !== undefined) {
565 | return true
566 | }
567 | parent = parent.parent
568 | }
569 | return false
570 | }
571 |
572 | function parseModifiers (name) {
573 | const match = name.match(modifierRE)
574 | if (match) {
575 | const ret = {}
576 | match.forEach(m => { ret[m.slice(1)] = true })
577 | return ret
578 | }
579 | }
580 |
581 | function makeAttrsMap (attrs) {
582 | const map = {}
583 | for (let i = 0, l = attrs.length; i < l; i++) {
584 | if (
585 | process.env.NODE_ENV !== 'production' &&
586 | map[attrs[i].name] && !isIE && !isEdge
587 | ) {
588 | warn('duplicate attribute: ' + attrs[i].name)
589 | }
590 | map[attrs[i].name] = attrs[i].value
591 | }
592 | return map
593 | }
594 |
595 | // for script (e.g. type="x/template") or style, do not decode content
596 | function isTextTag (el) {
597 | return el.tag === 'script' || el.tag === 'style'
598 | }
599 |
600 | function isForbiddenTag (el) {
601 | return (
602 | el.tag === 'style' ||
603 | (el.tag === 'script' && (
604 | !el.attrsMap.type ||
605 | el.attrsMap.type === 'text/javascript'
606 | ))
607 | )
608 | }
609 |
610 | const ieNSBug = /^xmlns:NS\d+/
611 | const ieNSPrefix = /^NS\d+:/
612 |
613 | /* istanbul ignore next */
614 | function guardIESVGBug (attrs) {
615 | const res = []
616 | for (let i = 0; i < attrs.length; i++) {
617 | const attr = attrs[i]
618 | if (!ieNSBug.test(attr.name)) {
619 | attr.name = attr.name.replace(ieNSPrefix, '')
620 | res.push(attr)
621 | }
622 | }
623 | return res
624 | }
625 |
626 | function checkForAliasModel (el, value) {
627 | let _el = el
628 | while (_el) {
629 | if (_el.for && _el.alias === value) {
630 | warn(
631 | `<${el.tag} v-model="${value}">: ` +
632 | 'You are binding v-model directly to a v-for iteration alias. ' +
633 | 'This will not be able to modify the v-for source array because ' +
634 | 'writing to the alias is like modifying a function local variable. ' +
635 | 'Consider using an array of objects and use v-model on an object property instead.'
636 | )
637 | }
638 | _el = _el.parent
639 | }
640 | }
641 |
--------------------------------------------------------------------------------
/packages/lone-compiler-core/parser/text-parser.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { cached } from 'lone-util'
4 | import { parseFilters } from './filter-parser'
5 |
6 | const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g
7 | // eslint-disable-next-line no-useless-escape
8 | const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g
9 |
10 | const buildRegex = cached(delimiters => {
11 | const open = delimiters[0].replace(regexEscapeRE, '\\$&')
12 | const close = delimiters[1].replace(regexEscapeRE, '\\$&')
13 | return new RegExp(open + '((?:.|\\n)+?)' + close, 'g')
14 | })
15 |
16 | export function parseText (text, delimiters) {
17 | const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
18 | if (!tagRE.test(text)) {
19 | return
20 | }
21 | const tokens = []
22 | let lastIndex = tagRE.lastIndex = 0
23 | let match, index
24 | while ((match = tagRE.exec(text))) {
25 | index = match.index
26 | // 先把 {{ 双大括号前边的 text push 到 tokens 中
27 | // push text token
28 | if (index > lastIndex) {
29 | tokens.push(JSON.stringify(text.slice(lastIndex, index)))
30 | }
31 | // tag token
32 | const exp = parseFilters(match[1].trim())
33 | tokens.push(`_s(${exp})`)
34 | lastIndex = index + match[0].length
35 | }
36 | if (lastIndex < text.length) {
37 | tokens.push(JSON.stringify(text.slice(lastIndex)))
38 | }
39 | return tokens.join('+')
40 | }
41 |
--------------------------------------------------------------------------------
/packages/lone-compiler-core/to-function.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { noop, extend, warn as baseWarn, tip } from 'lone-util'
4 |
5 | function createFunction (code, errors) {
6 | try {
7 | // eslint-disable-next-line no-new-func
8 | return new Function(code)
9 | } catch (err) {
10 | errors.push({ err, code })
11 | return noop
12 | }
13 | }
14 |
15 | export function createCompileToFunctionFn (compile) {
16 | const cache = Object.create(null)
17 |
18 | return function compileToFunctions (template, options, vm) {
19 | options = extend({}, options)
20 | const warn = options.warn || baseWarn
21 | delete options.warn
22 |
23 | // check cache
24 | const key = options.delimiters
25 | ? String(options.delimiters) + template
26 | : template
27 | if (cache[key]) {
28 | return cache[key]
29 | }
30 |
31 | // compile
32 | const compiled = compile(template, options)
33 |
34 | // check compilation errors/tips
35 | if (process.env.NODE_ENV !== 'production') {
36 | if (compiled.errors && compiled.errors.length) {
37 | warn(
38 | `Error compiling template:\n\n${template}\n\n` +
39 | compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
40 | vm
41 | )
42 | }
43 | if (compiled.tips && compiled.tips.length) {
44 | compiled.tips.forEach(msg => tip(msg, vm))
45 | }
46 | }
47 |
48 | // turn code into functions
49 | const res = {}
50 | const fnGenErrors = []
51 | res.render = createFunction(compiled.render, fnGenErrors)
52 | res.staticRenderFns = compiled.staticRenderFns.map(code => {
53 | return createFunction(code, fnGenErrors)
54 | })
55 |
56 | // check function generation errors.
57 | // this should only happen if there is a bug in the compiler itself.
58 | // mostly for codegen development use
59 | /* istanbul ignore if */
60 | if (process.env.NODE_ENV !== 'production') {
61 | if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
62 | warn(
63 | 'Failed to generate render function:\n\n' +
64 | fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'),
65 | vm
66 | )
67 | }
68 | }
69 |
70 | return (cache[key] = res)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/directives/html.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { addProp } from 'lone-compiler-core/helpers'
4 |
5 | export default function html (el, dir) {
6 | if (dir.value) {
7 | addProp(el, 'innerHTML', `_s(${dir.value})`)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/directives/index.js:
--------------------------------------------------------------------------------
1 | import model from './model'
2 | import text from './text'
3 | import html from './html'
4 |
5 | export default {
6 | model,
7 | text,
8 | html
9 | }
10 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/directives/model.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { isReservedTag } from 'lone-util/web/element'
4 | import { addHandler, addProp, addAttr, getBindingAttr } from 'lone-compiler-core/helpers'
5 | import { genComponentModel, genAssignmentCode } from 'lone-compiler-core/directives/model'
6 |
7 | let warn
8 |
9 | // in some cases, the event used has to be determined at runtime
10 | // so we used some reserved tokens during compile.
11 | export const RANGE_TOKEN = '__r'
12 | export const CHECKBOX_RADIO_TOKEN = '__c'
13 |
14 | export default function model (el, dir, _warn) {
15 | warn = _warn
16 | const value = dir.value
17 | const modifiers = dir.modifiers
18 | const tag = el.tag
19 | const type = el.attrsMap.type
20 |
21 | if (process.env.NODE_ENV !== 'production') {
22 | // inputs with type="file" are read only and setting the input's
23 | // value will throw an error.
24 | if (tag === 'input' && type === 'file') {
25 | warn(
26 | `<${el.tag} v-model="${value}" type="file">:\n` +
27 | 'File inputs are read only. Use a v-on:change listener instead.'
28 | )
29 | }
30 | }
31 |
32 | if (el.component) {
33 | genComponentModel(el, value, modifiers)
34 | // component v-model doesn't need extra runtime
35 | return false
36 | } else if (tag === 'select') {
37 | genSelect(el, value, modifiers)
38 | } else if (tag === 'input' && type === 'checkbox') {
39 | genCheckboxModel(el, value, modifiers)
40 | } else if (tag === 'input' && type === 'radio') {
41 | genRadioModel(el, value, modifiers)
42 | } else if (tag === 'input' || tag === 'textarea') {
43 | genDefaultModel(el, value, modifiers)
44 | } else if (!isReservedTag(tag)) {
45 | genComponentModel(el, value, modifiers)
46 | // component v-model doesn't need extra runtime
47 | return false
48 | } else if (process.env.NODE_ENV !== 'production') {
49 | warn(
50 | `<${el.tag} v-model="${value}">: ` +
51 | 'v-model is not supported on this element type. ' +
52 | 'If you are working with contenteditable, it\'s recommended to ' +
53 | 'wrap a library dedicated for that purpose inside a custom component.'
54 | )
55 | }
56 |
57 | // ensure runtime directive metadata
58 | return true
59 | }
60 |
61 | function genCheckboxModel (el, value, modifiers) {
62 | const number = modifiers && modifiers.number
63 | const valueBinding = getBindingAttr(el, 'value') || 'null'
64 | const trueValueBinding = getBindingAttr(el, 'true-value') || 'true'
65 | const falseValueBinding = getBindingAttr(el, 'false-value') || 'false'
66 | addProp(el, 'checked',
67 | `Array.isArray(${value})` +
68 | `?_i(${value},${valueBinding})>-1` + (
69 | trueValueBinding === 'true'
70 | ? `:(${value})`
71 | : `:_q(${value},${trueValueBinding})`
72 | )
73 | )
74 | addHandler(el, 'change',
75 | `var $$a=${value},` +
76 | '$$el=$event.target,' +
77 | `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` +
78 | 'if(Array.isArray($$a)){' +
79 | `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
80 | '$$i=_i($$a,$$v);' +
81 | `if($$el.checked){$$i<0&&(slave.send('page:data', getLogicChannel(), {id: id, data:{${value}: $$a.concat([$$v])}}))}` +
82 | `else{$$i>-1&&(slave.send('page:data', getLogicChannel(), {id: id, data:{${value}: $$a.slice(0,$$i).concat($$a.slice($$i+1))}}))}` +
83 | `}else{${genAssignmentCode(value, '$$c')}}`,
84 | null, true
85 | )
86 | }
87 |
88 | function genRadioModel (el, value, modifiers) {
89 | const number = modifiers && modifiers.number
90 | let valueBinding = getBindingAttr(el, 'value') || 'null'
91 | valueBinding = number ? `_n(${valueBinding})` : valueBinding
92 | addProp(el, 'checked', `_q(${value},${valueBinding})`)
93 | const name = getBindingAttr(el, 'name')
94 | if (!name) addAttr(el, 'name', `"${value}"`)
95 | addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
96 | }
97 |
98 | function genSelect (el, value, modifiers) {
99 | const number = modifiers && modifiers.number
100 | const selectedVal = 'Array.prototype.filter' +
101 | '.call($event.target.options,function(o){return o.selected})' +
102 | '.map(function(o){var val = "_value" in o ? o._value : o.value;' +
103 | `return ${number ? '_n(val)' : 'val'}})`
104 |
105 | const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]'
106 | let code = `var $$selectedVal = ${selectedVal};`
107 | code = `${code} ${genAssignmentCode(value, assignment)}`
108 | addHandler(el, 'change', code, null, true)
109 | }
110 |
111 | function genDefaultModel (el, value, modifiers) {
112 | const type = el.attrsMap.type
113 | const { lazy, number, trim } = modifiers || {}
114 | const needCompositionGuard = !lazy && type !== 'range'
115 | const event = lazy
116 | ? 'change'
117 | : type === 'range'
118 | ? RANGE_TOKEN
119 | : 'input'
120 |
121 | let valueExpression = '$event.target.value'
122 | if (trim) {
123 | valueExpression = '$event.target.value.trim()'
124 | }
125 | if (number) {
126 | valueExpression = `_n(${valueExpression})`
127 | }
128 |
129 | let code = genAssignmentCode(value, valueExpression)
130 | if (needCompositionGuard) {
131 | code = `if($event.target.composing)return;${code}`
132 | }
133 |
134 | addProp(el, 'value', `(${value})`)
135 | addHandler(el, event, code, null, true)
136 | if (trim || number) {
137 | addHandler(el, 'blur', '$forceUpdate()')
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/directives/text.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { addProp } from 'lone-compiler-core/helpers'
4 |
5 | export default function text (el, dir) {
6 | if (dir.value) {
7 | addProp(el, 'textContent', `_s(${dir.value})`)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/index.js:
--------------------------------------------------------------------------------
1 | import { baseOptions } from './options'
2 | import { createCompiler } from 'lone-compiler-core'
3 |
4 | const { compile, compileToFunctions } = createCompiler(baseOptions)
5 |
6 | export { compile, compileToFunctions }
7 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/modules/class.js:
--------------------------------------------------------------------------------
1 | import {
2 | getAndRemoveAttr,
3 | getBindingAttr
4 | } from 'lone-compiler-core/helpers'
5 |
6 | function transformNode (el) {
7 | const classBinding = getBindingAttr(el, 'class', false /* getStatic */)
8 | if (classBinding) {
9 | el.classBinding = classBinding
10 | }
11 | const staticClass = getAndRemoveAttr(el, 'class')
12 | if (staticClass) {
13 | const kclass = staticClass.trim().split(' ').map(name => `'${name}':true`).join(',')
14 | el.classBinding = el.classBinding
15 | ? el.classBinding.substring(0, el.classBinding.length - 1) + kclass + '}'
16 | : `{${kclass}}`
17 | }
18 | }
19 |
20 | function genData (el) {
21 | let data = ''
22 | if (el.classBinding) {
23 | data += `class:${el.classBinding},`
24 | }
25 | return data
26 | }
27 |
28 | export default {
29 | transformNode,
30 | genData
31 | }
32 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/modules/index.js:
--------------------------------------------------------------------------------
1 | import klass from './class'
2 | import style from './style'
3 | import model from './model'
4 |
5 | export default [
6 | klass,
7 | style,
8 | model
9 | ]
10 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/modules/model.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | /**
4 | * Expand input[v-model] with dyanmic type bindings into v-if-else chains
5 | * Turn this:
6 | *
7 | * into this:
8 | *
9 | *
10 | *
11 | */
12 |
13 | import {
14 | getBindingAttr,
15 | getAndRemoveAttr
16 | } from 'lone-compiler-core/helpers'
17 |
18 | import {
19 | processFor,
20 | processElement,
21 | addIfCondition,
22 | createASTElement
23 | } from 'lone-compiler-core/parser/index'
24 |
25 | function preTransformNode (el, options) {
26 | if (el.tag === 'input') {
27 | const map = el.attrsMap
28 | if (map['v-model'] && (map['v-bind:type'] || map[':type'])) {
29 | const typeBinding = getBindingAttr(el, 'type')
30 | const ifCondition = getAndRemoveAttr(el, 'v-if', true)
31 | const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : ''
32 | // 1. checkbox
33 | const branch0 = cloneASTElement(el)
34 | // process for on the main node
35 | processFor(branch0)
36 | addRawAttr(branch0, 'type', 'checkbox')
37 | processElement(branch0, options)
38 | branch0.processed = true // prevent it from double-processed
39 | branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra
40 | addIfCondition(branch0, {
41 | exp: branch0.if,
42 | block: branch0
43 | })
44 | // 2. add radio else-if condition
45 | const branch1 = cloneASTElement(el)
46 | getAndRemoveAttr(branch1, 'v-for', true)
47 | addRawAttr(branch1, 'type', 'radio')
48 | processElement(branch1, options)
49 | addIfCondition(branch0, {
50 | exp: `(${typeBinding})==='radio'` + ifConditionExtra,
51 | block: branch1
52 | })
53 | // 3. other
54 | const branch2 = cloneASTElement(el)
55 | getAndRemoveAttr(branch2, 'v-for', true)
56 | addRawAttr(branch2, ':type', typeBinding)
57 | processElement(branch2, options)
58 | addIfCondition(branch0, {
59 | exp: ifCondition,
60 | block: branch2
61 | })
62 | return branch0
63 | }
64 | }
65 | }
66 |
67 | function cloneASTElement (el) {
68 | return createASTElement(el.tag, el.attrsList.slice(), el.parent)
69 | }
70 |
71 | function addRawAttr (el, name, value) {
72 | el.attrsMap[name] = value
73 | el.attrsList.push({ name, value })
74 | }
75 |
76 | export default {
77 | preTransformNode
78 | }
79 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/modules/style.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { parseText } from 'lone-compiler-core/parser/text-parser'
4 | import { parseStyleText } from 'lone-util/web/style'
5 | import {
6 | getAndRemoveAttr,
7 | getBindingAttr,
8 | baseWarn
9 | } from 'lone-compiler-core/helpers'
10 |
11 | function transformNode (el, options) {
12 | const warn = options.warn || baseWarn
13 | const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
14 | if (styleBinding) {
15 | el.styleBinding = styleBinding
16 | }
17 |
18 | const staticStyle = getAndRemoveAttr(el, 'style')
19 | if (staticStyle) {
20 | /* istanbul ignore if */
21 | if (process.env.NODE_ENV !== 'production') {
22 | const expression = parseText(staticStyle, options.delimiters)
23 | if (expression) {
24 | warn(
25 | `style="${staticStyle}": ` +
26 | 'Interpolation inside attributes has been removed. ' +
27 | 'Use v-bind or the colon shorthand instead. For example, ' +
28 | 'instead of
, use
.'
29 | )
30 | }
31 | }
32 | el.styleBinding = el.styleBinding
33 | ? el.styleBinding.substring(0, el.styleBinding.length - 1) + ',' + JSON.stringify(parseStyleText(staticStyle)).substring(1)
34 | : JSON.stringify(parseStyleText(staticStyle))
35 | }
36 | }
37 |
38 | function genData (el) {
39 | let data = ''
40 | if (el.styleBinding) {
41 | data += `style:(${el.styleBinding}),`
42 | }
43 | return data
44 | }
45 |
46 | export default {
47 | staticKeys: ['staticStyle'],
48 | transformNode,
49 | genData
50 | }
51 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/options.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import {
4 | isPreTag,
5 | mustUseProp,
6 | isReservedTag,
7 | getTagNamespace
8 | } from 'lone-util/web'
9 |
10 | import modules from './modules/index'
11 | import directives from './directives/index'
12 | import { genStaticKeys, isUnaryTag, canBeLeftOpenTag } from 'lone-util'
13 |
14 | export const baseOptions = {
15 | expectHTML: true,
16 | modules,
17 | directives,
18 | isPreTag,
19 | isUnaryTag,
20 | mustUseProp,
21 | canBeLeftOpenTag,
22 | isReservedTag,
23 | getTagNamespace,
24 | staticKeys: genStaticKeys(modules)
25 | }
26 |
--------------------------------------------------------------------------------
/packages/lone-compiler-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-compiler-dom",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "license": "ISC",
10 | "dependencies": {
11 | "lone-compiler-core": "^0.0.0",
12 | "lone-util": "^0.0.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/lone-logic-master/index.js:
--------------------------------------------------------------------------------
1 | import { Slave } from 'lone-messenger'
2 | import schedule from 'lone-logic/schedule'
3 | import logicComponentRegister from 'lone-logic'
4 |
5 | export default function logicMaster (UIConf) {
6 | const mid = UIConf.mid
7 | const pid = UIConf.pid
8 | const components = UIConf.components
9 | const slave = new Slave({ env: 'postMessage', channel: `${mid}_${pid}_logic` })
10 | registerComponent(components)
11 | schedule(slave)
12 | }
13 |
14 | function registerComponent (components) {
15 | for (let i = 0, len = components.length; i < len; i++) {
16 | const component = components[i]
17 | const name = component.name
18 | const official = component.official
19 | if (official) {
20 | logicComponentRegister(name, component)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/lone-logic-master/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-logic-master",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "lone-logic": "^0.0.0",
13 | "lone-messenger": "^0.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/lone-logic-worker/index.js:
--------------------------------------------------------------------------------
1 | import { Slave } from 'lone-messenger'
2 | import schedule from 'lone-logic/schedule'
3 |
4 | export const slave = new Slave({ env: 'worker', channel: 'logic-worker' })
5 | schedule(slave)
6 | slave.send('logic:inited')
7 |
8 | export { default } from 'lone-logic'
9 |
--------------------------------------------------------------------------------
/packages/lone-logic-worker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-logic-worker",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "lone-logic": "^0.0.0",
13 | "lone-messenger": "^0.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/lone-logic/component/events.js:
--------------------------------------------------------------------------------
1 | import { handleError, isArray } from 'lone-util'
2 |
3 | export default function events (Lone) {
4 | const proto = Lone.prototype
5 | proto.$on = on
6 | proto.$once = once
7 | proto.$off = off
8 | proto.$emit = emit
9 | }
10 |
11 | export function initEvents (vm) {
12 | const parentListeners = vm.$options.parentListeners
13 | let i = parentListeners.length
14 | while (i--) {
15 | const name = parentListeners[i]
16 | vm.$on(name, function (data) {
17 | vm._slave.send('component:triggerParentEvent', vm._id, { name, data })
18 | })
19 | }
20 | }
21 |
22 | function on (event, fn) {
23 | const vm = this
24 | if (isArray(event)) {
25 | for (let i = 0, len = event.length; i < len; i++) {
26 | vm.$on(event[i], fn)
27 | }
28 | } else {
29 | // eslint-disable-next-line
30 | (vm._events[event] || (vm._events[event] = [])).push(fn)
31 | }
32 | }
33 |
34 | function once (event, fn) {
35 | const vm = this
36 | function on () {
37 | vm.$off(event, on)
38 | fn.apply(vm, arguments)
39 | }
40 | on.fn = fn
41 | vm.$on(event, on)
42 | }
43 |
44 | function off (event, fn) {
45 | const vm = this
46 | if (!arguments.length) {
47 | vm._events = Object.create(null)
48 | return vm
49 | }
50 | if (isArray(event)) {
51 | for (let i = 0, len = event.length; i < len; i++) {
52 | vm.$off(event[i], fn)
53 | }
54 | return vm
55 | }
56 | const fns = vm._events[event]
57 | if (!fns) return vm
58 | if (arguments.length === 1) {
59 | vm._events[event] = null
60 | }
61 | if (fns) {
62 | let i = fns.length
63 | while (i--) {
64 | if (fns[i] === fn || fns[i].fn === fn) {
65 | fns.splice(i, 1)
66 | }
67 | }
68 | }
69 | return vm
70 | }
71 |
72 | function emit (event) {
73 | const vm = this
74 | const events = vm._events[event]
75 | const slice = Array.prototype.slice
76 | if (events) {
77 | const args = slice.call(arguments, 1)
78 | for (let i = 0, len = events.length; i < len; i++) {
79 | try {
80 | events[i].apply(vm, args)
81 | } catch (e) {
82 | handleError(e, vm, `event handler for "${event}"`)
83 | }
84 | }
85 | }
86 | return vm
87 | }
88 |
--------------------------------------------------------------------------------
/packages/lone-logic/component/helper.js:
--------------------------------------------------------------------------------
1 | import { LIFECYCLE_HOOKS } from 'lone-util/constants'
2 | import { warn, handleError, isArray, isPlainObject, camelize, toArray } from 'lone-util'
3 |
4 | export function initOptions (options) {
5 | normalizeHooks(options)
6 | normalizePropsData(options)
7 | return options
8 | }
9 |
10 | function normalizeHooks (options) {
11 | for (const key in options) {
12 | if (LIFECYCLE_HOOKS.includes(key)) {
13 | options[key] = isArray(options[key])
14 | ? options[key]
15 | : [options[key]]
16 | }
17 | }
18 | }
19 |
20 | function normalizePropsData (options) {
21 | const props = options.props
22 | const res = {}
23 | let i, val, name
24 | if (isArray(props)) {
25 | i = props.length
26 | while (i--) {
27 | val = props[i]
28 | if (typeof val === 'string') {
29 | name = camelize(val)
30 | res[name] = { type: null }
31 | } else if (process.env.NODE_ENV !== 'production') {
32 | warn('props must be strings when using array syntax.')
33 | }
34 | }
35 | } else if (isPlainObject(props)) {
36 | for (const key in props) {
37 | val = props[key]
38 | name = camelize(key)
39 | res[name] = isPlainObject(val)
40 | ? val
41 | : { type: val }
42 | }
43 | }
44 | options.props = res
45 | }
46 |
47 | export function sendInitCommandToPageComponent (vm) {
48 | const reservedWords = [...LIFECYCLE_HOOKS, 'data', 'methods', 'slave', 'name', 'propsData', 'parentListeners', 'props']
49 | vm._slave.send('component:inited', vm._id, {
50 | data: vm.data || {},
51 | methods: [...Object.keys(vm.$options).filter(key => !reservedWords.includes(key)), ...Object.keys(vm.$options.methods || {})]
52 | })
53 | vm._inited = true
54 | }
55 |
56 | export function triggerEvent (vm, method, event) {
57 | const handler = vm.$options[method] || vm.$options.methods[method]
58 | try {
59 | handler.call(vm, event)
60 | } catch (e) {
61 | handleError(e, vm, `"${method}" event handler`)
62 | }
63 | }
64 |
65 | export function callHook (vm, hook) {
66 | const handlers = vm.$options[hook]
67 | if (handlers) {
68 | for (let i = 0, j = handlers.length; i < j; i++) {
69 | try {
70 | handlers[i].apply(vm, toArray(arguments, 2))
71 | } catch (e) {
72 | handleError(e, vm, `${hook} hook`)
73 | }
74 | }
75 | }
76 | vm.$emit('hook:' + hook)
77 | }
78 |
--------------------------------------------------------------------------------
/packages/lone-logic/component/index.js:
--------------------------------------------------------------------------------
1 | import { initOptions, sendInitCommandToPageComponent, callHook } from './helper'
2 | import initData from './state'
3 | import events, { initEvents } from './events'
4 | import router from './router'
5 | import { notifyPropsObserver } from './observer'
6 | import { looseEqual } from 'lone-util'
7 |
8 | const init = Symbol('lone-logic:init')
9 |
10 | @events
11 | @router
12 | class LogicComponent {
13 | constructor (id, options) {
14 | const vm = this
15 | vm._id = id
16 | vm[init](options)
17 | }
18 |
19 | [init] (options) {
20 | const vm = this
21 | vm._events = Object.create(null)
22 | vm._inited = false
23 | vm.$options = initOptions(options)
24 | vm._slave = vm.$options.slave
25 | initEvents(vm)
26 | callHook(vm, 'beforeCreate')
27 | initData(vm)
28 | sendInitCommandToPageComponent(vm)
29 | callHook(vm, 'created')
30 | callHook(vm, 'onLoad', vm.$options.query)
31 | callHook(vm, 'onShow')
32 | }
33 |
34 | setData (data) {
35 | const oldData = this.data
36 | this.data = Object.assign({}, oldData, data)
37 | if (looseEqual(oldData, this.data)) return
38 | notifyPropsObserver(this, oldData, this.data)
39 | if (!this._inited) return
40 | this._slave.send('component:data', this._id, this.data)
41 | }
42 |
43 | $destroy () {
44 | const vm = this
45 | if (vm._isBeingDestroyed) return
46 | callHook(vm, 'beforeDestroy')
47 | vm._isBeingDestroyed = true
48 | vm.data = Object.create(null)
49 | vm.$off()
50 | vm._slave.send('component:destroy', this._id)
51 | }
52 | }
53 |
54 | export default LogicComponent
55 |
--------------------------------------------------------------------------------
/packages/lone-logic/component/observer.js:
--------------------------------------------------------------------------------
1 | export function notifyPropsObserver (vm, oldData, newData) {
2 | const propsOptions = vm.$options.props
3 | for (const key in newData) {
4 | if (key in propsOptions) {
5 | const cur = newData[key]
6 | const old = oldData[key]
7 | const observer = propsOptions[key].observer
8 | if (JSON.stringify(cur) !== JSON.stringify(old)) {
9 | observer && observer.call(vm, cur, old)
10 | }
11 | }
12 | }
13 | for (const key in oldData) {
14 | if (!(key in newData)) {
15 | const old = oldData[key]
16 | const observer = propsOptions[key].observer
17 | observer && observer.call(vm, null, old)
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/lone-logic/component/router.js:
--------------------------------------------------------------------------------
1 | import { noop } from 'lone-util'
2 |
3 | export default function events (Lone) {
4 | const proto = Lone.prototype
5 | proto.navigateTo = navigateTo
6 | proto.redirectTo = redirectTo
7 | proto.navigateBack = navigateBack
8 | }
9 |
10 | function navigateTo ({ url, success = noop, fail = noop, complete = noop }) {
11 | this._slave
12 | .send('logic:navigateTo', null, { url })
13 | .then(res => success(res), err => fail(err))
14 | .then(complete)
15 | }
16 |
17 | function redirectTo ({ url, success = noop, fail = noop, complete = noop }) {
18 | this._slave
19 | .send('logic:redirectTo', null, { url })
20 | .then(res => success(res), err => fail(err))
21 | .then(complete)
22 | }
23 |
24 | function navigateBack ({ delta, success = noop, fail = noop, complete = noop } = {}) {
25 | this._slave
26 | .send('logic:navigateBack', null, { delta })
27 | .then(res => success(res), err => fail(err))
28 | .then(complete)
29 | }
30 |
--------------------------------------------------------------------------------
/packages/lone-logic/component/state.js:
--------------------------------------------------------------------------------
1 | import { warn, handleError, isFunction, isArray, hyphenate } from 'lone-util'
2 |
3 | export default function initState (vm) {
4 | vm.data = Object.create(null)
5 | initProps(vm)
6 | initData(vm)
7 | }
8 |
9 | function initData (vm) {
10 | const rawData = vm.$options.data
11 | const data = isFunction(rawData)
12 | ? getData(rawData, vm)
13 | : rawData
14 | for (const name in data) {
15 | if (name in vm.data) {
16 | warn('"data.' + name + '": already exists, Props and data are not recommended to have the same name', vm)
17 | }
18 | if (!(name in vm.data)) {
19 | vm.data[name] = data[name]
20 | }
21 | }
22 | }
23 |
24 | function getData (data, vm) {
25 | try {
26 | return data.call(vm, vm)
27 | } catch (e) {
28 | handleError(e, vm, 'data()')
29 | return {}
30 | }
31 | }
32 |
33 | function initProps (vm) {
34 | const propsOptions = vm.$options.props
35 | if (!propsOptions) return
36 | for (const key in propsOptions) {
37 | const propsData = vm.$options.propsData
38 | const value = validateProp(key, propsOptions, propsData, vm)
39 | vm.data[key] = value
40 | }
41 | }
42 |
43 | function validateProp (key, propsOptions, propsData, vm) {
44 | const prop = propsOptions[key]
45 | const absent = !(key in propsData)
46 | let value = propsData[key]
47 | if (isType(Boolean, prop.type)) {
48 | if (absent && !('default' in prop)) { // absent = false
49 | value = false
50 | } else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) { // '' = true
51 | value = true
52 | }
53 | }
54 | // check default value
55 | if (value === undefined) {
56 | value = getPropDefaultValue(vm, prop, key)
57 | }
58 | assertProp(prop, key, value, vm, absent)
59 | return value
60 | }
61 |
62 | /**
63 | * Get the default value of a prop.
64 | */
65 | function getPropDefaultValue (vm, prop, key) {
66 | // no default, return undefined
67 | if (!('default' in prop)) {
68 | return undefined
69 | }
70 | const def = prop.default
71 | // call factory function for non-Function types
72 | // a value is Function if its prototype is function even across different execution context
73 | return typeof def === 'function' && getType(prop.type) !== 'Function'
74 | ? def.call(vm)
75 | : def
76 | }
77 |
78 | function assertProp (prop, name, value, vm, absent) {
79 | if (prop.required && absent) {
80 | warn('Missing required prop: "' + name + '"', vm)
81 | return undefined
82 | }
83 |
84 | if (value == null && !prop.required) {
85 | return undefined
86 | }
87 |
88 | let type = prop.type
89 | let valid = !type || type === true
90 | if (type) {
91 | if (!isArray(type)) {
92 | type = [type]
93 | }
94 | for (let i = 0; i < type.length && !valid; i++) {
95 | const toString = Object.prototype.toString
96 | valid = toString.call(value) === toString.call(type[i]())
97 | }
98 | }
99 | if (!valid) {
100 | warn(
101 | `Invalid prop: type check failed for prop "${name}".`,
102 | vm
103 | )
104 | return undefined
105 | }
106 | const validator = prop.validator
107 | if (validator) {
108 | if (!validator(value)) {
109 | warn(
110 | 'Invalid prop: custom validator check failed for prop "' + name + '".',
111 | vm
112 | )
113 | }
114 | }
115 | }
116 |
117 | /**
118 | * Use function string name to check built-in types,
119 | * because a simple equality check will fail when running
120 | * across different vms / iframes.
121 | */
122 | function getType (fn) {
123 | const match = fn && fn.toString().match(/^\s*function (\w+)/)
124 | return match ? match[1] : ''
125 | }
126 |
127 | function isType (type, fn) {
128 | if (!Array.isArray(fn)) {
129 | return getType(fn) === getType(type)
130 | }
131 | for (let i = 0, len = fn.length; i < len; i++) {
132 | if (getType(fn[i]) === getType(type)) {
133 | return true
134 | }
135 | }
136 | return false
137 | }
138 |
--------------------------------------------------------------------------------
/packages/lone-logic/index.js:
--------------------------------------------------------------------------------
1 | import LogicComponent from './component'
2 |
3 | const componentStorage = new Map()
4 |
5 | export default function Component (name, options = {}) {
6 | componentStorage.set(name, options)
7 | }
8 |
9 | export function createComponentInstance (name, id, otherOptions) {
10 | const options = Object.assign(
11 | componentStorage.get(name),
12 | otherOptions
13 | )
14 | options.name = name
15 | return new LogicComponent(id, options)
16 | }
17 |
--------------------------------------------------------------------------------
/packages/lone-logic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-logic",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "license": "ISC",
9 | "dependencies": {
10 | "lone-util": "^0.0.0"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/lone-logic/schedule.js:
--------------------------------------------------------------------------------
1 | import { triggerEvent, callHook } from './component/helper'
2 | import { createComponentInstance } from './index'
3 | import { parseSearch } from 'lone-util/url'
4 |
5 | export default function (slave) {
6 | const instanceStorage = new Map()
7 |
8 | const MESSENGER_EVENTS_UI = {
9 | 'page:inited': function ({ name, id, propsData, parentListeners, search }) {
10 | const vm = createComponentInstance(name, id, { propsData, parentListeners, slave, query: parseSearch(search) })
11 | instanceStorage.set(id, vm)
12 | },
13 | 'page:ready': function ({ id }) {
14 | const vm = instanceStorage.get(id)
15 | callHook(vm, 'onReady')
16 | callHook(vm, 'mounted')
17 | },
18 | 'page:triggerEvent': function ({ id, method, event }) {
19 | const vm = instanceStorage.get(id)
20 | triggerEvent(vm, method, event)
21 | },
22 | 'page:data': function ({ id, data }) {
23 | const vm = instanceStorage.get(id)
24 | vm.setData(data)
25 | },
26 | 'page:beforeMount': function ({ id }) {
27 | const vm = instanceStorage.get(id)
28 | callHook(vm, 'beforeMount')
29 | },
30 | 'page:beforeUpdate': function ({ id }) {
31 | const vm = instanceStorage.get(id)
32 | callHook(vm, 'beforeUpdate')
33 | },
34 | 'page:updated': function ({ id }) {
35 | const vm = instanceStorage.get(id)
36 | callHook(vm, 'updated')
37 | },
38 | 'page:show': function ({ id }) {
39 | const vm = instanceStorage.get(id)
40 | callHook(vm, 'onShow')
41 | },
42 | 'page:hide': function ({ id }) {
43 | const vm = instanceStorage.get(id)
44 | callHook(vm, 'onHide')
45 | },
46 | 'page:destroyed': function ({ id }) {
47 | const vm = instanceStorage.get(id)
48 | callHook(vm, 'destroyed')
49 | instanceStorage.delete(id)
50 | }
51 | }
52 |
53 | for (const [event, fn] of Object.entries(MESSENGER_EVENTS_UI)) {
54 | slave.onmessage(event, fn)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/packages/lone-messenger/README.md:
--------------------------------------------------------------------------------
1 | # Messenger
2 |
3 | ## Install
4 |
5 | ```
6 | $ lerna add lone-messenger --scope=YourModule
7 | ```
8 |
9 | ## Usage
10 |
11 | ### Master
12 |
13 | Create a Master `messenger`:
14 |
15 | ```javascript
16 | import { Master } from 'lone-messenger'
17 |
18 | const master = new Master({
19 | env: 'postMessage'
20 | })
21 |
22 | master.onmessage('customType', function (channel, data) {
23 | console.log(data)
24 | })
25 |
26 | master.send('customType', 'channel', {name: 'Berwin'})
27 | ```
28 |
29 | #### Options
30 |
31 | * mid - 在多个小程序并存时,用 mid 区分信号
32 | * env - Switch PostMessage and NativeMessage, default is NativeMessage
33 |
34 | #### Methods
35 |
36 | * onmessage(type, fn)
37 | * type [String]
38 | * fn(data) data type is Object, 可以在该函数可以返回一个Promise,messenger内部会根据promise的状态发送反馈信息
39 | * send(type, channel, data)
40 | * type [String]
41 | * channel [String]
42 | * data [Object]
43 |
44 | ### Slave
45 |
46 | Create a Slave `messenger`:
47 |
48 | ```javascript
49 | import { Slave } from 'seapp-shared/messenger'
50 |
51 | const slave = new Slave({
52 | env: 'postMessage',
53 | channel: 'logic'
54 | })
55 |
56 | slave.onmessage('customType', function (data) {
57 | console.log(data)
58 | })
59 |
60 | slave.send('customType', 'channel', {name: 'Berwin'})
61 | ```
62 | #### Options
63 |
64 | * env - Switch PostMessage and NativeMessage, default is NativeMessage
65 | * channel - 设置自己的频道号
66 |
67 | #### Methods
68 |
69 | * onmessage(type, fn)
70 | * type [String]
71 | * fn(data) data type is Object, 可以在该函数可以返回一个Promise,messenger内部会根据promise的状态发送反馈信息
72 | * send(type, channel, data)
73 | * type [String]
74 | * channel [String]
75 | * data [Object]
76 |
77 | **slave.send**
78 |
79 | 该函数返回Promise,可以得到信号的反馈信息,已得知某项事情的状态(成功|失败)。
80 |
81 | ```javascript
82 | slave.send('customType', 'channel', {name: 'Berwin'}).then(res => {
83 | console.log('成功:', res)
84 | }).catch(err => {
85 | console.log('失败:', err)
86 | })
87 | ```
88 |
89 | **slave.onmessage(type, fn)**
90 |
91 | `fn` 可以返回一个Promise,messenger内部会根据promise的状态发送反馈信息。
92 |
93 | ```javascript
94 | slave.onmessage('customType', function (data) {
95 | return Promise.reject('事情做失败了')
96 | })
97 | ```
98 | 上面代码将会发送失败的反馈信号到`slave.send`处。
99 |
--------------------------------------------------------------------------------
/packages/lone-messenger/index.js:
--------------------------------------------------------------------------------
1 | import Master from './master'
2 | import Slave from './slave'
3 |
4 | export {
5 | Master,
6 | Slave
7 | }
8 |
--------------------------------------------------------------------------------
/packages/lone-messenger/master/index.js:
--------------------------------------------------------------------------------
1 | import Native from './native-messenger'
2 | import Post from './post-messenger'
3 | import WebWorker from './worker-messenger'
4 | import { feedbackType } from 'lone-util'
5 |
6 | const connection = Symbol('messenger:master#connection')
7 |
8 | export default class Master {
9 | constructor (options) {
10 | this._messages = Object.create(null)
11 | this.options = options
12 | this.native = new Native(options)
13 | this.post = new Post(options)
14 | this.worker = new WebWorker(options)
15 | this[connection]()
16 | this.listen()
17 | }
18 |
19 | [connection] () {
20 | if (this.options.env === 'native') this.native.connection()
21 | if (this.options.env === 'worker') this.worker.connection()
22 | this.post.connection()
23 | }
24 |
25 | onmessage (type, fn) {
26 | (this._messages[type] || (this._messages[type] = [])).push(fn)
27 | }
28 |
29 | send (type, targetChannel, data) {
30 | this._postMessage(type, null, data, targetChannel)
31 | }
32 |
33 | listen () {
34 | this._onmessage(evt => {
35 | // 如果是中转信号,则直接转发至Slave(存在targetChannel的为中转信号,没有则表示发送给Master的信号)
36 | if (evt.targetChannel) return this._postMessage(evt.type, evt.targetChannel, evt.data, evt.channel)
37 | const cbs = this._messages[evt.type]
38 | if (!cbs) return
39 | let i = cbs.length
40 | while (i--) {
41 | Promise.resolve(cbs[i].call(evt, evt.channel, evt.data))
42 | .then(
43 | data => this._postMessage(feedbackType(evt.type), evt.channel, { data }),
44 | err => this._postMessage(feedbackType(evt.type), evt.channel, { err })
45 | )
46 | }
47 | })
48 | }
49 |
50 | _onmessage (fn) {
51 | if (this.options.env === 'native') this.native.onmessage(fn)
52 | if (this.options.env === 'worker') this.worker.onmessage(fn)
53 | this.post.onmessage(fn)
54 | }
55 |
56 | _postMessage (type, targetChannel, data, channel) {
57 | // Only developer component logic running under the sandbox
58 | if (targetChannel === 'logic-worker') {
59 | if (this.options.env === 'native') return this.native.send(type, data, channel)
60 | if (this.options.env === 'worker') return this.worker.send(type, data, channel)
61 | }
62 | return this.post.send(type, targetChannel, data, channel)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/packages/lone-messenger/master/native-messenger.js:
--------------------------------------------------------------------------------
1 | import { isObject } from 'lone-util'
2 |
3 | class NativeMessenger {
4 | connection () {
5 | window.senative.call('frontPageReady', '', function (code, msg, data) {})
6 | }
7 |
8 | send (type, data, channel) {
9 | if (!isObject(data)) throw new TypeError('data must be plain object.')
10 | const bag = JSON.stringify({ type, data, channel })
11 | window.senative.call('sendMessage', bag, (code, data, msg) => {})
12 | }
13 |
14 | onmessage (fn) {
15 | window.onSeNativeMessage = function (rawData) {
16 | const data = JSON.parse(rawData)
17 | fn(data)
18 | }
19 | }
20 | }
21 |
22 | export default NativeMessenger
23 |
--------------------------------------------------------------------------------
/packages/lone-messenger/master/post-messenger.js:
--------------------------------------------------------------------------------
1 | const source = Symbol('messenger:master#connection')
2 |
3 | class PostMessenger {
4 | constructor (options) {
5 | this[source] = Object.create(null)
6 | this.mid = options.mid
7 | this.midRe = new RegExp('^' + this.mid)
8 | }
9 |
10 | connection () {
11 | const vm = this
12 | vm.onmessage(function ({ type, channel }) {
13 | if (type === 'connection') {
14 | vm[source][channel] = this.source
15 | }
16 | })
17 | }
18 |
19 | onmessage (fn) {
20 | const vm = this
21 | window.addEventListener('message', function (evt) {
22 | if (evt.origin !== location.origin || !vm.midRe.test(evt.data.channel)) return
23 | fn.call(evt, evt.data)
24 | })
25 | }
26 |
27 | send (type, targetChannel, data, channel) {
28 | const slave = this[source][targetChannel]
29 | if (!slave) throw new Error('No Slave Source, please connection first!')
30 | slave.postMessage({ type, targetChannel, data, channel }, slave.origin)
31 | }
32 | }
33 |
34 | export default PostMessenger
35 |
--------------------------------------------------------------------------------
/packages/lone-messenger/master/worker-messenger.js:
--------------------------------------------------------------------------------
1 | class WorkerMessenger {
2 | constructor (options) {
3 | this.worker = options.worker
4 | }
5 |
6 | connection () {
7 | this.source = this.worker
8 | }
9 |
10 | onmessage (fn) {
11 | this.source.onmessage = function (evt) {
12 | fn.call(evt, evt.data)
13 | }
14 | }
15 |
16 | send (type, data, channel) {
17 | this.source.postMessage({ type, data, channel })
18 | }
19 | }
20 |
21 | export default WorkerMessenger
22 |
--------------------------------------------------------------------------------
/packages/lone-messenger/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-messenger",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "license": "ISC",
9 | "dependencies": {
10 | "lone-util": "^0.0.0"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/lone-messenger/slave/base.js:
--------------------------------------------------------------------------------
1 | import { feedbackType } from 'lone-util'
2 |
3 | class Messenger {
4 | constructor () {
5 | this._messages = Object.create(null)
6 | }
7 |
8 | onmessage (type, fn) {
9 | (this._messages[type] || (this._messages[type] = [])).push(fn)
10 | }
11 |
12 | send (type, channel, data) {
13 | const vm = this
14 | return new Promise(function (resolve, reject) {
15 | vm._postMessage(type, channel, data)
16 | vm.onmessage(feedbackType(type), function ({ err, data }) {
17 | vm._messages[feedbackType(type)] = []
18 | return err
19 | ? reject(err)
20 | : resolve(data)
21 | })
22 | })
23 | }
24 |
25 | listen () {
26 | const vm = this
27 | vm._onmessage(evt => {
28 | const cbs = vm._messages[evt.type]
29 | if (!cbs) return
30 | let i = cbs.length
31 | while (i--) {
32 | Promise.resolve(cbs[i].call(evt, evt.data))
33 | .then(
34 | data => vm._postMessage(feedbackType(evt.type), evt.channel, { data }),
35 | err => vm._postMessage(feedbackType(evt.type), evt.channel, { err })
36 | )
37 | }
38 | })
39 | }
40 |
41 | _postMessage () {
42 | throw new TypeError('Subclass of Messenger doesn\'t provide the \'_postMessage\' method.')
43 | }
44 |
45 | _onmessage () {
46 | throw new TypeError('Subclass of Messenger doesn\'t provide the \'_onmessage\' method.')
47 | }
48 | }
49 |
50 | export default Messenger
51 |
--------------------------------------------------------------------------------
/packages/lone-messenger/slave/index.js:
--------------------------------------------------------------------------------
1 | import NativeMessenger from './native-messenger'
2 | import PostMessenger from './post-messenger'
3 | import WorkerMessenger from './worker-messenger'
4 |
5 | const slaveMap = {
6 | postMessage: PostMessenger,
7 | native: NativeMessenger,
8 | worker: WorkerMessenger
9 | }
10 |
11 | export default function (options) {
12 | return new slaveMap[options.env](options)
13 | }
14 |
--------------------------------------------------------------------------------
/packages/lone-messenger/slave/native-messenger.js:
--------------------------------------------------------------------------------
1 | // 该文件当前不可用,因为没有 Native 消息通道
2 | import Messenger from './base'
3 | import { isObject } from 'lone-util'
4 |
5 | class NativeMessenger extends Messenger {
6 | constructor () {
7 | super()
8 | this.listen()
9 | }
10 |
11 | _onmessage (fn) {
12 | window.onSeNativeMessage = function (rawData) {
13 | const data = JSON.parse(rawData)
14 | fn(data)
15 | }
16 | }
17 |
18 | _postMessage (type, channel, data) {
19 | if (!isObject(data)) throw new TypeError('data must be plain object.')
20 | const bag = JSON.stringify({ type, channel, data })
21 | window.senative.call('sendMessage', bag, (code, data, msg) => {})
22 | }
23 | }
24 |
25 | export default NativeMessenger
26 |
--------------------------------------------------------------------------------
/packages/lone-messenger/slave/post-messenger.js:
--------------------------------------------------------------------------------
1 | import BaseMessenger from './base'
2 |
3 | const connection = Symbol('messenger:slave#connection')
4 |
5 | class PostMessenger extends BaseMessenger {
6 | constructor (options) {
7 | super()
8 | this.channel = options.channel
9 | this.listen()
10 | this[connection]()
11 | }
12 |
13 | [connection] () {
14 | this._postMessage('connection')
15 | }
16 |
17 | _onmessage (fn) {
18 | const vm = this
19 | window.addEventListener('message', function (evt) {
20 | if (evt.data.targetChannel === vm.channel) {
21 | fn.call(evt, evt.data)
22 | }
23 | })
24 | }
25 |
26 | _postMessage (type, targetChannel, data) {
27 | const slave = window.parent
28 | slave.postMessage({ type, channel: this.channel, targetChannel, data }, slave.origin)
29 | }
30 | }
31 |
32 | export default PostMessenger
33 |
--------------------------------------------------------------------------------
/packages/lone-messenger/slave/worker-messenger.js:
--------------------------------------------------------------------------------
1 | import Messenger from './base'
2 |
3 | class WorkerMessenger extends Messenger {
4 | constructor (options) {
5 | super()
6 | this.channel = options.channel
7 | this.listen()
8 | }
9 |
10 | _postMessage (type, targetChannel, data) {
11 | self.postMessage({ type, channel: this.channel, targetChannel, data })
12 | }
13 |
14 | _onmessage (fn) {
15 | self.onmessage = function (evt) {
16 | fn.call(evt, evt.data)
17 | }
18 | }
19 | }
20 |
21 | export default WorkerMessenger
22 |
--------------------------------------------------------------------------------
/packages/lone-page/component/eventListener.js:
--------------------------------------------------------------------------------
1 | import { proxy } from 'lone-util'
2 |
3 | const customType = 'custom'
4 |
5 | export function initParentListener (vm) {
6 | vm._parentListeners = Object.create(null)
7 | const listeners = vm.options._parentListeners
8 | if (listeners) {
9 | vm._parentListeners = listeners
10 | }
11 | vm.slave.onmessage('component:triggerParentEvent', function ({ name, data }) {
12 | vm._parentListeners[name]({ type: customType, data })
13 | })
14 | }
15 |
16 | export function initEventListener (vm, methods) {
17 | vm._eventListener = Object.create(null)
18 | let i = methods.length
19 | while (i--) {
20 | const method = methods[i]
21 | vm._eventListener[method] = function (event) {
22 | vm.slave.send('page:triggerEvent', vm.getLogicChannel(), {
23 | id: vm.id,
24 | event: event.type === customType
25 | ? event.data
26 | : getEvent(event),
27 | method
28 | })
29 | }
30 | proxy(vm, '_eventListener', method)
31 | }
32 | }
33 |
34 | function getEvent (event) {
35 | // Custom parameters
36 | if (!event.type && !event.timeStamp && !event.target) return event
37 | return {
38 | type: event.type,
39 | timeStamp: event.timeStamp,
40 | target: {
41 | value: event.target.value
42 | },
43 | detail: {
44 | x: event.x,
45 | y: event.y
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/lone-page/component/index.js:
--------------------------------------------------------------------------------
1 | import { installRenderHelpers } from 'lone-virtualdom/render-helpers'
2 | import init from './init'
3 |
4 | @installRenderHelpers
5 | @init
6 | class Component {
7 | constructor (options) {
8 | this.init(options)
9 | }
10 |
11 | static setGlobalOptions (options) {
12 | this.options = options
13 | }
14 |
15 | getLogicChannel () {
16 | return this.$official ? `${this.mid}_${this.pid}_logic` : 'logic-worker'
17 | }
18 | }
19 |
20 | export default Component
21 |
--------------------------------------------------------------------------------
/packages/lone-page/component/init.js:
--------------------------------------------------------------------------------
1 | import { Slave } from 'lone-messenger'
2 | import { compileToFunctions } from 'lone-compiler-dom'
3 | import { patch } from 'lone-virtualdom'
4 | import { resolveSlots } from './slot'
5 | import { proxy } from 'lone-util'
6 | import {
7 | initParentListener,
8 | initEventListener
9 | } from './eventListener'
10 |
11 | let cid = 0
12 |
13 | export default function init (Component) {
14 | const proto = Component.prototype
15 | proto.init = function (options) {
16 | const vm = this
17 | initLifecycle(vm)
18 | initOptions(vm, options, Component)
19 | initMessenger(vm)
20 | initParentListener(vm)
21 | initRender(vm)
22 | reaction(vm)
23 | listenVisibilityChange(vm)
24 | listenDestroy(vm)
25 | vm.callHook('page:inited', {
26 | propsData: vm.propsData,
27 | parentListeners: Object.keys(vm._parentListeners),
28 | search: vm.search
29 | })
30 | }
31 |
32 | proto._setData = function (data) {
33 | const vm = this
34 | vm._data = data
35 | const keys = Object.keys(data)
36 | let i = keys.length
37 | while (i--) {
38 | const key = keys[i]
39 | proxy(vm, '_data', key)
40 | }
41 | const vnode = vm._render()
42 | vm._update(vnode)
43 | }
44 |
45 | proto._render = function () {
46 | const vm = this
47 | const render = this.options.render
48 | let vnode
49 | try {
50 | vnode = render.call(this)
51 | } catch (e) {
52 | console.error(e)
53 | vnode = vm._vnode
54 | }
55 | return vnode
56 | }
57 |
58 | proto._update = function (vnode) {
59 | const vm = this
60 | const oldVnode = this._vnode || this.options.el
61 | this._vnode = vnode
62 | if (vm._isMounted && !vm._isDestroyed) {
63 | vm.callHook('page:beforeUpdate')
64 | }
65 | patch(oldVnode, this._vnode)
66 | if (vm._isMounted && !vm._isDestroyed) {
67 | vm.callHook('page:updated')
68 | }
69 | }
70 |
71 | proto.callHook = function (hook, rest = {}) {
72 | const vm = this
73 | vm.slave.send(hook, vm.getLogicChannel(), { name: vm.name, id: vm.id, ...rest })
74 | }
75 |
76 | proto.updatePropsData = function (data) {
77 | const vm = this
78 | vm.propsData = data
79 | vm.slave.send('page:data', vm.getLogicChannel(), { id: vm.id, data })
80 | }
81 | }
82 |
83 | function initLifecycle (vm) {
84 | vm._isMounted = false
85 | vm._isDestroyed = false
86 | }
87 |
88 | function initOptions (vm, options, Component) {
89 | vm.options = options
90 | vm.mid = Component.options.mid
91 | vm.pid = Component.options.pid
92 | vm.cid = cid++
93 | vm.id = vm.mid + '_' + vm.pid + '_' + vm.cid
94 | vm.pathname = Component.options.pathname
95 | vm.search = Component.options.search
96 | const config = Component.options.components.find(item => item.name === vm.options.name)
97 | vm.name = config.name
98 | vm.template = config.template
99 | vm.propsData = options.propsData || {}
100 | vm.$slots = resolveSlots(options._renderChildren)
101 | vm.$official = !!config.official
102 | }
103 |
104 | function initMessenger (vm) {
105 | vm.slave = new Slave({ env: 'postMessage', channel: vm.id })
106 |
107 | vm.slave.onmessage('component:inited', function ({ data: initData = {}, methods = [] }) {
108 | initEventListener(vm, methods)
109 | vm.callHook('page:beforeMount')
110 | vm._setData(initData)
111 | vm._isMounted = true
112 | vm.callHook('page:ready')
113 | })
114 | }
115 |
116 | function initRender (vm) {
117 | const { render } = compileToFunctions(vm.template, undefined, vm)
118 | vm.options.render = render
119 | }
120 |
121 | function reaction (vm) {
122 | vm.slave.onmessage('component:data', function (data) {
123 | vm._setData(data)
124 | })
125 | }
126 |
127 | function listenVisibilityChange (vm) {
128 | const data = { pid: vm.pid }
129 | vm._onHide = _ => vm.callHook('page:hide', data)
130 | vm._onShow = _ => vm.callHook('page:show', data)
131 | document.addEventListener('onHide', vm._onHide)
132 | document.addEventListener('onShow', vm._onShow)
133 | }
134 |
135 | function listenDestroy (vm) {
136 | vm.slave.onmessage('component:destroy', function () {
137 | unlistenVisibilityChange(vm)
138 | patch((vm._vnode || vm.options.el), vm._c('!'))
139 | vm.callHook('page:destroyed')
140 | })
141 | }
142 |
143 | function unlistenVisibilityChange (vm) {
144 | document.removeEventListener('onHide', vm._onHide)
145 | document.removeEventListener('onShow', vm._onShow)
146 | }
147 |
--------------------------------------------------------------------------------
/packages/lone-page/component/slot.js:
--------------------------------------------------------------------------------
1 | export function resolveSlots (children) {
2 | const slots = {}
3 | if (!children) {
4 | return slots
5 | }
6 | const defaultSlot = []
7 | for (let i = 0, l = children.length; i < l; i++) {
8 | const child = children[i]
9 | const data = child.data
10 | // remove slot attribute if the node is resolved as a Vue slot node
11 | if (data && data.attrs && data.attrs.slot) {
12 | delete data.attrs.slot
13 | }
14 | if (data && data.slot != null) {
15 | const name = child.data.slot
16 | const slot = (slots[name] || (slots[name] = []))
17 | slot.push(child)
18 | } else {
19 | defaultSlot.push(child)
20 | }
21 | }
22 | // ignore whitespace
23 | if (!defaultSlot.every(isWhitespace)) {
24 | slots.default = defaultSlot
25 | }
26 | return slots
27 | }
28 |
29 | function isWhitespace (node) {
30 | return node.text === ' '
31 | }
32 |
--------------------------------------------------------------------------------
/packages/lone-page/index.js:
--------------------------------------------------------------------------------
1 | import logicMaster from 'lone-logic-master'
2 | import Component from './component'
3 |
4 | export default function (options) {
5 | const mid = window.frameElement.getAttribute('mid')
6 | const pid = window.frameElement.id
7 | const pathname = window.frameElement.getAttribute('pathname')
8 | const search = window.frameElement.getAttribute('search')
9 | const name = window.frameElement.getAttribute('component')
10 | Component.setGlobalOptions({ ...options, mid, pid, pathname, search })
11 | logicMaster(Component.options)
12 | return new Component({ name, el: document.getElementById('app') })
13 | }
14 |
--------------------------------------------------------------------------------
/packages/lone-page/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-page",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "license": "ISC",
9 | "dependencies": {
10 | "lone-compiler-dom": "^0.0.0",
11 | "lone-logic-master": "^0.0.0",
12 | "lone-messenger": "^0.0.0",
13 | "lone-util": "^0.0.0",
14 | "lone-virtualdom": "^0.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/lone-ui/index.js:
--------------------------------------------------------------------------------
1 | import Schedule from './schedule'
2 | import Router from './router'
3 |
4 | let id = 0
5 |
6 | class LoneUI {
7 | constructor (options) {
8 | this.options = options
9 | // ‘mid’, a shortened form of a Miniapp ID
10 | this.mid = options.mid || (Date.now() + '_' + id++)
11 | this.router = new Router({
12 | routes: this.options.routes,
13 | entry: this.options.entry,
14 | container: this.options.container || document.body,
15 | mid: this.mid
16 | })
17 | this.schedule = new Schedule({
18 | router: this.router,
19 | entry: this.options.entry,
20 | mid: this.mid
21 | })
22 | }
23 | }
24 |
25 | export default function (options) {
26 | return new LoneUI(options)
27 | }
28 |
--------------------------------------------------------------------------------
/packages/lone-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-ui",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "license": "ISC",
9 | "dependencies": {
10 | "lone-messenger": "^0.0.0",
11 | "lone-util": "^0.0.0"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/lone-ui/page.js:
--------------------------------------------------------------------------------
1 | let pid = 0
2 |
3 | export function createPage (route, { container, entry, mid, zIndex }) {
4 | const id = pid++
5 | const view = document.createElement('iframe')
6 | setAttr(id, view, { ...route, mid })
7 | setStyle(view, container, zIndex)
8 | container.appendChild(view)
9 | insertJS(view, entry)
10 | return view
11 | }
12 |
13 | export function removePage (container, page) {
14 | container.removeChild(page)
15 | }
16 |
17 | function setAttr (id, view, attrs) {
18 | view.id = id
19 | for (const [key, val] of Object.entries(attrs)) {
20 | view.setAttribute(key, val)
21 | }
22 | }
23 |
24 | function setStyle (view, container, zIndex) {
25 | const box = container.tagName === 'BODY'
26 | ? document.documentElement
27 | : container
28 | view.style.width = box.clientWidth + 'px'
29 | view.style.height = box.clientHeight + 'px'
30 | view.style.position = 'fixed'
31 | view.style.border = 'none'
32 | view.style.zIndex = zIndex
33 | view.style.backgroundColor = 'white'
34 | }
35 |
36 | function insertJS (view, entry) {
37 | const scriptTag = [insertContainer, insertPageJS, insertUserJS].reduce((pre, gen) => pre + gen(entry), '')
38 | view.contentDocument.write(`
39 |
40 |
41 |
42 |
43 |
44 |
45 |
Document
46 |
47 |
48 | ${scriptTag}
49 |
50 | `)
51 | }
52 |
53 | function insertPageJS () {
54 | return '' // eslint-disable-line
55 | }
56 |
57 | function insertUserJS (entry) {
58 | return ''
59 | }
60 |
61 | function insertContainer () {
62 | return '
'
63 | }
64 |
--------------------------------------------------------------------------------
/packages/lone-ui/router.js:
--------------------------------------------------------------------------------
1 | import { createPage, removePage } from './page'
2 | import { parse } from 'lone-util/url'
3 | import { query } from 'lone-util/web'
4 |
5 | const getRoute = Symbol('getRoute')
6 |
7 | class Router {
8 | constructor (options) {
9 | this.stack = []
10 | this.routes = options.routes
11 | this.entry = options.entry
12 | this.container = query(options.container)
13 | this.mid = options.mid
14 | }
15 |
16 | [getRoute] (url) {
17 | const { pathname } = parse(url)
18 | return this.routes.find(item => {
19 | return item.path === pathname
20 | })
21 | }
22 |
23 | currentPage () {
24 | return this.stack[this.stack.length - 1] || null
25 | }
26 |
27 | currentPages () {
28 | return this.stack
29 | }
30 |
31 | triggerCurrentPageHideHook () {
32 | const currentPage = this.currentPage()
33 | if (currentPage) {
34 | var hidechange = new Event('onHide')
35 | currentPage.contentDocument.dispatchEvent(hidechange)
36 | }
37 | }
38 |
39 | triggerCurrentPageShowHook () {
40 | const currentPage = this.currentPage()
41 | if (currentPage) {
42 | var showchange = new Event('onShow')
43 | currentPage.contentDocument.dispatchEvent(showchange)
44 | }
45 | }
46 |
47 | _push (url) {
48 | const route = this[getRoute](url)
49 | if (!route) throw new Error(`跳转到不存在的路由地址:${url}`)
50 | this.triggerCurrentPageHideHook() // 路由切换之前 执行当前iframe的onHide
51 | const { component } = route
52 | const { pathname, search } = parse(url)
53 | const view = createPage({
54 | component,
55 | pathname,
56 | search
57 | }, {
58 | entry: this.entry.page,
59 | container: this.container,
60 | mid: this.mid,
61 | zIndex: this.stack.length
62 | })
63 | this.stack.push(view)
64 | return view
65 | }
66 |
67 | _pop () {
68 | const oldView = this.stack.pop()
69 | removePage(this.container, oldView)
70 | this.triggerCurrentPageShowHook()
71 | }
72 |
73 | navigateTo (url) {
74 | return new Promise((resolve, reject) => {
75 | try {
76 | this._push(url)
77 | resolve()
78 | } catch (err) {
79 | reject(err)
80 | console.error(err)
81 | }
82 | })
83 | }
84 |
85 | redirectTo (url) {
86 | return new Promise((resolve, reject) => {
87 | try {
88 | this._pop()
89 | this._push(url)
90 | resolve()
91 | } catch (err) {
92 | reject(err)
93 | console.error(err)
94 | }
95 | })
96 | }
97 |
98 | // 如果 delta 大于现有页面数,则返回到首页。
99 | navigateBack (delta = 1) {
100 | return new Promise((resolve, reject) => {
101 | try {
102 | const len = this.stack.length
103 | if (delta >= len) delta = (len - 1)
104 | while (delta--) {
105 | this._pop()
106 | }
107 | resolve()
108 | } catch (err) {
109 | reject(err)
110 | console.error(err)
111 | }
112 | })
113 | }
114 | }
115 |
116 | export default Router
117 |
--------------------------------------------------------------------------------
/packages/lone-ui/schedule.js:
--------------------------------------------------------------------------------
1 | import { Master } from 'lone-messenger'
2 |
3 | class Schedule {
4 | constructor ({ router, entry, mid }) {
5 | const vm = this
6 | vm.router = router
7 | vm.entry = entry
8 | this.master = new Master({
9 | mid: mid,
10 | env: 'worker',
11 | worker: new Worker(vm.entry.logic)
12 | })
13 | vm.logicEvents = {
14 | 'logic:inited': function () {
15 | // Default Route Page
16 | vm.router.navigateTo(vm.router.routes[0].path)
17 | },
18 | 'logic:navigateTo': function (channel, { url }) {
19 | return vm.router.navigateTo(url)
20 | },
21 | 'logic:redirectTo': function (channel, { url }) {
22 | return vm.router.redirectTo(url)
23 | },
24 | 'logic:navigateBack': function (channel, { delta }) {
25 | return vm.router.navigateBack(delta)
26 | }
27 | }
28 | vm.pageEvents = {
29 | 'page:navigateTo': function () {
30 | console.log('ui-schedule: view:navigateTo')
31 | }
32 | }
33 | vm.init()
34 | vm.listenVisibilityChange(vm.router)
35 | }
36 |
37 | init () {
38 | this.listenEvents(this.master, this.logicEvents)
39 | this.listenEvents(this.master, this.pageEvents)
40 | }
41 |
42 | listenEvents (messenger, events) {
43 | for (const [event, fn] of Object.entries(events)) {
44 | messenger.onmessage(event, fn)
45 | }
46 | }
47 |
48 | listenVisibilityChange (router) {
49 | document.addEventListener('visibilitychange', function () {
50 | if (document.visibilityState === 'visible') {
51 | router.triggerCurrentPageShowHook()
52 | } else {
53 | router.triggerCurrentPageHideHook()
54 | }
55 | })
56 | }
57 | }
58 |
59 | export default Schedule
60 |
--------------------------------------------------------------------------------
/packages/lone-util/constants.js:
--------------------------------------------------------------------------------
1 | export const LIFECYCLE_HOOKS = [
2 | 'beforeCreate',
3 | 'created',
4 | 'beforeMount',
5 | 'mounted',
6 | 'beforeUpdate',
7 | 'updated',
8 | 'beforeDestroy',
9 | 'destroyed',
10 | 'activated',
11 | 'deactivated',
12 | 'errorCaptured',
13 | 'onHide',
14 | 'onLoad',
15 | 'onReady',
16 | 'onShow',
17 | 'onUnload'
18 | ]
19 |
--------------------------------------------------------------------------------
/packages/lone-util/env.js:
--------------------------------------------------------------------------------
1 | // Browser environment sniffing
2 | export const inBrowser = typeof window !== 'undefined'
3 | export const inWorker = typeof self !== 'undefined'
4 | export const UA = inBrowser && window.navigator.userAgent.toLowerCase()
5 | export const isIE = UA && /msie|trident/.test(UA)
6 | export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
7 | export const isEdge = UA && UA.indexOf('edge/') > 0
8 | export const isAndroid = UA && UA.indexOf('android') > 0
9 | export const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
10 | export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
11 |
--------------------------------------------------------------------------------
/packages/lone-util/error.js:
--------------------------------------------------------------------------------
1 | export function handleError (err, vm, info) {
2 | warn(`Error in ${info}: "${err.toString()}"`, vm, err)
3 | }
4 |
5 | const classifyRE = /(?:^|[-_])(\w)/g
6 | const classify = str =>
7 | str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
8 |
9 | export function warn (msg, vm, err) {
10 | const trace = vm ? generateComponentTrace(vm) : ''
11 | const hasErr = err
12 |
13 | err = err || { toString () { return this.name + this.message } }
14 | err.name = '[Lone warn]'
15 | err.message = ` ${msg}${trace}`
16 |
17 | console.error(hasErr ? err : String(err))
18 | }
19 |
20 | export function tip (msg, vm) {
21 | const trace = vm ? generateComponentTrace(vm) : ''
22 | console.warn(`[Lone tip]: ${msg}${trace}`)
23 | }
24 |
25 | export function formatComponentName (vm) {
26 | const name = vm.$options && vm.$options.name
27 | return (name ? `<${classify(name)}>` : '
')
28 | }
29 |
30 | export function generateComponentTrace (vm) {
31 | return `\n\n(found in ${formatComponentName(vm)})`
32 | }
33 |
--------------------------------------------------------------------------------
/packages/lone-util/index.js:
--------------------------------------------------------------------------------
1 | export * from './error'
2 |
3 | const _toString = Object.prototype.toString
4 |
5 | export const isString = s => _toString.call(s) === '[object String]'
6 | export const isObject = o => _toString.call(o) === '[object Object]'
7 | export const isBoolean = b => _toString.call(b) === '[object Boolean]'
8 | export const isArray = a => _toString.call(a) === '[object Array]'
9 | export const isFunction = f => _toString.call(f) === '[object Function]'
10 |
11 | export function noop () {}
12 |
13 | /**
14 | * Convert a value to a string that is actually rendered.
15 | */
16 | export function toString (val) {
17 | return val == null
18 | ? ''
19 | : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
20 | ? JSON.stringify(val, null, 2)
21 | : String(val)
22 | }
23 |
24 | /**
25 | * Convert an Array-like object to a real Array.
26 | */
27 | export function toArray (list, start) {
28 | start = start || 0
29 | let i = list.length - start
30 | const ret = new Array(i)
31 | while (i--) {
32 | ret[i] = list[i + start]
33 | }
34 | return ret
35 | }
36 |
37 | /**
38 | * Strict object type check. Only returns true
39 | * for plain JavaScript objects.
40 | */
41 | export function isPlainObject (obj) {
42 | return _toString.call(obj) === '[object Object]'
43 | }
44 |
45 | /**
46 | * Camelize a hyphen-delimited string.
47 | */
48 | const camelizeRE = /-(\w)/g
49 | export const camelize = cached(str => {
50 | return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
51 | })
52 |
53 | /**
54 | * Always return false.
55 | */
56 | export const no = _ => false
57 |
58 | /**
59 | * Create a cached version of a pure function.
60 | */
61 | export function cached (fn) {
62 | const cache = Object.create(null)
63 | return function cachedFn (str) {
64 | const hit = cache[str]
65 | return hit || (cache[str] = fn(str))
66 | }
67 | }
68 |
69 | /**
70 | * Mix properties into target object.
71 | */
72 | export function extend (to, _from) {
73 | for (const key in _from) {
74 | to[key] = _from[key]
75 | }
76 | return to
77 | }
78 |
79 | /**
80 | * Merge an Array of Objects into a single Object.
81 | */
82 | export function toObject (arr) {
83 | const res = {}
84 | for (let i = 0; i < arr.length; i++) {
85 | if (arr[i]) {
86 | extend(res, arr[i])
87 | }
88 | }
89 | return res
90 | }
91 |
92 | /**
93 | * Make a map and return a function for checking if a key
94 | * is in that map.
95 | */
96 | export function makeMap (str, expectsLowerCase) {
97 | const map = Object.create(null)
98 | const list = str.split(',')
99 | for (let i = 0; i < list.length; i++) {
100 | map[list[i]] = true
101 | }
102 | return expectsLowerCase
103 | ? val => map[val.toLowerCase()]
104 | : val => map[val]
105 | }
106 |
107 | /**
108 | * Check if a tag is a built-in tag.
109 | */
110 | export const isBuiltInTag = makeMap('slot,component', true)
111 |
112 | export const isUnaryTag = makeMap(
113 | 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
114 | 'link,meta,param,source,track,wbr'
115 | )
116 |
117 | // Elements that you can, intentionally, leave open
118 | // (and which close themselves)
119 | export const canBeLeftOpenTag = makeMap(
120 | 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'
121 | )
122 |
123 | // HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3
124 | // Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
125 | export const isNonPhrasingTag = makeMap(
126 | 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' +
127 | 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' +
128 | 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' +
129 | 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' +
130 | 'title,tr,track'
131 | )
132 |
133 | /**
134 | * Generate a static keys string from compiler modules.
135 | */
136 | export function genStaticKeys (modules) {
137 | return modules.reduce((keys, m) => {
138 | return keys.concat(m.staticKeys || [])
139 | }, []).join(',')
140 | }
141 |
142 | export function isDef (v) {
143 | return v !== undefined && v !== null
144 | }
145 |
146 | /**
147 | * Define a property.
148 | */
149 | export function def (obj, key, val, enumerable) {
150 | Object.defineProperty(obj, key, {
151 | value: val,
152 | enumerable: !!enumerable,
153 | writable: true,
154 | configurable: true
155 | })
156 | }
157 |
158 | /**
159 | * Hyphenate a camelCase string.
160 | */
161 | const hyphenateRE = /\B([A-Z])/g
162 | export const hyphenate = cached((str) => {
163 | return str.replace(hyphenateRE, '-$1').toLowerCase()
164 | })
165 |
166 | export const emptyObject = Object.freeze({})
167 |
168 | const sharedPropertyDefinition = {
169 | enumerable: true,
170 | configurable: true,
171 | get: noop,
172 | set: noop
173 | }
174 |
175 | export function proxy (target, sourceKey, key) {
176 | sharedPropertyDefinition.get = function proxyGetter () {
177 | return this[sourceKey][key]
178 | }
179 | sharedPropertyDefinition.set = function proxySetter (val) {
180 | this[sourceKey][key] = val
181 | }
182 | Object.defineProperty(target, key, sharedPropertyDefinition)
183 | }
184 |
185 | /**
186 | * Check if two values are loosely equal - that is,
187 | * if they are plain objects, do they have the same shape?
188 | */
189 | export function looseEqual (a, b) {
190 | if (a === b) return true
191 | const isObjectA = isObject(a)
192 | const isObjectB = isObject(b)
193 | if (isObjectA && isObjectB) {
194 | try {
195 | const isArrayA = Array.isArray(a)
196 | const isArrayB = Array.isArray(b)
197 | if (isArrayA && isArrayB) {
198 | return a.length === b.length && a.every((e, i) => {
199 | return looseEqual(e, b[i])
200 | })
201 | } else if (a instanceof Date && b instanceof Date) {
202 | return a.getTime() === b.getTime()
203 | } else if (!isArrayA && !isArrayB) {
204 | const keysA = Object.keys(a)
205 | const keysB = Object.keys(b)
206 | return keysA.length === keysB.length && keysA.every(key => {
207 | return looseEqual(a[key], b[key])
208 | })
209 | } else {
210 | /* istanbul ignore next */
211 | return false
212 | }
213 | } catch (e) {
214 | /* istanbul ignore next */
215 | return false
216 | }
217 | } else if (!isObjectA && !isObjectB) {
218 | return String(a) === String(b)
219 | } else {
220 | return false
221 | }
222 | }
223 |
224 | /**
225 | * Return the first index at which a loosely equal value can be
226 | * found in the array (if value is a plain object, the array must
227 | * contain an object of the same shape), or -1 if it is not present.
228 | */
229 | export function looseIndexOf (arr, val) {
230 | for (let i = 0; i < arr.length; i++) {
231 | if (looseEqual(arr[i], val)) return i
232 | }
233 | return -1
234 | }
235 |
236 | // generate messenger feedback type
237 | export function feedbackType (type) {
238 | return type + '#feedback'
239 | }
240 |
--------------------------------------------------------------------------------
/packages/lone-util/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-util",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "license": "ISC"
9 | }
10 |
--------------------------------------------------------------------------------
/packages/lone-util/url.js:
--------------------------------------------------------------------------------
1 | import { isArray } from './index'
2 |
3 | export function parse (url) {
4 | const a = document.createElement('a')
5 | a.href = url
6 | return {
7 | hash: a.hash,
8 | host: a.host,
9 | hostname: a.hostname,
10 | href: a.href,
11 | origin: a.origin,
12 | password: a.password,
13 | pathname: a.pathname,
14 | port: a.port,
15 | protocol: a.protocol,
16 | search: a.search,
17 | username: a.username
18 | }
19 | }
20 |
21 | // parseSearch('abc=123&abc=xyz') => { abc: ['123', 'xyz'] }
22 | export function parseSearch (search, sep = '&', eq = '=') {
23 | search = search.trim().replace(/^\?/, '').trim()
24 | const res = Object.create(null)
25 | if (search === '') return res
26 | return search.split(sep).reduce((res, param) => {
27 | const [key, value] = param.split(eq)
28 | if (!key) return res
29 | return {
30 | ...res,
31 | [key]: res[key]
32 | ? isArray(res[key])
33 | ? [...res[key], value]
34 | : [res[key], value]
35 | : value
36 | }
37 | }, res)
38 | }
39 |
--------------------------------------------------------------------------------
/packages/lone-util/web/attrs.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { makeMap } from '../index'
4 |
5 | // these are reserved for web because they are directly compiled away
6 | // during template compilation
7 | export const isReservedAttr = makeMap('style,class')
8 |
9 | // attributes that should be using props for binding
10 | const acceptValue = makeMap('input,textarea,option,select,progress')
11 | export const mustUseProp = (tag, type, attr) => {
12 | return (
13 | // eslint-disable-next-line no-mixed-operators
14 | (attr === 'value' && acceptValue(tag)) && type !== 'button' ||
15 | (attr === 'selected' && tag === 'option') ||
16 | (attr === 'checked' && tag === 'input') ||
17 | (attr === 'muted' && tag === 'video')
18 | )
19 | }
20 |
21 | export const isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck')
22 |
23 | export const isBooleanAttr = makeMap(
24 | 'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +
25 | 'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +
26 | 'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' +
27 | 'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' +
28 | 'required,reversed,scoped,seamless,selected,sortable,translate,' +
29 | 'truespeed,typemustmatch,visible'
30 | )
31 |
32 | export const xlinkNS = 'http://www.w3.org/1999/xlink'
33 |
34 | export const isXlink = (name) => {
35 | return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'
36 | }
37 |
38 | export const getXlinkProp = (name) => {
39 | return isXlink(name) ? name.slice(6, name.length) : ''
40 | }
41 |
42 | export const isFalsyAttrValue = (val) => {
43 | return val == null || val === false
44 | }
45 |
--------------------------------------------------------------------------------
/packages/lone-util/web/class.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { isDef, isObject } from '../index'
4 |
5 | export function genClassForVnode (vnode) {
6 | let data = vnode.data
7 | let parentNode = vnode
8 | let childNode = vnode
9 | while (isDef(childNode.componentInstance)) {
10 | childNode = childNode.componentInstance._vnode
11 | if (childNode.data) {
12 | data = mergeClassData(childNode.data, data)
13 | }
14 | }
15 | while (isDef(parentNode = parentNode.parent)) {
16 | if (parentNode.data) {
17 | data = mergeClassData(data, parentNode.data)
18 | }
19 | }
20 | return renderClass(data.staticClass, data.class)
21 | }
22 |
23 | function mergeClassData (child, parent) {
24 | return {
25 | staticClass: concat(child.staticClass, parent.staticClass),
26 | class: isDef(child.class)
27 | ? [child.class, parent.class]
28 | : parent.class
29 | }
30 | }
31 |
32 | export function renderClass (staticClass, dynamicClass) {
33 | if (isDef(staticClass) || isDef(dynamicClass)) {
34 | return concat(staticClass, stringifyClass(dynamicClass))
35 | }
36 | /* istanbul ignore next */
37 | return ''
38 | }
39 |
40 | export function concat (a, b) {
41 | return a ? b ? (a + ' ' + b) : a : (b || '')
42 | }
43 |
44 | export function stringifyClass (value) {
45 | if (Array.isArray(value)) {
46 | return stringifyArray(value)
47 | }
48 | if (isObject(value)) {
49 | return stringifyObject(value)
50 | }
51 | if (typeof value === 'string') {
52 | return value
53 | }
54 | /* istanbul ignore next */
55 | return ''
56 | }
57 |
58 | function stringifyArray (value) {
59 | let res = ''
60 | let stringified
61 | for (let i = 0, l = value.length; i < l; i++) {
62 | if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {
63 | if (res) res += ' '
64 | res += stringified
65 | }
66 | }
67 | return res
68 | }
69 |
70 | function stringifyObject (value) {
71 | let res = ''
72 | for (const key in value) {
73 | if (value[key]) {
74 | if (res) res += ' '
75 | res += key
76 | }
77 | }
78 | return res
79 | }
80 |
--------------------------------------------------------------------------------
/packages/lone-util/web/compat.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { inBrowser } from '../env'
4 |
5 | // check whether current browser encodes a char inside attribute values
6 | function shouldDecode (content, encoded) {
7 | const div = document.createElement('div')
8 | div.innerHTML = ``
9 | return div.innerHTML.indexOf(encoded) > 0
10 | }
11 |
12 | // #3663
13 | // IE encodes newlines inside attribute values while other browsers don't
14 | export const shouldDecodeNewlines = inBrowser ? shouldDecode('\n', '
') : false
15 |
--------------------------------------------------------------------------------
/packages/lone-util/web/element.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { inBrowser } from '../env'
4 | import { makeMap } from '../index'
5 |
6 | export const namespaceMap = {
7 | svg: 'http://www.w3.org/2000/svg',
8 | math: 'http://www.w3.org/1998/Math/MathML'
9 | }
10 |
11 | export const isHTMLTag = makeMap(
12 | 'html,body,base,head,link,meta,style,title,' +
13 | 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
14 | 'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
15 | 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
16 | 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
17 | 'embed,object,param,source,canvas,script,noscript,del,ins,' +
18 | 'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
19 | 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
20 | 'output,progress,select,textarea,' +
21 | 'details,dialog,menu,menuitem,summary,' +
22 | 'content,element,shadow,template,blockquote,iframe,tfoot'
23 | )
24 |
25 | // this map is intentionally selective, only covering SVG elements that may
26 | // contain child elements.
27 | export const isSVG = makeMap(
28 | 'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' +
29 | 'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' +
30 | 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',
31 | true
32 | )
33 |
34 | export const isPreTag = (tag) => tag === 'pre'
35 |
36 | export const isReservedTag = (tag) => {
37 | return isHTMLTag(tag) || isSVG(tag)
38 | }
39 |
40 | export function getTagNamespace (tag) {
41 | if (isSVG(tag)) {
42 | return 'svg'
43 | }
44 | // basic support for MathML
45 | // note it doesn't support other MathML elements being component roots
46 | if (tag === 'math') {
47 | return 'math'
48 | }
49 | }
50 |
51 | const unknownElementCache = Object.create(null)
52 | export function isUnknownElement (tag) {
53 | /* istanbul ignore if */
54 | if (!inBrowser) {
55 | return true
56 | }
57 | if (isReservedTag(tag)) {
58 | return false
59 | }
60 | tag = tag.toLowerCase()
61 | /* istanbul ignore if */
62 | if (unknownElementCache[tag] != null) {
63 | return unknownElementCache[tag]
64 | }
65 | const el = document.createElement(tag)
66 | if (tag.indexOf('-') > -1) {
67 | // http://stackoverflow.com/a/28210364/1070244
68 | return (unknownElementCache[tag] = (
69 | el.constructor === window.HTMLUnknownElement ||
70 | el.constructor === window.HTMLElement
71 | ))
72 | } else {
73 | return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString()))
74 | }
75 | }
76 |
77 | export const isTextInputType = makeMap('text,number,password,search,email,tel,url')
78 |
--------------------------------------------------------------------------------
/packages/lone-util/web/index.js:
--------------------------------------------------------------------------------
1 | import { warn } from '../index'
2 |
3 | export * from './attrs'
4 | export * from './class'
5 | export * from './element'
6 |
7 | /**
8 | * Query an element selector if it's not an element already.
9 | */
10 | export function query (el) {
11 | if (typeof el === 'string') {
12 | const selected = document.querySelector(el)
13 | if (!selected) {
14 | warn(
15 | 'Cannot find element: ' + el + '. Used body instead!'
16 | )
17 | return document.body
18 | }
19 | return selected
20 | } else {
21 | return el
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/lone-util/web/style.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { cached, extend, toObject } from '../index'
4 |
5 | export const parseStyleText = cached(function (cssText) {
6 | const res = {}
7 | const listDelimiter = /;(?![^(]*\))/g
8 | const propertyDelimiter = /:(.+)/
9 | cssText.split(listDelimiter).forEach(function (item) {
10 | if (item) {
11 | var tmp = item.split(propertyDelimiter)
12 | tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
13 | }
14 | })
15 | return res
16 | })
17 |
18 | // merge static and dynamic style data on the same vnode
19 | function normalizeStyleData (data) {
20 | const style = normalizeStyleBinding(data.style)
21 | // static style is pre-processed into an object during compilation
22 | // and is always a fresh object, so it's safe to merge into it
23 | return data.staticStyle
24 | ? extend(data.staticStyle, style)
25 | : style
26 | }
27 |
28 | // normalize possible array / string values into Object
29 | export function normalizeStyleBinding (bindingStyle) {
30 | if (Array.isArray(bindingStyle)) {
31 | return toObject(bindingStyle)
32 | }
33 | if (typeof bindingStyle === 'string') {
34 | return parseStyleText(bindingStyle)
35 | }
36 | return bindingStyle
37 | }
38 |
39 | /**
40 | * parent component style should be after child's
41 | * so that parent component's style could override it
42 | */
43 | export function getStyle (vnode, checkChild) {
44 | const res = {}
45 | let styleData
46 |
47 | if (checkChild) {
48 | let childNode = vnode
49 | while (childNode.componentInstance) {
50 | childNode = childNode.componentInstance._vnode
51 | if (childNode.data && (styleData = normalizeStyleData(childNode.data))) {
52 | extend(res, styleData)
53 | }
54 | }
55 | }
56 |
57 | if ((styleData = normalizeStyleData(vnode.data))) {
58 | extend(res, styleData)
59 | }
60 |
61 | let parentNode = vnode
62 | while ((parentNode = parentNode.parent)) {
63 | if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) {
64 | extend(res, styleData)
65 | }
66 | }
67 | return res
68 | }
69 |
--------------------------------------------------------------------------------
/packages/lone-virtualdom/create-component.js:
--------------------------------------------------------------------------------
1 | import { isReservedTag } from 'lone-util/web'
2 | import Component from 'lone-page/component'
3 |
4 | export default {
5 | create: function (oldVnode, vnode) {
6 | if (!isReservedTag(vnode.sel) && isComponent(vnode.sel)) {
7 | const { attrs, on } = vnode.data
8 | const component = new Component({
9 | name: vnode.sel,
10 | el: vnode.elm,
11 | propsData: attrs,
12 | _parentListeners: on,
13 | _renderChildren: vnode.children
14 | })
15 | vnode.elm.component = component
16 | }
17 | },
18 | update (oldVnode, vnode) {
19 | if (!isReservedTag(vnode.sel) && isComponent(vnode.sel)) {
20 | const oldAttrs = oldVnode.data.attrs
21 | const attrs = vnode.data.attrs
22 | const component = vnode.elm.component
23 | if ((oldAttrs || attrs) && JSON.stringify(oldAttrs) !== JSON.stringify(attrs)) {
24 | component.updatePropsData(attrs, oldAttrs)
25 | }
26 | }
27 | }
28 | }
29 |
30 | function isComponent (name) {
31 | return Component.options.components.find(component => component.name === name)
32 | }
33 |
--------------------------------------------------------------------------------
/packages/lone-virtualdom/index.js:
--------------------------------------------------------------------------------
1 | import { init } from 'snabbdom'
2 | import attrs from 'snabbdom/modules/attributes'
3 | import cls from 'snabbdom/modules/class' // makes it easy to toggle classes
4 | import props from 'snabbdom/modules/props' // for setting properties on DOM elements
5 | import style from 'snabbdom/modules/style' // handles styling on elements with support for animations
6 | import eventlisteners from 'snabbdom/modules/eventlisteners' // attaches event listeners
7 | import createComponent from './create-component'
8 |
9 | export const patch = init([
10 | attrs,
11 | cls,
12 | props,
13 | style,
14 | eventlisteners,
15 | createComponent
16 | ])
17 |
18 | export { default as h } from 'snabbdom/h'
19 |
--------------------------------------------------------------------------------
/packages/lone-virtualdom/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-virtualdom",
3 | "version": "0.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "snabbdom": {
8 | "version": "0.7.3",
9 | "resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-0.7.3.tgz",
10 | "integrity": "sha512-XNh90GQiV36hWdfSL46fIVrSSvmBuZlWk3++qaEgBeQWQJCqTphcbjTODPv8/vyZHJaB3VhePsWfGxi/zBxXyw=="
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/lone-virtualdom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lone-virtualdom",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "license": "ISC",
10 | "dependencies": {
11 | "lone-page": "^0.0.0",
12 | "lone-util": "^0.0.0",
13 | "snabbdom": "^0.7.3"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/lone-virtualdom/render-helpers/h.js:
--------------------------------------------------------------------------------
1 | import { h } from '../index'
2 | import { isArray } from 'lone-util'
3 |
4 | export default function (sel, a, b) {
5 | if (isArray(a)) a = normalizeArrayChildren(a)
6 | if (isArray(b)) b = normalizeArrayChildren(b)
7 | return h(sel, a, b)
8 | }
9 |
10 | function normalizeArrayChildren (children) {
11 | const res = []
12 | for (let i = 0, l = children.length; i < l; i++) {
13 | const child = children[i]
14 | if (isArray(child)) {
15 | res.push.apply(res, normalizeArrayChildren(child))
16 | } else {
17 | res.push(child)
18 | }
19 | }
20 | return res
21 | }
22 |
--------------------------------------------------------------------------------
/packages/lone-virtualdom/render-helpers/index.js:
--------------------------------------------------------------------------------
1 | import { toString, looseIndexOf, looseEqual } from 'lone-util'
2 | import h from './h'
3 | import renderList from './render-list'
4 | import renderSlot from './render-slot'
5 |
6 | export function installRenderHelpers (target) {
7 | const proto = target.prototype
8 | // createTextVNode
9 | proto._v = text => text
10 | // create Element Vnode
11 | proto._c = h
12 | proto._s = toString
13 | proto._l = renderList
14 | proto._t = renderSlot
15 | proto._i = looseIndexOf
16 | proto._q = looseEqual
17 |
18 | // target._o = markOnce
19 | // target._m = renderStatic
20 | // target._f = resolveFilter
21 | // target._k = checkKeyCodes
22 | // target._b = bindObjectProps
23 | // target._v = createTextVNode
24 | // target._e = createEmptyVNode
25 | // target._u = resolveScopedSlots
26 | // target._g = bindObjectListeners
27 | }
28 |
--------------------------------------------------------------------------------
/packages/lone-virtualdom/render-helpers/render-list.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { isObject, isDef } from 'lone-util'
4 |
5 | /**
6 | * Runtime helper for rendering v-for lists.
7 | */
8 | export default function renderList (val, render) {
9 | let ret, i, l, keys, key
10 | if (Array.isArray(val) || typeof val === 'string') {
11 | ret = new Array(val.length)
12 | for (i = 0, l = val.length; i < l; i++) {
13 | ret[i] = render(val[i], i)
14 | }
15 | } else if (typeof val === 'number') {
16 | ret = new Array(val)
17 | for (i = 0; i < val; i++) {
18 | ret[i] = render(i + 1, i)
19 | }
20 | } else if (isObject(val)) {
21 | keys = Object.keys(val)
22 | ret = new Array(keys.length)
23 | for (i = 0, l = keys.length; i < l; i++) {
24 | key = keys[i]
25 | ret[i] = render(val[key], key, i)
26 | }
27 | }
28 | if (isDef(ret)) {
29 | ret._isVList = true
30 | }
31 | return ret
32 | }
33 |
--------------------------------------------------------------------------------
/packages/lone-virtualdom/render-helpers/render-slot.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { warn } from 'lone-util'
4 |
5 | /**
6 | * Runtime helper for rendering
7 | */
8 | export default function renderSlot (name, fallback, props, bindObject) {
9 | const slotNodes = this.$slots[name]
10 | // warn duplicate slot usage
11 | if (slotNodes) {
12 | slotNodes._rendered && warn(
13 | `Duplicate presence of slot "${name}" found in the same render tree ` +
14 | '- this will likely cause render errors.',
15 | this
16 | )
17 | slotNodes._rendered = true
18 | }
19 | return slotNodes || fallback
20 | }
21 |
--------------------------------------------------------------------------------
/scripts/replace-loader.js:
--------------------------------------------------------------------------------
1 | const loaderUtils = require('loader-utils')
2 |
3 | module.exports = function (source) {
4 | this.cacheable && this.cacheable()
5 | const query = loaderUtils.getOptions(this) || {}
6 | source = source.toString()
7 | Object.keys(query).forEach(function (key) {
8 | const reg = new RegExp(key, 'g')
9 | source = source.replace(reg, query[key])
10 | })
11 | return source
12 | }
13 |
14 | module.exports.raw = true
15 |
--------------------------------------------------------------------------------
/scripts/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | function resolve (dir) {
4 | return path.resolve(__dirname, '../', dir)
5 | }
6 |
7 | module.exports = {
8 | entry: {
9 | logic: resolve('packages/lone-logic-worker/index.js'),
10 | ui: resolve('packages/lone-ui/index.js'),
11 | page: resolve('packages/lone-page/index.js')
12 | },
13 | output: {
14 | path: resolve('dist'),
15 | filename: 'Lone.[name].js',
16 | library: ['Lone', '[name]'],
17 | libraryExport: 'default'
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.(js)$/,
23 | loader: 'eslint-loader',
24 | enforce: 'pre',
25 | exclude: /node_modules/,
26 | options: {
27 | formatter: require('eslint-friendly-formatter')
28 | }
29 | },
30 | {
31 | test: /\.js$/,
32 | exclude: /node_modules/,
33 | loader: 'babel-loader',
34 | options: {
35 | presets: [
36 | ['@babel/preset-env', { targets: { ie: '8' } }]
37 | ],
38 | plugins: [
39 | ['@babel/plugin-transform-modules-commonjs', { loose: true }],
40 | ['@babel/plugin-proposal-decorators', { legacy: true }]
41 | ]
42 | }
43 | },
44 | {
45 | test: /\.js$/,
46 | loader: resolve('scripts/replace-loader.js'),
47 | exclude: /node_modules/,
48 | options: {
49 | __PAGEJS__: '"../../dist/lone.page.js"'
50 | }
51 | }
52 | ]
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/scripts/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | const merge = require("webpack-merge");
2 | const baseWebpackConfig = require("./webpack.base.conf");
3 |
4 | module.exports = merge(baseWebpackConfig, {
5 | mode: "development",
6 | // cheap-module-eval-source-map is faster for development
7 | devtool: "#source-map"
8 | });
9 |
--------------------------------------------------------------------------------
/scripts/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | const merge = require('webpack-merge')
2 | const baseWebpackConfig = require('./webpack.base.conf')
3 |
4 | module.exports = merge(baseWebpackConfig, {
5 | mode: 'production'
6 | })
7 |
--------------------------------------------------------------------------------