├── .babelrc ├── .editorconfig ├── .fecsignore ├── .fecsrc ├── .gitignore ├── LICENSE ├── README.md ├── build ├── build.js ├── rollup │ ├── alias.js │ ├── build.js │ └── rollup.config.js ├── webpack.config.base.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── deps ├── README.md ├── fetch.js └── zepto.js ├── docs ├── .gitkeep ├── assets │ ├── intro-view-layer-element.jpg │ └── mip2-component-lifecycle.png ├── basic │ ├── announcement.md │ ├── demo.md │ ├── introduction.md │ ├── meta.json │ ├── mip-cache.md │ └── principle-of-mip.md ├── cli │ ├── cli-usage.md │ ├── component-development.md │ ├── component-testing.md │ ├── contribute-to-official-repo.md │ └── start-writing-first-mip.md ├── components │ ├── builtin │ │ ├── mip-bind.md │ │ ├── mip-carousel.md │ │ ├── mip-iframe.md │ │ ├── mip-img.md │ │ ├── mip-pix.md │ │ └── mip-video.md │ ├── components-list.md │ ├── data-and-method.md │ ├── extensions │ │ ├── mip-access.md │ │ ├── mip-accordion.md │ │ ├── mip-analytics.md │ │ ├── mip-anim.md │ │ ├── mip-app-banner.md │ │ ├── mip-appdl.md │ │ ├── mip-audio.md │ │ ├── mip-bind.md │ │ ├── mip-custom.md │ │ ├── mip-experiment.md │ │ ├── mip-filter.md │ │ ├── mip-fixed.md │ │ ├── mip-form.md │ │ ├── mip-gototop.md │ │ ├── mip-history.md │ │ ├── mip-html-os.md │ │ ├── mip-infinitescroll.md │ │ ├── mip-install-serviceworker.md │ │ ├── mip-lightbox.md │ │ ├── mip-link.md │ │ ├── mip-list.md │ │ ├── mip-login-done.md │ │ ├── mip-map.md │ │ ├── mip-mustache.md │ │ ├── mip-nav-slidedown.md │ │ ├── mip-sample.md │ │ ├── mip-scrollbox.md │ │ ├── mip-semi-fixed.md │ │ ├── mip-share.md │ │ ├── mip-showmore.md │ │ ├── mip-sidebar.md │ │ ├── mip-stats-baidu.md │ │ ├── mip-stats-cnzz.md │ │ ├── mip-stats-tianrun.md │ │ ├── mip-story.md │ │ ├── mip-toggle.md │ │ ├── mip-vd-baidu.md │ │ └── mip-vd-tabs.md │ ├── instance-life-cycle.md │ ├── introduction.md │ ├── layout.md │ ├── meta.json │ ├── mip-ad │ │ ├── mip-ad-baidu.md │ │ ├── mip-ad-baidussp.md │ │ ├── mip-ad-comm.md │ │ ├── mip-ad-imageplus.md │ │ ├── mip-ad-qwang.md │ │ ├── mip-ad-ssp.md │ │ ├── mip-ad.md │ │ └── mip-baidu-wm-ext.md │ └── syntax.md ├── page │ └── index.md ├── specs │ ├── mip-cache-spec.md │ ├── mip-components-spec.md │ ├── mip-html-spec.md │ └── mip-validate.md └── util │ └── sandbox.md ├── examples ├── builtin │ └── README.md ├── douban │ └── index.html ├── event │ ├── index.html │ ├── mip-a.js │ └── mip-b.js ├── lavas │ ├── extensions │ │ ├── mip-fixed │ │ │ ├── mip-fixed.js │ │ │ └── viewport-scroll.js │ │ ├── mip-nav-slidedown.js │ │ └── mip-sidebar.js │ ├── guide.html │ └── index.html ├── lifecycle │ ├── data.json │ ├── index.html │ └── mip-list.js ├── mip-bind │ ├── bind.html │ └── index.html ├── mip-extensions │ ├── README.md │ ├── mip-ad.js │ ├── mip-app-banner.js │ ├── mip-cambrian.js │ ├── mip-lightbox.js │ ├── mip-sidebar.js │ └── mip-stats-baidu.js ├── mip │ ├── index.html │ ├── list.html │ ├── mip-bind.js │ ├── mip-carousel.html │ ├── mip-iframe.html │ ├── mip-img.html │ ├── mip-list.js │ ├── mip-tree.js │ ├── mip-video.html │ ├── props.html │ ├── template.html │ └── tree.html ├── mip1 │ ├── doc │ │ └── 00-mip-101.html │ └── index.html ├── page │ ├── components │ │ ├── mip-complevel1.js │ │ ├── mip-complevel2.js │ │ └── mip-tree.js │ ├── data.html │ ├── data │ │ └── testData.json │ ├── index.html │ ├── not-mip.html │ └── tree.html ├── sandbox │ ├── index.html │ └── index.js ├── server.js ├── server.rollup.js ├── simple │ ├── index.html │ ├── mip-slide.js │ ├── mip-tree.js │ └── storeData.js ├── ssr │ └── index.html ├── static │ └── fonts │ │ ├── MaterialIcons-Regular.570eb838.woff2 │ │ ├── fontawesome-webfont.97493d3f.woff2 │ │ └── fontawesome-webfont.woff2 ├── store │ ├── components │ │ ├── mip-contacts.js │ │ ├── mip-hello.js │ │ ├── mip-init.js │ │ └── mip-store.js │ ├── index.html │ ├── storeData.js │ ├── testdata.json │ └── testdata2.json └── wecoffee │ ├── common.css │ ├── component.js │ ├── index.html │ └── rt-view.css ├── package.json └── src ├── components ├── index.js ├── mip-bind │ ├── bind.js │ ├── compile.js │ ├── deps.js │ ├── mip-data.js │ ├── observer.js │ └── watcher.js ├── mip-carousel.js ├── mip-iframe.js ├── mip-img.js ├── mip-pix.js └── mip-video.js ├── custom-element.js ├── fixed-element.js ├── index.js ├── layout.js ├── log ├── coreTags.js ├── logSend.js └── monitor.js ├── mip1-polyfill ├── customElement.js ├── element.js ├── index.js └── naboo.js ├── page ├── appshell │ ├── header.js │ ├── index.js │ └── loading.js ├── const │ └── index.js ├── create-router.js ├── index.js ├── router │ ├── history.js │ ├── index.js │ └── matcher.js └── util │ ├── dom.js │ ├── index.js │ ├── link.js │ ├── path.js │ ├── push-state.js │ ├── query.js │ ├── route.js │ ├── scroll-behavior.js │ ├── transition.js │ └── url.js ├── performance.js ├── polyfills ├── array-includes.js ├── babel-runtime-helpers.js ├── index.js ├── object-assign.js └── promise.js ├── register-element.js ├── resources.js ├── sandbox.js ├── sleepWakeModule.js ├── styles ├── fonts │ ├── MaterialIcons-Regular.ttf │ ├── MaterialIcons-Regular.woff │ └── MaterialIcons-Regular.woff2 ├── material-icons.css ├── mip-base.less ├── mip-carousel.less ├── mip-fixed.less ├── mip-html.less ├── mip-iframe.less ├── mip-img.less ├── mip-page.less ├── mip-text.less ├── mip-video.less ├── mip.less ├── mixin.less └── variable.less ├── util ├── customStorage.js ├── dom │ ├── css-loader.js │ ├── css.js │ ├── dom.js │ ├── event.js │ └── rect.js ├── event-action.js ├── event-emitter.js ├── fn.js ├── gesture │ ├── data-processor.js │ ├── gesture-recognizer.js │ └── index.js ├── hash.js ├── index.js ├── platform.js └── templates.js ├── viewer.js ├── viewport.js ├── vue-custom-element ├── index.js └── utils │ ├── create-vue-instance.js │ ├── custom-event.js │ ├── helpers.js │ ├── props.js │ └── slots.js └── vue ├── compiler ├── codegen │ ├── events.js │ └── index.js ├── create-compiler.js ├── directives │ ├── bind.js │ ├── index.js │ ├── model.js │ └── on.js ├── error-detector.js ├── helpers.js ├── index.js ├── optimizer.js ├── parser │ ├── entity-decoder.js │ ├── filter-parser.js │ ├── html-parser.js │ ├── index.js │ └── text-parser.js └── to-function.js ├── core ├── components │ ├── index.js │ └── keep-alive.js ├── config.js ├── global-api │ ├── assets.js │ ├── extend.js │ ├── index.js │ ├── mixin.js │ └── use.js ├── index.js ├── instance │ ├── events.js │ ├── index.js │ ├── init.js │ ├── inject.js │ ├── lifecycle.js │ ├── proxy.js │ ├── render-helpers │ │ ├── bind-object-listeners.js │ │ ├── bind-object-props.js │ │ ├── check-keycodes.js │ │ ├── index.js │ │ ├── render-list.js │ │ ├── render-slot.js │ │ ├── render-static.js │ │ ├── resolve-filter.js │ │ └── resolve-slots.js │ ├── render.js │ └── state.js ├── observer │ ├── array.js │ ├── dep.js │ ├── index.js │ ├── scheduler.js │ └── watcher.js ├── util │ ├── debug.js │ ├── env.js │ ├── error.js │ ├── index.js │ ├── lang.js │ ├── options.js │ ├── perf.js │ └── props.js └── vdom │ ├── create-component.js │ ├── create-element.js │ ├── create-functional-component.js │ ├── helpers │ ├── extract-props.js │ ├── get-first-component-child.js │ ├── index.js │ ├── is-async-placeholder.js │ ├── merge-hook.js │ ├── normalize-children.js │ ├── resolve-async-component.js │ └── update-listeners.js │ ├── modules │ ├── directives.js │ ├── index.js │ └── ref.js │ ├── patch.js │ └── vnode.js ├── platforms └── web │ ├── compiler │ ├── directives │ │ ├── html.js │ │ ├── index.js │ │ ├── model.js │ │ └── text.js │ ├── index.js │ ├── modules │ │ ├── class.js │ │ ├── index.js │ │ ├── model.js │ │ └── style.js │ ├── options.js │ └── util.js │ ├── entry-runtime-with-compiler.js │ ├── entry-runtime.js │ ├── runtime │ ├── class-util.js │ ├── components │ │ ├── index.js │ │ ├── transition-group.js │ │ └── transition.js │ ├── directives │ │ ├── index.js │ │ ├── model.js │ │ └── show.js │ ├── index.js │ ├── modules │ │ ├── attrs.js │ │ ├── class.js │ │ ├── dom-props.js │ │ ├── events.js │ │ ├── index.js │ │ ├── style.js │ │ └── transition.js │ ├── node-ops.js │ ├── patch.js │ └── transition-util.js │ └── util │ ├── attrs.js │ ├── class.js │ ├── compat.js │ ├── element.js │ ├── index.js │ └── style.js └── shared ├── constants.js └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", { 5 | "modules": false 6 | } 7 | ], 8 | "vue" 9 | ], 10 | "plugins": [ 11 | "transform-custom-element-classes", 12 | "syntax-dynamic-import", 13 | "transform-object-rest-spread", 14 | [ 15 | "transform-runtime", 16 | { 17 | "helpers": false, 18 | "polyfill": false, 19 | "regenerator": true 20 | } 21 | ] 22 | ], 23 | "ignore": [ 24 | "dist/*.js" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | UTF-8 2 | # This file is for unifying the coding style for different editors and IDEs 3 | # editorconfig.org 4 | root = true 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | [*.{js,styl,html,json}] 11 | indent_size = 4 12 | indent_style = space 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.fecsignore: -------------------------------------------------------------------------------- 1 | src/page 2 | src/vue 3 | src/vuex 4 | src/router 5 | deps 6 | build 7 | dist 8 | examples 9 | node_modules 10 | -------------------------------------------------------------------------------- /.fecsrc: -------------------------------------------------------------------------------- 1 | { 2 | "eslint": { 3 | "env": { 4 | "node": true, 5 | "browser": true 6 | }, 7 | 8 | "globals": { 9 | "console": true, 10 | "require": true, 11 | "define": true 12 | }, 13 | 14 | "rules": { 15 | "no-console": 0, 16 | "brace-style": [2, "1tabs"], 17 | "fecs-no-require": "off", 18 | "fecs-prefer-async-await": "off", 19 | "fecs-one-var-per-line": "off", 20 | "prefer-rest-params": "off", 21 | "guard-for-in": "off", 22 | "fecs-export-on-declare": "off", 23 | "fecs-use-for-of": "off", 24 | "fecs-prefer-class": "off", 25 | "fecs-prefer-destructure": "off", 26 | "fecs-prefer-assign-pattern": "off", 27 | "fecs-use-computed-property": "off", 28 | "fecs-valid-map-set": "off" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.log 4 | dist 5 | .vscode 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present mip-project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MIP 2 | 3 | ## 开发 4 | 5 | - Node.js v6+ 6 | - Clone 代码之后运行 7 | 8 | ```sh 9 | $ cnpm install # npm install 10 | ``` 11 | 12 | ## 常用命令 13 | 14 | ```sh 15 | # watch and auto re-build dist/mip.js 16 | $ npm run dev 17 | ``` 18 | 监控代码改动并编译代码到 `dist/mip.js`,可通过 `open examples/simple/index.html` 预览效果 19 | 20 | ```sh 21 | # build all dist files, including npm packages 22 | $ npm run build 23 | ``` 24 | 编译代码到 dist 目录 25 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file webpack build 脚本 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | 'use strict'; 7 | 8 | process.env.NODE_ENV = 'production'; 9 | 10 | const ora = require('ora'); 11 | const rm = require('rimraf'); 12 | const path = require('path'); 13 | const chalk = require('chalk'); 14 | const webpack = require('webpack'); 15 | const webpackConfig = require('./webpack.config.prod'); 16 | 17 | const spinner = ora('building for production...'); 18 | spinner.start(); 19 | 20 | rm(path.join(__dirname, '../dist'), err => { 21 | if (err) { 22 | throw err; 23 | } 24 | 25 | webpack(webpackConfig, (err, stats) => { 26 | spinner.stop(); 27 | if (err) { 28 | throw err; 29 | } 30 | 31 | process.stdout.write(stats.toString({ 32 | colors: true, 33 | modules: false, 34 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 35 | chunks: false, 36 | chunkModules: false 37 | }) + '\n\n'); 38 | 39 | if (stats.hasErrors()) { 40 | console.log(chalk.red(' Build failed with errors.\n')); 41 | process.exit(1); 42 | } 43 | 44 | console.log(chalk.cyan(' Build complete.\n')); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /build/rollup/alias.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file alias.js 3 | * @author huanghuiquan (huanghuiquan@baidu.com) 4 | */ 5 | 6 | const path = require('path'); 7 | 8 | const resolve = p => path.resolve(__dirname, '../', p); 9 | 10 | module.exports = { 11 | vue: resolve('src/vue/platforms/web/entry-runtime'), 12 | compiler: resolve('src/vue/compiler'), 13 | core: resolve('src/vue/core'), 14 | shared: resolve('src/vue/shared'), 15 | web: resolve('src/vue/platforms/web'), 16 | // server: resolve('src/server'), 17 | // entries: resolve('src/entries'), 18 | sfc: resolve('src/vue/sfc'), 19 | deps: resolve('deps') 20 | }; 21 | -------------------------------------------------------------------------------- /build/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file webpack dev 配置 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | const path = require('path'); 7 | const merge = require('webpack-merge'); 8 | const baseConfig = require('./webpack.config.base'); 9 | 10 | const resolve = p => path.resolve(__dirname, '../', p); 11 | 12 | module.exports = merge.smart(baseConfig, { 13 | mode: 'development', 14 | devtool: 'inline-source-map', 15 | entry: resolve('src/index.js'), 16 | resolve: { 17 | alias: { 18 | vue: resolve('src/vue/platforms/web/entry-runtime-with-compiler') 19 | } 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /build/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file webpack prod 配置 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | const path = require('path'); 7 | const merge = require('webpack-merge'); 8 | const baseConfig = require('./webpack.config.base'); 9 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 10 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 11 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 12 | 13 | const resolve = p => path.resolve(__dirname, '../', p); 14 | 15 | module.exports = merge.smart(baseConfig, { 16 | mode: 'production', 17 | entry: resolve('src/index.js'), 18 | 19 | module: { 20 | rules: [] 21 | }, 22 | 23 | plugins: [ 24 | new MiniCssExtractPlugin({filename: 'mip.css'}), 25 | new UglifyJsPlugin(), 26 | new BundleAnalyzerPlugin() 27 | ] 28 | }); 29 | -------------------------------------------------------------------------------- /deps/README.md: -------------------------------------------------------------------------------- 1 | # deps 目录说明 2 | 3 | deps 目录均为第三方引用的 lib 4 | 5 | 有些 lib 有一些业务上的修改无法兼容,因此直接将源代码拷贝至此,而非通过 npm 来管理 6 | 7 | ## fetch 8 | 9 | 源文件:https://github.com/github/fetch/blob/master/fetch.js 10 | 11 | 主要修改点在于增加了 QQ 浏览器的判断,因为 QQ 浏览器的早起版本 fetch 实现有问题,发送请求会漏掉 cookie,因为在 QQ 浏览器中也通过 polyfill 的方式覆盖 fetch 12 | -------------------------------------------------------------------------------- /docs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/docs/.gitkeep -------------------------------------------------------------------------------- /docs/assets/intro-view-layer-element.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/docs/assets/intro-view-layer-element.jpg -------------------------------------------------------------------------------- /docs/assets/mip2-component-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/docs/assets/mip2-component-lifecycle.png -------------------------------------------------------------------------------- /docs/basic/announcement.md: -------------------------------------------------------------------------------- 1 | # MIP 内容声明 2 | 3 | **从搜索结果页点出的MIP页面,其页面上的任何内容(包括但不限于广告、在线咨询、统计等组件)均视为在原站点上的投放和使用。** 4 | 5 | MIP(Mobile Instant Pages - 移动网页加速器), 是一套应用于移动网页的开放性技术标准。通过提供 MIP-HTML 规范、MIP-JS 运行环境以及 MIP-Cache 页面缓存系统,实现移动网页加速。 6 | 7 | 移动站点可以根据需要使用 MIP 技术进行开发,从而达到移动页面加速效果。 8 | 9 | 1. 站点发布的 MIP 页面,由站点开发人员采用 MIP 技术自行开发。开发与发布过程完全由站点把控。 10 | 2. 采用 MIP 技术开发的移动端页面,页面中展现的内容和功能,与普通移动端页面一样,完全由开发方放置。 11 | 3. 采用 MIP 技术开发的移动端页面,站点流量以及所投放广告等内容的展现与点击等使用情况,完全由开发方进行统计和监控。 12 | 4. 在用户使用搜索引擎时,为了保证 MIP 页面的加速效果,搜索引擎可能会将 MIP 页面缓存到离用户更近的 CDN 服务器。目前,缓存刷新的平均时间为50分钟左右。 13 | 14 | 因此,无论是从搜索点出的MIP页面还是直接访问的 MIP 页面,页面开发过程、展现内容、相关监控,均完全由站点开发方进行把控。在这两种情况下的页面上的任何内容(包括但不限于:广告、在线咨询、统计等组件)均视为在原站点上的投放和使用。 15 | -------------------------------------------------------------------------------- /docs/basic/introduction.md: -------------------------------------------------------------------------------- 1 | # 什么是 MIP 2 | 3 | MIP(Mobile Instant Pages - 移动网页加速器),是一套应用于移动网页的开放性技术标准。通过提供 MIP-HTML 规范、MIP-JS 运行环境以及 MIP-Cache 页面缓存系统,实现移动网页加速。 4 | 5 | MIP 主要由三部分组织成: 6 | 7 | - MIP-HTML:基于 HTML 中的基础标签制定了全新的规范,通过对一部分基础标签的使用限制或功能扩展,使 HTML 能够展现更加丰富的内容。 8 | - MIP-JS:可以保证 MIP-HTML 页面的快速渲染。 9 | - MIP-Cache:用于实现 MIP 页面的高速缓存,从而进一步提高页面性能。 10 | 11 | ## 1. MIP-HTML 12 | 13 | > info 14 | > 提示:MIP-HTML 基于 HTML 基础规范进行了扩展。 15 | 16 | 下面是一段简单的 MIP-HTML 代码示例: 17 | 18 | ```html 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 39 |

Hello World!

40 | 41 | 42 | 43 | ``` 44 | 45 | MIP-HTML 规范中有两类标签,一类是 HTML 常规标签,另一类是 MIP 自定义标签。MIP 标签也被称作 MIP-HTML 组件,使用它们来替代 HTML 常规标签可以大幅提升页面性能。 46 | 47 | 例如:`` 标签,它使得图片只在需要时才进行加载,减少了页面渲染时间,节省了用户的流量。 48 | 49 | 阅读 [MIP-HTML 规范](./specs) 了解更多信息。 50 | 51 | ## 2. MIP-JS 52 | 53 | MIP-JS 用于管理资源的加载,并支持上述 MIP 标签的使用,从而确保页面的快速渲染,提高页面各方面的性能。 54 | 55 | MIP-JS 最显著的优势是能够异步加载所有外部资源,整个页面渲染过程不会被页面中的某些元素阻塞,从而实现页面渲染速度的提升。 56 | 57 | 此外,MIP-JS 还涵盖了所有 iframe 的沙盒、在资源加载前提前计算页面元素布局、禁用缓慢 CSS 选择器等技术性能。 58 | 59 | ## 3. MIP-Cache 60 | 61 | MIP-Cache 是一套基于代理的 CDN(Content Delivery Network) 缓存系统,可用于缓存所有被百度相关页面引用或者从百度相关服务点出的 MIP 页面。 62 | 63 | 用户在访问 MIP 页面的时候,请求首先会发到 CDN 服务器,如果页面存在,则从 CDN 返回,如果 CDN 上不存在,则会请求第三方服务器。同时 MIP-Cache 服务器会将页面缓存到 CDN 上。在使用 MIP-Cache 时,MIP 页面所需要的所有静态文件和外部资源都会被缓存到 CDN 上(视频除外),并且页面中的资源链接会被转换成相对地址,很大程度上提升了页面渲染速度。每一个 MIP 页面都会绑定一个验证系统,在页面进行渲染时,这种验证器可以直接在浏览器控制台中输出页面的错误。并且随着代码逻辑的变化,能够展示其对页面性能以及用户体验的影响。 64 | 65 | 阅读 [MIP-Cache](./mip-cache) 规范 了解更多信息。 66 | -------------------------------------------------------------------------------- /docs/basic/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /docs/basic/mip-cache.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/docs/basic/mip-cache.md -------------------------------------------------------------------------------- /docs/basic/principle-of-mip.md: -------------------------------------------------------------------------------- 1 | # MIP 加速原理 2 | 3 | 本文档为您详细阐释 MIP 页面的加速原理。 4 | 5 | ## 经过精心设计的 JavaScript 6 | 7 | 为了去除臃肿的客户端脚本,MIP 文件不允许自定义 JavaScript 。对一些强依赖 JavaScript 的功能(如:广告、统计和交互),MIP 提供与 MIP Runtime 兼容封装好的组件来实现。 8 | 9 | JavaScript 引用原则: 10 | 11 | - 目前 MIP 不允许用户自定义 JavaScript ,需要用 MIP 组件的形式引进来,从而确保安全性和性能表现。 12 | - 可以引用 [`组件`](/examples/mip/mip-iframe.html) 来引入实现部分富交互的功能,这样,即使开发时使用最影响性能的 `document.write()` ,也不会影响主页面的渲染。 13 | - MIP 组件是开源的,允许开发者自定义功能组件,项目也将持续提供多样的组件,以适应不同的需求。 14 | 15 | ## 所有静态资源需要标明尺寸 16 | 17 | 在页面开发时,资源常常不会被设定宽高,特别是使用广告或者通过调用 `document.write()` 注入的时候。由于资源大小不能确定,页面经常要进行反复重新的绘制。 18 | 19 | 现在,MIP 要求将所有的资源(广告、图片、音频和视频)标明尺寸。当资源真正加载时,所有资源大小可以被立即推断出并迅速的用于计算页面布局,加载中的资源将无缝呈现,不必因为页面频繁更新布局而影响到用户的阅读体验。 20 | 21 | ## 不允许任何机制阻止页面渲染 22 | 23 | 开发者的任何自定义脚本,都需要用 MIP 的标题反馈给 MIP,例如 ``、`` 等,这些方式不会阻塞页面的 `layout` 和渲染。 24 | 25 | ## 控制外部资源加载 26 | 27 | MIP Runtime 会控制外部资源的按需加载来确保其高效性,从而使用户想阅读的内容尽快出现在屏幕中。 28 | 29 | ## 封装交互功能 30 | 31 | MIP 提倡网页能给用户直接简单的体验,但这并不意味着 MIP 限制了页面的生动和有趣。MIP Runtime 提供了高度优化的被封装的 JavaScript ,开发者无需投入过多精力去实现复杂的交互功能。 32 | 33 | ## 建议使用 inline 的 CSS 34 | 35 | CSS 的加载,会阻止页面的渲染,CSS 内联可以减少客户端的开销。 36 | 37 | ## 只允许 GPU 加速的动画 38 | 39 | MIP 只允许用 `transforms` 和 `opacity` 来完成动画效果,当动画能在 GPU 上执行时,仅触发渲染层合并。 40 | 41 | ## MIP 缓存 42 | 43 | MIP 另一个重要的意义在于能够帮站长加速网页,[MIP-Cache](/doc/2-tech/3-mip-cache.html) 将会把 MIP 网页缓存到百度 CDN 中。只要符合 MIP 标准,都可以使用 MIP 缓存。 44 | 45 | ## 开放且持续更新 46 | 47 | MIP 是一个开源项目,所有的标准并非一成不变。我们会持续不断进行优化,期待您的共同参与! 48 | -------------------------------------------------------------------------------- /docs/cli/cli-usage.md: -------------------------------------------------------------------------------- 1 | # mip2 命令行工具 2 | 3 | mip2 CLI 是官方提供的命令行工具,它提供了脚手架、调试、预览、校验、构建等功能,方便开发者快速开发 MIP 页面及自定义组件。 4 | 5 | 6 | ## 1. 依赖环境 7 | 8 | mip2 CLI 使用 NPM 安装,依赖 Node 环境,推荐 9 | 10 | - [Node.js](https://nodejs.org/) (>=8.x) 11 | 12 | - [Git](https://git-scm.com/) 13 | 14 | 15 | ## 2. 安装 mip2 CLI 16 | 17 | 打开命令行工具,输入: 18 | 19 | ``` bash 20 | $ npm install -g mip2 21 | ``` 22 | 23 | 输入 `mip2 -V`,若能正常显示版本号,说明已经安装成功。 24 | 25 | 26 | ## 3. mip2 CLI 使用 27 | 28 | ### 创建项目脚手架 29 | 30 | ``` bash 31 | $ mip2 init 32 | ``` 33 | 34 | 根据提示输入项目名 `myproject`,生成项目结构如下 35 | 36 | ``` 37 | myproject 38 | ├── common // 组件公用代码,如 utils 等 39 | ├── components // 组件目录,编写组件代码 40 | │   └── mip-example 41 | │   ├── mip-example.md // 组件功能、属性说明 42 | │   ├── mip-example.vue // 组件本身 43 | │   └── test 44 | │   └── mip-example.html // 单个组件测试、预览 45 | ├── mip.config.js // 调试服务器配置 46 | ├── package.json 47 | ├── static // 静态资源,如图片、字体 48 | └── test 49 | └── index.html // 页面测试预览 50 | ``` 51 | 52 | 我们可以在项目的 `components` 目录中开发站点所需的自定义组件,然后依据 `test/index.html` 页面模板,引用官方或自定义组件来实现 MIP 页面。 53 | 54 | 通常情况下,官方提供的[通用组件库](https://github.com/mipengine/mip2-extensions)已经能满足站点的基本需求。如果站点有使用复杂组件的场景,我们可以[编写自定义组件](./start-writing-first-mip.md),并通过第三方组件仓库(TODO)进行提交,通过审核上线后,即能使用。 55 | 56 | 同时,我们也欢迎开发者向官方通用组件库[贡献优秀的组件](./contribute-to-official-repo.md)。 57 | 58 | ### 启动调试服务器 59 | 60 | 命令行工具内置了简单的调试服务器,方便开发者调试组件和页面。在项目根目录运行 61 | 62 | ``` bash 63 | $ mip2 dev 64 | ``` 65 | 66 | 默认会在 `8111` 端口启动服务器,并自动调起浏览器打开 `test/index.html` ,实现预览和调试。在修改组件和页面的代码时,无需手动重启和刷新,服务器内部已经帮我们实现了这一功能。 67 | 68 | 了解详细用法:[调试组件](./component-testing.md) 69 | 70 | ### 组件和页面校验 71 | 72 | MIP 组件和页面都需要遵循特定的[开发规范](../components/rules.md),开发者提交站点 url 和组件代码时,系统会进行审核和校验。命令行工具提供了校验功能,方便我们在开发阶段就能按照相关规范进行开发和调整。 73 | 74 | ``` bash 75 | # 校验 mip 组件,输入组件目录 76 | $ mip2 validate -c ./components 77 | 78 | # 校验 mip 页面,输入页面路径 79 | $ mip2 validate -p page.html 80 | ``` 81 | 82 | 我们可以根据校验结果,对不符合规范的组件/页面进行相应的改进,校验通过后再进行提交。 83 | 84 | ### 构建组件 85 | 86 | 自定义组件开发完成后,可以使用 `mip2 build` 命令将组件代码打包成为对应的 `mip-组件名.js` 文件,供发布使用。 87 | 88 | 在项目根目录运行 89 | 90 | ``` bash 91 | $ mip2 build 92 | ``` 93 | 94 | 默认将在 /dist 目录产出打包压缩后的组件资源。了解详细用法:[组件构建](./component-development.md) 95 | -------------------------------------------------------------------------------- /docs/cli/component-development.md: -------------------------------------------------------------------------------- 1 | # 组件开发说明 2 | 3 | ## 内置编译工具 4 | 5 | mip-cli 的组件编译模块使用 webpack4 实现,内置了以下 loader: 6 | 7 | 1. vue-loader: 默认将 `.vue` 文件编译成 `.js`,` 13 | 14 | 15 | 16 | 17 |

event

18 | 19 | { 20 | "success": false 21 | } 22 | 23 | 24 | 25 | 26 | 27 | 28 |
div item
29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/event/mip-a.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-test.js 3 | * @author huanghuiquan (huanghuiquan@baidu.com) 4 | */ 5 | 6 | /* global mip */ 7 | 8 | 9 | 10 | mip.registerVueCustomElement('mip-a', { 11 | template: ` 12 |
13 | haha a 14 | 15 |
16 | 17 | click me 18 |
19 | `, 20 | created() { 21 | // 绑定事件,其它元素可通过 on='xxx' 触发 22 | // this.addEventAction('custom_event', function (event /* 对应的事件对象 */ , str /* 事件参数 */ ) { 23 | // console.log(str); // undefined or 'test_button' or 'test_button1' 24 | // }); 25 | }, 26 | // createdVue() { 27 | // console.log('createdvue'); 28 | // // this.addEventAction('customevent', function (event, str) { 29 | // // console.log('get customevent'); 30 | // // }); 31 | // }, 32 | methods: { 33 | onClick() { 34 | console.log('emit customevent'); 35 | this.$emit('customevent', {userInfo: 'huanghuiqun'}); 36 | // console.log('onClick'); 37 | // mip.viewer.eventAction.execute('eventName', null, {form: 'a'}); 38 | } 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /examples/event/mip-b.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-test.js 3 | * @author huanghuiquan (huanghuiquan@baidu.com) 4 | */ 5 | 6 | /* global mip */ 7 | 8 | 9 | 10 | mip.registerVueCustomElement('mip-b', { 11 | template: `
haha b
`, 12 | created() { 13 | console.log('b: created'); 14 | }, 15 | methods: { 16 | onClick() { 17 | console.log('onClick'); 18 | mip.viewer.eventAction.execute('eventName', null, { 19 | form: 'b' 20 | }); 21 | } 22 | } 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /examples/lifecycle/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Tom", 4 | "sex": "M" 5 | }, 6 | { 7 | "name": "Green", 8 | "sex": "F" 9 | }, 10 | { 11 | "name": "Jerry", 12 | "sex": "?" 13 | } 14 | ] -------------------------------------------------------------------------------- /examples/lifecycle/mip-list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-image 3 | * @author sfe 4 | */ 5 | 6 | /* global MIP */ 7 | 8 | MIP.mip.registerVueCustomElement('mip-list', { 9 | template: ` 10 |
11 |
12 |

{{ item.name }}

13 |

{{ item.sex }}

14 |
15 |
16 | `, 17 | data() { 18 | return { 19 | list: [ 20 | { 21 | name: 'David', 22 | sex: 'F' 23 | }, 24 | { 25 | name: 'Trump', 26 | sex: '?' 27 | } 28 | ] 29 | }; 30 | }, 31 | 32 | asyncData() { 33 | let me = this; 34 | fetch('./data.json').then(res => { 35 | res.json().then(data => { 36 | console.log(data); 37 | me.list = me.list.concat(data); 38 | }); 39 | }); 40 | }, 41 | 42 | syncData() { 43 | console.log('------------ in syncData'); 44 | }, 45 | 46 | initState() { 47 | console.log('------------ in initState'); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /examples/mip-bind/bind.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | MIP Bind 8 | 9 | 12 | 13 | 14 | 15 | 16 |

bind data

17 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/mip-extensions/README.md: -------------------------------------------------------------------------------- 1 | # Common mip elements 2 | 3 | Here is some common mip elements using by exapmles. 4 | -------------------------------------------------------------------------------- /examples/mip-extensions/mip-ad.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-image 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-ad', { 9 | template: ` 10 |
11 | 12 |
13 | `, 14 | props: { 15 | name: String 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /examples/mip-extensions/mip-cambrian.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-image 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-cambrian', { 9 | template: ` 10 |
11 | 12 |
13 | `, 14 | props: { 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /examples/mip-extensions/mip-lightbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-image 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-lightbox', { 9 | template: ` 10 |
11 | 12 |
13 | `, 14 | props: { 15 | src: String, 16 | alt: String 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /examples/mip-extensions/mip-sidebar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 侧边栏组件 3 | * 4 | * @author wangpei07@baidu.com, liangjiaying 5 | * @version 1.0 6 | * @copyright 2016 Baidu.com, Inc. All Rights Reserved 7 | */ 8 | /* global mip */ 9 | (function () { 10 | var util = mip.util; 11 | 12 | let template = ` 13 | div 14 | `; 15 | 16 | mip.registerVueCustomElement('mip-sidebar', { 17 | props: { 18 | id: String, 19 | layout: String, 20 | side: String 21 | }, 22 | template: template 23 | 24 | }); 25 | })(); 26 | 27 | -------------------------------------------------------------------------------- /examples/mip-extensions/mip-stats-baidu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-image 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-stats-baidu', { 9 | template: ` 10 |
11 | 12 |
13 | `, 14 | props: { 15 | src: String, 16 | alt: String 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /examples/mip/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MIP list demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/mip/mip-bind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-bind 3 | * @author sfe 4 | */ 5 | 6 | // 为了兼容 mip 1.0 的语法,可以使用独立 js 的方式提供双向绑定功能 7 | // mip 2.0 中建议在组件中处理所有的交互逻辑,尽量不要在 HTML 页面的 custom element 中处理交互相关的操作 8 | // mip-bind 主要是为了识别 dom 中的 m-bind, m-text, on 属性,配合 标签来做双向数据绑定 9 | -------------------------------------------------------------------------------- /examples/mip/mip-carousel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mip-carousel 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 26 | 27 | 28 |
这里是title2
29 |
30 | 31 | 32 | 33 | 34 | 41 | 42 |
43 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/mip/mip-iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mip-iframe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/mip/mip-img.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mip-img 8 | 9 | 10 | 11 | 12 | 25 | 26 | 27 |
28 |

normal

29 | 30 | 31 |

layout - responsive

32 | 33 |

layout - fixed-height

34 | 35 | 36 | 37 |

layout - fill

38 | 39 |
40 | 41 |
42 | 43 | 44 |

layout - container

45 | 46 | 47 | 48 |

layout - fixed

49 | 50 | 51 | 52 | 53 |
54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/mip/mip-list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-list demo for compatible mip 1.0 3 | * @author mj(zoumiaojiang@gmail.com) 4 | */ 5 | 6 | /* global mip */ 7 | 8 | function mockMustacheRender(template, data) { 9 | return template.replace(/\{\{(\w+)\}\}/g, item => data[item.replace(/[\{\}]/ig, '')]); 10 | } 11 | 12 | mip.registerVueCustomElement('mip-list', { 13 | template: ` 14 |
15 |
21 | 22 |
23 | `, 24 | props: ['items'], 25 | data() { 26 | return { 27 | tdContents: [] 28 | }; 29 | }, 30 | 31 | mounted() { 32 | let me = this; 33 | let templateStr = ''; 34 | let template = this.$el.querySelector('[type="mip-mustache"]'); 35 | 36 | if (template) { 37 | templateStr = template.innerHTML; 38 | template.remove(); 39 | } 40 | 41 | if (templateStr) { 42 | this.items.forEach((item, index) => { 43 | // This place, you can use real mustache lib 44 | let content = mockMustacheRender(templateStr, item); 45 | mip.Vue.set(me.tdContents, index, content); 46 | }); 47 | } 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /examples/mip/mip-tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-tree 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-tree', { 9 | template: ` 10 |
  • 11 |
    12 | {{ model.name }} 13 | [{{ open ? '-' : '+' }}] 14 |
    15 |
      16 | 22 |
    • +
    • 23 |
    24 | 25 |
  • 26 | `, 27 | props: { 28 | model: { 29 | default() { 30 | return {}; 31 | }, 32 | type: Object 33 | } 34 | }, 35 | data() { 36 | return { 37 | open: false 38 | }; 39 | }, 40 | computed: { 41 | isFolder() { 42 | return this.model.children && this.model.children.length; 43 | } 44 | }, 45 | methods: { 46 | toggle() { 47 | if (this.isFolder) { 48 | this.open = !this.open; 49 | } 50 | }, 51 | changeType() { 52 | if (!this.isFolder) { 53 | mip.Vue.set(this.model, 'children', []); 54 | this.addChild(); 55 | this.open = true; 56 | } 57 | }, 58 | addChild() { 59 | this.model.children.push({ 60 | name: 'new stuff' 61 | }); 62 | }, 63 | 64 | stringify(data) { 65 | return JSON.stringify(data); 66 | } 67 | } 68 | }); 69 | -------------------------------------------------------------------------------- /examples/mip/mip-video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mip-video 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | dfasdf 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/mip/props.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | props 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 35 | 36 | 43 | 44 | 45 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /examples/mip/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MIP template demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 |
    hello, {{ name }}
    22 |
    23 | 24 | 25 | 26 | 34 |
    35 | hello from mip-template, {{ data.name }} 36 |
    37 |
    38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/page/components/mip-complevel1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-complevel1 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-complevel1', { 9 | template: ` 10 |
    11 | 12 |

    click me to change name:{{userinfo.name}}

    13 |
    14 | `, 15 | props: { 16 | userinfo: { 17 | default() { 18 | return {}; 19 | }, 20 | type: Object 21 | } 22 | }, 23 | updated() { 24 | }, 25 | methods: { 26 | changeData() { 27 | this.$emit('test-event', {name:'sfe-1'}); 28 | } 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /examples/page/components/mip-complevel2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-complevel2 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-complevel2', { 9 | template: ` 10 |

    {{userinfo.name}}

    11 | `, 12 | props: { 13 | userinfo: { 14 | default() { 15 | return {}; 16 | }, 17 | type: Object 18 | } 19 | }, 20 | updated() { 21 | // console.log(this.userinfo) 22 | }, 23 | methods: { 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /examples/page/components/mip-tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-tree 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-tree', { 9 | template: ` 10 |
  • 11 |
    12 | {{ model.name }} 13 | [{{ open ? '-' : '+' }}] 14 |
    15 |
      16 | 22 |
    • +
    • 23 |
    24 | 25 |
  • 26 | `, 27 | props: { 28 | model: { 29 | default() { 30 | return {}; 31 | }, 32 | type: Object 33 | } 34 | }, 35 | data: function () { 36 | return { 37 | open: false 38 | }; 39 | }, 40 | computed: { 41 | isFolder: function () { 42 | return this.model.children && this.model.children.length; 43 | } 44 | }, 45 | methods: { 46 | toggle: function () { 47 | if (this.isFolder) { 48 | this.open = !this.open; 49 | } 50 | }, 51 | changeType: function () { 52 | if (!this.isFolder) { 53 | mip.Vue.set(this.model, 'children', []); 54 | this.addChild(); 55 | this.open = true; 56 | } 57 | }, 58 | addChild: function () { 59 | this.model.children.push({ 60 | name: 'new stuff' 61 | }); 62 | }, 63 | 64 | stringify(data) { 65 | return JSON.stringify(data); 66 | } 67 | } 68 | }); 69 | -------------------------------------------------------------------------------- /examples/page/data/testData.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabs": [ 3 | "one", 4 | "two", 5 | "three" 6 | ] 7 | } -------------------------------------------------------------------------------- /examples/page/not-mip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Not MIP 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    This page is not a MIP page!

    12 |

    Rollup is a module bundler for JavaScript which compiles small pieces of code into something larger and more complex, such as a library or application. It uses the new standardized format for code modules included in the ES6 revision of JavaScript, instead of previous idiosyncratic solutions such as CommonJS and AMD. ES6 modules let you freely and seamlessly combine the most useful individual functions from your favorite libraries. This will eventually be possible natively, but Rollup lets you do it today.

    13 | 14 | Back to Index 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/page/tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MIP Tree Page 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 | 46 | 47 | 48 |

    This is Page#2, using different component (mip-tree)

    49 | 50 | 53 | 54 | 55 | Back to Index 56 | 57 |

    58 | Read from global store(group.info.name) from index.html 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/sandbox/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sandbox 5 | 6 | 14 | 15 |

    查看控制台的打印结果

    16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/sandbox/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let {window, document} = mip.sandbox; 3 | let { 4 | alert, 5 | close, 6 | confirm, 7 | prompt, 8 | setTimeout, 9 | setInterval, 10 | self, 11 | top, 12 | parent, 13 | customElements 14 | } = window; 15 | 16 | // alert('test'); 17 | // window.alert('test'); 18 | // confirm('a'); 19 | // document.createElement('a'); 20 | // document.createElementNS('a'); 21 | // document.write('a'); 22 | // document.writeln('a'); 23 | 24 | console.log( 25 | '在沙盒环境中使用一下属性/API的取值如下:', '\n', 26 | 'window: ', window, '\n', 27 | 'self: ', self, '\n', 28 | 'top: ', top, '\n', 29 | 'document: ', document, '\n', 30 | 'window.document: ', window.document, '\n', 31 | 'alert: ', alert, '\n', 32 | 'close: ', close, '\n', 33 | 'confirm: ', confirm, '\n', 34 | 'prompt: ', prompt, '\n', 35 | 'parent: ', parent, '\n', 36 | 'customElements', customElements, '\n', 37 | 'document.createElement: ', document.createElement, '\n', 38 | 'document.createElementNS: ', document.createElementNS, '\n', 39 | 'document.write: ', document.write, '\n', 40 | 'document.writeln: ', document.writeln, 41 | 'document.registerElement: ', document.registerElement 42 | ); 43 | 44 | setTimeout(function () { 45 | console.log('我是setTimeout中使用的this:', this); 46 | }); 47 | 48 | let interval = setInterval(function () { 49 | console.log('我是setInterval中使用的this:', this); 50 | }, 1000); 51 | 52 | setTimeout(function () { 53 | console.log('清除interval任务'); 54 | clearInterval(interval); 55 | }, 2000); 56 | 57 | })(); 58 | 59 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file examples server using webpack 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | const express = require('express'); 7 | const rewrite = require('express-urlrewrite'); 8 | const proxy = require('http-proxy-middleware'); 9 | const webpack = require('webpack'); 10 | const webpackDevMiddleware = require('webpack-dev-middleware') 11 | const WebpackConfig = require('../build/webpack.config.dev'); 12 | 13 | 14 | const app = express(); 15 | const path = require('path'); 16 | 17 | app.use(webpackDevMiddleware(webpack(WebpackConfig), { 18 | publicPath: '/dist/', 19 | stats: { 20 | colors: true, 21 | chunks: false 22 | } 23 | })); 24 | 25 | app.use(express.static(path.join(__dirname, '../'))); 26 | 27 | // wecoffee api proxy 28 | app.use('/api/store', proxy({ 29 | target: 'https://weecoffee-lighthouse.oott123.com', 30 | changeOrigin: true 31 | })); 32 | 33 | const port = process.env.PORT || 8080; 34 | 35 | module.exports = app.listen(port, () => { 36 | console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`); 37 | console.log(`View http://localhost:${port}/examples/page/index.html`); 38 | }); 39 | -------------------------------------------------------------------------------- /examples/server.rollup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file examples server 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | const express = require('express'); 7 | const rewrite = require('express-urlrewrite'); 8 | 9 | const app = express(); 10 | const path = require('path'); 11 | 12 | app.use(express.static(path.join(__dirname, '../'))); 13 | 14 | const port = process.env.PORT || 8080 15 | module.exports = app.listen(port, () => { 16 | console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`); 17 | console.log(`View http://localhost:${port}/examples/page/index.html`); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/simple/mip-slide.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-slide 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-slide', { 9 | template: ` 10 |
    11 | 12 |

    ----- :) ------

    13 |
    14 | 17 | 18 |
    19 | `, 20 | data() { 21 | return { 22 | show: true 23 | }; 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /examples/simple/mip-tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file demo data 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | // define the item component 9 | mip.registerVueCustomElement('mip-item', { 10 | template: '#item-template', 11 | props: { 12 | model: { 13 | default() { 14 | return {}; 15 | }, 16 | type: Object 17 | } 18 | }, 19 | data() { 20 | return { 21 | open: false 22 | }; 23 | }, 24 | computed: { 25 | ...mip.Store.mapState('test', ['count']), 26 | isFolder() { 27 | return this.model.children && this.model.children.length; 28 | } 29 | }, 30 | methods: { 31 | ...mip.Store.mapMutations('test', ['addCount']), 32 | toggle() { 33 | this.addCount(); 34 | console.log(this.count); 35 | if (this.isFolder) { 36 | this.open = !this.open; 37 | } 38 | }, 39 | changeType() { 40 | if (!this.isFolder) { 41 | mip.Vue.set(this.model, 'children', []); 42 | this.addChild(); 43 | this.open = true; 44 | } 45 | }, 46 | addChild() { 47 | this.model.children.push({ 48 | name: 'new stuff' 49 | }); 50 | }, 51 | 52 | stringify(data) { 53 | return JSON.stringify(data); 54 | } 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /examples/simple/storeData.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define([], factory); 4 | } else if (typeof exports === 'object') { 5 | module.exports = factory(); 6 | } else { 7 | root.storeData = factory(); 8 | } 9 | }(this, function () { 10 | return { 11 | modules: { 12 | test: { 13 | namespaced: true, 14 | state() { 15 | return { 16 | count: 47 17 | }; 18 | }, 19 | mutations: { 20 | addCount(state) { 21 | state.count += 1; 22 | } 23 | } 24 | } 25 | } 26 | }; 27 | })); 28 | -------------------------------------------------------------------------------- /examples/static/fonts/MaterialIcons-Regular.570eb838.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/examples/static/fonts/MaterialIcons-Regular.570eb838.woff2 -------------------------------------------------------------------------------- /examples/static/fonts/fontawesome-webfont.97493d3f.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/examples/static/fonts/fontawesome-webfont.97493d3f.woff2 -------------------------------------------------------------------------------- /examples/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/examples/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /examples/store/components/mip-contacts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file demo mip-contacts component 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | /* global mip */ 7 | mip.registerVueCustomElement('mip-contacts', { 8 | template: ` 9 |
    10 |
    hello {{group.info.name}}!
    11 |
      12 |
    • 13 |

      username: {{c.username}}

      14 |

      gender: {{c.gender}}

      15 |
    • 16 |
    17 |
      18 |
    • 19 |

      tab:{{t}}

      20 |
    • 21 |
    22 |
    23 | `, 24 | computed: { 25 | ...mip.Store.mapState('global', [ 26 | 'tabs', 27 | 'group', 28 | 'contacts', 29 | 'users' 30 | ]) 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /examples/store/components/mip-hello.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file demo mip-hello component 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | /* global mip */ 7 | mip.registerVueCustomElement('mip-hello', { 8 | template: '

    click me to get contacts data

    ', 9 | methods: { 10 | ...mip.Store.mapMutations('global', ['setData']), 11 | 12 | getData() { 13 | fetch('/examples/store/testdata2.json', { 14 | credentials: 'include' 15 | }).then(res => { 16 | if (res.ok) { 17 | res.json().then(data => { 18 | this.setData(data); 19 | }); 20 | } 21 | else { 22 | console.error('Fetch request failed!'); 23 | } 24 | }).catch(function (e) { 25 | console.error(e); 26 | }); 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /examples/store/components/mip-init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-init 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | let count = 0; 9 | 10 | mip.registerVueCustomElement('mip-init', { 11 | template: ` 12 |
    13 |

    moduleName: {{moduleName}}

    14 |
      15 |
    • {{name}}
    • 16 |
    17 |

    click me to change moduleNam to: test

    18 |
    19 | `, 20 | props: { 21 | src: String, 22 | model: { 23 | default() { 24 | return {}; 25 | }, 26 | type: Object 27 | } 28 | }, 29 | initStore() { 30 | return { 31 | namespace: 'mip/init', 32 | module: { 33 | state() { 34 | return { 35 | moduleName: 'mipInit', 36 | tabs: [ 37 | { 38 | name: 'tab1' 39 | }, 40 | { 41 | name: 'tab2' 42 | }, 43 | { 44 | name: 'tab3' 45 | } 46 | ] 47 | }; 48 | }, 49 | mutations: { 50 | ['CHANGE_NAME'](state, name) { 51 | state.moduleName = name; 52 | } 53 | }, 54 | actions: { 55 | changeName({commit}, name) { 56 | commit('CHANGE_NAME', name + (count++)); 57 | } 58 | }, 59 | getters: { 60 | tabNames(state) { 61 | return state.tabs.map(t => t.name); 62 | } 63 | } 64 | } 65 | }; 66 | }, 67 | computed: { 68 | ...mip.Store.mapState('mip/init', ['moduleName']), 69 | ...mip.Store.mapGetters('mip/init', ['tabNames']) 70 | }, 71 | 72 | methods: { 73 | ...mip.Store.mapActions('mip/init', [ 74 | 'changeName' 75 | ]) 76 | } 77 | }); 78 | -------------------------------------------------------------------------------- /examples/store/components/mip-store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-store 3 | * @author sfe 4 | */ 5 | 6 | /* global mip */ 7 | 8 | mip.registerVueCustomElement('mip-store', { 9 | template: '', 10 | props: { 11 | src: String, 12 | model: { 13 | default() { 14 | return {}; 15 | }, 16 | type: Object 17 | } 18 | }, 19 | created() { 20 | if (this.src) { 21 | this.getDataBySrc(this.src); 22 | } 23 | else if (typeof this.model === 'object' && Object.keys(this.model).length !== 0) { 24 | this.register(this.model); 25 | } 26 | }, 27 | methods: { 28 | getDataBySrc(url) { 29 | if (!url) { 30 | return {}; 31 | } 32 | 33 | fetch(this.src, { 34 | credentials: 'include' 35 | }).then(res => { 36 | if (res.ok) { 37 | res.json().then(data => { 38 | this.register(data); 39 | }); 40 | } 41 | else { 42 | console.error('Fetch request failed!'); 43 | } 44 | }).catch(function (e) { 45 | console.error(e); 46 | }); 47 | }, 48 | 49 | register(data) { 50 | if (this.$store.state.global) { 51 | data = Object.assign({}, this.$store.state.global, data); 52 | } 53 | this.$store.registerModule('global', { 54 | state: () => data, 55 | mutations: { 56 | setData(state, data) { 57 | state = Object.assign(state, data); 58 | } 59 | }, 60 | namespaced: true 61 | }); 62 | 63 | this.eventEmit(); 64 | }, 65 | 66 | eventEmit() { 67 | let globalStoreRegisteredEvent = mip.util.event.create('global-store-registered'); 68 | document.dispatchEvent(globalStoreRegisteredEvent); 69 | } 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /examples/store/storeData.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | (function (root, factory) { 3 | if (typeof define === 'function' && define.amd) { 4 | define([], factory); 5 | }else if (typeof exports === 'object') { 6 | module.exports = factory(); 7 | } else { 8 | root.storeData = factory(); 9 | } 10 | }(this, function () { 11 | return { 12 | modules: { 13 | test: { 14 | namespaced: true, 15 | state() { 16 | return { 17 | count: 47 18 | }; 19 | }, 20 | mutations: { 21 | addCount(state) { 22 | state.count += 1; 23 | } 24 | } 25 | } 26 | } 27 | }; 28 | })); 29 | -------------------------------------------------------------------------------- /examples/store/testdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabs": [ 3 | "one", 4 | "two", 5 | "three" 6 | ] 7 | } -------------------------------------------------------------------------------- /examples/store/testdata2.json: -------------------------------------------------------------------------------- 1 | { 2 | "contacts": [ 3 | { 4 | "username": "ckkk1", 5 | "gender": "m" 6 | }, 7 | { 8 | "username": "ckkk2", 9 | "gender": "f" 10 | }, 11 | { 12 | "username": "ckkk3", 13 | "gender": "f" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js Builtins register 3 | * @author zhangzhiqiang(zhiqiangzhang37@163.com) 4 | */ 5 | 6 | import MipImg from './mip-img'; 7 | import MipVideo from './mip-video'; 8 | import MipCarousel from './mip-carousel'; 9 | import MipIframe from './mip-iframe'; 10 | import MipPix from './mip-pix'; 11 | import MipBind from './mip-bind/bind'; 12 | import registerElement from '../register-element'; 13 | 14 | export default { 15 | 16 | /** 17 | * Register the builtin components. 18 | */ 19 | register() { 20 | registerElement('mip-pix', MipPix); 21 | registerElement('mip-img', MipImg); 22 | registerElement('mip-carousel', MipCarousel); 23 | registerElement('mip-iframe', MipIframe); 24 | registerElement('mip-video', MipVideo); 25 | // registerElement('mip-bind', MipBind); 26 | new MipBind(); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/mip-bind/deps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file deps.js 3 | * @author huanghuiquan (huanghuiquan@baidu.com) 4 | */ 5 | 6 | // Record watcher id, avoid add repeatly 7 | let uid = 0; 8 | 9 | class Deps { 10 | constructor() { 11 | this.subs = []; 12 | this.id = uid++; 13 | } 14 | 15 | addWatcher() { 16 | Deps.target.addWatcher(this); 17 | } 18 | 19 | notify() { 20 | this.subs.forEach(function (sub) { 21 | sub.update(); 22 | }); 23 | } 24 | 25 | update(watcher) { 26 | watcher && watcher.update && watcher.update(); 27 | } 28 | } 29 | 30 | export default Deps; 31 | -------------------------------------------------------------------------------- /src/components/mip-bind/mip-data.js: -------------------------------------------------------------------------------- 1 | 2 | import CustomElement from '../../custom-element'; 3 | // import Bind from './bind'; 4 | 5 | let uid = 0; 6 | 7 | class MipData extends CustomElement { 8 | build() { 9 | // this.uid = uid++; 10 | // this.bind = new Bind(this.uid); 11 | 12 | let src = this.element.getAttribute('src'); 13 | let ele = this.element.querySelector('script[type="application/json"]'); 14 | if (src) { 15 | this.getData(src); 16 | } 17 | else if (ele) { 18 | let data = ele.textContent.toString(); 19 | let result; 20 | try { 21 | result = JSON.parse(data); 22 | } 23 | catch (e) {} 24 | if (result) { 25 | // window.m = window.m ? window.m : {}; 26 | // MIP.$set(result, 0); 27 | this.postMessage(result); 28 | } 29 | } 30 | } 31 | 32 | getData(url) { 33 | if (!url) { 34 | return; 35 | } 36 | 37 | fetch(url, { 38 | credentials: 'include' 39 | }).then(res => { 40 | if (res.ok) { 41 | res.json().then(data => this.postMessage(data)); 42 | } 43 | else { 44 | console.error('Fetch request failed!'); 45 | } 46 | }).catch(console.error); 47 | } 48 | 49 | postMessage(data) { 50 | window.m = window.m ? window.m : {}; 51 | let loc = window.location; 52 | let domain = loc.protocol + '//' + loc.host; 53 | window.postMessage({ 54 | // type: 'bind' + this.uid, 55 | type: 'bind', 56 | m: data 57 | }, domain); 58 | } 59 | 60 | prerenderAllowed() { 61 | return true; 62 | } 63 | } 64 | 65 | export default MipData; 66 | -------------------------------------------------------------------------------- /src/components/mip-bind/observer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file observer.js 3 | * @author huanghuiquan (huanghuiquan@baidu.com) 4 | */ 5 | 6 | import Deps from './deps'; 7 | 8 | class Observer { 9 | _walk(data) { 10 | if (!data || typeof data !== 'object') { 11 | return; 12 | } 13 | 14 | for (let key in data) { 15 | this._define(data, key, data[key]); 16 | } 17 | } 18 | 19 | _define(data, key, value) { 20 | // // if value has observed, stop it 21 | // if (value && value.__ob__) { 22 | // return; 23 | // } 24 | 25 | // if value is object, define it's value 26 | let me = this; 27 | if (value && typeof value === 'object') { 28 | this.start(value); 29 | } 30 | 31 | let property = Object.getOwnPropertyDescriptor(data, key); 32 | if (property && property.configurable === false) { 33 | return; 34 | } 35 | let getter = property && property.get; 36 | let setter = property && property.set; 37 | 38 | let deps = new Deps(); 39 | Object.defineProperty(data, key, { 40 | enumerable: true, 41 | configurable: true, 42 | get() { 43 | value = getter ? getter.call(data) : value; 44 | if (Deps.target) { 45 | deps.addWatcher(); 46 | } 47 | return value; 48 | }, 49 | set(newVal) { 50 | value = getter ? getter.call(data) : value; 51 | if (newVal === value) { 52 | return; 53 | } 54 | if (setter) { 55 | setter.call(data, newVal); 56 | } 57 | else { 58 | value = newVal; 59 | } 60 | me._walk(newVal); 61 | deps.notify(); 62 | } 63 | }); 64 | 65 | // try { 66 | // value.__ob__ = this; 67 | // } 68 | // catch (e) {} 69 | } 70 | 71 | start(data) { 72 | this._walk(data); 73 | } 74 | } 75 | 76 | export default Observer; 77 | -------------------------------------------------------------------------------- /src/components/mip-bind/watcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file watcher.js 3 | * @author huanghuiquan (huanghuiquan@baidu.com) 4 | */ 5 | 6 | import Deps from './deps'; 7 | 8 | class Watcher { 9 | constructor(node, data, dir, exp, cb) { 10 | this._data = data; 11 | this._dir = dir; 12 | this._exp = exp; 13 | this._node = node; 14 | this._depIds = {}; 15 | if (typeof exp === 'function') { 16 | this._getter = exp; 17 | } 18 | else { 19 | let fn = this.getWithResult.bind(this, exp); 20 | this._getter = fn.call(this._data); 21 | } 22 | this._cb = cb; 23 | this._value = this._get(); 24 | } 25 | 26 | getWithResult(exp) { 27 | return new Function((`with(this){try {return ${exp}} catch(e) {}}`)); 28 | } 29 | 30 | update() { 31 | let newVal = this._get(); 32 | let oldVal = this._value; 33 | if (newVal !== oldVal) { 34 | this._value = newVal; 35 | if (this._dir) { 36 | this._cb.call(this._data, this._dir, newVal, oldVal); 37 | } 38 | else { 39 | this._cb.call(this._data, newVal, oldVal); 40 | } 41 | } 42 | } 43 | 44 | _get() { 45 | let value; 46 | Deps.target = this; 47 | if (this._getter) { 48 | value = this._getter.call(this._data, this._data); 49 | } 50 | Deps.target = null; 51 | return value; 52 | } 53 | 54 | addWatcher(dep) { 55 | if (!this._depIds.hasOwnProperty(dep.id)) { 56 | dep.subs.push(this); 57 | this._depIds[dep.id] = dep; 58 | } 59 | } 60 | 61 | teardown() { 62 | for (let key of Object.keys(this._depIds)) { 63 | this._depIds[key].subs = []; 64 | } 65 | } 66 | } 67 | 68 | export default Watcher; 69 | -------------------------------------------------------------------------------- /src/components/mip-iframe.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-iframe 3 | * @author zhangzhiqiang(zhiqiangzhang37@163.com) 4 | */ 5 | 6 | import util from '../util'; 7 | import CustomElement from '../custom-element'; 8 | 9 | let attrList = ['allowfullscreen', 'allowtransparency', 'sandbox']; 10 | 11 | class MipIframe extends CustomElement { 12 | 13 | build() { 14 | let element = this.element; 15 | let src = element.getAttribute('src'); 16 | let srcdoc = element.getAttribute('srcdoc'); 17 | if (srcdoc) { 18 | src = 'data:text/html;charset=utf-8;base64,' + window.btoa(srcdoc); 19 | } 20 | 21 | let height = element.getAttribute('height'); 22 | let width = element.getAttribute('width') || '100%'; 23 | 24 | if (!src || !height) { 25 | return; 26 | } 27 | 28 | let iframe = document.createElement('iframe'); 29 | iframe.frameBorder = '0'; 30 | iframe.scrolling = 'no'; 31 | util.css(iframe, { 32 | width, 33 | height 34 | }); 35 | 36 | this.applyFillContent(iframe); 37 | iframe.src = src; 38 | 39 | this.expendAttr(attrList, iframe); 40 | element.appendChild(iframe); 41 | } 42 | 43 | } 44 | 45 | export default MipIframe; 46 | -------------------------------------------------------------------------------- /src/components/mip-pix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mip-pix 统计组件 3 | * @author baidu-authors, liangjiaying 4 | */ 5 | 6 | import util from '../util'; 7 | import CustomElement from '../custom-element'; 8 | 9 | /** 10 | * 替换请求链接中的参数 11 | * 12 | * @param {string} src 用户填写在mip-pix中的src 13 | * @param {string} paraName key, 如"title" 14 | * @param {string} paraVal value, 如当前时间戳 15 | * @return {string} url 16 | */ 17 | function addParas(src, paraName, paraVal) { 18 | let paraNameQ = new RegExp('\\$?{' + paraName + '}', 'g'); 19 | if (src.search(paraNameQ) > -1) { 20 | return src.replace(paraNameQ, paraVal); 21 | } 22 | src += src.indexOf('?') > -1 ? '&' : '?'; 23 | return src + paraName + '=' + paraVal; 24 | } 25 | 26 | /** 27 | * 从body获取mip-expeirment实验分组 28 | * 29 | * @param {string} attr 实验名 30 | * @return {string} 实验分组 31 | */ 32 | function getBodyAttr(attr) { 33 | let body = document.getElementsByTagName('body')[0]; 34 | return body.getAttribute(attr) || 'default'; 35 | } 36 | 37 | class MipPix extends CustomElement { 38 | 39 | firstInviewCallback() { 40 | // 获取统计所需参数 41 | let ele = this.element; 42 | let src = ele.getAttribute('src'); 43 | let host = window.location.href; 44 | let title = (document.querySelector('title') || {}).innerHTML || ''; 45 | let time = Date.now(); 46 | 47 | // 替换通用参数 48 | src = addParas(src, 'TIME', time); 49 | src = addParas(src, 'TITLE', encodeURIComponent(title)); 50 | src = addParas(src, 'HOST', encodeURIComponent(host)); 51 | 52 | // 增加对支持,获取实验分组 53 | let expReg = /MIP-X-((\w|-|\d|_)+)/g; 54 | let matchExpArr = src.match(expReg); 55 | for (let i in matchExpArr) { 56 | let matchExp = matchExpArr[i]; 57 | src = addParas(src, matchExp, getBodyAttr(matchExp)); 58 | } 59 | 60 | // 去除匹配失败的其餘{參數} 61 | src = src.replace(/\$?{.+?}/g, ''); 62 | // 去除其餘 '${', '{', '}' 確保輸出不包含 MIP 定义的语法 63 | src = src.replace(/\$?{|}/g, ''); 64 | 65 | // 创建请求img 66 | let image = new Image(); 67 | image.src = src; 68 | image.setAttribute('width', 0); 69 | image.setAttribute('height', 0); 70 | ele.setAttribute('width', ''); 71 | ele.setAttribute('height', ''); 72 | ele.appendChild(image); 73 | util.css(ele, {display: 'none'}); 74 | } 75 | 76 | } 77 | 78 | export default MipPix; 79 | -------------------------------------------------------------------------------- /src/log/coreTags.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file coreTags.js 3 | * @description 官方组件list, 之后有编译自动维护 4 | * @author schoeu 5 | */ 6 | 7 | export default [ 8 | 'mip-carousel', 9 | 'mip-iframe', 10 | 'mip-img', 11 | 'mip-pix', 12 | 'mip-video', 13 | 'mip-access', 14 | 'mip-accordion', 15 | 'mip-ad', 16 | 'mip-analytics', 17 | 'mip-anim', 18 | 'mip-app-banner', 19 | 'mip-appdl', 20 | 'mip-audio', 21 | 'mip-bind', 22 | 'mip-custom', 23 | 'mip-experiment', 24 | 'mip-filter', 25 | 'mip-fixed', 26 | 'mip-form', 27 | 'mip-gototop', 28 | 'mip-history', 29 | 'mip-infinitescroll', 30 | 'mip-install-serviceworker', 31 | 'mip-lightbox', 32 | 'mip-link', 33 | 'mip-list', 34 | 'mip-login-done', 35 | 'mip-map', 36 | 'mip-mustache', 37 | 'mip-nav-slidedown', 38 | 'mip-sample', 39 | 'mip-semi-fixed', 40 | 'mip-share', 41 | 'mip-showmore', 42 | 'mip-sidebar', 43 | 'mip-stats-baidu', 44 | 'mip-stats-bidu', 45 | 'mip-stats-cnzz', 46 | 'mip-vd-baidu', 47 | 'mip-vd-tabs' 48 | ]; 49 | -------------------------------------------------------------------------------- /src/log/logSend.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @file logSend.js 4 | * @description 数据上报处理 5 | * @author schoeu 6 | */ 7 | 8 | export default { 9 | data: {}, 10 | 11 | /** 12 | * 数据上报逻辑 13 | * 14 | * @param {*} type type 15 | * @param {*} msg msg 16 | */ 17 | sendLog(type, msg = {}) { 18 | if (!type) { 19 | return; 20 | } 21 | msg.type = type; 22 | this.data.event = 'log'; 23 | this.data.data = msg || {}; 24 | 25 | if (window !== window.top) { 26 | window.parent.postMessage(this.data, '*'); 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/log/monitor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file monitor.js 3 | * @description 监控数据监控处理 4 | * @author schoeu 5 | */ 6 | 7 | const ls = require('./logSend'); 8 | let tags = require('./coreTags'); 9 | const RATE = 0.1; 10 | 11 | if (!Array.isArray(tags)) { 12 | tags = []; 13 | } 14 | 15 | tags = tags.filter((it = '') => !!it.trim()); 16 | 17 | /** 18 | * MIP错误捕获处理 19 | * 20 | * @param {Object} e 错误事件对象 21 | */ 22 | function errorHandle(e = {}) { 23 | // 报错文件请求路径, 跨域js文件中错误无信息暂不上报 24 | let filename = e.filename || ''; 25 | 26 | if (!filename) { 27 | return; 28 | } 29 | 30 | // 错误信息 31 | let message = e.message || ''; 32 | 33 | // 错误行号 34 | let lineno = e.lineno || ''; 35 | 36 | // 错误列号 37 | let colno = e.colno || 0; 38 | 39 | // 非百度cnd域名忽略 40 | if (!/(c\.mipcdn|mipcache\.bdstatic)\.com\/static\/v1/.test(filename)) { 41 | return; 42 | } 43 | 44 | let tagInfo = /\/(mip-.+)\//g.exec(filename) || []; 45 | let tagName = tagInfo[1] || ''; 46 | let sampling = Math.random() <= RATE; 47 | 48 | // 只记录官方组件错误 49 | if (tags.indexOf(tagName) > -1 && sampling) { 50 | // 数据处理 51 | let logData = { 52 | file: filename, 53 | msg: message, 54 | ln: lineno, 55 | col: colno || (window.event && window.event.errorCharacter) || 0, 56 | href: window.location.href 57 | }; 58 | setTimeout(() => ls.sendLog('mip-stability', logData), 0); 59 | // 其他善后处理 60 | } 61 | } 62 | 63 | window.removeEventListener('error', errorHandle); 64 | window.addEventListener('error', errorHandle); 65 | -------------------------------------------------------------------------------- /src/page/appshell/loading.js: -------------------------------------------------------------------------------- 1 | export default class Loading { 2 | constructor(options = {}) { 3 | this.$wrapper = options.wrapper || document.body; 4 | this.$el = null; 5 | this.data = options.data; 6 | 7 | this.value = 0; 8 | this.width = 4; 9 | this.size = 32; 10 | this.calculatedSize = Number(this.size); 11 | this.radius = 20; 12 | this.viewBoxSize = this.radius / (1 - this.width / this.size); 13 | this.circumference = 2 * Math.PI * this.radius; 14 | this.normalizedValue = Math.max(0, Math.min(this.value, 100)); 15 | 16 | this.strokeDashArray = Math.round(this.circumference * 1000) / 1000; 17 | this.strokeDashOffset = ((100 - this.normalizedValue) / 100) * this.circumference + 'px'; 18 | this.strokeWidth = this.width / this.size * this.viewBoxSize * 2; 19 | } 20 | 21 | init() { 22 | this.$el = document.createElement('div'); 23 | this.$el.classList.add('mip-appshell-loading'); 24 | this.$el.innerHTML = this.render(this.data); 25 | this.$wrapper.appendChild(this.$el); 26 | } 27 | 28 | render(data) { 29 | return ` 30 |
    32 | 34 | ${this.genCircle('overlay', this.strokeDashOffset)} 35 | 36 |
    37 | `; 38 | } 39 | 40 | genCircle (name, offset) { 41 | return ` 42 | 50 | `; 51 | } 52 | 53 | show() { 54 | this.$el.classList.add('show'); 55 | } 56 | 57 | hide() { 58 | this.$el.classList.remove('show'); 59 | } 60 | } -------------------------------------------------------------------------------- /src/page/const/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file const 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | export const MIP_CONTAINER_ID = 'mip-router__app';// deprecated 7 | export const MIP_VIEW_ID = 'mip-router__view';// deprecated 8 | export const MIP_IFRAME_CONTAINER = 'mip-page__iframe'; 9 | export const MIP_CONTENT_IGNORE_TAG_LIST = [ 10 | 'mip-shell', 11 | 'iframe' 12 | ]; 13 | export const MIP_ERROR_ROUTE_PATH = '/mip-error'; 14 | export const DEFAULT_SHELL_CONFIG = { 15 | header: { 16 | title: '', 17 | logo: '', 18 | buttonGroup: [], 19 | show: false 20 | }, 21 | view: { 22 | isIndex: false, 23 | transition: { 24 | mode: 'slide', 25 | effect: 'slide-left', 26 | alwaysBackPages: [] 27 | } 28 | }, 29 | footer: {} 30 | }; 31 | 32 | export const MESSAGE_APPSHELL_REFRESH = 'appshell-refresh'; 33 | export const MESSAGE_APPSHELL_EVENT = 'appshell-event'; 34 | export const MESSAGE_ROUTER_PUSH = 'router-push'; 35 | export const MESSAGE_ROUTER_REPLACE = 'router-replace'; 36 | export const MESSAGE_ROUTER_FORCE = 'router-force'; -------------------------------------------------------------------------------- /src/page/router/index.js: -------------------------------------------------------------------------------- 1 | import HTML5History from './history'; 2 | import {createMatcher, normalizeLocation} from './matcher'; 3 | import {cleanPath} from '../util/path'; 4 | 5 | export default class Router { 6 | constructor(options) { 7 | this.options = options; 8 | this.matcher = createMatcher(options.routes || [], this); 9 | this.mode = 'history'; 10 | this.history = new HTML5History(this, ''); 11 | } 12 | 13 | init() { 14 | const history = this.history; 15 | 16 | let currentLocation = history.getCurrentLocation(); 17 | history.transitionTo(currentLocation); 18 | } 19 | 20 | listen(cb) { 21 | this.history.listen(cb); 22 | } 23 | 24 | push(location, onComplete, onAbort) { 25 | this.history.push(location, onComplete, onAbort); 26 | } 27 | 28 | replace(location, onComplete, onAbort) { 29 | this.history.replace(location, onComplete, onAbort); 30 | } 31 | 32 | go(n) { 33 | this.history.go(n); 34 | } 35 | 36 | back() { 37 | this.go(-1); 38 | } 39 | 40 | forward() { 41 | this.go(1); 42 | } 43 | 44 | match(raw, current, redirectedFrom) { 45 | return this.matcher.match(raw, current, redirectedFrom); 46 | } 47 | 48 | resolve(to, current, append) { 49 | const location = normalizeLocation( 50 | to, 51 | current || this.history.current, 52 | append, 53 | this 54 | ); 55 | const route = this.match(location, current); 56 | const fullPath = route.redirectedFrom || route.fullPath; 57 | const base = this.history.base; 58 | const href = createHref(base, fullPath, this.mode); 59 | return { 60 | location, 61 | route, 62 | href 63 | }; 64 | } 65 | 66 | addRoute(route) { 67 | this.matcher.addRoutes([route]); 68 | } 69 | } 70 | 71 | function createHref(base, fullPath, mode) { 72 | var path = mode === 'hash' ? '#' + fullPath : fullPath; 73 | return base ? cleanPath(base + '/' + path) : path; 74 | } 75 | -------------------------------------------------------------------------------- /src/page/util/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file util entry 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | export * from './dom'; 7 | export * from './scroll-behavior'; 8 | export * from './transition'; 9 | export * from './link'; 10 | export * from './url'; 11 | export * from './route'; 12 | export * from './push-state'; 13 | -------------------------------------------------------------------------------- /src/page/util/link.js: -------------------------------------------------------------------------------- 1 | import event from '../../util/dom/event'; 2 | import {MESSAGE_ROUTER_PUSH, MESSAGE_ROUTER_REPLACE, MESSAGE_ROUTER_FORCE} from '../const'; 3 | 4 | function guardEvent(e, $a) { 5 | // don't redirect with control keys 6 | if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) { 7 | return; 8 | } 9 | // don't redirect when preventDefault called 10 | // if (e.defaultPrevented) { 11 | // return; 12 | // } 13 | // don't redirect if `target="_blank"` 14 | if ($a.getAttribute) { 15 | const target = $a.getAttribute('target'); 16 | if (/\b_blank\b/i.test(target)) { 17 | return; 18 | } 19 | } 20 | e.preventDefault(); 21 | return true; 22 | } 23 | 24 | export function installMipLink(router, {isRootPage, notifyRootPage}) { 25 | event.delegate(document, 'a', 'click', function (e) { 26 | let $a = this; 27 | let to = $a.getAttribute('href'); 28 | if ($a.hasAttribute('mip-link') || $a.getAttribute('data-type') === 'mip') { 29 | const location = router.resolve(to, router.currentRoute, false).location; 30 | if (guardEvent(e, $a)) { 31 | if ($a.hasAttribute('replace')) { 32 | if (isRootPage) { 33 | router.replace(location); 34 | } 35 | else { 36 | notifyRootPage({ 37 | type: MESSAGE_ROUTER_REPLACE, 38 | data: {location} 39 | }); 40 | } 41 | } 42 | else { 43 | if (isRootPage) { 44 | router.push(location); 45 | } 46 | else { 47 | notifyRootPage({ 48 | type: MESSAGE_ROUTER_PUSH, 49 | data: {location} 50 | }); 51 | } 52 | } 53 | } 54 | } 55 | else { 56 | notifyRootPage({ 57 | type: MESSAGE_ROUTER_FORCE, 58 | data: {location: to} 59 | }) 60 | } 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /src/page/util/path.js: -------------------------------------------------------------------------------- 1 | export function resolvePath(relative, base, append) { 2 | const firstChar = relative.charAt(0); 3 | if (firstChar === '/') { 4 | return relative; 5 | } 6 | 7 | if (firstChar === '?' || firstChar === '#') { 8 | return base + relative; 9 | } 10 | 11 | const stack = base.split('/'); 12 | 13 | // remove trailing segment if: 14 | // - not appending 15 | // - appending to trailing slash (last segment is empty) 16 | if (!append || !stack[stack.length - 1]) { 17 | stack.pop(); 18 | } 19 | 20 | // resolve relative path 21 | const segments = relative.replace(/^\//, '').split('/'); 22 | for (let i = 0; i < segments.length; i++) { 23 | const segment = segments[i]; 24 | if (segment === '..') { 25 | stack.pop(); 26 | } 27 | else if (segment !== '.') { 28 | stack.push(segment); 29 | } 30 | 31 | } 32 | 33 | // ensure leading slash 34 | if (stack[0] !== '') { 35 | stack.unshift(''); 36 | } 37 | 38 | return stack.join('/'); 39 | } 40 | 41 | export function parsePath(path) { 42 | let hash = ''; 43 | let query = ''; 44 | 45 | const hashIndex = path.indexOf('#'); 46 | if (hashIndex >= 0) { 47 | hash = path.slice(hashIndex); 48 | path = path.slice(0, hashIndex); 49 | } 50 | 51 | const queryIndex = path.indexOf('?'); 52 | if (queryIndex >= 0) { 53 | query = path.slice(queryIndex + 1); 54 | path = path.slice(0, queryIndex); 55 | } 56 | 57 | return { 58 | path, 59 | query, 60 | hash 61 | }; 62 | } 63 | 64 | export function cleanPath(path) { 65 | return path.replace(/\/\//g, '/'); 66 | } 67 | -------------------------------------------------------------------------------- /src/page/util/push-state.js: -------------------------------------------------------------------------------- 1 | import {inBrowser} from './dom'; 2 | 3 | export const supportsPushState = inBrowser 4 | && window.history 5 | && 'pushState' in window.history; 6 | 7 | // use User Timing api (if present) for more accurate key precision 8 | const Time = inBrowser 9 | && window.performance 10 | && window.performance.now 11 | ? window.performance 12 | : Date; 13 | 14 | let _key = genKey(); 15 | 16 | function genKey () { 17 | return Time.now().toFixed(3); 18 | } 19 | 20 | export function getStateKey () { 21 | return _key; 22 | } 23 | 24 | export function setStateKey (key) { 25 | _key = key; 26 | } 27 | 28 | export function pushState (url, replace) { 29 | // try...catch the pushState call to get around Safari 30 | // DOM Exception 18 where it limits to 100 pushState calls 31 | const history = window.history; 32 | try { 33 | if (replace) { 34 | history.replaceState({key: _key}, '', url); 35 | } 36 | else { 37 | _key = genKey(); 38 | history.pushState({key: _key}, '', url); 39 | } 40 | } 41 | catch (e) { 42 | window.location[replace ? 'replace' : 'assign'](url); 43 | } 44 | } 45 | 46 | export function replaceState (url) { 47 | pushState(url, true); 48 | } 49 | -------------------------------------------------------------------------------- /src/page/util/scroll-behavior.js: -------------------------------------------------------------------------------- 1 | const ENABLE_SCROLL_CLASS = 'mip-appshell-router-view-scroll-enabled'; 2 | 3 | /** 4 | * make current page container scrollable, 5 | * and restore its scroll position. 6 | */ 7 | export function restoreContainerScrollPosition(containerEl, scrollTop) { 8 | containerEl.classList.add(ENABLE_SCROLL_CLASS); 9 | containerEl.scrollTop = scrollTop; 10 | } 11 | 12 | /** 13 | * make body scrollable, 14 | * and restore its scroll position. 15 | */ 16 | export function restoreBodyScrollPosition(containerEl, scrollTop) { 17 | containerEl.classList.remove(ENABLE_SCROLL_CLASS); 18 | document.body.scrollTop = document.documentElement.scrollTop = scrollTop; 19 | } 20 | -------------------------------------------------------------------------------- /src/page/util/url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file url util 3 | * @author wangyisheng@baidu.com (wangyisheng) 4 | */ 5 | 6 | export function getPath(href) { 7 | if (!href) { 8 | return location.pathname + location.search; 9 | } 10 | 11 | return href.replace(/^http(s?):\/\/[^\/]+/, ''); 12 | } 13 | -------------------------------------------------------------------------------- /src/polyfills/array-includes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Array.prototype.includes polyfill 3 | * @author sekiyika(pengxing@baidu.com) 4 | */ 5 | 6 | /** 7 | * Returns true if the element is in the array and false otherwise. 8 | * 9 | * @param {*} value value 10 | * @param {number=} fromIndex fromIndex 11 | * @return {boolean} 12 | * @this {Array} 13 | */ 14 | function includes(value, fromIndex = 0) { 15 | let len = this.length; 16 | let i = fromIndex >= 0 ? fromIndex : Math.max(len + fromIndex, 0); 17 | 18 | for (; i < len; i++) { 19 | let other = this[i]; 20 | // If value has been found OR (value is NaN AND other is NaN) 21 | /* eslint-disable no-self-compare */ 22 | if (other === value || (value !== value && other !== other)) { 23 | return true; 24 | } 25 | } 26 | return false; 27 | } 28 | 29 | /** 30 | * Sets the Array.contains polyfill if it does not exist. 31 | * 32 | * @param {!Window} win window 33 | */ 34 | export function install(win) { 35 | if (!win.Array.prototype.includes) { 36 | win.Array.prototype.includes = includes; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/polyfills/babel-runtime-helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file babel runtime helper 3 | * @author clark-t (clarktanglei@163.com) 4 | */ 5 | 6 | const requires = require.context('babel-runtime/helpers', true, /\.js$/); 7 | const regenerator = require('babel-runtime/regenerator'); 8 | 9 | let helpers = requires.keys().reduce((obj, filename) => { 10 | obj[filename.slice(2, -3)] = requires(filename); 11 | return obj; 12 | }, {}); 13 | 14 | helpers.regenerator = regenerator; 15 | 16 | export function install(win) { 17 | win.babelRuntimeHelpers = helpers; 18 | } 19 | -------------------------------------------------------------------------------- /src/polyfills/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file polyfill entry 3 | * @author sekiyika(pengxing@baidu.com) 4 | */ 5 | 6 | 'use strict'; 7 | 8 | import {install as installArrayIncludes} from './array-includes'; 9 | // import {install as installDOMTokenListToggle} from './domtokenlist-toggle'; 10 | // import {install as installDocContains} from './document-contains'; 11 | // import {install as installMathSign} from './math-sign'; 12 | import {install as installObjectAssign} from './object-assign'; 13 | import {install as installPromise} from './promise'; 14 | import {install as installBabelRuntimeHelpers} from './babel-runtime-helpers'; 15 | 16 | installArrayIncludes(self); 17 | // installDOMTokenListToggle(self); 18 | // installDocContains(self); 19 | // installMathSign(self); 20 | installObjectAssign(self); 21 | installPromise(self); 22 | installBabelRuntimeHelpers(self); 23 | -------------------------------------------------------------------------------- /src/polyfills/object-assign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Object.assign polyfill 3 | * @author sekiyika(pengxing@baidu.com) 4 | */ 5 | 6 | 'use strict'; 7 | 8 | /** 9 | * Object.assign 10 | * 11 | * @param {!Object} target target object 12 | * @param {...Object} args args 13 | * @return {!Object} 14 | */ 15 | export function assign(target, ...args) { 16 | if (target == null) { 17 | throw new TypeError('Cannot convert undefined or null to object'); 18 | } 19 | 20 | let output = Object(target); 21 | for (let arg of args) { 22 | let source = args[arg]; 23 | if (source != null) { 24 | Object.keys(source).forEach(key => { 25 | if (source.hasOwnProperty(key)) { 26 | output[key] = source[key]; 27 | } 28 | }); 29 | } 30 | } 31 | return output; 32 | } 33 | 34 | 35 | /** 36 | * Sets the Object.assign polyfill if it does not exist. 37 | * 38 | * @param {!Window} win window 39 | */ 40 | export function install(win) { 41 | win.Object.assign = win.Object.assign || assign; 42 | } 43 | -------------------------------------------------------------------------------- /src/polyfills/promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file promise polyfill 3 | * @author sfe 4 | */ 5 | 6 | 'use strict'; 7 | 8 | import * as Promise from 'promise-pjs/promise'; 9 | 10 | /** 11 | * Sets the Promise polyfill if it does not exist. 12 | * 13 | * @param {!Window} win window 14 | */ 15 | export function install(win) { 16 | if (!win.Promise) { 17 | win.Promise = Promise; 18 | // In babel the * export is an Object with a default property. 19 | // In closure compiler it is the Promise function itself. 20 | if (Promise.default) { 21 | win.Promise = Promise.default; 22 | } 23 | // We copy the individual static methods, because closure 24 | // compiler flattens the polyfill namespace. 25 | win.Promise.resolve = Promise.resolve; 26 | win.Promise.reject = Promise.reject; 27 | win.Promise.all = Promise.all; 28 | win.Promise.race = Promise.race; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/styles/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/src/styles/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /src/styles/fonts/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/src/styles/fonts/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /src/styles/fonts/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mip-project/mip/c88a032bb319095b515b73a1bef271e09d476d17/src/styles/fonts/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /src/styles/material-icons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Material Icons"; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local("Material Icons"), 6 | local("MaterialIcons-Regular"), 7 | url(./fonts/MaterialIcons-Regular.woff2) format("woff2"), 8 | url(./fonts/MaterialIcons-Regular.woff) format("woff"), 9 | url(./fonts/MaterialIcons-Regular.ttf) format("truetype"); 10 | } 11 | 12 | .material-icons { 13 | font-family: "Material Icons"; 14 | font-weight: normal; 15 | font-style: normal; 16 | font-size: 24px; /* Preferred icon size */ 17 | display: inline-block; 18 | line-height: 1; 19 | text-transform: none; 20 | letter-spacing: normal; 21 | word-wrap: normal; 22 | white-space: nowrap; 23 | direction: ltr; 24 | 25 | /* Support for all WebKit browsers. */ 26 | -webkit-font-smoothing: antialiased; 27 | /* Support for Safari and Chrome. */ 28 | text-rendering: optimizeLegibility; 29 | 30 | /* Support for Firefox. */ 31 | -moz-osx-font-smoothing: grayscale; 32 | 33 | /* Support for IE. */ 34 | font-feature-settings: "liga"; 35 | } 36 | -------------------------------------------------------------------------------- /src/styles/mip-fixed.less: -------------------------------------------------------------------------------- 1 | mip-fixed { 2 | position: fixed !important; 3 | -webkit-transform: translate3d(0, 0, 0); 4 | transform: translate3d(0, 0, 0); 5 | } 6 | mip-fixed[type="top"] { 7 | top: 0; 8 | } 9 | mip-fixed[type="bottom"] { 10 | bottom: 0; 11 | } 12 | mip-fixed[type="left"] { 13 | left: 0; 14 | } 15 | mip-fixed[type="right"] { 16 | right: 0; 17 | } 18 | mip-fixed[type="top"], 19 | mip-fixed[type="bottom"] { 20 | width: 100%; 21 | overflow: hidden; 22 | left: 0; 23 | right: 0; 24 | } 25 | 26 | mip-fixed[type="left"], 27 | mip-fixed[type="right"] { 28 | overflow: hidden; 29 | } -------------------------------------------------------------------------------- /src/styles/mip-html.less: -------------------------------------------------------------------------------- 1 | @import "mixin.less"; 2 | @import "variable.less"; 3 | 4 | .mip-html { 5 | line-height: 26px; 6 | font-size: @ft14; 7 | color: @c3; 8 | text-align: justify; 9 | h3 { 10 | margin: 0; 11 | padding: 15px 0; 12 | line-height: 20px; 13 | font-size: @ft15; 14 | } 15 | ol, 16 | ul { 17 | padding: 10px 0 10px 20px; 18 | } 19 | blockquote { 20 | margin: 20px 0; 21 | padding-left: 8px; 22 | border-left: 4px solid #e9e9e9; 23 | } 24 | p { 25 | padding: 10px 0; 26 | word-break: break-word; 27 | word-wrap: break-word; 28 | } 29 | } 30 | 31 | body::before { 32 | display: block; 33 | content: ''; 34 | position: fixed; 35 | top: 0; 36 | left: 0; 37 | right: 0; 38 | bottom: 0; 39 | background: white; 40 | z-index: 99999; 41 | } 42 | 43 | body[mip-ready]::before { 44 | display: none; 45 | } 46 | -------------------------------------------------------------------------------- /src/styles/mip-iframe.less: -------------------------------------------------------------------------------- 1 | mip-iframe { 2 | display: block; 3 | position: relative; 4 | overflow: hidden; 5 | iframe { 6 | display: block; 7 | width: 100px; 8 | min-width: 100%; 9 | height: 100%; 10 | position: absolute; 11 | left: 0; 12 | top: 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/mip-text.less: -------------------------------------------------------------------------------- 1 | .mip-text { 2 | display: block; 3 | line-height: 26px; 4 | color: #333; 5 | padding: 10px 0; 6 | font-size: 16px; 7 | text-align: justify; 8 | word-wrap: break-word; 9 | word-break: break-word; 10 | } -------------------------------------------------------------------------------- /src/styles/mip-video.less: -------------------------------------------------------------------------------- 1 | mip-video { 2 | background: black; 3 | .mip-video-playbtn { 4 | display: inline-block; 5 | width: 60px; 6 | height: 60px; 7 | border: 4px solid white; 8 | border-radius: 100%; 9 | position: absolute; 10 | left: 50%; 11 | top: 50%; 12 | margin-left: -32px; 13 | margin-top: -32px; 14 | background-color: rgba(0, 0, 0, 0.3); 15 | -webkit-tap-highlight-color: rbga(0, 0, 0, 0.3); 16 | tap-highlight-color: rbga(0, 0, 0, 0.3); 17 | &:before { 18 | content: ''; 19 | position: absolute; 20 | width: 1px; 21 | height: 1px; 22 | border-style: solid; 23 | border-width: 16px 0 16px 26px; 24 | border-color: transparent transparent transparent white; 25 | left: 20px; 26 | top: 14px; 27 | } 28 | } 29 | .mip-video-poster { 30 | min-height: 150px; 31 | background: #333; 32 | } 33 | } -------------------------------------------------------------------------------- /src/styles/mip.less: -------------------------------------------------------------------------------- 1 | @import "./mip-base.less"; 2 | @import "./mip-text.less"; 3 | @import "./mip-html.less"; 4 | @import "./mip-fixed.less"; 5 | @import "./mip-img.less"; 6 | @import "./mip-carousel.less"; 7 | @import "./mip-video.less"; 8 | @import "./mip-iframe.less"; 9 | @import "./mip-page.less"; 10 | @import "./material-icons.css"; 11 | -------------------------------------------------------------------------------- /src/styles/mixin.less: -------------------------------------------------------------------------------- 1 | .display-flex() { 2 | display: flex; 3 | display: box; 4 | display: -webkit-box; 5 | display: -webkit-flex; 6 | } 7 | 8 | .text-overflow() { 9 | overflow: hidden; 10 | max-width: 100%; 11 | text-overflow: ellipsis; 12 | white-space: nowrap; 13 | } 14 | 15 | .flex() { 16 | flex: 1; 17 | box-flex: 1; 18 | -webkit-box-flex: 1; 19 | -webkit-flex: 1; 20 | } 21 | 22 | .box-sizing() { 23 | box-sizing: border-box; 24 | -webkit-box-sizing: border-box; 25 | } -------------------------------------------------------------------------------- /src/styles/variable.less: -------------------------------------------------------------------------------- 1 | @c3: #333; 2 | @c6: #666; 3 | @c9: #999; 4 | 5 | @ft12: 12px; 6 | @ft13: 13px; 7 | @ft14: 14px; 8 | @ft15: 15px; 9 | @ft16: 16px; 10 | @ft18: 18px; 11 | @ft22: 22px; 12 | 13 | @placeholder-bg: #ddd; 14 | @appshell-header-height: 44px; 15 | -------------------------------------------------------------------------------- /src/util/dom/css-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file css loader 3 | * @author sekiyika(pengxing@baidu.com) 4 | */ 5 | 6 | /** 7 | * Creates the properly configured style element. 8 | * 9 | * @param {Document} doc doc 10 | * @param {Element|ShadowRoot} cssRoot css root 11 | * @param {string} cssText css text 12 | * @param {string} name name 13 | * @param {boolean} isRuntimeCss is runtime css 14 | * @return {Element} 15 | */ 16 | function insertStyleElement(doc, cssRoot, cssText, name, isRuntimeCss) { 17 | let style = doc.createElement('style'); 18 | let afterElement = null; 19 | 20 | style.textContent = cssText; 21 | 22 | if (isRuntimeCss) { 23 | style.setAttribute('mip-main', ''); 24 | } 25 | else { 26 | style.setAttribute('mip-extension', name || ''); 27 | afterElement = cssRoot.querySelector('style[mip-main]'); 28 | } 29 | 30 | insertAfterOrAtStart(cssRoot, style, afterElement); 31 | return style; 32 | } 33 | 34 | function insertAfterOrAtStart(styleRoot, styleElement, afterElement) { 35 | afterElement 36 | ? afterElement.nextSibling 37 | ? styleRoot.insertBefore(styleElement, afterElement.nextSibling) 38 | : styleRoot.appendChild(styleElement) 39 | : styleRoot.insertBefore(styleElement, styleRoot.firstChild); 40 | } 41 | 42 | export default { 43 | insertStyleElement 44 | }; 45 | -------------------------------------------------------------------------------- /src/util/dom/event.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file event 3 | * @author sekiyika(pengxing@baidu.com) 4 | */ 5 | 6 | import dom from './dom'; 7 | 8 | function delegate(element, selector, event, handler, capture) { 9 | capture = !!capture; 10 | function eventHandler(event) { 11 | let target = event.target; 12 | let parent = dom.closestTo(target, selector, this); 13 | if (parent) { 14 | handler.call(parent, event); 15 | } 16 | } 17 | element.addEventListener(event, eventHandler, capture); 18 | return () => { 19 | element.removeEventListener(event, eventHandler); 20 | /* eslint-disable */ 21 | eventHandler = element = handler = null; 22 | /* eslint-enable */ 23 | }; 24 | } 25 | 26 | /** 27 | * Object for getting event's type. 28 | * 29 | * @inner 30 | * @type {Object} 31 | */ 32 | let specialEvents = {}; 33 | specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'; 34 | 35 | /** 36 | * Create a event object to dispatch 37 | * 38 | * @param {string} type Event name 39 | * @param {?Object} data Custom data 40 | * @return {Event} 41 | */ 42 | function createEvent(type, data) { 43 | let event = document.createEvent(specialEvents[type] || 'Event'); 44 | event.initEvent(type, true, true); 45 | data && (event.data = data); 46 | return event; 47 | } 48 | 49 | export default { 50 | delegate, 51 | create: createEvent 52 | }; 53 | -------------------------------------------------------------------------------- /src/vue-custom-element/utils/create-vue-instance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file create vue instance 3 | * @author sfe 4 | */ 5 | 6 | import {getPropsData, reactiveProps} from './props'; 7 | import {getSlots} from './slots'; 8 | import {customEmit} from './custom-event'; 9 | import viewer from '../../viewer'; 10 | 11 | export default function createVueInstance( 12 | element, 13 | {Vue, router}, 14 | componentDefinition, 15 | props 16 | ) { 17 | let ComponentDefinition = Vue.util.extend({}, componentDefinition); 18 | let propsData = getPropsData(element, ComponentDefinition, props); 19 | 20 | // for mip-template syntax 21 | // @TODO: 这里有个 bug, 如果 Vue 不带 compiler,那这个 template 没法用,只能用 render 方法 22 | if (element && element.tagName.toLowerCase() === 'mip-template') { 23 | ComponentDefinition.template = `
    ${element.innerHTML}
    `; 24 | } 25 | 26 | // Auto event handling based on $emit 27 | function beforeCreate() { // eslint-disable-line no-inner-declarations 28 | this.$emit = function emit(...args) { 29 | customEmit(element, ...args); 30 | viewer.eventAction.execute(args[0], element, args[1]); 31 | this.__proto__ && this.__proto__.$emit.call(this, ...args); // eslint-disable-line no-proto 32 | }; 33 | } 34 | ComponentDefinition.beforeCreate = [].concat(ComponentDefinition.beforeCreate || [], beforeCreate); 35 | 36 | // let elementOriginalChildren = [].slice.call(element.childNodes).map(node => node.cloneNode(true)); // clone hack due to IE compatibility 37 | 38 | element.innerHTML = '
    '; 39 | 40 | let rootElement = { 41 | propsData, 42 | router, 43 | props: props.camelCase, 44 | computed: { 45 | reactiveProps() { 46 | let reactivePropsList = {}; 47 | props.camelCase.forEach(prop => { 48 | reactivePropsList[prop] = this[prop]; 49 | }); 50 | 51 | return reactivePropsList; 52 | } 53 | }, 54 | el: element.children[0], 55 | render(createElement) { 56 | return createElement( 57 | ComponentDefinition, 58 | { 59 | props: this.reactiveProps 60 | }, 61 | getSlots(element.__innerHTML, createElement) 62 | ); 63 | } 64 | }; 65 | 66 | reactiveProps(element, props); 67 | 68 | return new Vue(rootElement); 69 | } 70 | -------------------------------------------------------------------------------- /src/vue-custom-element/utils/custom-event.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file custom-event.js 3 | * @author sfe-sy (sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable */ 7 | 8 | export default function customEvent(eventName, detail) { 9 | const params = {bubbles: false, cancelable: false, detail}; 10 | let event; 11 | if (typeof window.CustomEvent === 'function') { 12 | event = new CustomEvent(eventName, params); 13 | } 14 | else { 15 | event = document.createEvent('CustomEvent'); 16 | event.initCustomEvent(eventName, params.bubbles, params.cancelable, params.detail); 17 | } 18 | 19 | return event; 20 | } 21 | 22 | 23 | export function customEmit(element, eventName, ...args) { 24 | const event = customEvent(eventName, [...args]); 25 | element.dispatchEvent(event); 26 | } 27 | -------------------------------------------------------------------------------- /src/vue-custom-element/utils/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file helpers.js 3 | * @author sfe-sy (sfe-sy@baidu.com) 4 | */ 5 | 6 | 7 | /** 8 | * Camelize a hyphen-delimited string. 9 | */ 10 | const camelizeRE = /-(\w)/g; 11 | export const camelize = (str) => str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ''); // eslint-disable-line arrow-parens, no-confusing-arrow 12 | 13 | /** 14 | * Hyphenate a camelCase string. 15 | */ 16 | const hyphenateRE = /([^-])([A-Z])/g; 17 | export const hyphenate = str => str 18 | .replace(hyphenateRE, '$1-$2') 19 | .replace(hyphenateRE, '$1-$2') 20 | .toLowerCase(); 21 | 22 | // Convert an Array - like object to a real Array. 23 | export function toArray(list, start = 0) { 24 | let i = list.length - start; 25 | const ret = new Array(i); 26 | while (i--) { // eslint-disable-line no-plusplus 27 | ret[i] = list[i + start]; 28 | } 29 | return ret; 30 | } 31 | -------------------------------------------------------------------------------- /src/vue-custom-element/utils/slots.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file slots.js 3 | * @author sfe-sy (sfe-sy@baidu.com) 4 | */ 5 | 6 | import {toArray} from './helpers'; 7 | 8 | // Get attributes of given node 9 | export function getAttributes(children) { 10 | const attributes = {}; 11 | 12 | toArray(children.attributes).forEach(attribute => { 13 | attributes[attribute.nodeName === 'vue-slot' 14 | ? 'slot' 15 | : attribute.nodeName] = attribute.nodeValue; 16 | }); 17 | 18 | return attributes; 19 | } 20 | 21 | // Helper utility returning slots for render function 22 | export function getSlots(innerHTML, createElement) { 23 | let emptyNode = document.createElement('div'); 24 | emptyNode.innerHTML = innerHTML; 25 | let children = emptyNode.children; 26 | 27 | const slots = []; 28 | toArray(children).forEach(child => { 29 | if (child.nodeName === '#text') { 30 | if (child.nodeValue.trim()) { 31 | slots.push(createElement('span', child.nodeValue)); 32 | } 33 | } 34 | else if (child.nodeName !== '#comment') { 35 | const attributes = getAttributes(child); 36 | const elementOptions = { 37 | attrs: attributes, 38 | domProps: { 39 | innerHTML: child.innerHTML 40 | } 41 | }; 42 | 43 | if (attributes.slot) { 44 | elementOptions.slot = attributes.slot; 45 | attributes.slot = undefined; 46 | } 47 | 48 | slots.push(createElement(child.tagName, elementOptions)); 49 | } 50 | }); 51 | 52 | return slots; 53 | } 54 | -------------------------------------------------------------------------------- /src/vue/compiler/create-compiler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file create-compiler.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {extend} from 'shared/util'; 7 | import {detectErrors} from './error-detector'; 8 | import {createCompileToFunctionFn} from './to-function'; 9 | 10 | export function createCompilerCreator(baseCompile) { 11 | return function createCompiler(baseOptions) { 12 | function compile(template, options) { 13 | const finalOptions = Object.create(baseOptions); 14 | const errors = []; 15 | const tips = []; 16 | finalOptions.warn = (msg, tip) => { 17 | (tip ? tips : errors).push(msg); 18 | }; 19 | 20 | if (options) { 21 | // merge custom modules 22 | if (options.modules) { 23 | finalOptions.modules = (baseOptions.modules || []).concat(options.modules); 24 | } 25 | 26 | // merge custom directives 27 | if (options.directives) { 28 | finalOptions.directives = extend( 29 | Object.create(baseOptions.directives), 30 | options.directives 31 | ); 32 | } 33 | 34 | // copy other options 35 | for (const key in options) { 36 | if (key !== 'modules' && key !== 'directives') { 37 | finalOptions[key] = options[key]; 38 | } 39 | 40 | } 41 | } 42 | 43 | const compiled = baseCompile(template, finalOptions); 44 | if (process.env.NODE_ENV !== 'production') { 45 | errors.push.apply(errors, detectErrors(compiled.ast)); 46 | } 47 | 48 | compiled.errors = errors; 49 | compiled.tips = tips; 50 | return compiled; 51 | } 52 | 53 | return { 54 | compile, 55 | compileToFunctions: createCompileToFunctionFn(compile) 56 | }; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/vue/compiler/directives/bind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file bind.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | export default function bind(el, dir) { 7 | el.wrapData = code => ` 8 | _b(${code},'${el.tag}',${dir.value},${ 9 | dir.modifiers && dir.modifiers.prop ? 'true' : 'false' 10 | }${ 11 | dir.modifiers && dir.modifiers.sync ? ',true' : '' 12 | })`; 13 | } 14 | -------------------------------------------------------------------------------- /src/vue/compiler/directives/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import on from './on'; 7 | import bind from './bind'; 8 | import {noop} from 'shared/util'; 9 | 10 | export default { 11 | on, 12 | bind, 13 | cloak: noop 14 | }; 15 | -------------------------------------------------------------------------------- /src/vue/compiler/directives/on.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file on.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {warn} from 'core/util/index'; 7 | 8 | export default function on(el, dir) { 9 | if (process.env.NODE_ENV !== 'production' && dir.modifiers) { 10 | warn('v-on without argument does not support modifiers.'); 11 | } 12 | 13 | el.wrapListeners = code => `_g(${code},${dir.value})`; 14 | } 15 | -------------------------------------------------------------------------------- /src/vue/compiler/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {parse} from './parser/index'; 7 | import {optimize} from './optimizer'; 8 | import {generate} from './codegen/index'; 9 | import {createCompilerCreator} from './create-compiler'; 10 | 11 | // `createCompilerCreator` allows creating compilers that use alternative 12 | // parser/optimizer/codegen, e.g the SSR optimizing compiler. 13 | // Here we just export a default compiler using the default parts. 14 | export const createCompiler = createCompilerCreator(function baseCompile(template, options) { 15 | const ast = parse(template.trim(), options); 16 | optimize(ast, options); 17 | const code = generate(ast, options); 18 | 19 | return { 20 | ast, 21 | render: code.render, 22 | staticRenderFns: code.staticRenderFns 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /src/vue/compiler/parser/entity-decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file entity-decoder.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | let decoder; 7 | 8 | export default { 9 | decode(html) { 10 | decoder = decoder || document.createElement('div'); 11 | decoder.innerHTML = html; 12 | return decoder.textContent; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/vue/compiler/parser/text-parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file text-parser.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {cached} from 'shared/util'; 7 | import {parseFilters} from './filter-parser'; 8 | 9 | const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g; 10 | const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g; 11 | 12 | const buildRegex = cached(delimiters => { 13 | const open = delimiters[0].replace(regexEscapeRE, '\\$&'); 14 | const close = delimiters[1].replace(regexEscapeRE, '\\$&'); 15 | return new RegExp(open + '((?:.|\\n)+?)' + close, 'g'); 16 | }); 17 | 18 | export function parseText( 19 | text, 20 | delimiters 21 | ) { 22 | const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE; 23 | if (!tagRE.test(text)) { 24 | return; 25 | } 26 | 27 | const tokens = []; 28 | let lastIndex = tagRE.lastIndex = 0; 29 | let match; 30 | let index; 31 | while ((match = tagRE.exec(text))) { 32 | index = match.index; 33 | // push text token 34 | if (index > lastIndex) { 35 | tokens.push(JSON.stringify(text.slice(lastIndex, index))); 36 | } 37 | 38 | // tag token 39 | const exp = parseFilters(match[1].trim()); 40 | tokens.push(`_s(${exp})`); 41 | lastIndex = index + match[0].length; 42 | } 43 | if (lastIndex < text.length) { 44 | tokens.push(JSON.stringify(text.slice(lastIndex))); 45 | } 46 | 47 | return tokens.join('+'); 48 | } 49 | -------------------------------------------------------------------------------- /src/vue/core/components/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | import KeepAlive from './keep-alive'; 6 | 7 | export default { 8 | KeepAlive 9 | }; 10 | -------------------------------------------------------------------------------- /src/vue/core/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file config.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import { 7 | no, 8 | noop, 9 | identity 10 | } from 'shared/util'; 11 | 12 | import {LIFECYCLE_HOOKS} from 'shared/constants'; 13 | 14 | export default ({ 15 | 16 | /** 17 | * Option merge strategies (used in core/util/options) 18 | */ 19 | optionMergeStrategies: Object.create(null), 20 | 21 | /** 22 | * Whether to suppress warnings. 23 | */ 24 | silent: false, 25 | 26 | /** 27 | * Show production mode tip message on boot? 28 | */ 29 | productionTip: process.env.NODE_ENV !== 'production', 30 | 31 | /** 32 | * Whether to enable devtools 33 | */ 34 | devtools: process.env.NODE_ENV !== 'production', 35 | 36 | /** 37 | * Whether to record perf 38 | */ 39 | performance: false, 40 | 41 | /** 42 | * Error handler for watcher errors 43 | */ 44 | errorHandler: null, 45 | 46 | /** 47 | * Warn handler for watcher warns 48 | */ 49 | warnHandler: null, 50 | 51 | /** 52 | * Ignore certain custom elements 53 | */ 54 | ignoredElements: [], 55 | 56 | /** 57 | * Custom user key aliases for v-on 58 | */ 59 | keyCodes: Object.create(null), 60 | 61 | /** 62 | * Check if a tag is reserved so that it cannot be registered as a 63 | * component. This is platform-dependent and may be overwritten. 64 | */ 65 | isReservedTag: no, 66 | 67 | /** 68 | * Check if an attribute is reserved so that it cannot be used as a component 69 | * prop. This is platform-dependent and may be overwritten. 70 | */ 71 | isReservedAttr: no, 72 | 73 | /** 74 | * Check if a tag is an unknown element. 75 | * Platform-dependent. 76 | */ 77 | isUnknownElement: no, 78 | 79 | /** 80 | * Get the namespace of an element 81 | */ 82 | getTagNamespace: noop, 83 | 84 | /** 85 | * Parse the real tag name for the specific platform. 86 | */ 87 | parsePlatformTagName: identity, 88 | 89 | /** 90 | * Check if an attribute must be bound using property, e.g. value 91 | * Platform-dependent. 92 | */ 93 | mustUseProp: no, 94 | 95 | /* eslint-disable */ 96 | /** 97 | * Exposed for legacy reasons 98 | */ 99 | _lifecycleHooks: LIFECYCLE_HOOKS 100 | }); 101 | -------------------------------------------------------------------------------- /src/vue/core/global-api/assets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file assets.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import config from '../config'; 7 | import {ASSET_TYPES} from 'shared/constants'; 8 | import {warn, isPlainObject} from '../util/index'; 9 | 10 | export function initAssetRegisters(Vue) { 11 | 12 | /** 13 | * Create asset registration methods. 14 | */ 15 | ASSET_TYPES.forEach(type => { 16 | Vue[type] = function (id, definition) { 17 | if (!definition) { 18 | return this.options[type + 's'][id]; 19 | } 20 | 21 | /* istanbul ignore if */ 22 | if (process.env.NODE_ENV !== 'production') { 23 | if (type === 'component' && config.isReservedTag(id)) { 24 | warn( 25 | 'Do not use built-in or reserved HTML elements as component ' 26 | + 'id: ' + id 27 | ); 28 | } 29 | } 30 | 31 | if (type === 'component' && isPlainObject(definition)) { 32 | definition.name = definition.name || id; 33 | definition = this.options._base.extend(definition); 34 | } 35 | 36 | if (type === 'directive' && typeof definition === 'function') { 37 | definition = {bind: definition, update: definition}; 38 | } 39 | 40 | this.options[type + 's'][id] = definition; 41 | return definition; 42 | }; 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /src/vue/core/global-api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import config from '../config'; 7 | import {initUse} from './use'; 8 | import {initMixin} from './mixin'; 9 | import {initExtend} from './extend'; 10 | import {initAssetRegisters} from './assets'; 11 | import {set, del} from '../observer/index'; 12 | import {ASSET_TYPES} from 'shared/constants'; 13 | import builtInComponents from '../components/index'; 14 | 15 | import { 16 | warn, 17 | extend, 18 | nextTick, 19 | mergeOptions, 20 | defineReactive 21 | } from '../util/index'; 22 | 23 | export function initGlobalAPI(Vue) { 24 | // config 25 | const configDef = { 26 | get() { 27 | return config; 28 | } 29 | }; 30 | // configDef.get = () => config; 31 | if (process.env.NODE_ENV !== 'production') { 32 | configDef.set = () => { 33 | warn( 34 | 'Do not replace the Vue.config object, set individual fields instead.' 35 | ); 36 | }; 37 | } 38 | 39 | Object.defineProperty(Vue, 'config', configDef); 40 | 41 | // exposed util methods. 42 | // NOTE: these are not considered part of the public API - avoid relying on 43 | // them unless you are aware of the risk. 44 | Vue.util = { 45 | warn, 46 | extend, 47 | mergeOptions, 48 | defineReactive 49 | }; 50 | 51 | Vue.set = set; 52 | Vue.delete = del; 53 | Vue.nextTick = nextTick; 54 | 55 | Vue.options = Object.create(null); 56 | ASSET_TYPES.forEach(type => { 57 | Vue.options[type + 's'] = Object.create(null); 58 | }); 59 | 60 | // this is used to identify the "base" constructor to extend all plain-object 61 | // components with in Weex's multi-instance scenarios. 62 | Vue.options._base = Vue; 63 | 64 | extend(Vue.options.components, builtInComponents); 65 | 66 | initUse(Vue); 67 | initMixin(Vue); 68 | initExtend(Vue); 69 | initAssetRegisters(Vue); 70 | } 71 | -------------------------------------------------------------------------------- /src/vue/core/global-api/mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file mixin.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {mergeOptions} from '../util/index'; 7 | 8 | export function initMixin(Vue) { 9 | Vue.mixin = function (mixin) { 10 | this.options = mergeOptions(this.options, mixin); 11 | return this; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/vue/core/global-api/use.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file use.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {toArray} from '../util/index'; 7 | 8 | export function initUse(Vue) { 9 | Vue.use = function (plugin) { 10 | const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])); 11 | if (installedPlugins.indexOf(plugin) > -1) { 12 | return this; 13 | } 14 | 15 | // additional parameters 16 | const args = toArray(arguments, 1); 17 | args.unshift(this); 18 | if (typeof plugin.install === 'function') { 19 | plugin.install.apply(plugin, args); 20 | } 21 | else if (typeof plugin === 'function') { 22 | plugin.apply(null, args); 23 | } 24 | 25 | installedPlugins.push(plugin); 26 | return this; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/vue/core/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-class-jsdoc */ 7 | 8 | import Vue from './instance/index'; 9 | import {initGlobalAPI} from './global-api/index'; 10 | import {isServerRendering} from 'core/util/env'; 11 | 12 | initGlobalAPI(Vue); 13 | 14 | Object.defineProperty(Vue.prototype, '$isServer', { 15 | get: isServerRendering 16 | }); 17 | 18 | Object.defineProperty(Vue.prototype, '$ssrContext', { 19 | get() { 20 | /* istanbul ignore next */ 21 | return this.$vnode && this.$vnode.ssrContext; 22 | } 23 | }); 24 | 25 | Vue.version = __VERSION__; 26 | 27 | export default Vue; 28 | -------------------------------------------------------------------------------- /src/vue/core/instance/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-class-jsdoc */ 7 | 8 | import {initMixin} from './init'; 9 | import {stateMixin} from './state'; 10 | import {renderMixin} from './render'; 11 | import {eventsMixin} from './events'; 12 | import {lifecycleMixin} from './lifecycle'; 13 | import {warn} from '../util/index'; 14 | 15 | function Vue(options) { 16 | if (process.env.NODE_ENV !== 'production' 17 | && !(this instanceof Vue) 18 | ) { 19 | warn('Vue is a constructor and should be called with the `new` keyword'); 20 | } 21 | 22 | this._init(options); 23 | } 24 | 25 | initMixin(Vue); 26 | stateMixin(Vue); 27 | eventsMixin(Vue); 28 | lifecycleMixin(Vue); 29 | renderMixin(Vue); 30 | 31 | export default Vue; 32 | -------------------------------------------------------------------------------- /src/vue/core/instance/render-helpers/bind-object-listeners.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file bind-object-listeners.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable guard-for-in */ 7 | 8 | import { 9 | warn, 10 | extend, 11 | isPlainObject 12 | } from 'core/util/index'; 13 | 14 | export function bindObjectListeners(data, value) { 15 | if (value) { 16 | if (!isPlainObject(value)) { 17 | process.env.NODE_ENV !== 'production' && warn( 18 | 'v-on without argument expects an Object value', 19 | this 20 | ); 21 | } 22 | else { 23 | const on = data.on = data.on ? extend({}, data.on) : {}; 24 | for (const key in value) { 25 | const existing = on[key]; 26 | const ours = value[key]; 27 | on[key] = existing ? [].concat(ours, existing) : ours; 28 | } 29 | } 30 | } 31 | 32 | return data; 33 | } 34 | -------------------------------------------------------------------------------- /src/vue/core/instance/render-helpers/bind-object-props.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file bind-object-props.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable guard-for-in, fecs-valid-jsdoc */ 7 | 8 | import config from 'core/config'; 9 | 10 | import { 11 | warn, 12 | isObject, 13 | toObject, 14 | isReservedAttribute 15 | } from 'core/util/index'; 16 | 17 | /** 18 | * Runtime helper for merging v-bind="object" into a VNode's data. 19 | */ 20 | export function bindObjectProps( 21 | data, 22 | tag, 23 | value, 24 | asProp, 25 | isSync 26 | ) { 27 | if (value) { 28 | if (!isObject(value)) { 29 | process.env.NODE_ENV !== 'production' && warn( 30 | 'v-bind without argument expects an Object or Array value', 31 | this 32 | ); 33 | } 34 | else { 35 | if (Array.isArray(value)) { 36 | value = toObject(value); 37 | } 38 | 39 | let hash; 40 | for (const key in value) { 41 | if ( 42 | key === 'class' 43 | || key === 'style' 44 | || isReservedAttribute(key) 45 | ) { 46 | hash = data; 47 | } 48 | else { 49 | const type = data.attrs && data.attrs.type; 50 | hash = asProp || config.mustUseProp(tag, type, key) 51 | ? data.domProps || (data.domProps = {}) 52 | : data.attrs || (data.attrs = {}); 53 | } 54 | if (!(key in hash)) { 55 | hash[key] = value[key]; 56 | if (isSync) { 57 | const on = data.on || (data.on = {}); 58 | on[`update:${key}`] = function ($event) { 59 | value[key] = $event; 60 | }; 61 | } 62 | } 63 | 64 | } 65 | } 66 | } 67 | 68 | return data; 69 | } 70 | -------------------------------------------------------------------------------- /src/vue/core/instance/render-helpers/check-keycodes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file check-keycodes.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-jsdoc */ 7 | 8 | import config from 'core/config'; 9 | import {hyphenate} from 'shared/util'; 10 | 11 | /** 12 | * Runtime helper for checking keyCodes from config. 13 | * exposed as Vue.prototype._k 14 | * passing in eventKeyName as last argument separately for backwards compat 15 | */ 16 | export function checkKeyCodes( 17 | eventKeyCode, 18 | key, 19 | builtInAlias, 20 | eventKeyName 21 | ) { 22 | const keyCodes = config.keyCodes[key] || builtInAlias; 23 | if (keyCodes) { 24 | if (Array.isArray(keyCodes)) { 25 | return keyCodes.indexOf(eventKeyCode) === -1; 26 | } 27 | return keyCodes !== eventKeyCode; 28 | } 29 | else if (eventKeyName) { 30 | return hyphenate(eventKeyName) !== key; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/vue/core/instance/render-helpers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import { 7 | toNumber, 8 | toString, 9 | looseEqual, 10 | looseIndexOf 11 | } from 'shared/util'; 12 | import {createTextVNode, createEmptyVNode} from 'core/vdom/vnode'; 13 | import {renderList} from './render-list'; 14 | import {renderSlot} from './render-slot'; 15 | import {resolveFilter} from './resolve-filter'; 16 | import {checkKeyCodes} from './check-keycodes'; 17 | import {bindObjectProps} from './bind-object-props'; 18 | import {renderStatic, markOnce} from './render-static'; 19 | import {bindObjectListeners} from './bind-object-listeners'; 20 | import {resolveScopedSlots} from './resolve-slots'; 21 | 22 | export function installRenderHelpers(target) { 23 | target._o = markOnce; 24 | target._n = toNumber; 25 | target._s = toString; 26 | target._l = renderList; 27 | target._t = renderSlot; 28 | target._q = looseEqual; 29 | target._i = looseIndexOf; 30 | target._m = renderStatic; 31 | target._f = resolveFilter; 32 | target._k = checkKeyCodes; 33 | target._b = bindObjectProps; 34 | target._v = createTextVNode; 35 | target._e = createEmptyVNode; 36 | target._u = resolveScopedSlots; 37 | target._g = bindObjectListeners; 38 | } 39 | -------------------------------------------------------------------------------- /src/vue/core/instance/render-helpers/render-list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file render-list.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-jsdoc */ 7 | 8 | import {isObject, isDef} from 'core/util/index'; 9 | 10 | /** 11 | * Runtime helper for rendering v-for lists. 12 | */ 13 | export function renderList( 14 | val, 15 | render 16 | ) { 17 | let ret; 18 | let i; 19 | let l; 20 | let keys; 21 | let key; 22 | if (Array.isArray(val) || typeof val === 'string') { 23 | ret = new Array(val.length); 24 | for (i = 0, l = val.length; i < l; i++) { 25 | ret[i] = render(val[i], i); 26 | } 27 | } 28 | else if (typeof val === 'number') { 29 | ret = new Array(val); 30 | for (i = 0; i < val; i++) { 31 | ret[i] = render(i + 1, i); 32 | } 33 | } 34 | else if (isObject(val)) { 35 | keys = Object.keys(val); 36 | ret = new Array(keys.length); 37 | for (i = 0, l = keys.length; i < l; i++) { 38 | key = keys[i]; 39 | ret[i] = render(val[key], key, i); 40 | } 41 | } 42 | 43 | if (isDef(ret)) { 44 | (ret)._isVList = true; 45 | } 46 | 47 | return ret; 48 | } 49 | -------------------------------------------------------------------------------- /src/vue/core/instance/render-helpers/render-slot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file render-slot.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-jsdoc */ 7 | 8 | import { 9 | extend, 10 | warn, 11 | isObject 12 | } from 'core/util/index'; 13 | 14 | /** 15 | * Runtime helper for rendering 16 | */ 17 | export function renderSlot( 18 | name, 19 | fallback, 20 | props, 21 | bindObject 22 | ) { 23 | const scopedSlotFn = this.$scopedSlots[name]; 24 | if (scopedSlotFn) { // scoped slot 25 | props = props || {}; 26 | if (bindObject) { 27 | if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) { 28 | warn( 29 | 'slot v-bind without argument expects an Object', 30 | this 31 | ); 32 | } 33 | 34 | props = extend(extend({}, bindObject), props); 35 | } 36 | 37 | return scopedSlotFn(props) || fallback; 38 | } 39 | const slotNodes = this.$slots[name]; 40 | // warn duplicate slot usage 41 | if (slotNodes && process.env.NODE_ENV !== 'production') { 42 | slotNodes._rendered && warn( 43 | `Duplicate presence of slot "${name}" found in the same render tree ` 44 | + '- this will likely cause render errors.', 45 | this 46 | ); 47 | slotNodes._rendered = true; 48 | } 49 | 50 | return slotNodes || fallback; 51 | } 52 | -------------------------------------------------------------------------------- /src/vue/core/instance/render-helpers/render-static.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file render-static.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-jsdoc */ 7 | 8 | import {cloneVNode, cloneVNodes} from 'core/vdom/vnode'; 9 | 10 | /** 11 | * Runtime helper for rendering static trees. 12 | */ 13 | export function renderStatic( 14 | index, 15 | isInFor 16 | ) { 17 | // static trees can be rendered once and cached on the contructor options 18 | // so every instance shares the same cached trees 19 | const renderFns = this.$options.staticRenderFns; 20 | const cached = renderFns.cached || (renderFns.cached = []); 21 | let tree = cached[index]; 22 | // if has already-rendered static tree and not inside v-for, 23 | // we can reuse the same tree by doing a shallow clone. 24 | if (tree && !isInFor) { 25 | return Array.isArray(tree) 26 | ? cloneVNodes(tree) 27 | : cloneVNode(tree); 28 | } 29 | 30 | // otherwise, render a fresh tree. 31 | tree = cached[index] = renderFns[index].call(this._renderProxy, null, this); 32 | markStatic(tree, `__static__${index}`, false); 33 | return tree; 34 | } 35 | 36 | /** 37 | * Runtime helper for v-once. 38 | * Effectively it means marking the node as static with a unique key. 39 | */ 40 | export function markOnce( 41 | tree, 42 | index, 43 | key 44 | ) { 45 | markStatic(tree, `__once__${index}${key ? `_${key}` : ''}`, true); 46 | return tree; 47 | } 48 | 49 | function markStatic( 50 | tree, 51 | key, 52 | isOnce 53 | ) { 54 | if (Array.isArray(tree)) { 55 | for (let i = 0; i < tree.length; i++) { 56 | if (tree[i] && typeof tree[i] !== 'string') { 57 | markStaticNode(tree[i], `${key}_${i}`, isOnce); 58 | } 59 | 60 | } 61 | } 62 | else { 63 | markStaticNode(tree, key, isOnce); 64 | } 65 | } 66 | 67 | function markStaticNode(node, key, isOnce) { 68 | node.isStatic = true; 69 | node.key = key; 70 | node.isOnce = isOnce; 71 | } 72 | -------------------------------------------------------------------------------- /src/vue/core/instance/render-helpers/resolve-filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file resolve-filter.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-jsdoc */ 7 | 8 | import {identity, resolveAsset} from 'core/util/index'; 9 | 10 | /** 11 | * Runtime helper for resolving filters 12 | */ 13 | export function resolveFilter(id) { 14 | return resolveAsset(this.$options, 'filters', id, true) || identity; 15 | } 16 | -------------------------------------------------------------------------------- /src/vue/core/instance/render-helpers/resolve-slots.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file resolve-slots.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-jsdoc */ 7 | 8 | /** 9 | * Runtime helper for resolving raw children VNodes into a slot object. 10 | */ 11 | export function resolveSlots( 12 | children, 13 | context 14 | ) { 15 | const slots = {}; 16 | if (!children) { 17 | return slots; 18 | } 19 | 20 | const defaultSlot = []; 21 | for (let i = 0, l = children.length; i < l; i++) { 22 | const child = children[i]; 23 | const data = child.data; 24 | // remove slot attribute if the node is resolved as a Vue slot node 25 | if (data && data.attrs && data.attrs.slot) { 26 | delete data.attrs.slot; 27 | } 28 | 29 | // named slots should only be respected if the vnode was rendered in the 30 | // same context. 31 | if ((child.context === context || child.functionalContext === context) 32 | && data && data.slot != null 33 | ) { 34 | const name = child.data.slot; 35 | const slot = (slots[name] || (slots[name] = [])); 36 | if (child.tag === 'template') { 37 | slot.push.apply(slot, child.children); 38 | } 39 | else { 40 | slot.push(child); 41 | } 42 | } 43 | else { 44 | defaultSlot.push(child); 45 | } 46 | } 47 | // ignore whitespace 48 | if (!defaultSlot.every(isWhitespace)) { 49 | slots.default = defaultSlot; 50 | } 51 | 52 | return slots; 53 | } 54 | 55 | function isWhitespace(node) { 56 | return node.isComment || node.text === ' '; 57 | } 58 | 59 | export function resolveScopedSlots( 60 | fns, // see flow/vnode 61 | res 62 | ) { 63 | res = res || {}; 64 | for (let i = 0; i < fns.length; i++) { 65 | if (Array.isArray(fns[i])) { 66 | resolveScopedSlots(fns[i], res); 67 | } 68 | else { 69 | res[fns[i].key] = fns[i].fn; 70 | } 71 | } 72 | return res; 73 | } 74 | -------------------------------------------------------------------------------- /src/vue/core/observer/array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file array.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {def} from '../util/index'; 7 | 8 | const arrayProto = Array.prototype; 9 | export const arrayMethods = Object.create(arrayProto) 10 | 11 | /** 12 | * Intercept mutating methods and emit events 13 | */ ; 14 | [ 15 | 'push', 16 | 'pop', 17 | 'shift', 18 | 'unshift', 19 | 'splice', 20 | 'sort', 21 | 'reverse' 22 | ] 23 | .forEach(function (method) { 24 | // cache original method 25 | const original = arrayProto[method]; 26 | def(arrayMethods, method, function mutator(...args) { 27 | const result = original.apply(this, args); 28 | const ob = this.__ob__; 29 | let inserted; 30 | switch (method) { 31 | case 'push': 32 | case 'unshift': 33 | inserted = args; 34 | break; 35 | case 'splice': 36 | inserted = args.slice(2); 37 | break; 38 | } 39 | if (inserted) { 40 | ob.observeArray(inserted); 41 | } 42 | 43 | // notify change 44 | ob.dep.notify(); 45 | return result; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/vue/core/observer/dep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file dep.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {remove} from '../util/index'; 7 | 8 | let uid = 0; 9 | 10 | /** 11 | * A dep is an observable that can have multiple 12 | * directives subscribing to it. 13 | */ 14 | export default class Dep { 15 | 16 | constructor() { 17 | this.id = uid++; 18 | this.subs = []; 19 | } 20 | 21 | addSub(sub) { 22 | this.subs.push(sub); 23 | } 24 | 25 | removeSub(sub) { 26 | remove(this.subs, sub); 27 | } 28 | 29 | depend() { 30 | if (Dep.target) { 31 | Dep.target.addDep(this); 32 | } 33 | } 34 | 35 | notify() { 36 | // stabilize the subscriber list first 37 | const subs = this.subs.slice(); 38 | for (let i = 0, l = subs.length; i < l; i++) { 39 | subs[i].update(); 40 | } 41 | } 42 | } 43 | 44 | // the current target watcher being evaluated. 45 | // this is globally unique because there could be only one 46 | // watcher being evaluated at any time. 47 | Dep.target = null; 48 | const targetStack = []; 49 | 50 | export function pushTarget($target) { 51 | if (Dep.target) { 52 | targetStack.push(Dep.target); 53 | } 54 | 55 | Dep.target = $target; 56 | } 57 | 58 | export function popTarget() { 59 | Dep.target = targetStack.pop(); 60 | } 61 | -------------------------------------------------------------------------------- /src/vue/core/util/error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file error.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable no-console */ 7 | 8 | import config from '../config'; 9 | import {warn} from './debug'; 10 | import {inBrowser} from './env'; 11 | 12 | export function handleError(err, vm, info) { 13 | if (vm) { 14 | let cur = vm; 15 | while ((cur = cur.$parent)) { 16 | const hooks = cur.$options.errorCaptured; 17 | if (hooks) { 18 | for (let i = 0; i < hooks.length; i++) { 19 | try { 20 | const capture = hooks[i].call(cur, err, vm, info) === false; 21 | if (capture) { 22 | return; 23 | } 24 | 25 | } 26 | catch (e) { 27 | globalHandleError(e, cur, 'errorCaptured hook'); 28 | } 29 | } 30 | } 31 | 32 | } 33 | } 34 | 35 | globalHandleError(err, vm, info); 36 | } 37 | 38 | function globalHandleError(err, vm, info) { 39 | if (config.errorHandler) { 40 | try { 41 | return config.errorHandler.call(null, err, vm, info); 42 | } 43 | catch (e) { 44 | logError(e, null, 'config.errorHandler'); 45 | } 46 | } 47 | 48 | logError(err, vm, info); 49 | } 50 | 51 | function logError(err, vm, info) { 52 | if (process.env.NODE_ENV !== 'production') { 53 | warn(`Error in ${info}: "${err.toString()}"`, vm); 54 | } 55 | 56 | /* istanbul ignore else */ 57 | if (inBrowser && typeof console !== 'undefined') { 58 | console.error(err); 59 | } 60 | else { 61 | throw err; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/vue/core/util/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | export * from 'shared/util'; 7 | export * from './lang'; 8 | export * from './env'; 9 | export * from './options'; 10 | export * from './debug'; 11 | export * from './props'; 12 | export * from './error'; 13 | export {defineReactive} from '../observer/index'; 14 | -------------------------------------------------------------------------------- /src/vue/core/util/lang.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file lang.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-jsdoc */ 7 | 8 | export const emptyObject = Object.freeze({}); 9 | 10 | /** 11 | * Check if a string starts with $ or _ 12 | */ 13 | export function isReserved(str) { 14 | const c = (str + '').charCodeAt(0); 15 | return c === 0x24 || c === 0x5F; 16 | } 17 | 18 | /** 19 | * Define a property. 20 | */ 21 | export function def(obj, key, val, enumerable) { 22 | Object.defineProperty(obj, key, { 23 | value: val, 24 | enumerable: !!enumerable, 25 | writable: true, 26 | configurable: true 27 | }); 28 | } 29 | 30 | /** 31 | * Parse simple path. 32 | */ 33 | const bailRE = /[^\w.$]/; 34 | export function parsePath(path) { 35 | if (bailRE.test(path)) { 36 | return; 37 | } 38 | 39 | const segments = path.split('.'); 40 | return function (obj) { 41 | for (let i = 0; i < segments.length; i++) { 42 | if (!obj) { 43 | return; 44 | } 45 | 46 | obj = obj[segments[i]]; 47 | } 48 | return obj; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/vue/core/util/perf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file perf.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | import {inBrowser} from './env'; 6 | 7 | export let mark; 8 | export let measure; 9 | 10 | if (process.env.NODE_ENV !== 'production') { 11 | const perf = inBrowser && window.performance; 12 | 13 | /* istanbul ignore if */ 14 | if ( 15 | perf 16 | && perf.mark 17 | && perf.measure 18 | && perf.clearMarks 19 | && perf.clearMeasures 20 | ) { 21 | mark = tag => perf.mark(tag); 22 | measure = (name, startTag, endTag) => { 23 | perf.measure(name, startTag, endTag); 24 | perf.clearMarks(startTag); 25 | perf.clearMarks(endTag); 26 | perf.clearMeasures(name); 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/vue/core/vdom/helpers/get-first-component-child.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file get-first-component-child.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {isDef} from 'shared/util'; 7 | import {isAsyncPlaceholder} from './is-async-placeholder'; 8 | 9 | export function getFirstComponentChild(children) { 10 | if (Array.isArray(children)) { 11 | for (let i = 0; i < children.length; i++) { 12 | const c = children[i]; 13 | if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { 14 | return c; 15 | } 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/vue/core/vdom/helpers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | export * from './merge-hook'; 7 | export * from './extract-props'; 8 | export * from './update-listeners'; 9 | export * from './normalize-children'; 10 | export * from './resolve-async-component'; 11 | export * from './get-first-component-child'; 12 | export * from './is-async-placeholder'; 13 | -------------------------------------------------------------------------------- /src/vue/core/vdom/helpers/is-async-placeholder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file is-async-placeholder.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | export function isAsyncPlaceholder(node) { 7 | return node.isComment && node.asyncFactory; 8 | } 9 | -------------------------------------------------------------------------------- /src/vue/core/vdom/helpers/merge-hook.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file merge-hook.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {createFnInvoker} from './update-listeners'; 7 | import { 8 | remove, 9 | isDef, 10 | isUndef, 11 | isTrue 12 | } from 'shared/util'; 13 | 14 | export function mergeVNodeHook(def, hookKey, hook) { 15 | let invoker; 16 | const oldHook = def[hookKey]; 17 | function wrappedHook() { 18 | hook.apply(this, arguments); 19 | // important: remove merged hook to ensure it's called only once 20 | // and prevent memory leak 21 | remove(invoker.fns, wrappedHook); 22 | } 23 | 24 | if (isUndef(oldHook)) { 25 | // no existing hook 26 | invoker = createFnInvoker([wrappedHook]); 27 | } 28 | else { 29 | 30 | /* istanbul ignore if */ 31 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { 32 | // already a merged invoker 33 | invoker = oldHook; 34 | invoker.fns.push(wrappedHook); 35 | } 36 | else { 37 | // existing plain hook 38 | invoker = createFnInvoker([oldHook, wrappedHook]); 39 | } 40 | } 41 | 42 | invoker.merged = true; 43 | def[hookKey] = invoker; 44 | } 45 | -------------------------------------------------------------------------------- /src/vue/core/vdom/helpers/update-listeners.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file update-listeners.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable guard-for-in */ 7 | 8 | import {warn} from 'core/util/index'; 9 | import {cached, isUndef} from 'shared/util'; 10 | 11 | const normalizeEvent = cached(name => { 12 | const passive = name.charAt(0) === '&'; 13 | name = passive ? name.slice(1) : name; 14 | const once = name.charAt(0) === '~'; // Prefixed last, checked first 15 | name = once ? name.slice(1) : name; 16 | const capture = name.charAt(0) === '!'; 17 | name = capture ? name.slice(1) : name; 18 | return { 19 | name, 20 | once, 21 | capture, 22 | passive 23 | }; 24 | }); 25 | 26 | export function createFnInvoker(fns) { 27 | function invoker() { 28 | const fns = invoker.fns; 29 | if (Array.isArray(fns)) { 30 | const cloned = fns.slice(); 31 | for (let i = 0; i < cloned.length; i++) { 32 | cloned[i].apply(null, arguments); 33 | } 34 | } 35 | else { 36 | // return handler return value for single handlers 37 | return fns.apply(null, arguments); 38 | } 39 | } 40 | invoker.fns = fns; 41 | return invoker; 42 | } 43 | 44 | export function updateListeners( 45 | on, 46 | oldOn, 47 | add, 48 | remove, 49 | vm 50 | ) { 51 | let name; 52 | let cur; 53 | let old; 54 | let event; 55 | for (name in on) { 56 | cur = on[name]; 57 | old = oldOn[name]; 58 | event = normalizeEvent(name); 59 | if (isUndef(cur)) { 60 | process.env.NODE_ENV !== 'production' && warn( 61 | `Invalid handler for event "${event.name}": got ` + String(cur), 62 | vm 63 | ); 64 | } 65 | else if (isUndef(old)) { 66 | if (isUndef(cur.fns)) { 67 | cur = on[name] = createFnInvoker(cur); 68 | } 69 | 70 | add(event.name, cur, event.once, event.capture, event.passive); 71 | } 72 | else if (cur !== old) { 73 | old.fns = cur; 74 | on[name] = old; 75 | } 76 | } 77 | for (name in oldOn) { 78 | if (isUndef(on[name])) { 79 | event = normalizeEvent(name); 80 | remove(event.name, oldOn[name], event.capture); 81 | } 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/vue/core/vdom/modules/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | import directives from './directives'; 6 | import ref from './ref'; 7 | 8 | export default [ 9 | ref, 10 | directives 11 | ]; 12 | -------------------------------------------------------------------------------- /src/vue/core/vdom/modules/ref.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ref.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {remove} from 'shared/util'; 7 | 8 | export default { 9 | create(_, vnode) { 10 | registerRef(vnode); 11 | }, 12 | update(oldVnode, vnode) { 13 | if (oldVnode.data.ref !== vnode.data.ref) { 14 | registerRef(oldVnode, true); 15 | registerRef(vnode); 16 | } 17 | 18 | }, 19 | destroy(vnode) { 20 | registerRef(vnode, true); 21 | } 22 | }; 23 | 24 | export function registerRef(vnode, isRemoval) { 25 | const key = vnode.data.ref; 26 | if (!key) { 27 | return; 28 | } 29 | 30 | const vm = vnode.context; 31 | const ref = vnode.componentInstance || vnode.elm; 32 | const refs = vm.$refs; 33 | if (isRemoval) { 34 | if (Array.isArray(refs[key])) { 35 | remove(refs[key], ref); 36 | } 37 | else if (refs[key] === ref) { 38 | refs[key] = undefined; 39 | } 40 | } 41 | else { 42 | if (vnode.data.refInFor) { 43 | if (!Array.isArray(refs[key])) { 44 | refs[key] = [ref]; 45 | } 46 | else if (refs[key].indexOf(ref) < 0) { 47 | // $flow-disable-line 48 | refs[key].push(ref); 49 | } 50 | } 51 | else { 52 | refs[key] = ref; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/vue/platforms/web/compiler/directives/html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file html.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {addProp} from 'compiler/helpers'; 7 | 8 | export default function html(el, dir) { 9 | if (dir.value) { 10 | addProp(el, 'innerHTML', `_s(${dir.value})`); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/vue/platforms/web/compiler/directives/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | import model from './model'; 6 | import text from './text'; 7 | import html from './html'; 8 | 9 | export default { 10 | model, 11 | text, 12 | html 13 | }; 14 | -------------------------------------------------------------------------------- /src/vue/platforms/web/compiler/directives/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file text.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {addProp} from 'compiler/helpers'; 7 | 8 | export default function text(el, dir) { 9 | if (dir.value) { 10 | addProp(el, 'textContent', `_s(${dir.value})`); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/vue/platforms/web/compiler/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {baseOptions} from './options'; 7 | import {createCompiler} from 'compiler/index'; 8 | 9 | const {compile, compileToFunctions} = createCompiler(baseOptions); 10 | 11 | export {compile, compileToFunctions}; 12 | -------------------------------------------------------------------------------- /src/vue/platforms/web/compiler/modules/class.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file class.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {parseText} from 'compiler/parser/text-parser'; 7 | import { 8 | getAndRemoveAttr, 9 | getBindingAttr, 10 | baseWarn 11 | } from 'compiler/helpers'; 12 | 13 | function transformNode(el, options) { 14 | const warn = options.warn || baseWarn; 15 | const staticClass = getAndRemoveAttr(el, 'class'); 16 | if (process.env.NODE_ENV !== 'production' && staticClass) { 17 | const expression = parseText(staticClass, options.delimiters); 18 | if (expression) { 19 | warn( 20 | `class="${staticClass}": ` 21 | + 'Interpolation inside attributes has been removed. ' 22 | + 'Use v-bind or the colon shorthand instead. For example, ' 23 | + 'instead of
    , use
    .' 24 | ); 25 | } 26 | } 27 | 28 | if (staticClass) { 29 | el.staticClass = JSON.stringify(staticClass); 30 | } 31 | 32 | const classBinding = getBindingAttr( 33 | el, 'class', false 34 | // getStatic 35 | ); 36 | 37 | if (classBinding) { 38 | el.classBinding = classBinding; 39 | } 40 | } 41 | 42 | function genData(el) { 43 | let data = ''; 44 | if (el.staticClass) { 45 | data += `staticClass:${el.staticClass},`; 46 | } 47 | 48 | if (el.classBinding) { 49 | data += `class:${el.classBinding},`; 50 | } 51 | 52 | return data; 53 | } 54 | 55 | export default { 56 | staticKeys: ['staticClass'], 57 | transformNode, 58 | genData 59 | }; 60 | -------------------------------------------------------------------------------- /src/vue/platforms/web/compiler/modules/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | import klass from './class'; 6 | import style from './style'; 7 | import model from './model'; 8 | 9 | export default [ 10 | klass, 11 | style, 12 | model 13 | ]; 14 | -------------------------------------------------------------------------------- /src/vue/platforms/web/compiler/modules/style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file style.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {parseText} from 'compiler/parser/text-parser'; 7 | import {parseStyleText} from 'web/util/style'; 8 | import { 9 | getAndRemoveAttr, 10 | getBindingAttr, 11 | baseWarn 12 | } from 'compiler/helpers'; 13 | 14 | function transformNode(el, options) { 15 | const warn = options.warn || baseWarn; 16 | const staticStyle = getAndRemoveAttr(el, 'style'); 17 | if (staticStyle) { 18 | 19 | /* istanbul ignore if */ 20 | if (process.env.NODE_ENV !== 'production') { 21 | const expression = parseText(staticStyle, options.delimiters); 22 | if (expression) { 23 | warn( 24 | `style="${staticStyle}": ` 25 | + 'Interpolation inside attributes has been removed. ' 26 | + 'Use v-bind or the colon shorthand instead. For example, ' 27 | + 'instead of
    , use
    .' 28 | ); 29 | } 30 | } 31 | 32 | el.staticStyle = JSON.stringify(parseStyleText(staticStyle)); 33 | } 34 | 35 | const styleBinding = getBindingAttr(el, 'style', false 36 | // getStatic 37 | ); 38 | if (styleBinding) { 39 | el.styleBinding = styleBinding; 40 | } 41 | } 42 | 43 | function genData(el) { 44 | let data = ''; 45 | if (el.staticStyle) { 46 | data += `staticStyle:${el.staticStyle},`; 47 | } 48 | 49 | if (el.styleBinding) { 50 | data += `style:(${el.styleBinding}),`; 51 | } 52 | 53 | return data; 54 | } 55 | 56 | export default { 57 | staticKeys: ['staticStyle'], 58 | transformNode, 59 | genData 60 | }; 61 | -------------------------------------------------------------------------------- /src/vue/platforms/web/compiler/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file options.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import { 7 | isPreTag, 8 | mustUseProp, 9 | isReservedTag, 10 | getTagNamespace 11 | } from '../util/index'; 12 | 13 | import modules from './modules/index'; 14 | import directives from './directives/index'; 15 | import {genStaticKeys} from 'shared/util'; 16 | import {isUnaryTag, canBeLeftOpenTag} from './util'; 17 | 18 | export const baseOptions = { 19 | expectHTML: true, 20 | modules, 21 | directives, 22 | isPreTag, 23 | isUnaryTag, 24 | mustUseProp, 25 | canBeLeftOpenTag, 26 | isReservedTag, 27 | getTagNamespace, 28 | staticKeys: genStaticKeys(modules) 29 | }; 30 | -------------------------------------------------------------------------------- /src/vue/platforms/web/compiler/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file util.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {makeMap} from 'shared/util'; 7 | 8 | export const isUnaryTag = makeMap( 9 | 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' 10 | + 'link,meta,param,source,track,wbr' 11 | ); 12 | 13 | // Elements that you can, intentionally, leave open 14 | // (and which close themselves) 15 | export const canBeLeftOpenTag = makeMap( 16 | 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' 17 | ); 18 | 19 | // HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3 20 | // Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content 21 | export const isNonPhrasingTag = makeMap( 22 | 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' 23 | + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' 24 | + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' 25 | + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' 26 | + 'title,tr,track' 27 | ); 28 | -------------------------------------------------------------------------------- /src/vue/platforms/web/entry-runtime.js: -------------------------------------------------------------------------------- 1 | import Vue from './runtime/index'; 2 | 3 | export default Vue; 4 | -------------------------------------------------------------------------------- /src/vue/platforms/web/runtime/class-util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file class-util.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | /* eslint-disable fecs-valid-jsdoc */ 7 | 8 | /** 9 | * Add class with compatibility for SVG since classList is not supported on 10 | * SVG elements in IE 11 | */ 12 | export function addClass(el, cls) { 13 | 14 | /* istanbul ignore if */ 15 | if (!cls || !(cls = cls.trim())) { 16 | return; 17 | } 18 | 19 | /* istanbul ignore else */ 20 | if (el.classList) { 21 | if (cls.indexOf(' ') > -1) { 22 | cls.split(/\s+/).forEach(c => el.classList.add(c)); 23 | } 24 | else { 25 | el.classList.add(cls); 26 | } 27 | } 28 | else { 29 | const cur = ` ${el.getAttribute('class') || ''} `; 30 | if (cur.indexOf(' ' + cls + ' ') < 0) { 31 | el.setAttribute('class', (cur + cls).trim()); 32 | } 33 | } 34 | } 35 | 36 | /** 37 | * Remove class with compatibility for SVG since classList is not supported on 38 | * SVG elements in IE 39 | */ 40 | export function removeClass(el, cls) { 41 | 42 | /* istanbul ignore if */ 43 | if (!cls || !(cls = cls.trim())) { 44 | return; 45 | } 46 | 47 | /* istanbul ignore else */ 48 | if (el.classList) { 49 | if (cls.indexOf(' ') > -1) { 50 | cls.split(/\s+/).forEach(c => el.classList.remove(c)); 51 | } 52 | else { 53 | el.classList.remove(cls); 54 | } 55 | if (!el.classList.length) { 56 | el.removeAttribute('class'); 57 | } 58 | } 59 | else { 60 | let cur = ` ${el.getAttribute('class') || ''} `; 61 | const tar = ' ' + cls + ' '; 62 | while (cur.indexOf(tar) >= 0) { 63 | cur = cur.replace(tar, ' '); 64 | } 65 | cur = cur.trim(); 66 | if (cur) { 67 | el.setAttribute('class', cur); 68 | } 69 | else { 70 | el.removeAttribute('class'); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/vue/platforms/web/runtime/components/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | import Transition from './transition'; 6 | import TransitionGroup from './transition-group'; 7 | 8 | export default { 9 | Transition, 10 | TransitionGroup 11 | }; 12 | -------------------------------------------------------------------------------- /src/vue/platforms/web/runtime/directives/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | import model from './model'; 6 | import show from './show'; 7 | 8 | export default { 9 | model, 10 | show 11 | }; 12 | -------------------------------------------------------------------------------- /src/vue/platforms/web/runtime/directives/show.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file show.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {enter, leave} from '../modules/transition'; 7 | 8 | // recursively search for possible transition defined inside the component root 9 | function locateNode(vnode) { 10 | return vnode.componentInstance && (!vnode.data || !vnode.data.transition) 11 | ? locateNode(vnode.componentInstance._vnode) 12 | : vnode; 13 | } 14 | 15 | export default { 16 | bind(el, {value}, vnode) { 17 | vnode = locateNode(vnode); 18 | const transition = vnode.data && vnode.data.transition; 19 | const originalDisplay = el.__vOriginalDisplay = el.style.display === 'none' ? '' : el.style.display; 20 | if (value && transition) { 21 | vnode.data.show = true; 22 | enter(vnode, () => { 23 | el.style.display = originalDisplay; 24 | }); 25 | } 26 | else { 27 | el.style.display = value ? originalDisplay : 'none'; 28 | } 29 | }, 30 | 31 | update(el, {value, oldValue}, vnode) { 32 | 33 | /* istanbul ignore if */ 34 | if (value === oldValue) { 35 | return; 36 | } 37 | 38 | vnode = locateNode(vnode); 39 | const transition = vnode.data && vnode.data.transition; 40 | if (transition) { 41 | vnode.data.show = true; 42 | if (value) { 43 | enter(vnode, () => { 44 | el.style.display = el.__vOriginalDisplay; 45 | }); 46 | } 47 | else { 48 | leave(vnode, () => { 49 | el.style.display = 'none'; 50 | }); 51 | } 52 | } 53 | else { 54 | el.style.display = value ? el.__vOriginalDisplay : 'none'; 55 | } 56 | }, 57 | 58 | unbind( 59 | el, 60 | binding, 61 | vnode, 62 | oldVnode, 63 | isDestroy 64 | ) { 65 | if (!isDestroy) { 66 | el.style.display = el.__vOriginalDisplay; 67 | } 68 | 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/vue/platforms/web/runtime/modules/class.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file class.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import { 7 | isDef, 8 | isUndef 9 | } from 'shared/util'; 10 | 11 | import { 12 | concat, 13 | stringifyClass, 14 | genClassForVnode 15 | } from 'web/util/index'; 16 | 17 | function updateClass(oldVnode, vnode) { 18 | const el = vnode.elm; 19 | const data = vnode.data; 20 | const oldData = oldVnode.data; 21 | if ( 22 | isUndef(data.staticClass) 23 | && isUndef(data.class) 24 | && ( 25 | isUndef(oldData) || ( 26 | isUndef(oldData.staticClass) 27 | && isUndef(oldData.class) 28 | ) 29 | ) 30 | ) { 31 | return; 32 | } 33 | 34 | let cls = genClassForVnode(vnode); 35 | 36 | // handle transition classes 37 | const transitionClass = el._transitionClasses; 38 | if (isDef(transitionClass)) { 39 | cls = concat(cls, stringifyClass(transitionClass)); 40 | } 41 | 42 | // set the class 43 | if (cls !== el._prevClass) { 44 | el.setAttribute('class', cls); 45 | el._prevClass = cls; 46 | } 47 | } 48 | 49 | export default { 50 | create: updateClass, 51 | update: updateClass 52 | }; 53 | -------------------------------------------------------------------------------- /src/vue/platforms/web/runtime/modules/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | import attrs from './attrs'; 6 | import klass from './class'; 7 | import events from './events'; 8 | import domProps from './dom-props'; 9 | import style from './style'; 10 | import transition from './transition'; 11 | 12 | export default [ 13 | attrs, 14 | klass, 15 | events, 16 | domProps, 17 | style, 18 | transition 19 | ]; 20 | -------------------------------------------------------------------------------- /src/vue/platforms/web/runtime/node-ops.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file node-ops.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {namespaceMap} from 'web/util/index'; 7 | 8 | export function createElement(tagName, vnode) { 9 | const elm = document.createElement(tagName); 10 | if (tagName !== 'select') { 11 | return elm; 12 | } 13 | 14 | // false or null will remove the attribute but undefined will not 15 | if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) { 16 | elm.setAttribute('multiple', 'multiple'); 17 | } 18 | 19 | return elm; 20 | } 21 | 22 | export function createElementNS(namespace, tagName) { 23 | return document.createElementNS(namespaceMap[namespace], tagName); 24 | } 25 | 26 | export function createTextNode(text) { 27 | return document.createTextNode(text); 28 | } 29 | 30 | export function createComment(text) { 31 | return document.createComment(text); 32 | } 33 | 34 | export function insertBefore(parentNode, newNode, referenceNode) { 35 | parentNode.insertBefore(newNode, referenceNode); 36 | } 37 | 38 | export function removeChild(node, child) { 39 | node.removeChild(child); 40 | } 41 | 42 | export function appendChild(node, child) { 43 | node.appendChild(child); 44 | } 45 | 46 | export function parentNode(node) { 47 | return node.parentNode; 48 | } 49 | 50 | export function nextSibling(node) { 51 | return node.nextSibling; 52 | } 53 | 54 | export function tagName(node) { 55 | return node.tagName; 56 | } 57 | 58 | export function setTextContent(node, text) { 59 | node.textContent = text; 60 | } 61 | 62 | export function setAttribute(node, key, val) { 63 | node.setAttribute(key, val); 64 | } 65 | -------------------------------------------------------------------------------- /src/vue/platforms/web/runtime/patch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file patch.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import * as nodeOps from 'web/runtime/node-ops'; 7 | import {createPatchFunction} from 'core/vdom/patch'; 8 | import baseModules from 'core/vdom/modules/index'; 9 | import platformModules from 'web/runtime/modules/index'; 10 | 11 | // the directive module should be applied last, after all 12 | // built-in modules have been applied. 13 | const modules = platformModules.concat(baseModules); 14 | 15 | export const patch = createPatchFunction({nodeOps, modules}); 16 | -------------------------------------------------------------------------------- /src/vue/platforms/web/util/attrs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file attrs.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {makeMap} from 'shared/util'; 7 | 8 | // these are reserved for web because they are directly compiled away 9 | // during template compilation 10 | export const isReservedAttr = makeMap('style,class'); 11 | 12 | // attributes that should be using props for binding 13 | const acceptValue = makeMap('input,textarea,option,select,progress'); 14 | export const mustUseProp = (tag, type, attr) => ( 15 | (attr === 'value' && acceptValue(tag)) && type !== 'button' 16 | || (attr === 'selected' && tag === 'option') 17 | || (attr === 'checked' && tag === 'input') 18 | || (attr === 'muted' && tag === 'video') 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,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 => name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'; 35 | 36 | export const getXlinkProp = name => (isXlink(name) ? name.slice(6, name.length) : ''); 37 | 38 | export const isFalsyAttrValue = val => (val == null || val === false); 39 | -------------------------------------------------------------------------------- /src/vue/platforms/web/util/compat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file compat.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {inBrowser} from 'core/util/index'; 7 | 8 | // check whether current browser encodes a char inside attribute values 9 | function shouldDecode(content, encoded) { 10 | const div = document.createElement('div'); 11 | div.innerHTML = `
    `; 12 | return div.innerHTML.indexOf(encoded) > 0; 13 | } 14 | 15 | // #3663 16 | // IE encodes newlines inside attribute values while other browsers don't 17 | export const shouldDecodeNewlines = inBrowser ? shouldDecode('\n', ' ') : false; 18 | -------------------------------------------------------------------------------- /src/vue/platforms/web/util/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file index.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import {warn} from 'core/util/index'; 7 | 8 | export * from './attrs'; 9 | export * from './class'; 10 | export * from './element'; 11 | 12 | /** 13 | * Query an element selector if it's not an element already. 14 | * 15 | * @param {Object} el element 16 | * @return {any} any result 17 | */ 18 | export function query(el) { 19 | if (typeof el === 'string') { 20 | const selected = document.querySelector(el); 21 | if (!selected) { 22 | process.env.NODE_ENV !== 'production' && warn( 23 | 'Cannot find element: ' + el 24 | ); 25 | return document.createElement('div'); 26 | } 27 | 28 | return selected; 29 | } 30 | return el; 31 | } 32 | -------------------------------------------------------------------------------- /src/vue/platforms/web/util/style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file style.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | 6 | import { 7 | cached, 8 | extend, 9 | toObject 10 | } from 'shared/util'; 11 | 12 | export const parseStyleText = cached(function (cssText) { 13 | const res = {}; 14 | const listDelimiter = /;(?![^(]*\))/g; 15 | const propertyDelimiter = /:(.+)/; 16 | cssText.split(listDelimiter).forEach(function (item) { 17 | if (item) { 18 | let tmp = item.split(propertyDelimiter); 19 | tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()); 20 | } 21 | 22 | }); 23 | return res; 24 | }); 25 | 26 | // merge static and dynamic style data on the same vnode 27 | function normalizeStyleData(data) { 28 | const style = normalizeStyleBinding(data.style); 29 | // static style is pre-processed into an object during compilation 30 | // and is always a fresh object, so it's safe to merge into it 31 | return data.staticStyle 32 | ? extend(data.staticStyle, style) 33 | : style; 34 | } 35 | 36 | // normalize possible array / string values into Object 37 | export function normalizeStyleBinding(bindingStyle) { 38 | if (Array.isArray(bindingStyle)) { 39 | return toObject(bindingStyle); 40 | } 41 | 42 | if (typeof bindingStyle === 'string') { 43 | return parseStyleText(bindingStyle); 44 | } 45 | 46 | return bindingStyle; 47 | } 48 | 49 | /** 50 | * parent component style should be after child's 51 | * so that parent component's style could override it 52 | * 53 | * @param {Object} vnode vnode object 54 | * @param {boolen} checkChild if check child flag 55 | * @return {Object} result 56 | */ 57 | export function getStyle(vnode, checkChild) { 58 | const res = {}; 59 | let styleData; 60 | 61 | if (checkChild) { 62 | let childNode = vnode; 63 | while (childNode.componentInstance) { 64 | childNode = childNode.componentInstance._vnode; 65 | if (childNode.data && (styleData = normalizeStyleData(childNode.data))) { 66 | extend(res, styleData); 67 | } 68 | 69 | } 70 | } 71 | 72 | if ((styleData = normalizeStyleData(vnode.data))) { 73 | extend(res, styleData); 74 | } 75 | 76 | let parentNode = vnode; 77 | while ((parentNode = parentNode.parent)) { 78 | if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) { 79 | extend(res, styleData); 80 | } 81 | 82 | } 83 | return res; 84 | } 85 | -------------------------------------------------------------------------------- /src/vue/shared/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file constants.js 3 | * @author sfe-sy(sfe-sy@baidu.com) 4 | */ 5 | export const SSR_ATTR = 'data-server-rendered'; 6 | 7 | export const ASSET_TYPES = [ 8 | 'component', 9 | 'directive', 10 | 'filter' 11 | ]; 12 | 13 | export const LIFECYCLE_HOOKS = [ 14 | 'beforeCreate', 15 | 'created', 16 | 'beforeMount', 17 | 'mounted', 18 | 'beforeUpdate', 19 | 'updated', 20 | 'beforeDestroy', 21 | 'destroyed', 22 | 'activated', 23 | 'deactivated', 24 | 'errorCaptured' 25 | ]; 26 | --------------------------------------------------------------------------------