Может ли кто-нибудь сказать мне, почему это приложение запускает функцию, называемую ошибкой инициализации внешнего компонента? (Обновление: обнаружена причина этой конкретной ошибки, но все еще есть вопросы ниже о лучших практики использования свертки со стройными библиотеками.)
Кажется, это происходит только тогда, когда я вызываю getContext
(или onMount
и т. Д.) Из компонента (что должно быть разрешено) внутри цикла {#each}
. Но это произойдет только в том случае, если я включу external: ['svelte']
в библиотеку, так что это может быть скорее вопрос для накопления, чем вопрос Svelte.
Вот мой код (который вы можете клонировать с здесь и попробовать сами):
"dependencies": {
"my-new-component": "file:packages/my-new-component",
…
}
src/App.svelte
:
<script>
import { FieldArray } from "my-new-component";
import { UsesContext } from "my-new-component";
</script>
<FieldArray let:names>
{#each names as name, i}
<div>{name}: <UsesContext /></div>
{/each}
</FieldArray>
packages/my-new-component/src/FieldArray.svelte
:
<script>
let names = ['a']
const handleClick = () => {
names = ['a', 'b']
}
</script>
<button on:click={handleClick}>Blow up</button>
<slot names={names} />
packages/my-new-component/src/UsesContext.svelte
:
<script>
import {setContext, getContext} from 'svelte'
const key = {}
setContext(key, 'context')
let context = getContext(key)
</script>
{context}
Довольно простые вещи, правда?
Что я делаю неправильно?
Я понимаю, что setContext
можно вызывать только синхронно во время инициализации компонента (на верхнем уровне раздела <script>
) и что вызов _14 _ / _ 15_ или любых методов жизненного цикла (onMount
) асинхронно после инициализации компонента (например, из обработчик событий) может привести (и, вероятно, является наиболее частой причиной) этой ошибки.
Но я я вызываю его только синхронно из сценария верхнего уровня компонента UsesContext.svelte
... так что это не может быть проблемой, верно?
Единственное, что я делаю асинхронно, - это обновляю переменную let
. Но это единственное, что разрешено делать (и обычно это делается) асинхронно со Svelte, не так ли?
(Конечно, это надуманный пример, чтобы сделать его как можно более минимальным воспроизводимым примером. В разделе настоящая библиотека, над которой я работаю, я подписываюсь на form.registerField
из окончательной формы и асинхронное обновление let
переменных компонента из этого обратного вызова ... подход, который отлично работает в текущей версии - но вызывает эту ошибку, когда я пытаюсь использовать его в способ, описанный здесь.)
Я не чувствую, что делаю что-то, что запрещено в Svelte. Я?
Вещи, из-за которых ошибка исчезает
Если я изменю один из следующих факторов (что не должно иметь никакого значения), все будет работать нормально:
Уберите петлю
{#each}
. (совершить)<FieldArray let:names> <div>{names}</div> <UsesContext /> </FieldArray>
Обновлять переменную синхронно, а не асинхронно. (совершить)
Скопируйте компонент
UsesContext
из библиотеки в приложение и вместо этого импортируйте локальную копию компонента. (совершить)Несмотря на то, что это идентичная копия компонента, она работает при импорте из приложения, но с ошибками при импорте из библиотеки.
Используйте локальную копию (фиксация версии () или встроенная версия //github.com/TylerRick/repro_getContext_from_comp_inside_each/commit/2885063cdb327afea731bcc3569018048af2aa6e "rel =" nofollow noreferrer "> commit) компонента
FieldArray
.Почему это не работает, если любой из них импортирован из пакета? Может быть связано со следующим фактором ...
Удаление
external: ['svelte']
изpackages/my-new-component/rollup.config.js
приводит к исчезновению ошибки. (совершить)См. Раздел «Следует ли библиотекам Svelte использовать
external: ['svelte']
» ниже.
Почему любой из них решает проблему? Как все они связаны?
Чья это ошибка?
Это ошибка Svelte? Это может быть ошибкой, связанной с инициализацией / отсоединением компонентов внутри цикла {#each}
(поскольку это произошло у меня только с этой комбинацией) ...
Но я подозреваю, что проблема напрямую связана с тем, как библиотеки, которые я использую, упаковывают свой код (с накоплением). В частности, включают ли они дополнительные копии внутреннего кода Svelte.
Следует ли библиотекам Svelte использовать external: ['svelte']
?
Насколько я понимаю, при создании библиотеки другие библиотеки, от которых они зависят, такие как React или Svelte, должны быть указаны в обоих разделах:
peerDependencies
external: [...]
так что дублирующая копия React / Svelte / etc не будет установлена в node_modules (в случае peerDependencies
) или встроена как часть пакета dist, который строится накопительным пакетом (в случае параметра external
накопления). (См. эту статью.)
Вероятно, гораздо важнее включить дополнительную копию гигантской библиотеки времени выполнения, такой как React или Angular, чем включить дополнительную копию минимального кода времени выполнения, используемого Svelte. Но меня беспокоит не столько размер пакета, сколько возможные побочные эффекты / ошибки, которые могут возникнуть из-за наличия более чем одной копии Svelte. (Я определенно сталкивался с подобными проблемами раньше с React, когда у меня было несколько экземпляров ReactDOM
, плавающих вокруг.)
Так почему же нет официального component-template
в том числе external: ['svelte']
? (И почему этот комментарий предлагает добавить external: ['svelte/internal']
, а не external: ['svelte']
? Кто импортирует напрямую from 'svelte/internal'
? Неважно, я думаю, что нашел ответ на этот вопрос. Подробнее ниже.)
Но почему (например) svelte-urql использовать external
для всех из его _41 _ / _ 42_ (включая svelte
)? Должны ли они не делать этого? Конечно, в их случае они в настоящее время не включают каких-либо изящных компонентов (только вспомогательные функции и setContext
), так что может быть поэтому это еще не вызвало у них никаких проблем.
По иронии судьбы, я считаю, что на самом деле эта функция, вызванная ошибкой инициализации внешнего компонента, сначала побудила меня добавить эту external: ['svelte']
строку.
В пакете моего приложения (созданном с использованием webpack) я заметил, что он включает несколько копий svelte - и под этим я имею в виду несколько копий общих функций, таких как setContext
. Это обеспокоило меня, поэтому я решил попытаться выяснить, как сделать так, чтобы в мой комплект была включена только одна копия svelte.
Я был особенно обеспокоен, когда увидел несколько случаев появления _47 _ / _ 48_ в моем наборе приложений.
Если вам интересно, из каких библиотек / модулей берутся эти копии, похоже, вот эти (комментарии любезно добавлены веб-пакетом):
!*** /home/…/svelte-final-form/dist/index.mjs ***!
(с нетexternal: ['svelte']
)let current_component; function set_current_component(component) { current_component = component; } function get_current_component() { if (!current_component) throw new Error(`Function called outside component initialization`); return current_component; } function onMount(fn) { get_current_component().$$.on_mount.push(fn); } function onDestroy(fn) { get_current_component().$$.on_destroy.push(fn); } function setContext(key, context) { get_current_component().$$.context.set(key, context); }
!*** /home/…/my-new-component/dist/index.mjs ***!
(сexternal: ['svelte']
)let current_component; function set_current_component(component) { current_component = component; } const dirty_components = []; const binding_callbacks = []; …
(
function get_current_component()
даже не фигурировал в этом разделе, по-видимому, потому что сценарий компонента ссылается наgetContext
из другой внешней копии Svelte, поэтому при раскачивании дерева было замечено, что его локальная версияget_current_component()
не использовалась, и он не нужно было включать его определение :)function instance$1($$self) { console.log("my-new-component UsesContext"); const key = {}; Object(svelte__WEBPACK_IMPORTED_MODULE_0__["setContext"])(key, "context"); let context = Object(svelte__WEBPACK_IMPORTED_MODULE_0__["getContext"])(key); return [context]; }
!*** ./node_modules/svelte-forms-lib/build/index.mjs ***!
(с нетexternal: ['svelte']
)var current_component; function set_current_component(component) { current_component = component; } function get_current_component() { if (!current_component) throw new Error("Function called outside component initialization"); return current_component; } function setContext(key, context) { get_current_component().$$.context.set(key, context); }
!*** ./node_modules/svelte-select/index.mjs ***!
(с нетexternal: ['svelte']
)var current_component; function set_current_component(component) { current_component = component; } function get_current_component() { if (!current_component) throw new Error("Function called outside component initialization"); return current_component; } function beforeUpdate(fn) { get_current_component().$$.before_update.push(fn); }
!*** ./node_modules/svelte/internal/index.mjs ***!
(с[email protected]
)var current_component; function set_current_component(component) { current_component = component; } function get_current_component() { if (!current_component) throw new Error("Function called outside component initialization"); return current_component; } function beforeUpdate(fn) { get_current_component().$$.before_update.push(fn); } … function setContext(key, context) { get_current_component().$$.context.set(key, context); }
Как видите, каждая копия представляет собой немного отличающуюся версию svelte (в зависимости от номера версии svelte, используемой для сборки каждого модуля, и от того, какие неиспользуемые функции были удалены из-за встряхивания дерева).
Моя первоначальная гипотеза заключалась в том, что ошибка if (!current_component) throw new Error("Function called outside component initialization");
была обнаружена , потому что каждый компонент / библиотека поддерживал свою собственную копию current_component
, поэтому, возможно, когда она пересекала границу от одного компонента приложения / библиотеки (копия Svelte) до компонент другой библиотеки (копия Svelte), current_component
не был определен в этой новой области, хотя он был правильно установлен в старой области?
Я до сих пор этого не исключил. И это предчувствие заставило меня попытаться уничтожить эти лишние копии, добавив в первую очередь external: ['svelte']
- чтобы попытаться устранить ошибку.
Как external: ['svelte']
влияет на содержимое my-new-component
пакета
Вот как изменяется вывод my-new-component
, когда я добавляю external: ['svelte']
:
⟫ git diff
diff --git a/dist/index.mjs b/dist/index.mjs
index a0dbbc7..01938f3 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -1,3 +1,5 @@
+import { setContext, getContext } from 'svelte';
+
function noop() { }
function assign(tar, src) {
// @ts-ignore
@@ -76,17 +78,6 @@ let current_component;
function set_current_component(component) {
current_component = component;
}
-function get_current_component() {
- if (!current_component)
- throw new Error(`Function called outside component initialization`);
- return current_component;
-}
-function setContext(key, context) {
- get_current_component().$$.context.set(key, context);
-}
-function getContext(key) {
- return get_current_component().$$.context.get(key);
-}
const dirty_components = [];
const binding_callbacks = [];
Сначала это выглядело действительно хорошо, потому что это означало, что эта библиотека может повторно использовать функции setContext
, getContext
(и, предположительно, любые другие функции Svelte API) из своей одноранговой зависимости - пакета svelte
, установленного в приложении < / em> node_modules/
dir - вместо того, чтобы без необходимости включать дублирующую копию этих функций в пакет библиотеки.
Но чем больше я вглядываюсь в это, я задаюсь вопросом, не совсем ли это правильно. Больше всего беспокоит то, что хотя некоторые функции Svelte исчезли из пакета JS моей библиотеки, некоторые из них - особенно set_current_component
и init
- остались в пакете, потому что моя библиотека не import
их специально - это внутренние методы, вставленные компилятором Svelte ...
Так что, возможно, именно эта проблема вызвала ошибку: функции _84 _ / _ 85_, которые остаются в пакете моей библиотеки, ссылаются на свои собственные current_component
с локальной областью видимости, но _87 _ / _ 88_, который я специально импортировал, в конечном итоге вызывает get_current_component
из другого внешнего копия Svelte, которая относится к другому current_component
в другом объеме.
О, так вот почему этот комментарий предлагаю добавить external: ['svelte/internal']
, а не external: ['svelte']
!
Обновление: найдено решение ошибки (по крайней мере, для этой конкретной ситуации)!
Когда я попытался добавить 'svelte/internal'
в список external
, набор общих функций svelte исчез из пакета моей библиотеки и был заменен другими Svelte import
s:
+import { SvelteComponent, init, safe_not_equal, text, insert, noop, detach, create_slot, update_slot, transition_in, transition_out } from 'svelte/internal';
import { setContext, getContext } from 'svelte';
-function noop() { }
-function assign(tar, src) {
…
-let current_component;
-function set_current_component(component) {
- current_component = component;
-}
Единственными строками, которые остались теперь, являются сгенерированные функции (create_fragment
, create_fragment$1
,…), которые относятся к конкретным компонентам. Пакет теперь очень маленький - 148 строк, по сравнению с 432. Это именно то, что я хотел! Лучше всего то, что он заставляет код работать (устраняет ошибку) (совершить)
Итак, я предполагаю, что проблема, с которой я столкнулся, заключается в том, что я только частично внешне реализовал svelte, поэтому пакет моей библиотеки содержал смесь ссылок на внешнюю копию Svelte и внутреннюю копию Svelte ... что не могло ' t видеть друг друга или делиться своими экземплярами let current_component
друг с другом.
Эта ошибка особенно неприятна, поскольку может быть вызвана разными способами, и ошибка не раскрывает точную причину проблемы. Конечно, это исправление применимо только к этой конкретной причине ошибки.
Я до сих пор не уверен, что заставило меня получить эту ошибку первый раз (это побудило меня добавить external: ['svelte']
). Раньше это должно было быть вызвано чем-то другим. Я предполагаю, что делал что-то вроде попытки вызвать getContext
из обратного вызова, который был запущен final-form
асинхронно. Если это произойдет снова, по крайней мере, я буду лучше подготовлен и буду знать, как решить эту проблему на этот раз (возможно, переместите getContext()
в верхнюю часть тега сценария и используйте хранилища для обработки асинхронных обратных вызовов).
Вопросов
Чтобы собрать все это воедино, вот несколько общих вопросов, которые мне действительно хотелось бы понять:
Является ли Svelte исключением из общего принципа, согласно которому библиотеки, которые, как ожидается, будут использоваться приложением, и одна или несколько его зависимостей должны быть указаны в
peerDependencies
иexternal
этих зависимостей, чтобы только одна копия этих библиотек попала в итоговый пакет приложений? Или этот принцип звучит, но я просто делаю что-то не так?Ожидается ли / нормально ли, что в пакете .js моего приложения будет несколько копий _106 _ / _ 107_? Или я должен быть обеспокоен этим?
Если ожидается наличие нескольких копий
current_component
(в приложении, содержащем компоненты из нескольких библиотек), как различные копии Svelte координируются между собой? Или они не нужны, потому что каждый класс компонентов самодостаточен?Например, меня может беспокоить то, что когда компонент переходит к следующему экземпляру Svelte (я полагаю, его дочерние компоненты), _109 _ / _ 110_ здесь будет неопределенным (но, может быть, это не имеет значения?):
function init(component, options, instance, create_fragment, not_equal, props) { var dirty = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : [-1]; var parent_component = current_component; set_current_component(component); … set_current_component(parent_component); }
Что, если разные копии Svelte на самом деле являются разными версиями пакета svelte? Не может ли это вызвать ошибки, если они взаимодействуют друг с другом, но имеют разные API? (Или, может быть, внешние API класса компонента стабильны, поэтому не имеет значения, отличается ли внутренний API?)
- The nice thing about
peerDependencies
is that you only have one copy of each of them in your app. It just seems wrong to have multiple copies have a library in your app. But then I keep wondering if Svelte is an exception to that rule because it compiles components into self-contained classes (that can be used stand-alone or together) rather than needing a single run-time to tie them together into a single unified component tree like React? Does Svelte not need something like that, too, in order to handle context and stores that may cross library/component boundaries? How Svelte works is still too much of a mystery to me.
- The nice thing about
Есть ли лучший способ использования библиотеки Svelte
external
, чтобы избежать подобных проблем? И если да, можем ли мы канонизировать его, включив в шаблон компонента? (Я открою там вопрос.)Очень странно перечислять и
'svelte/internal'
, и'svelte'
. Кажется, чтоsvelte/internal
должна быть деталью реализации (того, как svelte организовал свое дерево исходных текстов внутри), о которой потребители svelte не должны беспокоиться. Почему это необходимо и есть ли способ изменить svelte так, чтобы не указывать и то, и другое?Я никогда не видел примеров других пакетов, которые требуют нечетного суффикса, такого как
/internal
, при добавлении кexternals
. Все примеры, которые вы видите (например, в docs), являются просто основным именем библиотеки:external: ['some-externally-required-library'],
external: ['d3'],
Почему стройность - исключение из этого обычного соглашения?