feat: docker compose maybe
This commit is contained in:
7
node_modules/svelte-hmr/runtime/hot-api-esm.js
generated
vendored
Normal file
7
node_modules/svelte-hmr/runtime/hot-api-esm.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import { makeApplyHmr } from '../runtime/index.js'
|
||||
|
||||
export const applyHmr = makeApplyHmr(args =>
|
||||
Object.assign({}, args, {
|
||||
hot: args.m.hot,
|
||||
})
|
||||
)
|
172
node_modules/svelte-hmr/runtime/hot-api.js
generated
vendored
Normal file
172
node_modules/svelte-hmr/runtime/hot-api.js
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
import { createProxy, hasFatalError } from './proxy.js'
|
||||
|
||||
const logPrefix = '[HMR:Svelte]'
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
const log = (...args) => console.log(logPrefix, ...args)
|
||||
|
||||
const domReload = () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
const win = typeof window !== 'undefined' && window
|
||||
if (win && win.location && win.location.reload) {
|
||||
log('Reload')
|
||||
win.location.reload()
|
||||
} else {
|
||||
log('Full reload required')
|
||||
}
|
||||
}
|
||||
|
||||
const replaceCss = (previousId, newId) => {
|
||||
if (typeof document === 'undefined') return false
|
||||
if (!previousId) return false
|
||||
if (!newId) return false
|
||||
// svelte-xxx-style => svelte-xxx
|
||||
const previousClass = previousId.slice(0, -6)
|
||||
const newClass = newId.slice(0, -6)
|
||||
// eslint-disable-next-line no-undef
|
||||
document.querySelectorAll('.' + previousClass).forEach(el => {
|
||||
el.classList.remove(previousClass)
|
||||
el.classList.add(newClass)
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
const removeStylesheet = cssId => {
|
||||
if (cssId == null) return
|
||||
if (typeof document === 'undefined') return
|
||||
// eslint-disable-next-line no-undef
|
||||
const el = document.getElementById(cssId)
|
||||
if (el) el.remove()
|
||||
return
|
||||
}
|
||||
|
||||
const defaultArgs = {
|
||||
reload: domReload,
|
||||
}
|
||||
|
||||
export const makeApplyHmr = transformArgs => args => {
|
||||
const allArgs = transformArgs({ ...defaultArgs, ...args })
|
||||
return applyHmr(allArgs)
|
||||
}
|
||||
|
||||
let needsReload = false
|
||||
|
||||
function applyHmr(args) {
|
||||
const {
|
||||
id,
|
||||
cssId,
|
||||
nonCssHash,
|
||||
reload = domReload,
|
||||
// normalized hot API (must conform to rollup-plugin-hot)
|
||||
hot,
|
||||
hotOptions,
|
||||
Component,
|
||||
acceptable, // some types of components are impossible to HMR correctly
|
||||
preserveLocalState,
|
||||
ProxyAdapter,
|
||||
emitCss,
|
||||
} = args
|
||||
|
||||
const existing = hot.data && hot.data.record
|
||||
|
||||
const canAccept = acceptable && (!existing || existing.current.canAccept)
|
||||
|
||||
const r =
|
||||
existing ||
|
||||
createProxy({
|
||||
Adapter: ProxyAdapter,
|
||||
id,
|
||||
Component,
|
||||
hotOptions,
|
||||
canAccept,
|
||||
preserveLocalState,
|
||||
})
|
||||
|
||||
const cssOnly =
|
||||
hotOptions.injectCss &&
|
||||
existing &&
|
||||
nonCssHash &&
|
||||
existing.current.nonCssHash === nonCssHash
|
||||
|
||||
r.update({
|
||||
Component,
|
||||
hotOptions,
|
||||
canAccept,
|
||||
nonCssHash,
|
||||
cssId,
|
||||
previousCssId: r.current.cssId,
|
||||
cssOnly,
|
||||
preserveLocalState,
|
||||
})
|
||||
|
||||
hot.dispose(data => {
|
||||
// handle previous fatal errors
|
||||
if (needsReload || hasFatalError()) {
|
||||
if (hotOptions && hotOptions.noReload) {
|
||||
log('Full reload required')
|
||||
} else {
|
||||
reload()
|
||||
}
|
||||
}
|
||||
|
||||
// 2020-09-21 Snowpack master doesn't pass data as arg to dispose handler
|
||||
data = data || hot.data
|
||||
|
||||
data.record = r
|
||||
|
||||
if (!emitCss && cssId && r.current.cssId !== cssId) {
|
||||
if (hotOptions.cssEjectDelay) {
|
||||
setTimeout(() => removeStylesheet(cssId), hotOptions.cssEjectDelay)
|
||||
} else {
|
||||
removeStylesheet(cssId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (canAccept) {
|
||||
hot.accept(async arg => {
|
||||
const { bubbled } = arg || {}
|
||||
|
||||
// NOTE Snowpack registers accept handlers only once, so we can NOT rely
|
||||
// on the surrounding scope variables -- they're not the last version!
|
||||
const { cssId: newCssId, previousCssId } = r.current
|
||||
const cssChanged = newCssId !== previousCssId
|
||||
// ensure old style sheet has been removed by now
|
||||
if (!emitCss && cssChanged) removeStylesheet(previousCssId)
|
||||
// guard: css only change
|
||||
if (
|
||||
// NOTE bubbled is provided only by rollup-plugin-hot, and we
|
||||
// can't safely assume a CSS only change without it... this means we
|
||||
// can't support CSS only injection with Nollup or Webpack currently
|
||||
bubbled === false && // WARNING check false, not falsy!
|
||||
r.current.cssOnly &&
|
||||
(!cssChanged || replaceCss(previousCssId, newCssId))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const success = await r.reload()
|
||||
|
||||
if (hasFatalError() || (!success && !hotOptions.optimistic)) {
|
||||
needsReload = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// well, endgame... we won't be able to render next updates, even successful,
|
||||
// if we don't have proxies in svelte's tree
|
||||
//
|
||||
// since we won't return the proxy and the app will expect a svelte component,
|
||||
// it's gonna crash... so it's best to report the real cause
|
||||
//
|
||||
// full reload required
|
||||
//
|
||||
const proxyOk = r && r.proxy
|
||||
if (!proxyOk) {
|
||||
throw new Error(`Failed to create HMR proxy for Svelte component ${id}`)
|
||||
}
|
||||
|
||||
return r.proxy
|
||||
}
|
1
node_modules/svelte-hmr/runtime/index.js
generated
vendored
Normal file
1
node_modules/svelte-hmr/runtime/index.js
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { makeApplyHmr } from './hot-api.js'
|
133
node_modules/svelte-hmr/runtime/overlay.js
generated
vendored
Normal file
133
node_modules/svelte-hmr/runtime/overlay.js
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
const removeElement = el => el && el.parentNode && el.parentNode.removeChild(el)
|
||||
|
||||
const ErrorOverlay = () => {
|
||||
let errors = []
|
||||
let compileError = null
|
||||
|
||||
const errorsTitle = 'Failed to init component'
|
||||
const compileErrorTitle = 'Failed to compile'
|
||||
|
||||
const style = {
|
||||
section: `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 32px;
|
||||
background: rgba(0, 0, 0, .85);
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
font-size: large;
|
||||
color: rgb(232, 232, 232);
|
||||
overflow: auto;
|
||||
z-index: 2147483647;
|
||||
`,
|
||||
h1: `
|
||||
margin-top: 0;
|
||||
color: #E36049;
|
||||
font-size: large;
|
||||
font-weight: normal;
|
||||
`,
|
||||
h2: `
|
||||
margin: 32px 0 0;
|
||||
font-size: large;
|
||||
font-weight: normal;
|
||||
`,
|
||||
pre: ``,
|
||||
}
|
||||
|
||||
const createOverlay = () => {
|
||||
const h1 = document.createElement('h1')
|
||||
h1.style = style.h1
|
||||
const section = document.createElement('section')
|
||||
section.appendChild(h1)
|
||||
section.style = style.section
|
||||
const body = document.createElement('div')
|
||||
section.appendChild(body)
|
||||
return { h1, el: section, body }
|
||||
}
|
||||
|
||||
const setTitle = title => {
|
||||
overlay.h1.textContent = title
|
||||
}
|
||||
|
||||
const show = () => {
|
||||
const { el } = overlay
|
||||
if (!el.parentNode) {
|
||||
const target = document.body
|
||||
target.appendChild(overlay.el)
|
||||
}
|
||||
}
|
||||
|
||||
const hide = () => {
|
||||
const { el } = overlay
|
||||
if (el.parentNode) {
|
||||
overlay.el.remove()
|
||||
}
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
if (compileError) {
|
||||
overlay.body.innerHTML = ''
|
||||
setTitle(compileErrorTitle)
|
||||
const errorEl = renderError(compileError)
|
||||
overlay.body.appendChild(errorEl)
|
||||
show()
|
||||
} else if (errors.length > 0) {
|
||||
overlay.body.innerHTML = ''
|
||||
setTitle(errorsTitle)
|
||||
errors.forEach(({ title, message }) => {
|
||||
const errorEl = renderError(message, title)
|
||||
overlay.body.appendChild(errorEl)
|
||||
})
|
||||
show()
|
||||
} else {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
const renderError = (message, title) => {
|
||||
const div = document.createElement('div')
|
||||
if (title) {
|
||||
const h2 = document.createElement('h2')
|
||||
h2.textContent = title
|
||||
h2.style = style.h2
|
||||
div.appendChild(h2)
|
||||
}
|
||||
const pre = document.createElement('pre')
|
||||
pre.textContent = message
|
||||
div.appendChild(pre)
|
||||
return div
|
||||
}
|
||||
|
||||
const addError = (error, title) => {
|
||||
const message = (error && error.stack) || error
|
||||
errors.push({ title, message })
|
||||
update()
|
||||
}
|
||||
|
||||
const clearErrors = () => {
|
||||
errors.forEach(({ element }) => {
|
||||
removeElement(element)
|
||||
})
|
||||
errors = []
|
||||
update()
|
||||
}
|
||||
|
||||
const setCompileError = message => {
|
||||
compileError = message
|
||||
update()
|
||||
}
|
||||
|
||||
const overlay = createOverlay()
|
||||
|
||||
return {
|
||||
addError,
|
||||
clearErrors,
|
||||
setCompileError,
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorOverlay
|
105
node_modules/svelte-hmr/runtime/proxy-adapter-dom.js
generated
vendored
Normal file
105
node_modules/svelte-hmr/runtime/proxy-adapter-dom.js
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
/* global window, document */
|
||||
import * as svelteInternal from 'svelte/internal'
|
||||
// NOTE from 3.38.3 (or so), insert was carrying the hydration logic, that must
|
||||
// be used because DOM elements are reused more (and so insertion points are not
|
||||
// necessarily added in order); then in 3.40 the logic was moved to
|
||||
// insert_hydration, which is the one we must use for HMR
|
||||
const svelteInsert = svelteInternal.insert_hydration || svelteInternal.insert
|
||||
if (!svelteInsert) {
|
||||
throw new Error(
|
||||
'failed to find insert_hydration and insert in svelte/internal'
|
||||
)
|
||||
}
|
||||
|
||||
import ErrorOverlay from './overlay.js'
|
||||
|
||||
const removeElement = el => el && el.parentNode && el.parentNode.removeChild(el)
|
||||
|
||||
export const adapter = class ProxyAdapterDom {
|
||||
constructor(instance) {
|
||||
this.instance = instance
|
||||
this.insertionPoint = null
|
||||
|
||||
this.afterMount = this.afterMount.bind(this)
|
||||
this.rerender = this.rerender.bind(this)
|
||||
|
||||
this._noOverlay = !!instance.hotOptions.noOverlay
|
||||
}
|
||||
|
||||
// NOTE overlay is only created before being actually shown to help test
|
||||
// runner (it won't have to account for error overlay when running assertions
|
||||
// about the contents of the rendered page)
|
||||
static getErrorOverlay(noCreate = false) {
|
||||
if (!noCreate && !this.errorOverlay) {
|
||||
this.errorOverlay = ErrorOverlay()
|
||||
}
|
||||
return this.errorOverlay
|
||||
}
|
||||
|
||||
// TODO this is probably unused now: remove in next breaking release
|
||||
static renderCompileError(message) {
|
||||
const noCreate = !message
|
||||
const overlay = this.getErrorOverlay(noCreate)
|
||||
if (!overlay) return
|
||||
overlay.setCompileError(message)
|
||||
}
|
||||
|
||||
dispose() {
|
||||
// Component is being destroyed, detaching is not optional in Svelte3's
|
||||
// component API, so we can dispose of the insertion point in every case.
|
||||
if (this.insertionPoint) {
|
||||
removeElement(this.insertionPoint)
|
||||
this.insertionPoint = null
|
||||
}
|
||||
this.clearError()
|
||||
}
|
||||
|
||||
// NOTE afterMount CAN be called multiple times (e.g. keyed list)
|
||||
afterMount(target, anchor) {
|
||||
const {
|
||||
instance: { debugName },
|
||||
} = this
|
||||
if (!this.insertionPoint) {
|
||||
this.insertionPoint = document.createComment(debugName)
|
||||
}
|
||||
svelteInsert(target, this.insertionPoint, anchor)
|
||||
}
|
||||
|
||||
rerender() {
|
||||
this.clearError()
|
||||
const {
|
||||
instance: { refreshComponent },
|
||||
insertionPoint,
|
||||
} = this
|
||||
if (!insertionPoint) {
|
||||
throw new Error('Cannot rerender: missing insertion point')
|
||||
}
|
||||
refreshComponent(insertionPoint.parentNode, insertionPoint)
|
||||
}
|
||||
|
||||
renderError(err) {
|
||||
if (this._noOverlay) return
|
||||
const {
|
||||
instance: { debugName },
|
||||
} = this
|
||||
const title = debugName || err.moduleName || 'Error'
|
||||
this.constructor.getErrorOverlay().addError(err, title)
|
||||
}
|
||||
|
||||
clearError() {
|
||||
if (this._noOverlay) return
|
||||
const overlay = this.constructor.getErrorOverlay(true)
|
||||
if (!overlay) return
|
||||
overlay.clearErrors()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this is probably unused now: remove in next breaking release
|
||||
if (typeof window !== 'undefined') {
|
||||
window.__SVELTE_HMR_ADAPTER = adapter
|
||||
}
|
||||
|
||||
// mitigate situation with Snowpack remote source pulling latest of runtime,
|
||||
// but using previous version of the Node code transform in the plugin
|
||||
// see: https://github.com/rixo/svelte-hmr/issues/27
|
||||
export default adapter
|
427
node_modules/svelte-hmr/runtime/proxy.js
generated
vendored
Normal file
427
node_modules/svelte-hmr/runtime/proxy.js
generated
vendored
Normal file
@ -0,0 +1,427 @@
|
||||
/* eslint-env browser */
|
||||
/**
|
||||
* The HMR proxy is a component-like object whose task is to sit in the
|
||||
* component tree in place of the proxied component, and rerender each
|
||||
* successive versions of said component.
|
||||
*/
|
||||
|
||||
import { createProxiedComponent } from './svelte-hooks.js'
|
||||
|
||||
const handledMethods = ['constructor', '$destroy']
|
||||
const forwardedMethods = ['$set', '$on']
|
||||
|
||||
const logError = (msg, err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[HMR][Svelte]', msg)
|
||||
if (err) {
|
||||
// NOTE avoid too much wrapping around user errors
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const posixify = file => file.replace(/[/\\]/g, '/')
|
||||
|
||||
const getBaseName = id =>
|
||||
id
|
||||
.split('/')
|
||||
.pop()
|
||||
.split('.')
|
||||
.slice(0, -1)
|
||||
.join('.')
|
||||
|
||||
const capitalize = str => str[0].toUpperCase() + str.slice(1)
|
||||
|
||||
const getFriendlyName = id => capitalize(getBaseName(posixify(id)))
|
||||
|
||||
const getDebugName = id => `<${getFriendlyName(id)}>`
|
||||
|
||||
const relayCalls = (getTarget, names, dest = {}) => {
|
||||
for (const key of names) {
|
||||
dest[key] = function(...args) {
|
||||
const target = getTarget()
|
||||
if (!target) {
|
||||
return
|
||||
}
|
||||
return target[key] && target[key].call(this, ...args)
|
||||
}
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
const isInternal = key => key !== '$$' && key.slice(0, 2) === '$$'
|
||||
|
||||
// This is intented as a somewhat generic / prospective fix to the situation
|
||||
// that arised with the introduction of $$set in Svelte 3.24.1 -- trying to
|
||||
// avoid giving full knowledge (like its name) of this implementation detail
|
||||
// to the proxy. The $$set method can be present or not on the component, and
|
||||
// its presence impacts the behaviour (but with HMR it will be tested if it is
|
||||
// present _on the proxy_). So the idea here is to expose exactly the same $$
|
||||
// props as the current version of the component and, for those that are
|
||||
// functions, proxy the calls to the current component.
|
||||
const relayInternalMethods = (proxy, cmp) => {
|
||||
// delete any previously added $$ prop
|
||||
Object.keys(proxy)
|
||||
.filter(isInternal)
|
||||
.forEach(key => {
|
||||
delete proxy[key]
|
||||
})
|
||||
// guard: no component
|
||||
if (!cmp) return
|
||||
// proxy current $$ props to the actual component
|
||||
Object.keys(cmp)
|
||||
.filter(isInternal)
|
||||
.forEach(key => {
|
||||
Object.defineProperty(proxy, key, {
|
||||
configurable: true,
|
||||
get() {
|
||||
const value = cmp[key]
|
||||
if (typeof value !== 'function') return value
|
||||
return (
|
||||
value &&
|
||||
function(...args) {
|
||||
return value.apply(this, args)
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// proxy custom methods
|
||||
const copyComponentProperties = (proxy, cmp, previous) => {
|
||||
if (previous) {
|
||||
previous.forEach(prop => {
|
||||
delete proxy[prop]
|
||||
})
|
||||
}
|
||||
|
||||
const props = Object.getOwnPropertyNames(Object.getPrototypeOf(cmp))
|
||||
const wrappedProps = props.filter(prop => {
|
||||
if (!handledMethods.includes(prop) && !forwardedMethods.includes(prop)) {
|
||||
Object.defineProperty(proxy, prop, {
|
||||
configurable: true,
|
||||
get() {
|
||||
return cmp[prop]
|
||||
},
|
||||
set(value) {
|
||||
// we're changing it on the real component first to see what it
|
||||
// gives... if it throws an error, we want to throw the same error in
|
||||
// order to most closely follow non-hmr behaviour.
|
||||
cmp[prop] = value
|
||||
},
|
||||
})
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
return wrappedProps
|
||||
}
|
||||
|
||||
// everything in the constructor!
|
||||
//
|
||||
// so we don't polute the component class with new members
|
||||
//
|
||||
class ProxyComponent {
|
||||
constructor(
|
||||
{
|
||||
Adapter,
|
||||
id,
|
||||
debugName,
|
||||
current, // { Component, hotOptions: { preserveLocalState, ... } }
|
||||
register,
|
||||
},
|
||||
options // { target, anchor, ... }
|
||||
) {
|
||||
let cmp
|
||||
let disposed = false
|
||||
let lastError = null
|
||||
|
||||
const setComponent = _cmp => {
|
||||
cmp = _cmp
|
||||
relayInternalMethods(this, cmp)
|
||||
}
|
||||
|
||||
const getComponent = () => cmp
|
||||
|
||||
const destroyComponent = () => {
|
||||
// destroyComponent is tolerant (don't crash on no cmp) because it
|
||||
// is possible that reload/rerender is called after a previous
|
||||
// createComponent has failed (hence we have a proxy, but no cmp)
|
||||
if (cmp) {
|
||||
cmp.$destroy()
|
||||
setComponent(null)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshComponent = (target, anchor, conservativeDestroy) => {
|
||||
if (lastError) {
|
||||
lastError = null
|
||||
adapter.rerender()
|
||||
} else {
|
||||
try {
|
||||
const replaceOptions = {
|
||||
target,
|
||||
anchor,
|
||||
preserveLocalState: current.preserveLocalState,
|
||||
}
|
||||
if (conservativeDestroy) {
|
||||
replaceOptions.conservativeDestroy = true
|
||||
}
|
||||
cmp.$replace(current.Component, replaceOptions)
|
||||
} catch (err) {
|
||||
setError(err, target, anchor)
|
||||
if (
|
||||
!current.hotOptions.optimistic ||
|
||||
// non acceptable components (that is components that have to defer
|
||||
// to their parent for rerender -- e.g. accessors, named exports)
|
||||
// are most tricky, and they havent been considered when most of the
|
||||
// code has been written... as a result, they are especially tricky
|
||||
// to deal with, it's better to consider any error with them to be
|
||||
// fatal to avoid odities
|
||||
!current.canAccept ||
|
||||
(err && err.hmrFatal)
|
||||
) {
|
||||
throw err
|
||||
} else {
|
||||
// const errString = String((err && err.stack) || err)
|
||||
logError(`Error during component init: ${debugName}`, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setError = err => {
|
||||
lastError = err
|
||||
adapter.renderError(err)
|
||||
}
|
||||
|
||||
const instance = {
|
||||
hotOptions: current.hotOptions,
|
||||
proxy: this,
|
||||
id,
|
||||
debugName,
|
||||
refreshComponent,
|
||||
}
|
||||
|
||||
const adapter = new Adapter(instance)
|
||||
|
||||
const { afterMount, rerender } = adapter
|
||||
|
||||
// $destroy is not called when a child component is disposed, so we
|
||||
// need to hook from fragment.
|
||||
const onDestroy = () => {
|
||||
// NOTE do NOT call $destroy on the cmp from here; the cmp is already
|
||||
// dead, this would not work
|
||||
if (!disposed) {
|
||||
disposed = true
|
||||
adapter.dispose()
|
||||
unregister()
|
||||
}
|
||||
}
|
||||
|
||||
// ---- register proxy instance ----
|
||||
|
||||
const unregister = register(rerender)
|
||||
|
||||
// ---- augmented methods ----
|
||||
|
||||
this.$destroy = () => {
|
||||
destroyComponent()
|
||||
onDestroy()
|
||||
}
|
||||
|
||||
// ---- forwarded methods ----
|
||||
|
||||
relayCalls(getComponent, forwardedMethods, this)
|
||||
|
||||
// ---- create & mount target component instance ---
|
||||
|
||||
try {
|
||||
let lastProperties
|
||||
createProxiedComponent(current.Component, options, {
|
||||
allowLiveBinding: current.hotOptions.allowLiveBinding,
|
||||
onDestroy,
|
||||
onMount: afterMount,
|
||||
onInstance: comp => {
|
||||
setComponent(comp)
|
||||
// WARNING the proxy MUST use the same $$ object as its component
|
||||
// instance, because a lot of wiring happens during component
|
||||
// initialisation... lots of references to $$ and $$.fragment have
|
||||
// already been distributed around when the component constructor
|
||||
// returns, before we have a chance to wrap them (and so we can't
|
||||
// wrap them no more, because existing references would become
|
||||
// invalid)
|
||||
this.$$ = comp.$$
|
||||
lastProperties = copyComponentProperties(this, comp, lastProperties)
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
const { target, anchor } = options
|
||||
setError(err, target, anchor)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const syncStatics = (component, proxy, previousKeys) => {
|
||||
// remove previously copied keys
|
||||
if (previousKeys) {
|
||||
for (const key of previousKeys) {
|
||||
delete proxy[key]
|
||||
}
|
||||
}
|
||||
|
||||
// forward static properties and methods
|
||||
const keys = []
|
||||
for (const key in component) {
|
||||
keys.push(key)
|
||||
proxy[key] = component[key]
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
const globalListeners = {}
|
||||
|
||||
const onGlobal = (event, fn) => {
|
||||
event = event.toLowerCase()
|
||||
if (!globalListeners[event]) globalListeners[event] = []
|
||||
globalListeners[event].push(fn)
|
||||
}
|
||||
|
||||
const fireGlobal = (event, ...args) => {
|
||||
const listeners = globalListeners[event]
|
||||
if (!listeners) return
|
||||
for (const fn of listeners) {
|
||||
fn(...args)
|
||||
}
|
||||
}
|
||||
|
||||
const fireBeforeUpdate = () => fireGlobal('beforeupdate')
|
||||
|
||||
const fireAfterUpdate = () => fireGlobal('afterupdate')
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.__SVELTE_HMR = {
|
||||
on: onGlobal,
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('svelte-hmr:ready'))
|
||||
}
|
||||
|
||||
let fatalError = false
|
||||
|
||||
export const hasFatalError = () => fatalError
|
||||
|
||||
/**
|
||||
* Creates a HMR proxy and its associated `reload` function that pushes a new
|
||||
* version to all existing instances of the component.
|
||||
*/
|
||||
export function createProxy({
|
||||
Adapter,
|
||||
id,
|
||||
Component,
|
||||
hotOptions,
|
||||
canAccept,
|
||||
preserveLocalState,
|
||||
}) {
|
||||
const debugName = getDebugName(id)
|
||||
const instances = []
|
||||
|
||||
// current object will be updated, proxy instances will keep a ref
|
||||
const current = {
|
||||
Component,
|
||||
hotOptions,
|
||||
canAccept,
|
||||
preserveLocalState,
|
||||
}
|
||||
|
||||
const name = `Proxy${debugName}`
|
||||
|
||||
// this trick gives the dynamic name Proxy<MyComponent> to the concrete
|
||||
// proxy class... unfortunately, this doesn't shows in dev tools, but
|
||||
// it stills allow to inspect cmp.constructor.name to confirm an instance
|
||||
// is a proxy
|
||||
const proxy = {
|
||||
[name]: class extends ProxyComponent {
|
||||
constructor(options) {
|
||||
try {
|
||||
super(
|
||||
{
|
||||
Adapter,
|
||||
id,
|
||||
debugName,
|
||||
current,
|
||||
register: rerender => {
|
||||
instances.push(rerender)
|
||||
const unregister = () => {
|
||||
const i = instances.indexOf(rerender)
|
||||
instances.splice(i, 1)
|
||||
}
|
||||
return unregister
|
||||
},
|
||||
},
|
||||
options
|
||||
)
|
||||
} catch (err) {
|
||||
// If we fail to create a proxy instance, any instance, that means
|
||||
// that we won't be able to fix this instance when it is updated.
|
||||
// Recovering to normal state will be impossible. HMR's dead.
|
||||
//
|
||||
// Fatal error will trigger a full reload on next update (reloading
|
||||
// right now is kinda pointless since buggy code still exists).
|
||||
//
|
||||
// NOTE Only report first error to avoid too much polution -- following
|
||||
// errors are probably caused by the first one, or they will show up
|
||||
// in turn when the first one is fixed ¯\_(ツ)_/¯
|
||||
//
|
||||
if (!fatalError) {
|
||||
fatalError = true
|
||||
logError(
|
||||
`Unrecoverable HMR error in ${debugName}: ` +
|
||||
`next update will trigger a full reload`
|
||||
)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
},
|
||||
}[name]
|
||||
|
||||
// initialize static members
|
||||
let previousStatics = syncStatics(current.Component, proxy)
|
||||
|
||||
const update = newState => Object.assign(current, newState)
|
||||
|
||||
// reload all existing instances of this component
|
||||
const reload = () => {
|
||||
fireBeforeUpdate()
|
||||
|
||||
// copy statics before doing anything because a static prop/method
|
||||
// could be used somewhere in the create/render call
|
||||
previousStatics = syncStatics(current.Component, proxy, previousStatics)
|
||||
|
||||
const errors = []
|
||||
|
||||
instances.forEach(rerender => {
|
||||
try {
|
||||
rerender()
|
||||
} catch (err) {
|
||||
logError(`Failed to rerender ${debugName}`, err)
|
||||
errors.push(err)
|
||||
}
|
||||
})
|
||||
|
||||
if (errors.length > 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
fireAfterUpdate()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const hasFatalError = () => fatalError
|
||||
|
||||
return { id, proxy, update, reload, hasFatalError, current }
|
||||
}
|
347
node_modules/svelte-hmr/runtime/svelte-hooks.js
generated
vendored
Normal file
347
node_modules/svelte-hmr/runtime/svelte-hooks.js
generated
vendored
Normal file
@ -0,0 +1,347 @@
|
||||
/**
|
||||
* Emulates forthcoming HMR hooks in Svelte.
|
||||
*
|
||||
* All references to private component state ($$) are now isolated in this
|
||||
* module.
|
||||
*/
|
||||
import {
|
||||
current_component,
|
||||
get_current_component,
|
||||
set_current_component,
|
||||
} from 'svelte/internal'
|
||||
|
||||
const captureState = cmp => {
|
||||
// sanity check: propper behaviour here is to crash noisily so that
|
||||
// user knows that they're looking at something broken
|
||||
if (!cmp) {
|
||||
throw new Error('Missing component')
|
||||
}
|
||||
if (!cmp.$$) {
|
||||
throw new Error('Invalid component')
|
||||
}
|
||||
|
||||
const {
|
||||
$$: { callbacks, bound, ctx, props },
|
||||
} = cmp
|
||||
|
||||
const state = cmp.$capture_state()
|
||||
|
||||
// capturing current value of props (or we'll recreate the component with the
|
||||
// initial prop values, that may have changed -- and would not be reflected in
|
||||
// options.props)
|
||||
const hmr_props_values = {}
|
||||
Object.keys(cmp.$$.props).forEach(prop => {
|
||||
hmr_props_values[prop] = ctx[props[prop]]
|
||||
})
|
||||
|
||||
return {
|
||||
ctx,
|
||||
props,
|
||||
callbacks,
|
||||
bound,
|
||||
state,
|
||||
hmr_props_values,
|
||||
}
|
||||
}
|
||||
|
||||
// remapping all existing bindings (including hmr_future_foo ones) to the
|
||||
// new version's props indexes, and refresh them with the new value from
|
||||
// context
|
||||
const restoreBound = (cmp, restore) => {
|
||||
// reverse prop:ctxIndex in $$.props to ctxIndex:prop
|
||||
//
|
||||
// ctxIndex can be either a regular index in $$.ctx or a hmr_future_ prop
|
||||
//
|
||||
const propsByIndex = {}
|
||||
for (const [name, i] of Object.entries(restore.props)) {
|
||||
propsByIndex[i] = name
|
||||
}
|
||||
|
||||
// NOTE $$.bound cannot change in the HMR lifetime of a component, because
|
||||
// if bindings changes, that means the parent component has changed,
|
||||
// which means the child (current) component will be wholly recreated
|
||||
for (const [oldIndex, updateBinding] of Object.entries(restore.bound)) {
|
||||
// can be either regular prop, or future_hmr_ prop
|
||||
const propName = propsByIndex[oldIndex]
|
||||
|
||||
// this should never happen if remembering of future props is enabled...
|
||||
// in any case, there's nothing we can do about it if we have lost prop
|
||||
// name knowledge at this point
|
||||
if (propName == null) continue
|
||||
|
||||
// NOTE $$.props[propName] also propagates knowledge of a possible
|
||||
// future prop to the new $$.props (via $$.props being a Proxy)
|
||||
const newIndex = cmp.$$.props[propName]
|
||||
cmp.$$.bound[newIndex] = updateBinding
|
||||
|
||||
// NOTE if the prop doesn't exist or doesn't exist anymore in the new
|
||||
// version of the component, clearing the binding is the expected
|
||||
// behaviour (since that's what would happen in non HMR code)
|
||||
const newValue = cmp.$$.ctx[newIndex]
|
||||
updateBinding(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
// restoreState
|
||||
//
|
||||
// It is too late to restore context at this point because component instance
|
||||
// function has already been called (and so context has already been read).
|
||||
// Instead, we rely on setting current_component to the same value it has when
|
||||
// the component was first rendered -- which fix support for context, and is
|
||||
// also generally more respectful of normal operation.
|
||||
//
|
||||
const restoreState = (cmp, restore) => {
|
||||
if (!restore) return
|
||||
|
||||
if (restore.callbacks) {
|
||||
cmp.$$.callbacks = restore.callbacks
|
||||
}
|
||||
|
||||
if (restore.bound) {
|
||||
restoreBound(cmp, restore)
|
||||
}
|
||||
|
||||
// props, props.$$slots are restored at component creation (works
|
||||
// better -- well, at all actually)
|
||||
}
|
||||
|
||||
const get_current_component_safe = () => {
|
||||
// NOTE relying on dynamic bindings (current_component) makes us dependent on
|
||||
// bundler config (and apparently it does not work in demo-svelte-nollup)
|
||||
try {
|
||||
// unfortunately, unlike current_component, get_current_component() can
|
||||
// crash in the normal path (when there is really no parent)
|
||||
return get_current_component()
|
||||
} catch (err) {
|
||||
// ... so we need to consider that this error means that there is no parent
|
||||
//
|
||||
// that makes us tightly coupled to the error message but, at least, we
|
||||
// won't mute an unexpected error, which is quite a horrible thing to do
|
||||
if (err.message === 'Function called outside component initialization') {
|
||||
// who knows...
|
||||
return current_component
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createProxiedComponent = (
|
||||
Component,
|
||||
initialOptions,
|
||||
{ allowLiveBinding, onInstance, onMount, onDestroy }
|
||||
) => {
|
||||
let cmp
|
||||
let options = initialOptions
|
||||
|
||||
const isCurrent = _cmp => cmp === _cmp
|
||||
|
||||
const assignOptions = (target, anchor, restore, preserveLocalState) => {
|
||||
const props = Object.assign({}, options.props)
|
||||
|
||||
// Filtering props to avoid "unexpected prop" warning
|
||||
// NOTE this is based on props present in initial options, but it should
|
||||
// always works, because props that are passed from the parent can't
|
||||
// change without a code change to the parent itself -- hence, the
|
||||
// child component will be fully recreated, and initial options should
|
||||
// always represent props that are currnetly passed by the parent
|
||||
if (options.props && restore.hmr_props_values) {
|
||||
for (const prop of Object.keys(options.props)) {
|
||||
if (restore.hmr_props_values.hasOwnProperty(prop)) {
|
||||
props[prop] = restore.hmr_props_values[prop]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (preserveLocalState && restore.state) {
|
||||
if (Array.isArray(preserveLocalState)) {
|
||||
// form ['a', 'b'] => preserve only 'a' and 'b'
|
||||
props.$$inject = {}
|
||||
for (const key of preserveLocalState) {
|
||||
props.$$inject[key] = restore.state[key]
|
||||
}
|
||||
} else {
|
||||
props.$$inject = restore.state
|
||||
}
|
||||
} else {
|
||||
delete props.$$inject
|
||||
}
|
||||
options = Object.assign({}, initialOptions, {
|
||||
target,
|
||||
anchor,
|
||||
props,
|
||||
hydrate: false,
|
||||
})
|
||||
}
|
||||
|
||||
// Preserving knowledge of "future props" -- very hackish version (maybe
|
||||
// there should be an option to opt out of this)
|
||||
//
|
||||
// The use case is bind:something where something doesn't exist yet in the
|
||||
// target component, but comes to exist later, after a HMR update.
|
||||
//
|
||||
// If Svelte can't map a prop in the current version of the component, it
|
||||
// will just completely discard it:
|
||||
// https://github.com/sveltejs/svelte/blob/1632bca34e4803d6b0e0b0abd652ab5968181860/src/runtime/internal/Component.ts#L46
|
||||
//
|
||||
const rememberFutureProps = cmp => {
|
||||
if (typeof Proxy === 'undefined') return
|
||||
|
||||
cmp.$$.props = new Proxy(cmp.$$.props, {
|
||||
get(target, name) {
|
||||
if (target[name] === undefined) {
|
||||
target[name] = 'hmr_future_' + name
|
||||
}
|
||||
return target[name]
|
||||
},
|
||||
set(target, name, value) {
|
||||
target[name] = value
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const instrument = targetCmp => {
|
||||
const createComponent = (Component, restore, previousCmp) => {
|
||||
set_current_component(parentComponent || previousCmp)
|
||||
const comp = new Component(options)
|
||||
// NOTE must be instrumented before restoreState, because restoring
|
||||
// bindings relies on hacked $$.props
|
||||
instrument(comp)
|
||||
restoreState(comp, restore)
|
||||
return comp
|
||||
}
|
||||
|
||||
rememberFutureProps(targetCmp)
|
||||
|
||||
targetCmp.$$.on_hmr = []
|
||||
|
||||
// `conservative: true` means we want to be sure that the new component has
|
||||
// actually been successfuly created before destroying the old instance.
|
||||
// This could be useful for preventing runtime errors in component init to
|
||||
// bring down the whole HMR. Unfortunately the implementation bellow is
|
||||
// broken (FIXME), but that remains an interesting target for when HMR hooks
|
||||
// will actually land in Svelte itself.
|
||||
//
|
||||
// The goal would be to render an error inplace in case of error, to avoid
|
||||
// losing the navigation stack (especially annoying in native, that is not
|
||||
// based on URL navigation, so we lose the current page on each error).
|
||||
//
|
||||
targetCmp.$replace = (
|
||||
Component,
|
||||
{
|
||||
target = options.target,
|
||||
anchor = options.anchor,
|
||||
preserveLocalState,
|
||||
conservative = false,
|
||||
}
|
||||
) => {
|
||||
const restore = captureState(targetCmp)
|
||||
assignOptions(
|
||||
target || options.target,
|
||||
anchor,
|
||||
restore,
|
||||
preserveLocalState
|
||||
)
|
||||
|
||||
const callbacks = cmp ? cmp.$$.on_hmr : []
|
||||
|
||||
const afterCallbacks = callbacks.map(fn => fn(cmp)).filter(Boolean)
|
||||
|
||||
const previous = cmp
|
||||
if (conservative) {
|
||||
try {
|
||||
const next = createComponent(Component, restore, previous)
|
||||
// prevents on_destroy from firing on non-final cmp instance
|
||||
cmp = null
|
||||
previous.$destroy()
|
||||
cmp = next
|
||||
} catch (err) {
|
||||
cmp = previous
|
||||
throw err
|
||||
}
|
||||
} else {
|
||||
// prevents on_destroy from firing on non-final cmp instance
|
||||
cmp = null
|
||||
if (previous) {
|
||||
// previous can be null if last constructor has crashed
|
||||
previous.$destroy()
|
||||
}
|
||||
cmp = createComponent(Component, restore, cmp)
|
||||
}
|
||||
|
||||
cmp.$$.hmr_cmp = cmp
|
||||
|
||||
for (const fn of afterCallbacks) {
|
||||
fn(cmp)
|
||||
}
|
||||
|
||||
cmp.$$.on_hmr = callbacks
|
||||
|
||||
return cmp
|
||||
}
|
||||
|
||||
// NOTE onMount must provide target & anchor (for us to be able to determinate
|
||||
// actual DOM insertion point)
|
||||
//
|
||||
// And also, to support keyed list, it needs to be called each time the
|
||||
// component is moved (same as $$.fragment.m)
|
||||
if (onMount) {
|
||||
const m = targetCmp.$$.fragment.m
|
||||
targetCmp.$$.fragment.m = (...args) => {
|
||||
const result = m(...args)
|
||||
onMount(...args)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE onDestroy must be called even if the call doesn't pass through the
|
||||
// component's $destroy method (that we can hook onto by ourselves, since
|
||||
// it's public API) -- this happens a lot in svelte's internals, that
|
||||
// manipulates cmp.$$.fragment directly, often binding to fragment.d,
|
||||
// for example
|
||||
if (onDestroy) {
|
||||
targetCmp.$$.on_destroy.push(() => {
|
||||
if (isCurrent(targetCmp)) {
|
||||
onDestroy()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (onInstance) {
|
||||
onInstance(targetCmp)
|
||||
}
|
||||
|
||||
// Svelte 3 creates and mount components from their constructor if
|
||||
// options.target is present.
|
||||
//
|
||||
// This means that at this point, the component's `fragment.c` and,
|
||||
// most notably, `fragment.m` will already have been called _from inside
|
||||
// createComponent_. That is: before we have a chance to hook on it.
|
||||
//
|
||||
// Proxy's constructor
|
||||
// -> createComponent
|
||||
// -> component constructor
|
||||
// -> component.$$.fragment.c(...) (or l, if hydrate:true)
|
||||
// -> component.$$.fragment.m(...)
|
||||
//
|
||||
// -> you are here <-
|
||||
//
|
||||
if (onMount) {
|
||||
const { target, anchor } = options
|
||||
if (target) {
|
||||
onMount(target, anchor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parentComponent = allowLiveBinding
|
||||
? current_component
|
||||
: get_current_component_safe()
|
||||
|
||||
cmp = new Component(options)
|
||||
cmp.$$.hmr_cmp = cmp
|
||||
|
||||
instrument(cmp)
|
||||
|
||||
return cmp
|
||||
}
|
62
node_modules/svelte-hmr/runtime/svelte-native/patch-page-show-modal.js
generated
vendored
Normal file
62
node_modules/svelte-hmr/runtime/svelte-native/patch-page-show-modal.js
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
// This module monkey patches Page#showModal in order to be able to
|
||||
// access from the HMR proxy data passed to `showModal` in svelte-native.
|
||||
//
|
||||
// Data are stored in a opaque prop accessible with `getModalData`.
|
||||
//
|
||||
// It also switches the `closeCallback` option with a custom brewed one
|
||||
// in order to give the proxy control over when its own instance will be
|
||||
// destroyed.
|
||||
//
|
||||
// Obviously this method suffer from extreme coupling with the target code
|
||||
// in svelte-native. So it would be wise to recheck compatibility on SN
|
||||
// version upgrades.
|
||||
//
|
||||
// Relevant code is there (last checked version):
|
||||
//
|
||||
// https://github.com/halfnelson/svelte-native/blob/48fdc97d2eb4d3958cfcb4ff6cf5755a220829eb/src/dom/navigation.ts#L132
|
||||
//
|
||||
|
||||
// FIXME should we override ViewBase#showModal instead?
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { Page } from '@nativescript/core'
|
||||
|
||||
const prop =
|
||||
typeof Symbol !== 'undefined'
|
||||
? Symbol('hmr_svelte_native_modal')
|
||||
: '___HMR_SVELTE_NATIVE_MODAL___'
|
||||
|
||||
const sup = Page.prototype.showModal
|
||||
|
||||
let patched = false
|
||||
|
||||
export const patchShowModal = () => {
|
||||
// guard: already patched
|
||||
if (patched) return
|
||||
patched = true
|
||||
|
||||
Page.prototype.showModal = function(modalView, options) {
|
||||
const modalData = {
|
||||
originalOptions: options,
|
||||
closeCallback: options.closeCallback,
|
||||
}
|
||||
|
||||
modalView[prop] = modalData
|
||||
|
||||
// Proxies to a function that can be swapped on the fly by HMR proxy.
|
||||
//
|
||||
// The default is still to call the original closeCallback from svelte
|
||||
// navtive, which will destroy the modal view & component. This way, if
|
||||
// no HMR happens on the modal content, normal behaviour is preserved
|
||||
// without the proxy having any work to do.
|
||||
//
|
||||
const closeCallback = (...args) => {
|
||||
return modalData.closeCallback(...args)
|
||||
}
|
||||
|
||||
const tamperedOptions = Object.assign({}, options, { closeCallback })
|
||||
|
||||
return sup.call(this, modalView, tamperedOptions)
|
||||
}
|
||||
}
|
||||
|
||||
export const getModalData = modalView => modalView[prop]
|
341
node_modules/svelte-hmr/runtime/svelte-native/proxy-adapter-native.js
generated
vendored
Normal file
341
node_modules/svelte-hmr/runtime/svelte-native/proxy-adapter-native.js
generated
vendored
Normal file
@ -0,0 +1,341 @@
|
||||
/* global document */
|
||||
|
||||
import { adapter as ProxyAdapterDom } from '../proxy-adapter-dom'
|
||||
|
||||
import { patchShowModal, getModalData } from './patch-page-show-modal'
|
||||
|
||||
patchShowModal()
|
||||
|
||||
// Svelte Native support
|
||||
// =====================
|
||||
//
|
||||
// Rerendering Svelte Native page proves challenging...
|
||||
//
|
||||
// In NativeScript, pages are the top level component. They are normally
|
||||
// introduced into NativeScript's runtime by its `navigate` function. This
|
||||
// is how Svelte Natives handles it: it renders the Page component to a
|
||||
// dummy fragment, and "navigate" to the page element thus created.
|
||||
//
|
||||
// As long as modifications only impact child components of the page, then
|
||||
// we can keep the existing page and replace its content for HMR.
|
||||
//
|
||||
// However, if the page component itself is modified (including its system
|
||||
// title bar), things get hairy...
|
||||
//
|
||||
// Apparently, the sole way of introducing a new page in a NS application is
|
||||
// to navigate to it (no way to just replace it in its parent "element", for
|
||||
// example). This is how it is done in NS's own "core" HMR.
|
||||
//
|
||||
// NOTE The last paragraph has not really been confirmed with NS6.
|
||||
//
|
||||
// Unfortunately the API they're using to do that is not public... Its various
|
||||
// parts remain exposed though (but documented as private), so this exploratory
|
||||
// work now relies on it. It might be fragile...
|
||||
//
|
||||
// The problem is that there is no public API that can navigate to a page and
|
||||
// replace (like location.replace) the current history entry. Actually there
|
||||
// is an active issue at NS asking for that. Incidentally, members of
|
||||
// NativeScript-Vue have commented on the issue to weight in for it -- they
|
||||
// probably face some similar challenge.
|
||||
//
|
||||
// https://github.com/NativeScript/NativeScript/issues/6283
|
||||
|
||||
const getNavTransition = ({ transition }) => {
|
||||
if (typeof transition === 'string') {
|
||||
transition = { name: transition }
|
||||
}
|
||||
return transition ? { animated: true, transition } : { animated: false }
|
||||
}
|
||||
|
||||
// copied from TNS FrameBase.replacePage
|
||||
//
|
||||
// it is not public but there is a comment in there indicating it is for
|
||||
// HMR (probably their own core HMR though)
|
||||
//
|
||||
// NOTE this "worked" in TNS 5, but not anymore in TNS 6: updated version bellow
|
||||
//
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const replacePage_tns5 = (frame, newPageElement, hotOptions) => {
|
||||
const currentBackstackEntry = frame._currentEntry
|
||||
frame.navigationType = 2
|
||||
frame.performNavigation({
|
||||
isBackNavigation: false,
|
||||
entry: {
|
||||
resolvedPage: newPageElement.nativeView,
|
||||
//
|
||||
// entry: currentBackstackEntry.entry,
|
||||
entry: Object.assign(
|
||||
currentBackstackEntry.entry,
|
||||
getNavTransition(hotOptions)
|
||||
),
|
||||
navDepth: currentBackstackEntry.navDepth,
|
||||
fragmentTag: currentBackstackEntry.fragmentTag,
|
||||
frameId: currentBackstackEntry.frameId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Updated for TNS v6
|
||||
//
|
||||
// https://github.com/NativeScript/NativeScript/blob/6.1.1/tns-core-modules/ui/frame/frame-common.ts#L656
|
||||
const replacePage = (frame, newPageElement) => {
|
||||
const currentBackstackEntry = frame._currentEntry
|
||||
const newPage = newPageElement.nativeView
|
||||
const newBackstackEntry = {
|
||||
entry: currentBackstackEntry.entry,
|
||||
resolvedPage: newPage,
|
||||
navDepth: currentBackstackEntry.navDepth,
|
||||
fragmentTag: currentBackstackEntry.fragmentTag,
|
||||
frameId: currentBackstackEntry.frameId,
|
||||
}
|
||||
const navigationContext = {
|
||||
entry: newBackstackEntry,
|
||||
isBackNavigation: false,
|
||||
navigationType: 2 /* NavigationType replace */,
|
||||
}
|
||||
frame._navigationQueue.push(navigationContext)
|
||||
frame._processNextNavigationEntry()
|
||||
}
|
||||
|
||||
export const adapter = class ProxyAdapterNative extends ProxyAdapterDom {
|
||||
constructor(instance) {
|
||||
super(instance)
|
||||
|
||||
this.nativePageElement = null
|
||||
this.originalNativeView = null
|
||||
this.navigatedFromHandler = null
|
||||
|
||||
this.relayNativeNavigatedFrom = this.relayNativeNavigatedFrom.bind(this)
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose()
|
||||
this.releaseNativePageElement()
|
||||
}
|
||||
|
||||
releaseNativePageElement() {
|
||||
if (this.nativePageElement) {
|
||||
// native cleaning will happen when navigating back from the page
|
||||
this.nativePageElement = null
|
||||
}
|
||||
}
|
||||
|
||||
// svelte-native uses navigateFrom event + e.isBackNavigation to know
|
||||
// when to $destroy the component -- but we don't want our proxy instance
|
||||
// destroyed when we renavigate to the same page for navigation purposes!
|
||||
interceptPageNavigation(pageElement) {
|
||||
const originalNativeView = pageElement.nativeView
|
||||
const { on } = originalNativeView
|
||||
const ownOn = originalNativeView.hasOwnProperty('on')
|
||||
// tricks svelte-native into giving us its handler
|
||||
originalNativeView.on = function(type, handler) {
|
||||
if (type === 'navigatedFrom') {
|
||||
this.navigatedFromHandler = handler
|
||||
if (ownOn) {
|
||||
originalNativeView.on = on
|
||||
} else {
|
||||
delete originalNativeView.on
|
||||
}
|
||||
} else {
|
||||
//some other handler wireup, we will just pass it on.
|
||||
if (on) {
|
||||
on(type, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterMount(target, anchor) {
|
||||
// nativePageElement needs to be updated each time (only for page
|
||||
// components, native component that are not pages follow normal flow)
|
||||
//
|
||||
// TODO quid of components that are initially a page, but then have the
|
||||
// <page> tag removed while running? or the opposite?
|
||||
//
|
||||
// insertionPoint needs to be updated _only when the target changes_ --
|
||||
// i.e. when the component is mount, i.e. (in svelte3) when the component
|
||||
// is _created_, and svelte3 doesn't allow it to move afterward -- that
|
||||
// is, insertionPoint only needs to be created once when the component is
|
||||
// first mounted.
|
||||
//
|
||||
// TODO is it really true that components' elements cannot move in the
|
||||
// DOM? what about keyed list?
|
||||
//
|
||||
|
||||
const isNativePage =
|
||||
(target.tagName === 'fragment' || target.tagName === 'frame') &&
|
||||
target.firstChild &&
|
||||
target.firstChild.tagName == 'page'
|
||||
if (isNativePage) {
|
||||
const nativePageElement = target.firstChild
|
||||
this.interceptPageNavigation(nativePageElement)
|
||||
this.nativePageElement = nativePageElement
|
||||
} else {
|
||||
// try to protect against components changing from page to no-page
|
||||
// or vice versa -- see DEBUG 1 above. NOT TESTED so prolly not working
|
||||
this.nativePageElement = null
|
||||
super.afterMount(target, anchor)
|
||||
}
|
||||
}
|
||||
|
||||
rerender() {
|
||||
const { nativePageElement } = this
|
||||
if (nativePageElement) {
|
||||
this.rerenderNative()
|
||||
} else {
|
||||
super.rerender()
|
||||
}
|
||||
}
|
||||
|
||||
rerenderNative() {
|
||||
const { nativePageElement: oldPageElement } = this
|
||||
const nativeView = oldPageElement.nativeView
|
||||
const frame = nativeView.frame
|
||||
if (frame) {
|
||||
return this.rerenderPage(frame, nativeView)
|
||||
}
|
||||
const modalParent = nativeView._modalParent // FIXME private API
|
||||
if (modalParent) {
|
||||
return this.rerenderModal(modalParent, nativeView)
|
||||
}
|
||||
// wtf? hopefully a race condition with a destroyed component, so
|
||||
// we have nothing more to do here
|
||||
//
|
||||
// for once, it happens when hot reloading dev deps, like this file
|
||||
//
|
||||
}
|
||||
|
||||
rerenderPage(frame, previousPageView) {
|
||||
const isCurrentPage = frame.currentPage === previousPageView
|
||||
if (isCurrentPage) {
|
||||
const {
|
||||
instance: { hotOptions },
|
||||
} = this
|
||||
const newPageElement = this.createPage()
|
||||
if (!newPageElement) {
|
||||
throw new Error('Failed to create updated page')
|
||||
}
|
||||
const isFirstPage = !frame.canGoBack()
|
||||
|
||||
if (isFirstPage) {
|
||||
// NOTE not so sure of bellow with the new NS6 method for replace
|
||||
//
|
||||
// The "replacePage" strategy does not work on the first page
|
||||
// of the stack.
|
||||
//
|
||||
// Resulting bug:
|
||||
// - launch
|
||||
// - change first page => HMR
|
||||
// - navigate to other page
|
||||
// - back
|
||||
// => actual: back to OS
|
||||
// => expected: back to page 1
|
||||
//
|
||||
// Fortunately, we can overwrite history in this case.
|
||||
//
|
||||
const nativeView = newPageElement.nativeView
|
||||
frame.navigate(
|
||||
Object.assign(
|
||||
{},
|
||||
{
|
||||
create: () => nativeView,
|
||||
clearHistory: true,
|
||||
},
|
||||
getNavTransition(hotOptions)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
replacePage(frame, newPageElement, hotOptions)
|
||||
}
|
||||
} else {
|
||||
const backEntry = frame.backStack.find(
|
||||
({ resolvedPage: page }) => page === previousPageView
|
||||
)
|
||||
if (!backEntry) {
|
||||
// well... looks like we didn't make it to history after all
|
||||
return
|
||||
}
|
||||
// replace existing nativeView
|
||||
const newPageElement = this.createPage()
|
||||
if (newPageElement) {
|
||||
backEntry.resolvedPage = newPageElement.nativeView
|
||||
} else {
|
||||
throw new Error('Failed to create updated page')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// modalParent is the page on which showModal(...) was called
|
||||
// oldPageElement is the modal content, that we're actually trying to reload
|
||||
rerenderModal(modalParent, modalView) {
|
||||
const modalData = getModalData(modalView)
|
||||
|
||||
modalData.closeCallback = () => {
|
||||
const nativePageElement = this.createPage()
|
||||
if (!nativePageElement) {
|
||||
throw new Error('Failed to created updated modal page')
|
||||
}
|
||||
const { nativeView } = nativePageElement
|
||||
const { originalOptions } = modalData
|
||||
// Options will get monkey patched again, the only work left for us
|
||||
// is to try to reduce visual disturbances.
|
||||
//
|
||||
// FIXME Even that proves too much unfortunately... Apparently TNS
|
||||
// does not respect the `animated` option in this context:
|
||||
// https://docs.nativescript.org/api-reference/interfaces/_ui_core_view_base_.showmodaloptions#animated
|
||||
//
|
||||
const options = Object.assign({}, originalOptions, { animated: false })
|
||||
modalParent.showModal(nativeView, options)
|
||||
}
|
||||
|
||||
modalView.closeModal()
|
||||
}
|
||||
|
||||
createPage() {
|
||||
const {
|
||||
instance: { refreshComponent },
|
||||
} = this
|
||||
const { nativePageElement, relayNativeNavigatedFrom } = this
|
||||
const oldNativeView = nativePageElement.nativeView
|
||||
// rerender
|
||||
const target = document.createElement('fragment')
|
||||
// not using conservative for now, since there's nothing in place here to
|
||||
// leverage it (yet?) -- and it might be easier to miss breakages in native
|
||||
// only code paths
|
||||
refreshComponent(target, null)
|
||||
// this.nativePageElement is updated in afterMount, triggered by proxy / hooks
|
||||
const newPageElement = this.nativePageElement
|
||||
// update event proxy
|
||||
oldNativeView.off('navigatedFrom', relayNativeNavigatedFrom)
|
||||
nativePageElement.nativeView.on('navigatedFrom', relayNativeNavigatedFrom)
|
||||
return newPageElement
|
||||
}
|
||||
|
||||
relayNativeNavigatedFrom({ isBackNavigation }) {
|
||||
const { originalNativeView, navigatedFromHandler } = this
|
||||
if (!isBackNavigation) {
|
||||
return
|
||||
}
|
||||
if (originalNativeView) {
|
||||
const { off } = originalNativeView
|
||||
const ownOff = originalNativeView.hasOwnProperty('off')
|
||||
originalNativeView.off = function() {
|
||||
this.navigatedFromHandler = null
|
||||
if (ownOff) {
|
||||
originalNativeView.off = off
|
||||
} else {
|
||||
delete originalNativeView.off
|
||||
}
|
||||
}
|
||||
}
|
||||
if (navigatedFromHandler) {
|
||||
return navigatedFromHandler.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
renderError(err /* , target, anchor */) {
|
||||
// TODO fallback on TNS error handler for now... at least our error
|
||||
// is more informative
|
||||
throw err
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user