feat: docker compose maybe

This commit is contained in:
2023-11-13 16:10:04 -05:00
parent 180b261e40
commit b625ccd8d6
8031 changed files with 2182966 additions and 0 deletions

7
node_modules/svelte-hmr/runtime/hot-api-esm.js generated vendored Normal file
View 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
View 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
View File

@ -0,0 +1 @@
export { makeApplyHmr } from './hot-api.js'

133
node_modules/svelte-hmr/runtime/overlay.js generated vendored Normal file
View 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
View 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
View 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
View 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
}

View 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]

View 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
}
}