diff --git a/docs/site/mobile.js b/docs/site/mobile.js index 880e84fc3..a1f425679 100644 --- a/docs/site/mobile.js +++ b/docs/site/mobile.js @@ -1,13 +1,12 @@ import { DemoLocaleMixin } from './demo-locale'; -// import Lazyload from '../../src/lazyload'; - -// TODO -// Vue.use(Lazyload, { -// lazyComponent: true, -// }); +import Lazyload from '../../src/lazyload'; const { app } = window; if (app) { // helper for demo locales app.mixin(DemoLocaleMixin); + + app.use(Lazyload, { + lazyComponent: true, + }); } diff --git a/packages/vant-lazyload/.eslintignore b/packages/vant-lazyload/.eslintignore new file mode 100644 index 000000000..a65b41774 --- /dev/null +++ b/packages/vant-lazyload/.eslintignore @@ -0,0 +1 @@ +lib diff --git a/packages/vant-lazyload/README.md b/packages/vant-lazyload/README.md new file mode 100644 index 000000000..62840789b --- /dev/null +++ b/packages/vant-lazyload/README.md @@ -0,0 +1,13 @@ +# @vant/lazyload + +This is a of [vue-lazyload](https://github.com/hilongjw/vue-lazyload) with Vue 3 support. + +## Install + +```shell +yarn add @vant/lazyload +``` + +## Usage + +see: https://github.com/hilongjw/vue-lazyload diff --git a/packages/vant-lazyload/babel.config.js b/packages/vant-lazyload/babel.config.js new file mode 100644 index 000000000..fa5ee221e --- /dev/null +++ b/packages/vant-lazyload/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['@vant/cli/preset'], +}; diff --git a/packages/vant-lazyload/package.json b/packages/vant-lazyload/package.json new file mode 100644 index 000000000..8f6552fa4 --- /dev/null +++ b/packages/vant-lazyload/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vant/lazyload", + "version": "1.0.0", + "description": "", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "rollup --config rollup.config.js --watch", + "build": "rollup --config rollup.config.js && tsc -p ./tsconfig.json --emitDeclarationOnly" + }, + "license": "MIT", + "repository": "https://github.com/youzan/vant/tree/dev/packages/vant-lazyload", + "devDependencies": { + "@babel/runtime": "7.x", + "@rollup/plugin-babel": "^5.2.1", + "@rollup/plugin-node-resolve": "^10.0.0", + "rollup": "^2.33.3", + "vue": "^3.0.0" + }, + "browserslist": [ + "Android >= 4.0", + "iOS >= 8" + ] +} diff --git a/packages/vant-lazyload/rollup.config.js b/packages/vant-lazyload/rollup.config.js new file mode 100644 index 000000000..98e844da4 --- /dev/null +++ b/packages/vant-lazyload/rollup.config.js @@ -0,0 +1,13 @@ +import path from 'path'; +import babel from '@rollup/plugin-babel'; +import nodeResolve from '@rollup/plugin-node-resolve'; + +export default { + input: path.join(__dirname, 'src', 'index.js'), + output: { + dir: 'lib', + format: 'cjs', + }, + external: ['vue'], + plugins: [nodeResolve(), babel({ babelHelpers: 'runtime' })], +}; diff --git a/packages/vant-lazyload/src/index.js b/packages/vant-lazyload/src/index.js new file mode 100644 index 000000000..a612b7b5c --- /dev/null +++ b/packages/vant-lazyload/src/index.js @@ -0,0 +1,40 @@ +import Lazy from './lazy'; +import LazyComponent from './lazy-component'; +import LazyContainer from './lazy-container'; +import LazyImage from './lazy-image'; + +export const Lazyload = { + /* + * install function + * @param {App} app + * @param {object} options lazyload options + */ + install(app, options = {}) { + const LazyClass = Lazy(); + const lazy = new LazyClass(options); + const lazyContainer = new LazyContainer({ lazy }); + + app.config.globalProperties.$Lazyload = lazy; + + if (options.lazyComponent) { + app.component('LazyComponent', LazyComponent(lazy)); + } + + if (options.lazyImage) { + app.component('LazyImage', LazyImage(lazy)); + } + + app.directive('lazy', { + beforeMount: lazy.add.bind(lazy), + update: lazy.update.bind(lazy), + updated: lazy.lazyLoadHandler.bind(lazy), + unmounted: lazy.remove.bind(lazy), + }); + + app.directive('lazy-container', { + beforeMount: lazyContainer.bind.bind(lazyContainer), + updated: lazyContainer.update.bind(lazyContainer), + unmounted: lazyContainer.unbind.bind(lazyContainer), + }); + }, +}; diff --git a/packages/vant-lazyload/src/lazy-component.js b/packages/vant-lazyload/src/lazy-component.js new file mode 100644 index 000000000..c6e9ac4fe --- /dev/null +++ b/packages/vant-lazyload/src/lazy-component.js @@ -0,0 +1,58 @@ +import { h } from 'vue'; +import { inBrowser } from './util'; + +export default (lazy) => { + return { + props: { + tag: { + type: String, + default: 'div', + }, + }, + emits: ['show'], + render() { + return h(this.tag, null, this.show ? this.$slots.default : null); + }, + data() { + return { + el: null, + state: { + loaded: false, + }, + rect: {}, + show: false, + }; + }, + mounted() { + this.el = this.$el; + lazy.addLazyBox(this); + lazy.lazyLoadHandler(); + }, + beforeUnmount() { + lazy.removeComponent(this); + }, + methods: { + getRect() { + this.rect = this.$el.getBoundingClientRect(); + }, + checkInView() { + this.getRect(); + return ( + inBrowser && + this.rect.top < window.innerHeight * lazy.options.preLoad && + this.rect.bottom > 0 && + this.rect.left < window.innerWidth * lazy.options.preLoad && + this.rect.right > 0 + ); + }, + load() { + this.show = true; + this.state.loaded = true; + this.$emit('show', this); + }, + destroy() { + return this.$destroy; + }, + }, + }; +}; diff --git a/packages/vant-lazyload/src/lazy-container.js b/packages/vant-lazyload/src/lazy-container.js new file mode 100644 index 000000000..08698e35f --- /dev/null +++ b/packages/vant-lazyload/src/lazy-container.js @@ -0,0 +1,90 @@ +/* eslint-disable max-classes-per-file */ +import { find, remove, ArrayFrom } from './util'; + +const defaultOptions = { + selector: 'img', +}; + +class LazyContainer { + constructor({ el, binding, vnode, lazy }) { + this.el = null; + this.vnode = vnode; + this.binding = binding; + this.options = {}; + this.lazy = lazy; + + this._queue = []; + this.update({ el, binding }); + } + + update({ el, binding }) { + this.el = el; + this.options = { ...defaultOptions, ...binding.value }; + + const imgs = this.getImgs(); + imgs.forEach((el) => { + this.lazy.add( + el, + { + ...this.binding, + value: { + src: 'dataset' in el ? el.dataset.src : el.getAttribute('data-src'), + error: + ('dataset' in el + ? el.dataset.error + : el.getAttribute('data-error')) || this.options.error, + loading: + ('dataset' in el + ? el.dataset.loading + : el.getAttribute('data-loading')) || this.options.loading, + }, + }, + this.vnode + ); + }); + } + + getImgs() { + return ArrayFrom(this.el.querySelectorAll(this.options.selector)); + } + + clear() { + const imgs = this.getImgs(); + imgs.forEach((el) => this.lazy.remove(el)); + + this.vnode = null; + this.binding = null; + this.lazy = null; + } +} + +export default class LazyContainerMananger { + constructor({ lazy }) { + this.lazy = lazy; + lazy.lazyContainerMananger = this; + this._queue = []; + } + + bind(el, binding, vnode) { + const container = new LazyContainer({ + el, + binding, + vnode, + lazy: this.lazy, + }); + this._queue.push(container); + } + + update(el, binding, vnode) { + const container = find(this._queue, (item) => item.el === el); + if (!container) return; + container.update({ el, binding, vnode }); + } + + unbind(el) { + const container = find(this._queue, (item) => item.el === el); + if (!container) return; + container.clear(); + remove(this._queue, container); + } +} diff --git a/packages/vant-lazyload/src/lazy-image.js b/packages/vant-lazyload/src/lazy-image.js new file mode 100644 index 000000000..ac6d9a5af --- /dev/null +++ b/packages/vant-lazyload/src/lazy-image.js @@ -0,0 +1,107 @@ +import { inBrowser, loadImageAsync, noop } from './util'; + +export default (lazyManager) => { + return { + props: { + src: [String, Object], + tag: { + type: String, + default: 'img', + }, + }, + render(h) { + return h( + this.tag, + { + attrs: { + src: this.renderSrc, + }, + }, + this.$slots.default + ); + }, + data() { + return { + el: null, + options: { + src: '', + error: '', + loading: '', + attempt: lazyManager.options.attempt, + }, + state: { + loaded: false, + error: false, + attempt: 0, + }, + rect: {}, + renderSrc: '', + }; + }, + watch: { + src() { + this.init(); + lazyManager.addLazyBox(this); + lazyManager.lazyLoadHandler(); + }, + }, + created() { + this.init(); + this.renderSrc = this.options.loading; + }, + mounted() { + this.el = this.$el; + lazyManager.addLazyBox(this); + lazyManager.lazyLoadHandler(); + }, + beforeUnmount() { + lazyManager.removeComponent(this); + }, + methods: { + init() { + const { src, loading, error } = lazyManager._valueFormatter(this.src); + this.state.loaded = false; + this.options.src = src; + this.options.error = error; + this.options.loading = loading; + this.renderSrc = this.options.loading; + }, + getRect() { + this.rect = this.$el.getBoundingClientRect(); + }, + checkInView() { + this.getRect(); + return ( + inBrowser && + this.rect.top < window.innerHeight * lazyManager.options.preLoad && + this.rect.bottom > 0 && + this.rect.left < window.innerWidth * lazyManager.options.preLoad && + this.rect.right > 0 + ); + }, + load(onFinish = noop) { + if (this.state.attempt > this.options.attempt - 1 && this.state.error) { + if (!lazyManager.options.silent) + console.log( + `VueLazyload log: ${this.options.src} tried too more than ${this.options.attempt} times` + ); + onFinish(); + return; + } + const { src } = this.options; + loadImageAsync( + { src }, + ({ src }) => { + this.renderSrc = src; + this.state.loaded = true; + }, + () => { + this.state.attempt++; + this.renderSrc = this.options.error; + this.state.error = true; + } + ); + }, + }, + }; +}; diff --git a/packages/vant-lazyload/src/lazy.js b/packages/vant-lazyload/src/lazy.js new file mode 100644 index 000000000..d6cf14d74 --- /dev/null +++ b/packages/vant-lazyload/src/lazy.js @@ -0,0 +1,493 @@ +import { nextTick } from 'vue'; +import { + inBrowser, + CustomEvent, + remove, + some, + find, + _, + throttle, + supportWebp, + getDPR, + scrollParent, + getBestSelectionFromSrcset, + isObject, + hasIntersectionObserver, + modeType, + ImageCache, +} from './util'; +import ReactiveListener from './listener'; + +const DEFAULT_URL = + 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; +const DEFAULT_EVENTS = [ + 'scroll', + 'wheel', + 'mousewheel', + 'resize', + 'animationend', + 'transitionend', + 'touchmove', +]; +const DEFAULT_OBSERVER_OPTIONS = { + rootMargin: '0px', + threshold: 0, +}; + +export default function () { + return class Lazy { + constructor({ + preLoad, + error, + throttleWait, + preLoadTop, + dispatchEvent, + loading, + attempt, + silent = true, + scale, + listenEvents, + filter, + adapter, + observer, + observerOptions, + }) { + this.version = '__VUE_LAZYLOAD_VERSION__'; + this.mode = modeType.event; + this.ListenerQueue = []; + this.TargetIndex = 0; + this.TargetQueue = []; + this.options = { + silent, + dispatchEvent: !!dispatchEvent, + throttleWait: throttleWait || 200, + preLoad: preLoad || 1.3, + preLoadTop: preLoadTop || 0, + error: error || DEFAULT_URL, + loading: loading || DEFAULT_URL, + attempt: attempt || 3, + scale: scale || getDPR(scale), + ListenEvents: listenEvents || DEFAULT_EVENTS, + hasbind: false, + supportWebp: supportWebp(), + filter: filter || {}, + adapter: adapter || {}, + observer: !!observer, + observerOptions: observerOptions || DEFAULT_OBSERVER_OPTIONS, + }; + this._initEvent(); + this._imageCache = new ImageCache({ max: 200 }); + this.lazyLoadHandler = throttle( + this._lazyLoadHandler.bind(this), + this.options.throttleWait + ); + + this.setMode(this.options.observer ? modeType.observer : modeType.event); + } + + /** + * update config + * @param {Object} config params + * @return + */ + config(options = {}) { + this.options = { + ...this.options, + options, + }; + } + + /** + * output listener's load performance + * @return {Array} + */ + performance() { + return this.ListenerQueue.map((item) => item.performance()); + } + + /* + * add lazy component to queue + * @param {Vue} vm lazy component instance + * @return + */ + addLazyBox(vm) { + this.ListenerQueue.push(vm); + if (inBrowser) { + this._addListenerTarget(window); + this._observer && this._observer.observe(vm.el); + if (vm.$el && vm.$el.parentNode) { + this._addListenerTarget(vm.$el.parentNode); + } + } + } + + /* + * add image listener to queue + * @param {DOM} el + * @param {object} binding vue directive binding + * @param {vnode} vnode vue directive vnode + * @return + */ + add(el, binding, vnode) { + if (some(this.ListenerQueue, (item) => item.el === el)) { + this.update(el, binding); + return nextTick(this.lazyLoadHandler); + } + + const value = this._valueFormatter(binding.value); + let { src } = value; + + nextTick(() => { + src = getBestSelectionFromSrcset(el, this.options.scale) || src; + this._observer && this._observer.observe(el); + + const container = Object.keys(binding.modifiers)[0]; + let $parent; + + if (container) { + $parent = vnode.context.$refs[container]; + // if there is container passed in, try ref first, then fallback to getElementById to support the original usage + $parent = $parent + ? $parent.$el || $parent + : document.getElementById(container); + } + + if (!$parent) { + $parent = scrollParent(el); + } + + const newListener = new ReactiveListener({ + bindType: binding.arg, + $parent, + el, + src, + loading: value.loading, + error: value.error, + cors: value.cors, + elRenderer: this._elRenderer.bind(this), + options: this.options, + imageCache: this._imageCache, + }); + + this.ListenerQueue.push(newListener); + + if (inBrowser) { + this._addListenerTarget(window); + this._addListenerTarget($parent); + } + + this.lazyLoadHandler(); + nextTick(() => this.lazyLoadHandler()); + }); + } + + /** + * update image src + * @param {DOM} el + * @param {object} vue directive binding + * @return + */ + update(el, binding, vnode) { + const value = this._valueFormatter(binding.value); + let { src } = value; + src = getBestSelectionFromSrcset(el, this.options.scale) || src; + + const exist = find(this.ListenerQueue, (item) => item.el === el); + if (!exist) { + this.add(el, binding, vnode); + } else { + exist.update({ + src, + error: value.error, + loading: value.loading, + }); + } + if (this._observer) { + this._observer.unobserve(el); + this._observer.observe(el); + } + this.lazyLoadHandler(); + nextTick(() => this.lazyLoadHandler()); + } + + /** + * remove listener form list + * @param {DOM} el + * @return + */ + remove(el) { + if (!el) return; + this._observer && this._observer.unobserve(el); + const existItem = find(this.ListenerQueue, (item) => item.el === el); + if (existItem) { + this._removeListenerTarget(existItem.$parent); + this._removeListenerTarget(window); + remove(this.ListenerQueue, existItem); + existItem.$destroy(); + } + } + + /* + * remove lazy components form list + * @param {Vue} vm Vue instance + * @return + */ + removeComponent(vm) { + if (!vm) return; + remove(this.ListenerQueue, vm); + this._observer && this._observer.unobserve(vm.el); + if (vm.$parent && vm.$el.parentNode) { + this._removeListenerTarget(vm.$el.parentNode); + } + this._removeListenerTarget(window); + } + + setMode(mode) { + if (!hasIntersectionObserver && mode === modeType.observer) { + mode = modeType.event; + } + + this.mode = mode; // event or observer + + if (mode === modeType.event) { + if (this._observer) { + this.ListenerQueue.forEach((listener) => { + this._observer.unobserve(listener.el); + }); + this._observer = null; + } + + this.TargetQueue.forEach((target) => { + this._initListen(target.el, true); + }); + } else { + this.TargetQueue.forEach((target) => { + this._initListen(target.el, false); + }); + this._initIntersectionObserver(); + } + } + + /* + *** Private functions *** + */ + + /* + * add listener target + * @param {DOM} el listener target + * @return + */ + _addListenerTarget(el) { + if (!el) return; + let target = find(this.TargetQueue, (target) => target.el === el); + if (!target) { + target = { + el, + id: ++this.TargetIndex, + childrenCount: 1, + listened: true, + }; + this.mode === modeType.event && this._initListen(target.el, true); + this.TargetQueue.push(target); + } else { + target.childrenCount++; + } + return this.TargetIndex; + } + + /* + * remove listener target or reduce target childrenCount + * @param {DOM} el or window + * @return + */ + _removeListenerTarget(el) { + this.TargetQueue.forEach((target, index) => { + if (target.el === el) { + target.childrenCount--; + if (!target.childrenCount) { + this._initListen(target.el, false); + this.TargetQueue.splice(index, 1); + target = null; + } + } + }); + } + + /* + * add or remove eventlistener + * @param {DOM} el DOM or Window + * @param {boolean} start flag + * @return + */ + _initListen(el, start) { + this.options.ListenEvents.forEach((evt) => + _[start ? 'on' : 'off'](el, evt, this.lazyLoadHandler) + ); + } + + _initEvent() { + this.Event = { + listeners: { + loading: [], + loaded: [], + error: [], + }, + }; + + this.$on = (event, func) => { + if (!this.Event.listeners[event]) this.Event.listeners[event] = []; + this.Event.listeners[event].push(func); + }; + + this.$once = (event, func) => { + const on = (...args) => { + this.$off(event, on); + func.apply(this, args); + }; + this.$on(event, on); + }; + + this.$off = (event, func) => { + if (!func) { + if (!this.Event.listeners[event]) return; + this.Event.listeners[event].length = 0; + return; + } + remove(this.Event.listeners[event], func); + }; + + this.$emit = (event, context, inCache) => { + if (!this.Event.listeners[event]) return; + this.Event.listeners[event].forEach((func) => func(context, inCache)); + }; + } + + /** + * find nodes which in viewport and trigger load + * @return + */ + _lazyLoadHandler() { + const freeList = []; + this.ListenerQueue.forEach((listener) => { + if (!listener.el || !listener.el.parentNode) { + freeList.push(listener); + } + const catIn = listener.checkInView(); + if (!catIn) return; + listener.load(); + }); + freeList.forEach((item) => { + remove(this.ListenerQueue, item); + item.$destroy(); + }); + } + + /** + * init IntersectionObserver + * set mode to observer + * @return + */ + _initIntersectionObserver() { + if (!hasIntersectionObserver) { + return; + } + + this._observer = new IntersectionObserver( + this._observerHandler.bind(this), + this.options.observerOptions + ); + + if (this.ListenerQueue.length) { + this.ListenerQueue.forEach((listener) => { + this._observer.observe(listener.el); + }); + } + } + + /** + * init IntersectionObserver + * @return + */ + _observerHandler(entries) { + entries.forEach((entry) => { + if (entry.isIntersecting) { + this.ListenerQueue.forEach((listener) => { + if (listener.el === entry.target) { + if (listener.state.loaded) + return this._observer.unobserve(listener.el); + listener.load(); + } + }); + } + }); + } + + /** + * set element attribute with image'url and state + * @param {object} lazyload listener object + * @param {string} state will be rendered + * @param {bool} inCache is rendered from cache + * @return + */ + _elRenderer(listener, state, cache) { + if (!listener.el) return; + const { el, bindType } = listener; + + let src; + switch (state) { + case 'loading': + src = listener.loading; + break; + case 'error': + src = listener.error; + break; + default: + ({ src } = listener); + break; + } + + if (bindType) { + el.style[bindType] = 'url("' + src + '")'; + } else if (el.getAttribute('src') !== src) { + el.setAttribute('src', src); + } + + el.setAttribute('lazy', state); + + this.$emit(state, listener, cache); + this.options.adapter[state] && + this.options.adapter[state](listener, this.options); + + if (this.options.dispatchEvent) { + const event = new CustomEvent(state, { + detail: listener, + }); + el.dispatchEvent(event); + } + } + + /** + * generate loading loaded error image url + * @param {string} image's src + * @return {object} image's loading, loaded, error url + */ + _valueFormatter(value) { + let src = value; + let { loading, error } = this.options; + + // value is object + if (isObject(value)) { + if (!value.src && !this.options.silent) + console.error('Vue Lazyload warning: miss src with ' + value); + ({ src } = value); + loading = value.loading || this.options.loading; + error = value.error || this.options.error; + } + return { + src, + loading, + error, + }; + } + }; +} diff --git a/packages/vant-lazyload/src/listener.js b/packages/vant-lazyload/src/listener.js new file mode 100644 index 000000000..bd41ced8b --- /dev/null +++ b/packages/vant-lazyload/src/listener.js @@ -0,0 +1,256 @@ +import { loadImageAsync, noop } from './util'; + +// el: { +// state, +// src, +// error, +// loading +// } + +export default class ReactiveListener { + constructor({ + el, + src, + error, + loading, + bindType, + $parent, + options, + cors, + elRenderer, + imageCache, + }) { + this.el = el; + this.src = src; + this.error = error; + this.loading = loading; + this.bindType = bindType; + this.attempt = 0; + this.cors = cors; + + this.naturalHeight = 0; + this.naturalWidth = 0; + + this.options = options; + + this.rect = null; + + this.$parent = $parent; + this.elRenderer = elRenderer; + this._imageCache = imageCache; + this.performanceData = { + init: Date.now(), + loadStart: 0, + loadEnd: 0, + }; + + this.filter(); + this.initState(); + this.render('loading', false); + } + + /* + * init listener state + * @return + */ + initState() { + if ('dataset' in this.el) { + this.el.dataset.src = this.src; + } else { + this.el.setAttribute('data-src', this.src); + } + + this.state = { + loading: false, + error: false, + loaded: false, + rendered: false, + }; + } + + /* + * record performance + * @return + */ + record(event) { + this.performanceData[event] = Date.now(); + } + + /* + * update image listener data + * @param {String} image uri + * @param {String} loading image uri + * @param {String} error image uri + * @return + */ + update({ src, loading, error }) { + const oldSrc = this.src; + this.src = src; + this.loading = loading; + this.error = error; + this.filter(); + if (oldSrc !== this.src) { + this.attempt = 0; + this.initState(); + } + } + + /* + * get el node rect + * @return + */ + getRect() { + this.rect = this.el.getBoundingClientRect(); + } + + /* + * check el is in view + * @return {Boolean} el is in view + */ + checkInView() { + this.getRect(); + return ( + this.rect.top < window.innerHeight * this.options.preLoad && + this.rect.bottom > this.options.preLoadTop && + this.rect.left < window.innerWidth * this.options.preLoad && + this.rect.right > 0 + ); + } + + /* + * listener filter + */ + filter() { + Object.keys(this.options.filter).forEach((key) => { + this.options.filter[key](this, this.options); + }); + } + + /* + * render loading first + * @params cb:Function + * @return + */ + renderLoading(cb) { + this.state.loading = true; + loadImageAsync( + { + src: this.loading, + cors: this.cors, + }, + () => { + this.render('loading', false); + this.state.loading = false; + cb(); + }, + () => { + // handler `loading image` load failed + cb(); + this.state.loading = false; + if (!this.options.silent) + console.warn( + `VueLazyload log: load failed with loading image(${this.loading})` + ); + } + ); + } + + /* + * try load image and render it + * @return + */ + load(onFinish = noop) { + if (this.attempt > this.options.attempt - 1 && this.state.error) { + if (!this.options.silent) + console.log( + `VueLazyload log: ${this.src} tried too more than ${this.options.attempt} times` + ); + onFinish(); + return; + } + if (this.state.rendered && this.state.loaded) return; + if (this._imageCache.has(this.src)) { + this.state.loaded = true; + this.render('loaded', true); + this.state.rendered = true; + return onFinish(); + } + + this.renderLoading(() => { + this.attempt++; + + this.options.adapter.beforeLoad?.(this, this.options); + this.record('loadStart'); + + loadImageAsync( + { + src: this.src, + cors: this.cors, + }, + (data) => { + this.naturalHeight = data.naturalHeight; + this.naturalWidth = data.naturalWidth; + this.state.loaded = true; + this.state.error = false; + this.record('loadEnd'); + this.render('loaded', false); + this.state.rendered = true; + this._imageCache.add(this.src); + onFinish(); + }, + (err) => { + !this.options.silent && console.error(err); + this.state.error = true; + this.state.loaded = false; + this.render('error', false); + } + ); + }); + } + + /* + * render image + * @param {String} state to render // ['loading', 'src', 'error'] + * @param {String} is form cache + * @return + */ + render(state, cache) { + this.elRenderer(this, state, cache); + } + + /* + * output performance data + * @return {Object} performance data + */ + performance() { + let state = 'loading'; + let time = 0; + + if (this.state.loaded) { + state = 'loaded'; + time = + (this.performanceData.loadEnd - this.performanceData.loadStart) / 1000; + } + + if (this.state.error) state = 'error'; + + return { + src: this.src, + state, + time, + }; + } + + /* + * $destroy + * @return + */ + $destroy() { + this.el = null; + this.src = null; + this.error = null; + this.loading = null; + this.bindType = null; + this.attempt = 0; + } +} diff --git a/packages/vant-lazyload/src/util.js b/packages/vant-lazyload/src/util.js new file mode 100644 index 000000000..197f6ef2e --- /dev/null +++ b/packages/vant-lazyload/src/util.js @@ -0,0 +1,347 @@ +const inBrowser = typeof window !== 'undefined' && window !== null; + +function checkIntersectionObserver() { + if ( + inBrowser && + 'IntersectionObserver' in window && + 'IntersectionObserverEntry' in window && + 'intersectionRatio' in window.IntersectionObserverEntry.prototype + ) { + // Minimal polyfill for Edge 15's lack of `isIntersecting` + // See: https://github.com/w3c/IntersectionObserver/issues/211 + if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) { + Object.defineProperty( + window.IntersectionObserverEntry.prototype, + 'isIntersecting', + { + get() { + return this.intersectionRatio > 0; + }, + } + ); + } + return true; + } + return false; +} + +export const hasIntersectionObserver = checkIntersectionObserver(); + +export const modeType = { + event: 'event', + observer: 'observer', +}; + +// CustomEvent polyfill +const CustomEvent = (function () { + if (!inBrowser) return; + if (typeof window.CustomEvent === 'function') return window.CustomEvent; + function CustomEvent(event, params) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + const evt = document.createEvent('CustomEvent'); + evt.initCustomEvent( + event, + params.bubbles, + params.cancelable, + params.detail + ); + return evt; + } + CustomEvent.prototype = window.Event.prototype; + return CustomEvent; +})(); + +function remove(arr, item) { + if (!arr.length) return; + const index = arr.indexOf(item); + if (index > -1) return arr.splice(index, 1); +} + +function some(arr, fn) { + let has = false; + for (let i = 0, len = arr.length; i < len; i++) { + if (fn(arr[i])) { + has = true; + break; + } + } + return has; +} + +function getBestSelectionFromSrcset(el, scale) { + if (el.tagName !== 'IMG' || !el.getAttribute('data-srcset')) return; + + let options = el.getAttribute('data-srcset'); + []; + const container = el.parentNode; + const containerWidth = container.offsetWidth * scale; + + let spaceIndex; + let tmpSrc; + let tmpWidth; + + options = options.trim().split(','); + + const result = options.map((item) => { + item = item.trim(); + spaceIndex = item.lastIndexOf(' '); + if (spaceIndex === -1) { + tmpSrc = item; + tmpWidth = 999998; + } else { + tmpSrc = item.substr(0, spaceIndex); + tmpWidth = parseInt( + item.substr(spaceIndex + 1, item.length - spaceIndex - 2), + 10 + ); + } + return [tmpWidth, tmpSrc]; + }); + + result.sort(function (a, b) { + if (a[0] < b[0]) { + return 1; + } + if (a[0] > b[0]) { + return -1; + } + if (a[0] === b[0]) { + if (b[1].indexOf('.webp', b[1].length - 5) !== -1) { + return 1; + } + if (a[1].indexOf('.webp', a[1].length - 5) !== -1) { + return -1; + } + } + return 0; + }); + let bestSelectedSrc = ''; + let tmpOption; + + for (let i = 0; i < result.length; i++) { + tmpOption = result[i]; + bestSelectedSrc = tmpOption[1]; + const next = result[i + 1]; + if (next && next[0] < containerWidth) { + bestSelectedSrc = tmpOption[1]; + break; + } else if (!next) { + bestSelectedSrc = tmpOption[1]; + break; + } + } + + return bestSelectedSrc; +} + +function find(arr, fn) { + let item; + for (let i = 0, len = arr.length; i < len; i++) { + if (fn(arr[i])) { + item = arr[i]; + break; + } + } + return item; +} + +const getDPR = (scale = 1) => + inBrowser ? window.devicePixelRatio || scale : scale; + +function supportWebp() { + if (!inBrowser) return false; + + let support = true; + + try { + const elem = document.createElement('canvas'); + + if (elem.getContext && elem.getContext('2d')) { + support = elem.toDataURL('image/webp').indexOf('data:image/webp') === 0; + } + } catch (err) { + support = false; + } + + return support; +} + +function throttle(action, delay) { + let timeout = null; + let lastRun = 0; + return function (...args) { + if (timeout) { + return; + } + const elapsed = Date.now() - lastRun; + const runCallback = () => { + lastRun = Date.now(); + timeout = false; + action.apply(this, args); + }; + if (elapsed >= delay) { + runCallback(); + } else { + timeout = setTimeout(runCallback, delay); + } + }; +} + +function testSupportsPassive() { + if (!inBrowser) return; + let support = false; + try { + const opts = Object.defineProperty({}, 'passive', { + // eslint-disable-next-line getter-return + get() { + support = true; + }, + }); + window.addEventListener('test', null, opts); + } catch (e) { + // + } + return support; +} + +const supportsPassive = testSupportsPassive(); + +const _ = { + on(el, type, func, capture = false) { + if (supportsPassive) { + el.addEventListener(type, func, { + capture, + passive: true, + }); + } else { + el.addEventListener(type, func, capture); + } + }, + off(el, type, func, capture = false) { + el.removeEventListener(type, func, capture); + }, +}; + +const loadImageAsync = (item, resolve, reject) => { + const image = new Image(); + + if (!item || !item.src) { + const err = new Error('image src is required'); + return reject(err); + } + + image.src = item.src; + if (item.cors) { + image.crossOrigin = item.cors; + } + + image.onload = function () { + resolve({ + naturalHeight: image.naturalHeight, + naturalWidth: image.naturalWidth, + src: image.src, + }); + }; + + image.onerror = function (e) { + reject(e); + }; +}; + +const style = (el, prop) => { + return typeof getComputedStyle !== 'undefined' + ? getComputedStyle(el, null).getPropertyValue(prop) + : el.style[prop]; +}; + +const overflow = (el) => { + return ( + style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x') + ); +}; + +const scrollParent = (el) => { + if (!inBrowser) return; + if (!(el instanceof HTMLElement)) { + return window; + } + + let parent = el; + + while (parent) { + if (parent === document.body || parent === document.documentElement) { + break; + } + + if (!parent.parentNode) { + break; + } + + if (/(scroll|auto)/.test(overflow(parent))) { + return parent; + } + + parent = parent.parentNode; + } + + return window; +}; + +function isObject(obj) { + return obj !== null && typeof obj === 'object'; +} + +function ArrayFrom(arrLike) { + const len = arrLike.length; + const list = []; + for (let i = 0; i < len; i++) { + list.push(arrLike[i]); + } + return list; +} + +function noop() {} + +class ImageCache { + constructor({ max }) { + this.options = { + max: max || 100, + }; + this._caches = []; + } + + has(key) { + return this._caches.indexOf(key) > -1; + } + + add(key) { + if (this.has(key)) return; + this._caches.push(key); + if (this._caches.length > this.options.max) { + this.free(); + } + } + + free() { + this._caches.shift(); + } +} + +export { + ImageCache, + inBrowser, + CustomEvent, + remove, + some, + find, + noop, + ArrayFrom, + _, + isObject, + throttle, + supportWebp, + getDPR, + scrollParent, + loadImageAsync, + getBestSelectionFromSrcset, +}; diff --git a/packages/vant-lazyload/yarn.lock b/packages/vant-lazyload/yarn.lock new file mode 100644 index 000000000..2fd903d0f --- /dev/null +++ b/packages/vant-lazyload/yarn.lock @@ -0,0 +1,238 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/helper-module-imports@^7.10.4": + version "7.12.5" + resolved "https://registry.npm.taobao.org/@babel/helper-module-imports/download/@babel/helper-module-imports-7.12.5.tgz?cache=0&sync_timestamp=1604441102741&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-module-imports%2Fdownload%2F%40babel%2Fhelper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb" + integrity sha1-G/wCKfeUmI927QpNTpCGCFC1Tfs= + dependencies: + "@babel/types" "^7.12.5" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.npm.taobao.org/@babel/helper-validator-identifier/download/@babel/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha1-p4x6clHgH2FlEtMbEK3PUq2l4NI= + +"@babel/parser@^7.12.0": + version "7.12.7" + resolved "https://registry.npm.taobao.org/@babel/parser/download/@babel/parser-7.12.7.tgz?cache=0&sync_timestamp=1605904696032&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fparser%2Fdownload%2F%40babel%2Fparser-7.12.7.tgz#fee7b39fe809d0e73e5b25eecaf5780ef3d73056" + integrity sha1-/uezn+gJ0Oc+WyXuyvV4DvPXMFY= + +"@babel/runtime@7.x": + version "7.12.5" + resolved "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.12.5.tgz?cache=0&sync_timestamp=1604443606981&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" + integrity sha1-QQ5+SHRB4bNgwpvnFdhw2bmFiC4= + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/types@^7.12.0", "@babel/types@^7.12.5": + version "7.12.7" + resolved "https://registry.npm.taobao.org/@babel/types/download/@babel/types-7.12.7.tgz?cache=0&sync_timestamp=1605904888635&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftypes%2Fdownload%2F%40babel%2Ftypes-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13" + integrity sha1-YDn/HiQmQKKUUsmuVyFi7JqPXRM= + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@rollup/plugin-babel@^5.2.1": + version "5.2.1" + resolved "https://registry.npm.taobao.org/@rollup/plugin-babel/download/@rollup/plugin-babel-5.2.1.tgz#20fc8f8864dc0eaa1c5578408459606808f72924" + integrity sha1-IPyPiGTcDqocVXhAhFlgaAj3KSQ= + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@rollup/pluginutils" "^3.1.0" + +"@rollup/plugin-node-resolve@^10.0.0": + version "10.0.0" + resolved "https://registry.npm.taobao.org/@rollup/plugin-node-resolve/download/@rollup/plugin-node-resolve-10.0.0.tgz?cache=0&sync_timestamp=1603768362523&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40rollup%2Fplugin-node-resolve%2Fdownload%2F%40rollup%2Fplugin-node-resolve-10.0.0.tgz#44064a2b98df7530e66acf8941ff262fc9b4ead8" + integrity sha1-RAZKK5jfdTDmas+JQf8mL8m06tg= + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + builtin-modules "^3.1.0" + deepmerge "^4.2.2" + is-module "^1.0.0" + resolve "^1.17.0" + +"@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.npm.taobao.org/@rollup/pluginutils/download/@rollup/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha1-cGtFJO5tyLEDs8mVUz5a1oDAK5s= + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.npm.taobao.org/@types/estree/download/@types/estree-0.0.39.tgz?cache=0&sync_timestamp=1605053132167&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Festree%2Fdownload%2F%40types%2Festree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha1-4Xfmme4bjCLSMXTKqnQiZEOJUJ8= + +"@types/node@*": + version "14.14.10" + resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-14.14.10.tgz#5958a82e41863cfc71f2307b3748e3491ba03785" + integrity sha1-WVioLkGGPPxx8jB7N0jjSRugN4U= + +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.npm.taobao.org/@types/resolve/download/@types/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha1-Ov1q2JZ8d+Q3bFmKgt3Vj0bsRdY= + dependencies: + "@types/node" "*" + +"@vue/compiler-core@3.0.3": + version "3.0.3" + resolved "https://registry.npm.taobao.org/@vue/compiler-core/download/@vue/compiler-core-3.0.3.tgz?cache=0&sync_timestamp=1606319146786&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fcompiler-core%2Fdownload%2F%40vue%2Fcompiler-core-3.0.3.tgz#dbb4d5eb91f294038f0bed170a1c25f59f7dc74f" + integrity sha1-27TV65HylAOPC+0XChwl9Z99x08= + dependencies: + "@babel/parser" "^7.12.0" + "@babel/types" "^7.12.0" + "@vue/shared" "3.0.3" + estree-walker "^2.0.1" + source-map "^0.6.1" + +"@vue/compiler-dom@3.0.3": + version "3.0.3" + resolved "https://registry.npm.taobao.org/@vue/compiler-dom/download/@vue/compiler-dom-3.0.3.tgz?cache=0&sync_timestamp=1606319141697&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fcompiler-dom%2Fdownload%2F%40vue%2Fcompiler-dom-3.0.3.tgz#582ba30bc82da8409868bc1153ff0e0e2be617e5" + integrity sha1-WCujC8gtqECYaLwRU/8ODivmF+U= + dependencies: + "@vue/compiler-core" "3.0.3" + "@vue/shared" "3.0.3" + +"@vue/reactivity@3.0.3": + version "3.0.3" + resolved "https://registry.npm.taobao.org/@vue/reactivity/download/@vue/reactivity-3.0.3.tgz?cache=0&sync_timestamp=1606319146956&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Freactivity%2Fdownload%2F%40vue%2Freactivity-3.0.3.tgz#681ee01ceff9219bc4da6bbb7d9c97d452e44d1d" + integrity sha1-aB7gHO/5IZvE2mu7fZyX1FLkTR0= + dependencies: + "@vue/shared" "3.0.3" + +"@vue/runtime-core@3.0.3": + version "3.0.3" + resolved "https://registry.npm.taobao.org/@vue/runtime-core/download/@vue/runtime-core-3.0.3.tgz?cache=0&sync_timestamp=1606319736782&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fruntime-core%2Fdownload%2F%40vue%2Fruntime-core-3.0.3.tgz#edab3c9ad122cf8afd034b174cd20c073fbf950a" + integrity sha1-7as8mtEiz4r9A0sXTNIMBz+/lQo= + dependencies: + "@vue/reactivity" "3.0.3" + "@vue/shared" "3.0.3" + +"@vue/runtime-dom@3.0.3": + version "3.0.3" + resolved "https://registry.npm.taobao.org/@vue/runtime-dom/download/@vue/runtime-dom-3.0.3.tgz?cache=0&sync_timestamp=1606319734520&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fruntime-dom%2Fdownload%2F%40vue%2Fruntime-dom-3.0.3.tgz#5e3e5e5418b9defcac988d2be0cf65596fa2cc03" + integrity sha1-Xj5eVBi53vysmI0r4M9lWW+izAM= + dependencies: + "@vue/runtime-core" "3.0.3" + "@vue/shared" "3.0.3" + csstype "^2.6.8" + +"@vue/shared@3.0.3": + version "3.0.3" + resolved "https://registry.npm.taobao.org/@vue/shared/download/@vue/shared-3.0.3.tgz?cache=0&sync_timestamp=1606319734637&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fshared%2Fdownload%2F%40vue%2Fshared-3.0.3.tgz#ef12ebff93a446df281e8a0fd765b5aea8e7745b" + integrity sha1-7xLr/5OkRt8oHooP12W1rqjndFs= + +builtin-modules@^3.1.0: + version "3.1.0" + resolved "https://registry.npm.taobao.org/builtin-modules/download/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" + integrity sha1-qtl8FRMet2tltQ7yCOdYTNdqdIQ= + +csstype@^2.6.8: + version "2.6.14" + resolved "https://registry.npm.taobao.org/csstype/download/csstype-2.6.14.tgz#004822a4050345b55ad4dcc00be1d9cf2f4296de" + integrity sha1-AEgipAUDRbVa1NzAC+HZzy9Clt4= + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.npm.taobao.org/deepmerge/download/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha1-RNLqNnm49NT/ujPwPYZfwee/SVU= + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.npm.taobao.org/estree-walker/download/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha1-MbxdYSyWtwQQa0d+bdXYqhOMtwA= + +estree-walker@^2.0.1: + version "2.0.1" + resolved "https://registry.npm.taobao.org/estree-walker/download/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0" + integrity sha1-+OAw+yHO+hg7RLetUWt0dDTno+A= + +fsevents@~2.1.2: + version "2.1.3" + resolved "https://registry.npm.taobao.org/fsevents/download/fsevents-2.1.3.tgz?cache=0&sync_timestamp=1604593304255&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha1-+3OHA66NL5/pAMM4Nt3r7ouX8j4= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0= + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npm.taobao.org/has/download/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y= + dependencies: + function-bind "^1.1.1" + +is-core-module@^2.1.0: + version "2.1.0" + resolved "https://registry.npm.taobao.org/is-core-module/download/is-core-module-2.1.0.tgz?cache=0&sync_timestamp=1604519172113&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-core-module%2Fdownload%2Fis-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946" + integrity sha1-pMwDHZsaymPuy9GKZQ4Ty07quUY= + dependencies: + has "^1.0.3" + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.npm.taobao.org/is-module/download/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + +lodash@^4.17.19: + version "4.17.20" + resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha1-tEqbYpe8tpjxxRo1RaKzs2jVnFI= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.npm.taobao.org/path-parse/download/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha1-1i27VnlAXXLEc37FhgDp3c8G0kw= + +picomatch@^2.2.2: + version "2.2.2" + resolved "https://registry.npm.taobao.org/picomatch/download/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha1-IfMz6ba46v8CRo9RRupAbTRfTa0= + +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha1-ysLazIoepnX+qrrriugziYrkb1U= + +resolve@^1.17.0: + version "1.19.0" + resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha1-GvW/YwQJc0oGfK4pMYqsf6KaJnw= + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + +rollup@^2.33.3: + version "2.33.3" + resolved "https://registry.npm.taobao.org/rollup/download/rollup-2.33.3.tgz?cache=0&sync_timestamp=1605677163089&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frollup%2Fdownload%2Frollup-2.33.3.tgz#ae72ce31f992b09a580072951bfea76e9df17342" + integrity sha1-rnLOMfmSsJpYAHKVG/6nbp3xc0I= + optionalDependencies: + fsevents "~2.1.2" + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha1-dHIq8y6WFOnCh6jQu95IteLxomM= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npm.taobao.org/to-fast-properties/download/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +vue@^3.0.0: + version "3.0.3" + resolved "https://registry.npm.taobao.org/vue/download/vue-3.0.3.tgz?cache=0&sync_timestamp=1606321380255&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue%2Fdownload%2Fvue-3.0.3.tgz#ad94a475e6ebbf3904673b6a0ae46e47b957bd72" + integrity sha1-rZSkdebrvzkEZztqCuRuR7lXvXI= + dependencies: + "@vue/compiler-dom" "3.0.3" + "@vue/runtime-dom" "3.0.3" + "@vue/shared" "3.0.3" diff --git a/packages/vant-popperjs/.eslintignore b/packages/vant-popperjs/.eslintignore new file mode 100644 index 000000000..a65b41774 --- /dev/null +++ b/packages/vant-popperjs/.eslintignore @@ -0,0 +1 @@ +lib