diff --git a/.editorconfig b/.editorconfig index 854167bbed..ed2a319d58 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,9 @@ indent_size = 2 charset = utf-8 trim_trailing_whitespace = true +[test/**/expected.css] +insert_final_newline = false + [{package.json,.travis.yml,.eslintrc.json}] indent_style = space indent_size = 2 diff --git a/.gitignore b/.gitignore index 5d7f11f151..472328464c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,19 @@ .DS_Store -node_modules -compiler -ssr -shared.js -scratch -!test/compiler -!test/ssr .nyc_output -coverage -coverage.lcov -test/sourcemaps/samples/*/output.js -test/sourcemaps/samples/*/output.js.map -_actual.* -_actual-bundle.* -src/generators/dom/shared.ts -package-lock.json -.idea/ -*.iml -store.umd.js \ No newline at end of file +node_modules +/cli/ +/compiler/ +/ssr/ +/shared.js +/scratch/ +/coverage/ +/coverage.lcov/ +/test/cli/samples/*/actual +/test/sourcemaps/samples/*/output.js +/test/sourcemaps/samples/*/output.js.map +/test/sourcemaps/samples/*/output.css +/test/sourcemaps/samples/*/output.css.map +/src/compile/shared.ts +/store.umd.js +/yarn-error.log +_actual*.* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 5aba7e8a73..de91162ece 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ node_js: env: global: - - BUILD_TIMEOUT=10000 + - BUILD_TIMEOUT=20000 addons: apt: @@ -15,6 +15,6 @@ addons: install: - export DISPLAY=':99.0' - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & - - npm install + - npm ci || npm install after_success: npm run codecov diff --git a/CHANGELOG.md b/CHANGELOG.md index e1103eb326..e325b45793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,424 @@ # Svelte changelog +## 2.14.3 + +* Account for directive dependencies ([#1793](https://github.com/sveltejs/svelte/issues/1793)) +* Detach each block iterations in each blocks with no update method ([#1795](https://github.com/sveltejs/svelte/issues/1795)) + +## 2.14.2 + +* Fix issue with nested `{#if}` blocks ([#1780](https://github.com/sveltejs/svelte/issues/1780)) + +## 2.14.1 + +* Fix block insertion order regression ([#1778](https://github.com/sveltejs/svelte/issues/1778)) +* Fix blocks inside `` ([#1774](https://github.com/sveltejs/svelte/issues/1774)) +* Better attribute parsing ([#1772](https://github.com/sveltejs/svelte/issues/1772)) +* Fix parse errors inside directives ([#1788](https://github.com/sveltejs/svelte/issues/1788)) + + +## 2.14.0 + +* Refactor internals ([#1678](https://github.com/sveltejs/svelte/issues/1678)) +* Deprecate `onerror` option ([#1745](https://github.com/sveltejs/svelte/issues/1745)) +* Handle edge cases where `destroy` is called before `mount` ([#1653](https://github.com/sveltejs/svelte/pull/1653)) +* Make `scroll` binding more efficient ([#1579](https://github.com/sveltejs/svelte/pull/1770)) +* Make 'readonly property' store error more informative ([#1761](https://github.com/sveltejs/svelte/pull/1761)) + +## 2.13.5 + +* Fix missing dependencies in shorthand class directives ([#1739](https://github.com/sveltejs/svelte/issues/1739)) + +## 2.13.4 + +* Support dynamic `import()` in template expressions + +## 2.13.3 + +* Fix bug with keyed each blocks and nested components ([#1706](https://github.com/sveltejs/svelte/issues/1706)) + +## 2.13.2 + +* Coalesce simultaneous store/component updates ([#1520](https://github.com/sveltejs/svelte/issues/1520)) +* Fix nested transitions preventing each block item removal ([#1617](https://github.com/sveltejs/svelte/issues/1617)) +* Add `class` directive shorthand and encapsulate styles ([#1695](https://github.com/sveltejs/svelte/pull/1695)) +* Prevent erroneous updates of bound inputs ([#1699](https://github.com/sveltejs/svelte/issues/1699)) + +## 2.13.1 + +* Coerce second argument to `toggleClass` ([#1685](https://github.com/sveltejs/svelte/issues/1685)) + +## 2.13.0 + +* Add `class` directive ([#890](https://github.com/sveltejs/svelte/issues/890)) +* Remove sourcemaps from npm package ([#1690](https://github.com/sveltejs/svelte/pull/1690)) + +## 2.12.1 + +* Allow actions to take any expression ([#1676](https://github.com/sveltejs/svelte/issues/1676)) +* Run transitions in component context ([#1675](https://github.com/sveltejs/svelte/issues/1675)) +* Correctly set select value on mount ([#1666](https://github.com/sveltejs/svelte/issues/1666)) +* Support `{@debug}` in SSR ([#1659](https://github.com/sveltejs/svelte/issues/1659)) +* Don't treat ` ` as empty whitespace ([#1658](https://github.com/sveltejs/svelte/issues/1658)) +* Fix outros for if blocks with no else ([#1688](https://github.com/sveltejs/svelte/pull/1688)) +* Set `style.cssText` in spread attributes ([#1684](https://github.com/sveltejs/svelte/pull/1684)) + + +## 2.12.0 + +* Initialise actions on mount rather than hydrate ([#1653](https://github.com/sveltejs/svelte/pull/1653)) +* Allow non-existent components to be destroyed ([#1677](https://github.com/sveltejs/svelte/pull/1677)) +* Pass AMD ID from CLI correctly ([#1672](https://github.com/sveltejs/svelte/pull/1672)) +* Minor AST tweaks ([#1673](https://github.com/sveltejs/svelte/pull/1673), [#1674](https://github.com/sveltejs/svelte/pull/1674)) +* Reduce code duplication in component initialisation ([#1670](https://github.com/sveltejs/svelte/pull/1670)) + + +## 2.11.0 + +* Add `--shared` CLI option ([#1649](https://github.com/sveltejs/svelte/pull/1649)) +* Run first `onstate` *before* fragment is rendered ([#1522](https://github.com/sveltejs/svelte/issues/1522)) +* Exclude current computed prop from state object ([#1544](https://github.com/sveltejs/svelte/issues/1544)) + + +## 2.10.1 + +* Add sourcemaps to `{@debug}` tags ([#1647](https://github.com/sveltejs/svelte/pull/1647)) + +## 2.10.0 + +* Add a `{@debug}` tag, for inspecting values in templates in dev mode ([#1635](https://github.com/sveltejs/svelte/issues/1635)) +* Fix dimension bindings in iOS ([#1642](https://github.com/sveltejs/svelte/pull/1642)) + +## 2.9.11 + +* Pass props to custom elements rather than setting attributes, where appropriate ([#875](https://github.com/sveltejs/svelte/issues/875)) +* Handle whitespace in lists consistently between SSR and DOM renderers ([#1637](https://github.com/sveltejs/svelte/pull/1637)) +* Improve error for invalid `ref` names ([#1613](https://github.com/sveltejs/svelte/issues/1613)) + +## 2.9.10 + +* Handle `null` consistently in tags ([#1598](https://github.com/sveltejs/svelte/issues/1598)) +* Support object rest in computed properties ([#1540](https://github.com/sveltejs/svelte/issues/1540)) +* Always update dynamic components when expression changes ([#1621](https://github.com/sveltejs/svelte/issues/1621)) +* Encapsulate local styles inside global styles ([#1618](https://github.com/sveltejs/svelte/issues/1618)) + +## 2.9.9 + +* Fix attribute name regex ([#1623](https://github.com/sveltejs/svelte/pull/1623)) + +## 2.9.8 + +* Sanitize spread attributes in SSR — fixes vulnerability CVE-2018-6341 ([#1623](https://github.com/sveltejs/svelte/pull/1623)) + +## 2.9.7 + +* Allow `` ([#1608](https://github.com/sveltejs/svelte/issues/1608)) +* Ensure child window exists before removing listener in `addResizeHandler` ([#1600](https://github.com/sveltejs/svelte/issues/1600)) +* Handle transitions in `else` block ([#1589](https://github.com/sveltejs/svelte/issues/1589)) + +## 2.9.6 + +* Provide more useful error if SSR component attempts to render non-SSR component ([#1605](https://github.com/sveltejs/svelte/issues/1605)) + +## 2.9.5 + +* Null out refs to dynamic components ([#1596](https://github.com/sveltejs/svelte/issues/1596)) + +## 2.9.4 + +* Make identifier optional for `then` and `catch` blocks ([#1507](https://github.com/sveltejs/svelte/issues/1507)) +* Group outros correctly ([#1575](https://github.com/sveltejs/svelte/issues/1575)) + +## 2.9.3 + +* Fix bug when an each block contains transitions but its else branch does not ([#1559](https://github.com/sveltejs/svelte/issues/1559)) +* If an event handler throws an exception, don't block all future calls to that handler ([#1573](https://github.com/sveltejs/svelte/issues/1573)) + +## 2.9.2 + +* Fix conflict when using multiple if-else blocks, some of which use outros and some of which do not ([#1580](https://github.com/sveltejs/svelte/issues/1580)) +* Fix some cases where `.innerHTML` was being used to create child elements when it shouldn't ([#1581](https://github.com/sveltejs/svelte/issues/1581)) + +## 2.9.1 + +* Use `template.content` instead of `template` where appropriate ([#1571](https://github.com/sveltejs/svelte/issues/1571)) + +## 2.9.0 + +* Play outro transitions on `` if `nestedTransitions` is true ([#1568](https://github.com/sveltejs/svelte/issues/1568)) +* Allow illegal identifiers to be component prop names, for e.g. spreading `data-foo` props ([#887](https://github.com/sveltejs/svelte/issues/887)) +* Abort transition when node is detached ([#1561](https://github.com/sveltejs/svelte/issues/1561)) +* Only include `transitionManager` when necessary ([#1514](https://github.com/sveltejs/svelte/issues/1514)) + +## 2.8.1 + +* Fix prefixed animation name replacement ([#1556](https://github.com/sveltejs/svelte/pull/1556)) + +## 2.8.0 + +* Correctly set store on nested components (to parent store, not root store) ([#1538](https://github.com/sveltejs/svelte/issues/1538)) + +## 2.7.2 + +* Prevent unnecessary remounts ([#1527](https://github.com/sveltejs/svelte/issues/1527)) +* Allow `refs.*` as callee ([#1526](https://github.com/sveltejs/svelte/pull/1526)) +* Handle empty lists when outroing ([#1532](https://github.com/sveltejs/svelte/issues/1532)) + +## 2.7.1 + +* Fix spread props with multiple dependencies ([#1515](https://github.com/sveltejs/svelte/issues/1515)) + +## 2.7.0 + +* Add `__svelte_meta` object to elements in dev mode, containing source info ([#1499](https://github.com/sveltejs/svelte/issues/1499)) +* Fix `bind:online` in dev mode ([#1502](https://github.com/sveltejs/svelte/issues/1502)) +* Update v1 warnings/errors ([#1508](https://github.com/sveltejs/svelte/pull/1508)) +* Transform prefixed keyframes ([#1504](https://github.com/sveltejs/svelte/issues/1504)) + +## 2.6.6 + +* Fix nested transition bug ([#1497](https://github.com/sveltejs/svelte/issues/1497)) + +## 2.6.5 + +* Handle cases where only some `if` block branches have outros ([#1492](https://github.com/sveltejs/svelte/issues/1492)) + +## 2.6.4 + +* Web worker support ([#1487](https://github.com/sveltejs/svelte/issues/1487)) +* Update dynamic component bindings when component changes ([#1489](https://github.com/sveltejs/svelte/issues/1489)) + +## 2.6.3 + +* Nested transitions respect `skipIntroByDefault` ([#1460](https://github.com/sveltejs/svelte/issues/1460)) +* Always create outro for top-level block ([#1470](https://github.com/sveltejs/svelte/issues/1470)) + +## 2.6.2 + +* Fix spread+bindings on dynamic components ([#1433](https://github.com/sveltejs/svelte/issues/1433)) +* Abort in-progress animations, if a new one starts ([#1458](https://github.com/sveltejs/svelte/issues/1458)) +* Allow animations to be parameterised ([#1462](https://github.com/sveltejs/svelte/issues/1462)) + +## 2.6.1 + +* Absolutely position outroing animated nodes ([#1457](https://github.com/sveltejs/svelte/pull/1457)) + +## 2.6.0 + +* Add `animate` directive ([#1454](https://github.com/sveltejs/svelte/pull/1454)) +* Add `skipIntroByDefault` compiler option and `intro: true` init option ([#1448](https://github.com/sveltejs/svelte/pull/1448)) +* Add `nestedTransitions` compiler option ([#1451](https://github.com/sveltejs/svelte/pull/1451)) +* Component outros, if `nestedTransitions` is true ([#1211](https://github.com/sveltejs/svelte/issues/1211)) +* Allow transition functions to return a function, for inter-transition coordination ([#1453](https://github.com/sveltejs/svelte/pull/1453)) +* Pass `1 - t` as second argument to transition functions ([#1452](https://github.com/sveltejs/svelte/pull/1452)) + +## 2.5.1 + +* Add new ARIA attributes ([#1436](https://github.com/sveltejs/svelte/pull/1436)) +* Add `Promise` to whitelisted globals ([#1441](https://github.com/sveltejs/svelte/issues/1441)) +* Allow spaces around reserved keyword attributes ([#1445](https://github.com/sveltejs/svelte/issues/1445)) + +## 2.5.0 + +* Support transitions in `await` blocks ([#956](https://github.com/sveltejs/svelte/issues/956)) +* Abort outros if block is recreated ([#1425](https://github.com/sveltejs/svelte/issues/1425)) +* Wait until transitions have completed before removing styles ([#648](https://github.com/sveltejs/svelte/issues/648)) +* Support event shorthand on dynamic components ([#1427](https://github.com/sveltejs/svelte/pull/1427)) +* Various codegen improvements ([#1419](https://github.com/sveltejs/svelte/pull/1419), [#1421](https://github.com/sveltejs/svelte/pull/1421), [#1422](https://github.com/sveltejs/svelte/pull/1422), [#1424](https://github.com/sveltejs/svelte/pull/1424)) +* Correctly handle `await` blocks with no dynamic content ([#1417](https://github.com/sveltejs/svelte/issues/1417)) +* Allow spread props on elements with static attribute tests ([#1429](https://github.com/sveltejs/svelte/pull/1429)) + + +## 2.4.4 + +* Declare missing variable in Store ([#1415](https://github.com/sveltejs/svelte/issues/1415)) +* ALways declare spread levels ([#1413](https://github.com/sveltejs/svelte/issues/1413)) + +## 2.4.3 + +* `ref` directives prevent HTMLified content ([#1407](https://github.com/sveltejs/svelte/issues/1407)) +* Store computed properties update components immediately upon declaration ([#1327](https://github.com/sveltejs/svelte/issues/1327)) + +## 2.4.2 + +* Evaluate `each` key in child scope ([#1397](https://github.com/sveltejs/svelte/issues/1397)) +* Prevent false negatives and positives when detecting cyclical computed store properties ([#1399](https://github.com/sveltejs/svelte/issues/1399)) +* Only update dynamic component props ([#1394](https://github.com/sveltejs/svelte/issues/1394)) + +## 2.4.1 + +* Fix DOM event context ([#1390](https://github.com/sveltejs/svelte/issues/1390)) + +## 2.4.0 + +* Integrate CLI ([#1360](https://github.com/sveltejs/svelte/issues/1360)) +* Allow arbitrary destructuring for each block items, with binding ([#1385](https://github.com/sveltejs/svelte/pull/1385)) +* Each block keys can use arbitrary expressions ([#703](https://github.com/sveltejs/svelte/issues/703)) +* `bind:offsetWidth`, `bind:offsetHeight`, `bind:clientWidth` and `bind:clientHeight` ([#984](https://github.com/sveltejs/svelte/issues/984)) +* Leaner generated code for `each` blocks ([#1287](https://github.com/sveltejs/svelte/issues/1287)) + + +## 2.3.0 + +* Allow computed properties to have entire state object as dependency ([#1303](https://github.com/sveltejs/svelte/issues/1303)) +* Fix `stats` when `options.generate` is `false` ([#1368](https://github.com/sveltejs/svelte/issues/1368)) +* Assign custom methods to custom elements ([#1369](https://github.com/sveltejs/svelte/issues/1369)) +* Fix `this` value in custom event handlers ([#1297](https://github.com/sveltejs/svelte/issues/1297)) +* Re-evaluate `each` values lazily ([#1286](https://github.com/sveltejs/svelte/issues/1286)) +* Preserve outer context in `await` blocks ([#1251](https://github.com/sveltejs/svelte/issues/1251)) + +## 2.2.0 + +* Internal refactoring ([#1367](https://github.com/sveltejs/svelte/pull/1367)) + +## 2.1.1 + +* Report initial `changed` based on state, not expected props ([#1356](https://github.com/sveltejs/svelte/issues/1356)) +* Set state to empty object, not null, on destroy ([#1354](https://github.com/sveltejs/svelte/issues/1354)) +* Prevent stale state in component event handlers ([#1353](https://github.com/sveltejs/svelte/issues/1353)) + +## 2.1.0 + +* Allow shorthand imports ([#1038](https://github.com/sveltejs/svelte/issues/1038)) +* Update spread props inside each blocks ([#1337](https://github.com/sveltejs/svelte/issues/1337)) + +## 2.0.0 + +*See [the blog post](https://svelte.technology/blog/version-2) for information on how to upgrade your apps* + +* New template syntax ([#1318](https://github.com/sveltejs/svelte/issues/1318)) +* Emit ES2015 code, not ES5 ([#1348](https://github.com/sveltejs/svelte/pull/1348)) +* Add `onstate` and `onupdate` hooks, remove `component.observe` method ([#1197](https://github.com/sveltejs/svelte/issues/1197)) +* Use destructuring syntax for computed properties ([#1069](https://github.com/sveltejs/svelte/issues/1069) +* Change signature of `svelte.compile` ([#1298](https://github.com/sveltejs/svelte/pull/1298)) +* Remove `validate` and `Stylesheet` from public API ([#1348](https://github.com/sveltejs/svelte/pull/1348)) +* Don't typecast numeric attributes ([#657](https://github.com/sveltejs/svelte/issues/657)) +* Always compile with `Store` support, and cascading disabled ([#1348](https://github.com/sveltejs/svelte/pull/1348)) +* Remove unused `hash` property from AST ([#1348](https://github.com/sveltejs/svelte/pull/1348)) +* Rename `loc` property to `start` in warnings and errors ([#1348](https://github.com/sveltejs/svelte/pull/1348)) + +## 1.64.1 + +* Fix computed properties in SSR renderer ([#1349](https://github.com/sveltejs/svelte/issues/1349)) + +## 1.64.0 + +* Deprecate passing a string argument to `component.get` ([#1347](https://github.com/sveltejs/svelte/pull/1347)) + +## 1.63.1 + +* Allow `observe` method to be overwritten + +## 1.63.0 + +* Add `onstate` and `onupdate` lifecycle hooks and deprecate `component.observe` ([#1197](https://github.com/sveltejs/svelte/issues/1197)) +* Add `on` and `fire` to `Store`, deprecate `onchange` and `observe` ([#1344](https://github.com/sveltejs/svelte/pull/1344)) +* Require computed properties to have destructured argument in v2 mode ([#1069](https://github.com/sveltejs/svelte/issues/1069)) + +## 1.62.0 + +* Add a `code` field to errors and warnings ([#474](https://github.com/sveltejs/svelte/issues/474)) +* When using v2 syntax, do not use interpolation in non-root `\`;`} + ` : + (component.stylesheet.hasStyles && options.css !== false && + `if (!document.getElementById("${component.stylesheet.id}-style")) @add_css();`) + } + + ${templateProperties.onstate && `%onstate.call(this, { changed: @assignTrue({}, this._state), current: this._state });`} + + this._fragment = @create_main_fragment(this, this._state); + + ${hasInitHooks && deindent` + this.root._oncreate.push(() => { + ${templateProperties.oncreate && `%oncreate.call(this);`} + this.fire("update", { changed: @assignTrue({}, this._state), current: this._state }); + }); + `} + + ${component.customElement ? deindent` + this._fragment.c(); + this._fragment.${block.hasIntroMethod ? 'i' : 'm'}(this.shadowRoot, null); + + if (options.target) this._mount(options.target, options.anchor); + ` : deindent` + if (options.target) { + ${component.options.hydratable + ? deindent` + var nodes = @children(options.target); + options.hydrate ? this._fragment.l(nodes) : this._fragment.c(); + nodes.forEach(@detachNode);` : + deindent` + ${options.dev && + `if (options.hydrate) throw new Error("options.hydrate only works if the component was compiled with the \`hydratable: true\` option");`} + this._fragment.c();`} + this._mount(options.target, options.anchor); + + ${(component.hasComponents || renderer.hasComplexBindings || hasInitHooks || renderer.hasIntroTransitions) && + `@flush(this);`} + } + `} + + ${component.options.skipIntroByDefault && `this._intro = true;`} + `; + + if (component.customElement) { + const props = component.props || Array.from(component.expectedProperties); + + builder.addBlock(deindent` + class ${name} extends HTMLElement { + constructor(options = {}) { + super(); + ${constructorBody} + } + + static get observedAttributes() { + return ${JSON.stringify(props)}; + } + + ${props.map(prop => deindent` + get ${prop}() { + return this.get().${prop}; + } + + set ${prop}(value) { + this.set({ ${prop}: value }); + } + `).join('\n\n')} + + ${renderer.slots.size && deindent` + connectedCallback() { + Object.keys(this._slotted).forEach(key => { + this.appendChild(this._slotted[key]); + }); + }`} + + attributeChangedCallback(attr, oldValue, newValue) { + this.set({ [attr]: newValue }); + } + + ${(component.hasComponents || renderer.hasComplexBindings || templateProperties.oncreate || renderer.hasIntroTransitions) && deindent` + connectedCallback() { + @flush(this); + } + `} + } + + @assign(${name}.prototype, ${proto}); + ${templateProperties.methods && `@assign(${name}.prototype, %methods);`} + @assign(${name}.prototype, { + _mount(target, anchor) { + target.insertBefore(this, anchor); + } + }); + + customElements.define("${component.tag}", ${name}); + `); + } else { + builder.addBlock(deindent` + function ${name}(options) { + ${constructorBody} + } + + @assign(${name}.prototype, ${proto}); + ${templateProperties.methods && `@assign(${name}.prototype, %methods);`} + `); + } + + const immutable = templateProperties.immutable ? templateProperties.immutable.value.value : options.immutable; + + builder.addBlock(deindent` + ${options.dev && deindent` + ${name}.prototype._checkReadOnly = function _checkReadOnly(newState) { + ${Array.from(renderer.readonly).map( + prop => + `if ('${prop}' in newState && !this._updatingReadonlyProperty) throw new Error("${debugName}: Cannot set read-only property '${prop}'");` + )} + }; + `} + + ${computations.length ? deindent` + ${name}.prototype._recompute = function _recompute(changed, state) { + ${computationBuilder} + } + ` : (!sharedPath && `${name}.prototype._recompute = @noop;`)} + + ${templateProperties.setup && `%setup(${name});`} + + ${templateProperties.preload && `${name}.preload = %preload;`} + + ${immutable && `${name}.prototype._differs = @_differsImmutable;`} + `); + + let result = builder.toString(); + + return component.generate(result, options, { + banner: `/* ${component.file ? `${component.file} ` : ``}generated by Svelte v${"__VERSION__"} */`, + sharedPath, + name, + format, + }); +} diff --git a/src/compile/render-dom/wrappers/AwaitBlock.ts b/src/compile/render-dom/wrappers/AwaitBlock.ts new file mode 100644 index 0000000000..bfd3143333 --- /dev/null +++ b/src/compile/render-dom/wrappers/AwaitBlock.ts @@ -0,0 +1,230 @@ +import Wrapper from './shared/Wrapper'; +import Renderer from '../Renderer'; +import Block from '../Block'; +import AwaitBlock from '../../nodes/AwaitBlock'; +import createDebuggingComment from '../../../utils/createDebuggingComment'; +import deindent from '../../../utils/deindent'; +import FragmentWrapper from './Fragment'; +import PendingBlock from '../../nodes/PendingBlock'; +import ThenBlock from '../../nodes/ThenBlock'; +import CatchBlock from '../../nodes/CatchBlock'; + +class AwaitBlockBranch extends Wrapper { + node: PendingBlock | ThenBlock | CatchBlock; + block: Block; + fragment: FragmentWrapper; + isDynamic: boolean; + + var = null; + + constructor( + status: string, + renderer: Renderer, + block: Block, + parent: Wrapper, + node: AwaitBlock, + stripWhitespace: boolean, + nextSibling: Wrapper + ) { + super(renderer, block, parent, node); + + this.block = block.child({ + comment: createDebuggingComment(node, this.renderer.component), + name: this.renderer.component.getUniqueName(`create_${status}_block`) + }); + + this.fragment = new FragmentWrapper( + renderer, + this.block, + this.node.children, + parent, + stripWhitespace, + nextSibling + ); + + this.isDynamic = this.block.dependencies.size > 0; + } +} + +export default class AwaitBlockWrapper extends Wrapper { + node: AwaitBlock; + + pending: AwaitBlockBranch; + then: AwaitBlockBranch; + catch: AwaitBlockBranch; + + var = 'await_block'; + + constructor( + renderer: Renderer, + block: Block, + parent: Wrapper, + node: AwaitBlock, + stripWhitespace: boolean, + nextSibling: Wrapper + ) { + super(renderer, block, parent, node); + + this.cannotUseInnerHTML(); + + block.addDependencies(this.node.expression.dependencies); + + let isDynamic = false; + let hasIntros = false; + let hasOutros = false; + + ['pending', 'then', 'catch'].forEach(status => { + const child = this.node[status]; + + const branch = new AwaitBlockBranch( + status, + renderer, + block, + parent, + child, + stripWhitespace, + nextSibling + ); + + renderer.blocks.push(branch.block); + + if (branch.isDynamic) { + isDynamic = true; + // TODO should blocks update their own parents? + block.addDependencies(branch.block.dependencies); + } + + if (branch.block.hasIntros) hasIntros = true; + if (branch.block.hasOutros) hasOutros = true; + + this[status] = branch; + }); + + this.pending.block.hasUpdateMethod = isDynamic; + this.then.block.hasUpdateMethod = isDynamic; + this.catch.block.hasUpdateMethod = isDynamic; + + this.pending.block.hasIntroMethod = hasIntros; + this.then.block.hasIntroMethod = hasIntros; + this.catch.block.hasIntroMethod = hasIntros; + + this.pending.block.hasOutroMethod = hasOutros; + this.then.block.hasOutroMethod = hasOutros; + this.catch.block.hasOutroMethod = hasOutros; + + if (hasOutros && this.renderer.options.nestedTransitions) { + block.addOutro(); + } + } + + render( + block: Block, + parentNode: string, + parentNodes: string + ) { + const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes); + const updateMountNode = this.getUpdateMountNode(anchor); + + const { snippet } = this.node.expression; + + const info = block.getUniqueName(`info`); + const promise = block.getUniqueName(`promise`); + + block.addVariable(promise); + + block.maintainContext = true; + + const infoProps = [ + block.alias('component') === 'component' ? 'component' : `component: #component`, + 'ctx', + 'current: null', + this.pending.block.name && `pending: ${this.pending.block.name}`, + this.then.block.name && `then: ${this.then.block.name}`, + this.catch.block.name && `catch: ${this.catch.block.name}`, + this.then.block.name && `value: '${this.node.value}'`, + this.catch.block.name && `error: '${this.node.error}'`, + this.pending.block.hasOutroMethod && `blocks: Array(3)` + ].filter(Boolean); + + block.builders.init.addBlock(deindent` + let ${info} = { + ${infoProps.join(',\n')} + }; + `); + + block.builders.init.addBlock(deindent` + @handlePromise(${promise} = ${snippet}, ${info}); + `); + + block.builders.create.addBlock(deindent` + ${info}.block.c(); + `); + + if (parentNodes) { + block.builders.claim.addBlock(deindent` + ${info}.block.l(${parentNodes}); + `); + } + + const initialMountNode = parentNode || '#target'; + const anchorNode = parentNode ? 'null' : 'anchor'; + + const hasTransitions = this.pending.block.hasIntroMethod || this.pending.block.hasOutroMethod; + + block.builders.mount.addBlock(deindent` + ${info}.block.${hasTransitions ? 'i' : 'm'}(${initialMountNode}, ${info}.anchor = ${anchorNode}); + ${info}.mount = () => ${updateMountNode}; + `); + + const conditions = []; + if (this.node.expression.dependencies.size > 0) { + conditions.push( + `(${[...this.node.expression.dependencies].map(dep => `'${dep}' in changed`).join(' || ')})` + ); + } + + conditions.push( + `${promise} !== (${promise} = ${snippet})`, + `@handlePromise(${promise}, ${info})` + ); + + block.builders.update.addLine( + `${info}.ctx = ctx;` + ); + + if (this.pending.block.hasUpdateMethod) { + block.builders.update.addBlock(deindent` + if (${conditions.join(' && ')}) { + // nothing + } else { + ${info}.block.p(changed, @assign(@assign({}, ctx), ${info}.resolved)); + } + `); + } else { + block.builders.update.addBlock(deindent` + ${conditions.join(' && ')} + `); + } + + if (this.pending.block.hasOutroMethod && this.renderer.options.nestedTransitions) { + const countdown = block.getUniqueName('countdown'); + block.builders.outro.addBlock(deindent` + const ${countdown} = @callAfter(#outrocallback, 3); + for (let #i = 0; #i < 3; #i += 1) { + const block = ${info}.blocks[#i]; + if (block) block.o(${countdown}); + else ${countdown}(); + } + `); + } + + block.builders.destroy.addBlock(deindent` + ${info}.block.d(${parentNode ? '' : 'detach'}); + ${info} = null; + `); + + [this.pending, this.then, this.catch].forEach(branch => { + branch.fragment.render(branch.block, null, 'nodes'); + }); + } +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/DebugTag.ts b/src/compile/render-dom/wrappers/DebugTag.ts new file mode 100644 index 0000000000..294276a4ea --- /dev/null +++ b/src/compile/render-dom/wrappers/DebugTag.ts @@ -0,0 +1,72 @@ +import Renderer from '../Renderer'; +import Wrapper from './shared/Wrapper'; +import Block from '../Block'; +import DebugTag from '../../nodes/DebugTag'; +import addToSet from '../../../utils/addToSet'; +import deindent from '../../../utils/deindent'; + +export default class DebugTagWrapper extends Wrapper { + node: DebugTag; + + constructor( + renderer: Renderer, + block: Block, + parent: Wrapper, + node: DebugTag, + stripWhitespace: boolean, + nextSibling: Wrapper + ) { + super(renderer, block, parent, node); + } + + render(block: Block, parentNode: string, parentNodes: string) { + const { renderer } = this; + const { component } = renderer; + + if (!renderer.options.dev) return; + + const { code } = component; + + if (this.node.expressions.length === 0) { + // Debug all + code.overwrite(this.node.start + 1, this.node.start + 7, 'debugger', { + storeName: true + }); + const statement = `[✂${this.node.start + 1}-${this.node.start + 7}✂];`; + + block.builders.create.addLine(statement); + block.builders.update.addLine(statement); + } else { + const { code } = component; + code.overwrite(this.node.start + 1, this.node.start + 7, 'log', { + storeName: true + }); + const log = `[✂${this.node.start + 1}-${this.node.start + 7}✂]`; + + const dependencies = new Set(); + this.node.expressions.forEach(expression => { + addToSet(dependencies, expression.dependencies); + }); + + const condition = [...dependencies].map(d => `changed.${d}`).join(' || '); + + const identifiers = this.node.expressions.map(e => e.node.name).join(', '); + + block.builders.update.addBlock(deindent` + if (${condition}) { + const { ${identifiers} } = ctx; + console.${log}({ ${identifiers} }); + debugger; + } + `); + + block.builders.create.addBlock(deindent` + { + const { ${identifiers} } = ctx; + console.${log}({ ${identifiers} }); + debugger; + } + `); + } + } +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/EachBlock.ts b/src/compile/render-dom/wrappers/EachBlock.ts new file mode 100644 index 0000000000..22515206c1 --- /dev/null +++ b/src/compile/render-dom/wrappers/EachBlock.ts @@ -0,0 +1,503 @@ +import Renderer from '../Renderer'; +import Block from '../Block'; +import Node from '../../nodes/shared/Node'; +import Wrapper from './shared/Wrapper'; +import createDebuggingComment from '../../../utils/createDebuggingComment'; +import EachBlock from '../../nodes/EachBlock'; +import FragmentWrapper from './Fragment'; +import deindent from '../../../utils/deindent'; +import ElseBlock from '../../nodes/ElseBlock'; + +class ElseBlockWrapper extends Wrapper { + node: ElseBlock; + block: Block; + fragment: FragmentWrapper; + isDynamic: boolean; + + var = null; + + constructor( + renderer: Renderer, + block: Block, + parent: Wrapper, + node: ElseBlock, + stripWhitespace: boolean, + nextSibling: Wrapper + ) { + super(renderer, block, parent, node); + + this.block = block.child({ + comment: createDebuggingComment(node, this.renderer.component), + name: this.renderer.component.getUniqueName(`create_else_block`) + }); + + this.fragment = new FragmentWrapper( + renderer, + this.block, + this.node.children, + parent, + stripWhitespace, + nextSibling + ); + + this.isDynamic = this.block.dependencies.size > 0; + if (this.isDynamic) { + // TODO this can't be right + this.block.hasUpdateMethod = true; + } + } +} + +export default class EachBlockWrapper extends Wrapper { + block: Block; + node: EachBlock; + fragment: FragmentWrapper; + else?: ElseBlockWrapper; + var: string; + vars: { + anchor: string; + create_each_block: string; + each_block_value: string; + get_each_context: string; + iterations: string; + length: string; + mountOrIntro: string; + } + + constructor( + renderer: Renderer, + block: Block, + parent: Wrapper, + node: EachBlock, + stripWhitespace: boolean, + nextSibling: Wrapper + ) { + super(renderer, block, parent, node); + this.cannotUseInnerHTML(); + + this.var = 'each'; + + const { dependencies } = node.expression; + block.addDependencies(dependencies); + + this.block = block.child({ + comment: createDebuggingComment(this.node, this.renderer.component), + name: renderer.component.getUniqueName('create_each_block'), + key: node.key, // TODO... + + bindings: new Map(block.bindings) + }); + + // TODO this seems messy + this.block.hasAnimation = this.node.hasAnimation; + + this.indexName = this.node.index || renderer.component.getUniqueName(`${this.node.context}_index`); + + node.contexts.forEach(prop => { + // TODO this doesn't feel great + this.block.bindings.set(prop.key.name, () => `ctx.${this.vars.each_block_value}[ctx.${this.indexName}]${prop.tail}`); + }); + + if (this.node.index) { + this.block.getUniqueName(this.node.index); // this prevents name collisions (#1254) + } + + renderer.blocks.push(this.block); + + this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, stripWhitespace, nextSibling); + + if (this.node.else) { + this.else = new ElseBlockWrapper( + renderer, + block, + this, + this.node.else, + stripWhitespace, + nextSibling + ); + + renderer.blocks.push(this.else.block); + + if (this.else.isDynamic) { + this.block.addDependencies(this.else.block.dependencies); + } + } + + block.addDependencies(this.block.dependencies); + this.block.hasUpdateMethod = this.block.dependencies.size > 0; // TODO should this logic be in Block? + + if (this.block.hasOutros || (this.else && this.else.block.hasOutros)) { + block.addOutro(); + } + } + + render(block: Block, parentNode: string, parentNodes: string) { + if (this.fragment.nodes.length === 0) return; + + const { renderer } = this; + const { component } = renderer; + + // hack the sourcemap, so that if data is missing the bug + // is easy to find + let c = this.node.start + 2; + while (component.source[c] !== 'e') c += 1; + component.code.overwrite(c, c + 4, 'length'); + const length = `[✂${c}-${c+4}✂]`; + + const needsAnchor = this.next + ? !this.next.isDomNode() : + !parentNode || !this.parent.isDomNode(); + + this.vars = { + anchor: needsAnchor + ? block.getUniqueName(`${this.var}_anchor`) + : (this.next && this.next.var) || 'null', + create_each_block: this.block.name, + each_block_value: renderer.component.getUniqueName(`${this.var}_value`), + get_each_context: renderer.component.getUniqueName(`get_${this.var}_context`), + iterations: block.getUniqueName(`${this.var}_blocks`), + length: `[✂${c}-${c+4}✂]`, + mountOrIntro: (this.block.hasIntroMethod || this.block.hasOutroMethod) + ? 'i' + : 'm' + }; + + this.contextProps = this.node.contexts.map(prop => `child_ctx.${prop.key.name} = list[i]${prop.tail};`); + + // TODO only add these if necessary + this.contextProps.push( + `child_ctx.${this.vars.each_block_value} = list;`, + `child_ctx.${this.indexName} = i;` + ); + + const { snippet } = this.node.expression; + + block.builders.init.addLine(`var ${this.vars.each_block_value} = ${snippet};`); + + renderer.blocks.push(deindent` + function ${this.vars.get_each_context}(ctx, list, i) { + const child_ctx = Object.create(ctx); + ${this.contextProps} + return child_ctx; + } + `); + + if (this.node.key) { + this.renderKeyed(block, parentNode, parentNodes, snippet); + } else { + this.renderUnkeyed(block, parentNode, parentNodes, snippet); + } + + if (needsAnchor) { + block.addElement( + this.vars.anchor, + `@createComment()`, + parentNodes && `@createComment()`, + parentNode + ); + } + + if (this.else) { + const each_block_else = component.getUniqueName(`${this.var}_else`); + const mountOrIntro = (this.else.block.hasIntroMethod || this.else.block.hasOutroMethod) ? 'i' : 'm'; + + block.builders.init.addLine(`var ${each_block_else} = null;`); + + // TODO neaten this up... will end up with an empty line in the block + block.builders.init.addBlock(deindent` + if (!${this.vars.each_block_value}.${length}) { + ${each_block_else} = ${this.else.block.name}(#component, ctx); + ${each_block_else}.c(); + } + `); + + block.builders.mount.addBlock(deindent` + if (${each_block_else}) { + ${each_block_else}.${mountOrIntro}(${parentNode || '#target'}, null); + } + `); + + const initialMountNode = parentNode || `${this.vars.anchor}.parentNode`; + + if (this.else.block.hasUpdateMethod) { + block.builders.update.addBlock(deindent` + if (!${this.vars.each_block_value}.${length} && ${each_block_else}) { + ${each_block_else}.p(changed, ctx); + } else if (!${this.vars.each_block_value}.${length}) { + ${each_block_else} = ${this.else.block.name}(#component, ctx); + ${each_block_else}.c(); + ${each_block_else}.${mountOrIntro}(${initialMountNode}, ${this.vars.anchor}); + } else if (${each_block_else}) { + ${each_block_else}.d(1); + ${each_block_else} = null; + } + `); + } else { + block.builders.update.addBlock(deindent` + if (${this.vars.each_block_value}.${length}) { + if (${each_block_else}) { + ${each_block_else}.d(1); + ${each_block_else} = null; + } + } else if (!${each_block_else}) { + ${each_block_else} = ${this.else.block.name}(#component, ctx); + ${each_block_else}.c(); + ${each_block_else}.${mountOrIntro}(${initialMountNode}, ${this.vars.anchor}); + } + `); + } + + block.builders.destroy.addBlock(deindent` + if (${each_block_else}) ${each_block_else}.d(${parentNode ? '' : 'detach'}); + `); + } + + this.fragment.render(this.block, null, 'nodes'); + + if (this.else) { + this.else.fragment.render(this.else.block, null, 'nodes'); + } + } + + renderKeyed( + block: Block, + parentNode: string, + parentNodes: string, + snippet: string + ) { + const { + create_each_block, + length, + anchor, + mountOrIntro, + } = this.vars; + + const get_key = block.getUniqueName('get_key'); + const blocks = block.getUniqueName(`${this.var}_blocks`); + const lookup = block.getUniqueName(`${this.var}_lookup`); + + block.addVariable(blocks, '[]'); + block.addVariable(lookup, `@blankObject()`); + + if (this.fragment.nodes[0].isDomNode()) { + this.block.first = this.fragment.nodes[0].var; + } else { + this.block.first = this.block.getUniqueName('first'); + this.block.addElement( + this.block.first, + `@createComment()`, + parentNodes && `@createComment()`, + null + ); + } + + block.builders.init.addBlock(deindent` + const ${get_key} = ctx => ${this.node.key.snippet}; + + for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) { + let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i); + let key = ${get_key}(child_ctx); + ${blocks}[#i] = ${lookup}[key] = ${create_each_block}(#component, key, child_ctx); + } + `); + + const initialMountNode = parentNode || '#target'; + const updateMountNode = this.getUpdateMountNode(anchor); + const anchorNode = parentNode ? 'null' : 'anchor'; + + block.builders.create.addBlock(deindent` + for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].c(); + `); + + if (parentNodes) { + block.builders.claim.addBlock(deindent` + for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].l(${parentNodes}); + `); + } + + block.builders.mount.addBlock(deindent` + for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode}); + `); + + const dynamic = this.block.hasUpdateMethod; + + const rects = block.getUniqueName('rects'); + const destroy = this.node.hasAnimation + ? `@fixAndOutroAndDestroyBlock` + : this.block.hasOutros + ? `@outroAndDestroyBlock` + : `@destroyBlock`; + + block.builders.update.addBlock(deindent` + const ${this.vars.each_block_value} = ${snippet}; + + ${this.block.hasOutros && `@groupOutros();`} + ${this.node.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].r();`} + ${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.vars.each_block_value}, ${lookup}, ${updateMountNode}, ${destroy}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.vars.get_each_context}); + ${this.node.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].a();`} + `); + + if (this.block.hasOutros && this.renderer.component.options.nestedTransitions) { + const countdown = block.getUniqueName('countdown'); + block.builders.outro.addBlock(deindent` + const ${countdown} = @callAfter(#outrocallback, ${blocks}.length); + for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].o(${countdown}); + `); + } + + block.builders.destroy.addBlock(deindent` + for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].d(${parentNode ? '' : 'detach'}); + `); + } + + renderUnkeyed( + block: Block, + parentNode: string, + parentNodes: string, + snippet: string + ) { + const { + create_each_block, + length, + iterations, + anchor, + mountOrIntro, + } = this.vars; + + block.builders.init.addBlock(deindent` + var ${iterations} = []; + + for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) { + ${iterations}[#i] = ${create_each_block}(#component, ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i)); + } + `); + + const initialMountNode = parentNode || '#target'; + const updateMountNode = this.getUpdateMountNode(anchor); + const anchorNode = parentNode ? 'null' : 'anchor'; + + block.builders.create.addBlock(deindent` + for (var #i = 0; #i < ${iterations}.length; #i += 1) { + ${iterations}[#i].c(); + } + `); + + if (parentNodes) { + block.builders.claim.addBlock(deindent` + for (var #i = 0; #i < ${iterations}.length; #i += 1) { + ${iterations}[#i].l(${parentNodes}); + } + `); + } + + block.builders.mount.addBlock(deindent` + for (var #i = 0; #i < ${iterations}.length; #i += 1) { + ${iterations}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode}); + } + `); + + const allDependencies = new Set(this.block.dependencies); + const { dependencies } = this.node.expression; + dependencies.forEach((dependency: string) => { + allDependencies.add(dependency); + }); + + const outroBlock = this.block.hasOutros && block.getUniqueName('outroBlock') + if (outroBlock) { + block.builders.init.addBlock(deindent` + function ${outroBlock}(i, detach, fn) { + if (${iterations}[i]) { + ${iterations}[i].o(() => { + if (detach) { + ${iterations}[i].d(detach); + ${iterations}[i] = null; + } + if (fn) fn(); + }); + } + } + `); + } + + // TODO do this for keyed blocks as well + const condition = Array.from(allDependencies) + .map(dependency => `changed.${dependency}`) + .join(' || '); + + if (condition !== '') { + const forLoopBody = this.block.hasUpdateMethod + ? (this.block.hasIntros || this.block.hasOutros) + ? deindent` + if (${iterations}[#i]) { + ${iterations}[#i].p(changed, child_ctx); + } else { + ${iterations}[#i] = ${create_each_block}(#component, child_ctx); + ${iterations}[#i].c(); + } + ${iterations}[#i].i(${updateMountNode}, ${anchor}); + ` + : deindent` + if (${iterations}[#i]) { + ${iterations}[#i].p(changed, child_ctx); + } else { + ${iterations}[#i] = ${create_each_block}(#component, child_ctx); + ${iterations}[#i].c(); + ${iterations}[#i].m(${updateMountNode}, ${anchor}); + } + ` + : deindent` + ${iterations}[#i] = ${create_each_block}(#component, child_ctx); + ${iterations}[#i].c(); + ${iterations}[#i].${mountOrIntro}(${updateMountNode}, ${anchor}); + `; + + const start = this.block.hasUpdateMethod ? '0' : `${iterations}.length`; + + let destroy; + + if (this.block.hasOutros) { + destroy = deindent` + @groupOutros(); + for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1); + `; + } else { + destroy = deindent` + for (${this.block.hasUpdateMethod ? `` : `#i = ${this.vars.each_block_value}.${length}`}; #i < ${iterations}.length; #i += 1) { + ${iterations}[#i].d(1); + } + ${iterations}.length = ${this.vars.each_block_value}.${length}; + `; + } + + block.builders.update.addBlock(deindent` + if (${condition}) { + ${this.vars.each_block_value} = ${snippet}; + + for (var #i = ${start}; #i < ${this.vars.each_block_value}.${length}; #i += 1) { + const child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i); + + ${forLoopBody} + } + + ${destroy} + } + `); + } + + if (outroBlock && this.renderer.component.options.nestedTransitions) { + const countdown = block.getUniqueName('countdown'); + block.builders.outro.addBlock(deindent` + ${iterations} = ${iterations}.filter(Boolean); + const ${countdown} = @callAfter(#outrocallback, ${iterations}.length); + for (let #i = 0; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 0, ${countdown});` + ); + } + + block.builders.destroy.addBlock(`@destroyEach(${iterations}, detach);`); + } + + remount(name: string) { + // TODO consider keyed blocks + return `for (var #i = 0; #i < ${this.vars.iterations}.length; #i += 1) ${this.vars.iterations}[#i].m(${name}._slotted.default, null);`; + } +} \ No newline at end of file diff --git a/src/compile/render-dom/wrappers/Element/Attribute.ts b/src/compile/render-dom/wrappers/Element/Attribute.ts new file mode 100644 index 0000000000..2d52b6dd7d --- /dev/null +++ b/src/compile/render-dom/wrappers/Element/Attribute.ts @@ -0,0 +1,456 @@ +import Attribute from '../../../nodes/Attribute'; +import Block from '../../Block'; +import fixAttributeCasing from '../../../../utils/fixAttributeCasing'; +import ElementWrapper from './index'; +import { stringify } from '../../../../utils/stringify'; +import deindent from '../../../../utils/deindent'; + +export default class AttributeWrapper { + node: Attribute; + parent: ElementWrapper; + + constructor(parent: ElementWrapper, block: Block, node: Attribute) { + this.node = node; + this.parent = parent; + + if (node.dependencies.size > 0) { + parent.cannotUseInnerHTML(); + + block.addDependencies(node.dependencies); + + // special case —