mirror of https://github.com/sveltejs/svelte
commit
dc25e2e0cf
@ -0,0 +1,3 @@
|
||||
This repository is governed by the Svelte Code of Conduct.
|
||||
|
||||
https://github.com/sveltejs/community/blob/main/CODE_OF_CONDUCT.md
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,106 @@
|
||||
---
|
||||
title: "What's new in Svelte: February 2022"
|
||||
description: "Rapid-fire releases across Svelte, SvelteKit and the community"
|
||||
author: Daniel Sandoval
|
||||
authorURL: https://desandoval.net
|
||||
---
|
||||
|
||||
Happy February, everyone! Over the last month or so, we've seen Svelte and SvelteKit [develop at rapid speed](accelerating-sveltes-development), new community rules across the [Reddit](https://www.reddit.com/r/sveltejs/comments/s9n8ou/new_rules/), [GitHub](https://github.com/sveltejs/community/blob/main/CODE_OF_CONDUCT.md) and [Discord](https://discord.com/channels/457912077277855764/831611707667382303/935264550436102315), and quite a few amazing apps, tutorials and libraries.
|
||||
|
||||
Let's take a look...
|
||||
|
||||
## Highlights from the Svelte changelog
|
||||
- **3.45.0** brought a [new a11y warning `a11y-no-redundant-roles`](https://svelte.dev/docs#accessibility-warnings-a11y-no-redundant-roles), destructuring and caching fixes
|
||||
- **3.46.0** added the much requested [`{@const}` tag](https://svelte.dev/docs#template-syntax-const) and [`style:` directive](https://svelte.dev/docs#template-syntax-element-directives-style-property)
|
||||
- Check out **3.46.1 - 3.46.3** for fixes to the `{@const}` tag and `style:` directive, along with a number of fixes to animations
|
||||
- [AST output is now available in the Svelte REPL](https://svelte.dev/repl/hello-world)
|
||||
|
||||
## What's new in SvelteKit
|
||||
- `inlineStyleThreshold` allows you to specify where inline stylesheets are inserted into the page ([Docs](https://kit.svelte.dev/docs#configuration-inlinestylethreshold), [#2620](https://github.com/sveltejs/kit/pull/2620))
|
||||
- `beforeNavigate`/`afterNavigate` lifecycle functions lets you add functionality before or after a page navigation ([Docs](https://kit.svelte.dev/docs#modules-$app-navigation), [#3293](https://github.com/sveltejs/kit/pull/3293))
|
||||
- Platform context can now be passed from adapters ([Docs](https://kit.svelte.dev/docs#adapters-supported-environments-platform-specific-context), [#3429](https://github.com/sveltejs/kit/pull/3429))
|
||||
- Hooks now have an `ssr` parameter in `resolve` to make it easier to skip SSR, when needed ([Docs](https://kit.svelte.dev/docs#hooks-handle), [#2804](https://github.com/sveltejs/kit/pull/2804))
|
||||
- `$page.stuff` provides a mechanism for pages to pass data 'upward' to layouts ([Docs](https://kit.svelte.dev/docs#loading-input-stuff), [#3252](https://github.com/sveltejs/kit/pull/3252))
|
||||
- Fallthrough routes let you specify where to route when an route can't be loaded ([Docs](https://kit.svelte.dev/docs#routing-advanced-fallthrough-routes), [#3217](https://github.com/sveltejs/kit/pull/3217))
|
||||
|
||||
**New configs**
|
||||
- Content Security Policy (CSP) is now supported for increased security when using inline javascript or stylesheets ([Docs](https://kit.svelte.dev/docs#configuration-csp), [#3499](https://github.com/sveltejs/kit/pull/3499))
|
||||
- `kit.routes` config allows you to customise public/private modules during build ([Docs](https://kit.svelte.dev/docs#configuration-routes), [#3576](https://github.com/sveltejs/kit/pull/3576))
|
||||
- `prerender.createIndexFiles` config lets you prerender index.html files as their subfolder's name ([Docs](https://kit.svelte.dev/docs#configuration-prerender), [#2632](https://github.com/sveltejs/kit/pull/2632))
|
||||
- HTTP methods can now be overridden using `kit.methodOverride` ([Docs](https://kit.svelte.dev/docs#routing-endpoints-http-method-overrides), [#2989](https://github.com/sveltejs/kit/pull/2989))
|
||||
|
||||
**Config changes**
|
||||
- `config.kit.hydrate` and `config.kit.router` are now nested under `config.kit.browser` ([Docs](https://kit.svelte.dev/docs#configuration-browser), [3578](https://github.com/sveltejs/kit/pull/3578))
|
||||
|
||||
**Breaking change**
|
||||
- use `Request` and `Response` objects in endpoints and hooks ([#3384](https://github.com/sveltejs/kit/pull/3384))
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Community Showcase
|
||||
|
||||
**Apps & Sites**
|
||||
- [timb(re)](https://paullj.github.io/timb) is a live music programming environment
|
||||
- [Music for Programming](https://musicforprogramming.net/latest/) is a series of mixes intended for listening while `${task}` to focus the brain and inspire the mind
|
||||
- [Team Tale](https://teamtale.app/) allows two authors to write the same story in a tag-team sort of fashion
|
||||
- [Puzzlez](https://www.puzzlez.io/) is an online place to play Sudoku and Wordle
|
||||
- [Closed Caption Creator](https://www.closedcaptioncreator.com/) makes it easy to add subtitles to your video on Windows, Mac and Google Chrome
|
||||
- [SC3Lab](https://sc3-lab.netlify.app/) is a code generator for experimenting with svelte-cubed and three.js
|
||||
- [Donkeytype](https://github.com/0ql/Donkeytype) is a minimalistic and lightweight typingtest inspired by Monkeytype.
|
||||
- [Above](https://above.silas.pro/) is a visual routine timer built for the ADHD/autistic mind
|
||||
- [base.report](https://base.report/) is a modern research platform for serious investors
|
||||
- [String](https://string.kampsy.xyz/) turns your Phone into a secure portable audio recorder, making it easy to capture and share personal notes, family moments, classroom lectures, and more
|
||||
- [The Raytracer Challenge REPL](https://github.com/jakobwesthoff/the_raytracer_challenge_repl) provides a live editor interface to configure a raytraced scene and render it live in any modern browser
|
||||
- [awesome-svelte-kit](https://github.com/janosh/awesome-svelte-kit) is a list of awesome examples of SvelteKit in the wild
|
||||
- [Map Projection Explorer](https://www.geo-projections.com/) lets you explore different map projections and explains their differences
|
||||
- [Rubiks](https://github.com/MeharGaur/rubiks) is a Rubik's Cube simulator
|
||||
- [Pianisto](https://pianisto.net/) is a working piano made with SVG, ToneJS and a lot of patience
|
||||
|
||||
Want to work on a SvelteKit site with others, [try contributing to the Svelte Society site](https://github.com/svelte-society/sveltesociety-2021/issues)!
|
||||
|
||||
|
||||
**Learning and Listening**
|
||||
|
||||
_To Read_
|
||||
- [Accelerating Svelte's Development](https://svelte.dev/blog/accelerating-sveltes-development) by Ben McCann
|
||||
- [Storybook for Vite](https://storybook.js.org/blog/storybook-for-vite/)
|
||||
- [Let's learn SvelteKit by building a static Markdown blog from scratch](https://joshcollinsworth.com/blog/build-static-sveltekit-markdown-blog) by Josh Collinsworth
|
||||
- [Building an iOS app with Svelte, Capacitor and Firebase](https://harryherskowitz.com/2022/01/05/tapedrop-app.html) by Harry Herskowitz
|
||||
- [Mutating Query Params in SvelteKit Without Page Reloads or Navigations](https://dev.to/mohamadharith/mutating-query-params-in-sveltekit-without-page-reloads-or-navigations-2i2b) and [Workaround for Bubbling Custom Events in Svelte](https://dev.to/mohamadharith/workaround-for-bubbling-custom-events-in-svelte-3khk) by Mohamad Harith
|
||||
- [How to build a full stack serverless application with Svelte and GraphQL](https://dev.to/shadid12/how-to-build-a-full-stack-serverless-application-with-svelte-graphql-and-fauna-5427) by Shadid Haque
|
||||
- [How to Deploy SvelteKit Apps to Github Pages](https://sveltesaas.com/articles/sveltekit-github-pages-guide/)
|
||||
- [Creating a dApp with SvelteKit](https://anthonyriley.org/2021/12/31/creating-a-dapp-with-sveltekit/) by Anthony Riley
|
||||
- [Comparing Svelte Reactivity Options](https://opendirective.net/2022/01/06/comparing-svelte-reactivity-options/) by Steve Lee
|
||||
|
||||
_To Watch_
|
||||
- [Integrating Storybook with SvelteKit](https://www.youtube.com/watch?v=Kc1ULlfyUcw) and [Integrating FaunaDB with Svelte](https://www.youtube.com/watch?v=zaoLZc76uZM) by the Svelte Sirens
|
||||
- [SvelteKit Crash Course Tutorial](https://www.youtube.com/watch?v=9OlLxkaeVvw&list=PL4cUxeGkcC9hpM9ARM59Ve3jqcb54dqiP) by The Net Ninja
|
||||
- [Svelte for Beginners](https://www.youtube.com/watch?v=BrkrOjknC_E&list=PLA9WiRZ-IS_ylnMYxIFCsZN6xVVSvLuHk) by Joy of Code
|
||||
- [SvelteKit For Beginners | Movie App Tutorial](https://www.youtube.com/watch?v=ydR_M0fw9Xc) by Dev Ed
|
||||
- [SvelteKit $app/stores](https://www.youtube.com/watch?v=gBPhr1xbgaQ) by lihautan
|
||||
- [Sveltekit - Get All Routes/Pages](https://www.youtube.com/watch?v=Y_NE2R3HuOU) by WebJeda
|
||||
|
||||
_To Listen To_
|
||||
- [New Year, New Svelte!?](https://share.transistor.fm/s/36212cdc) from Svelte Radio
|
||||
- [So much Sveltey goodness (featuring Rich Harris)](https://changelog.com/jsparty/205) from JS Party
|
||||
- [The Other Side of Tech: A Documentarian Perspective (with Stefan Kingham)](https://codingcat.dev/podcast/2-4-the-other-side-of-tech-a-documentarian-perspective) from Purrfect.dev
|
||||
|
||||
**Libraries, Tools & Components**
|
||||
- [threlte](https://github.com/grischaerbe/threlte) is a three.js component library for Svelte
|
||||
- [svelte-formify](https://github.com/nodify-at/svelte-formify) is a library to manage and validate forms that uses decorators to define validations
|
||||
- [gQuery](https://github.com/leveluptuts/gQuery) is a GraphQL Fetcher & Cache for Svelte Kit
|
||||
- [Unlock-protocol](https://github.com/novum-insights/sveltekit-unlock-firebase) is an integration to help login with MetaMask, Firebase, and paywall customers
|
||||
- [AgnosticUI](https://github.com/AgnosticUI/agnosticui) is a set of UI primitives that start their lives in clean HTML and CSS
|
||||
- [Vitebook](https://github.com/vitebook/vitebook) is a fast and lightweight alternative to Storybook that's powered by Vite
|
||||
- [SwyxKit](https://swyxkit.netlify.app/) is an opinionated blog starter for SvelteKit + Tailwind + Netlify. Refreshed for 2022!
|
||||
- [svelte-themes](https://github.com/beynar/svelte-themes) is an abstraction for themes in your SvelteKit app
|
||||
- [svelte-transition](https://www.npmjs.com/package/svelte-transition) is a Svelte component to make using CSS class based transitions easier - ideally suited for use with TailwindCSS
|
||||
- [Svelte Inview](https://www.npmjs.com/package/svelte-inview) is a Svelte action that monitors an element enters or leaves the viewport/parent element
|
||||
- [svelte-inline-compile](https://github.com/DockYard/svelte-inline-compile) is a babel transform that allows for a much more pleasant experience when testing svelte components using Jest and `@testing-library/svelte`
|
||||
- [@feltcoop/svelte-mutable-store](https://github.com/feltcoop/svelte-mutable-store) is a Svelte store for mutable values with an `immutable` compiler option
|
||||
- [headless-svelte-ui](https://www.npmjs.com/package/@bojalelabs/headless-svelte-ui) is a group of headless components that can be used in building Svelte Apps.
|
||||
|
||||
Did we miss something? Need help bringing your next idea to life in Svelte? Join us on [Reddit](https://www.reddit.com/r/sveltejs/) or [Discord](https://discord.com/invite/yy75DKs).
|
||||
|
||||
See ya next month!
|
||||
@ -0,0 +1,72 @@
|
||||
import Node from './shared/Node';
|
||||
import Expression from './shared/Expression';
|
||||
import Component from '../Component';
|
||||
import TemplateScope from './shared/TemplateScope';
|
||||
import { Context, unpack_destructuring } from './shared/Context';
|
||||
import { ConstTag as ConstTagType } from '../../interfaces';
|
||||
import { INodeAllowConstTag } from './interfaces';
|
||||
import { walk } from 'estree-walker';
|
||||
import { extract_identifiers } from 'periscopic';
|
||||
import is_reference, { NodeWithPropertyDefinition } from 'is-reference';
|
||||
import get_object from '../utils/get_object';
|
||||
import compiler_errors from '../compiler_errors';
|
||||
|
||||
const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate']);
|
||||
|
||||
export default class ConstTag extends Node {
|
||||
type: 'ConstTag';
|
||||
expression: Expression;
|
||||
contexts: Context[] = [];
|
||||
node: ConstTagType;
|
||||
scope: TemplateScope;
|
||||
|
||||
assignees: Set<string> = new Set();
|
||||
dependencies: Set<string> = new Set();
|
||||
|
||||
constructor(component: Component, parent: INodeAllowConstTag, scope: TemplateScope, info: ConstTagType) {
|
||||
super(component, parent, scope, info);
|
||||
|
||||
if (!allowed_parents.has(parent.type)) {
|
||||
component.error(info, compiler_errors.invalid_const_placement);
|
||||
}
|
||||
this.node = info;
|
||||
this.scope = scope;
|
||||
|
||||
const { assignees, dependencies } = this;
|
||||
|
||||
extract_identifiers(info.expression.left).forEach(({ name }) => {
|
||||
assignees.add(name);
|
||||
const owner = this.scope.get_owner(name);
|
||||
if (owner === parent) {
|
||||
component.error(info, compiler_errors.invalid_const_declaration(name));
|
||||
}
|
||||
});
|
||||
|
||||
walk(info.expression.right, {
|
||||
enter(node, parent) {
|
||||
if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) {
|
||||
const identifier = get_object(node as any);
|
||||
const { name } = identifier;
|
||||
dependencies.add(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
parse_expression() {
|
||||
unpack_destructuring({
|
||||
contexts: this.contexts,
|
||||
node: this.node.expression.left,
|
||||
scope: this.scope,
|
||||
component: this.component
|
||||
});
|
||||
this.expression = new Expression(this.component, this, this.scope, this.node.expression.right);
|
||||
this.contexts.forEach(context => {
|
||||
const owner = this.scope.get_owner(context.key.name);
|
||||
if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) {
|
||||
this.component.error(this.node, compiler_errors.invalid_const_declaration(context.key.name));
|
||||
}
|
||||
this.scope.add(context.key.name, this.expression.dependencies, this);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import Component from '../Component';
|
||||
import TemplateScope from './shared/TemplateScope';
|
||||
import Node from './shared/Node';
|
||||
import Let from './Let';
|
||||
import { INode } from './interfaces';
|
||||
|
||||
export default class DefaultSlotTemplate extends Node {
|
||||
type: 'SlotTemplate';
|
||||
scope: TemplateScope;
|
||||
children: INode[];
|
||||
lets: Let[] = [];
|
||||
slot_template_name = 'default';
|
||||
|
||||
constructor(
|
||||
component: Component,
|
||||
parent: INode,
|
||||
scope: TemplateScope,
|
||||
info: any,
|
||||
lets: Let[],
|
||||
children: INode[]
|
||||
) {
|
||||
super(component, parent, scope, info);
|
||||
this.type = 'SlotTemplate';
|
||||
this.children = children;
|
||||
this.scope = scope;
|
||||
this.lets = lets;
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import Node from './shared/Node';
|
||||
import Expression from './shared/Expression';
|
||||
import { TemplateNode } from '../../interfaces';
|
||||
import TemplateScope from './shared/TemplateScope';
|
||||
import Component from '../Component';
|
||||
|
||||
export default class Style extends Node {
|
||||
type: 'Style';
|
||||
name: string;
|
||||
expression: Expression;
|
||||
should_cache: boolean;
|
||||
|
||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
||||
super(component, parent, scope, info);
|
||||
|
||||
this.name = info.name;
|
||||
|
||||
this.expression = new Expression(component, this, scope, info.expression);
|
||||
|
||||
this.should_cache = info.expression.type === 'TemplateLiteral' && info.expression.expressions.length > 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import { TemplateNode } from '../../interfaces';
|
||||
import Component from '../Component';
|
||||
import { nodes_to_template_literal } from '../utils/nodes_to_template_literal';
|
||||
import Expression from './shared/Expression';
|
||||
import Node from './shared/Node';
|
||||
import TemplateScope from './shared/TemplateScope';
|
||||
|
||||
export default class StyleDirective extends Node {
|
||||
type: 'StyleDirective';
|
||||
name: string;
|
||||
expression: Expression;
|
||||
should_cache: boolean;
|
||||
|
||||
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
|
||||
super(component, parent, scope, info);
|
||||
|
||||
this.name = info.name;
|
||||
|
||||
// Convert the value array to an expression so it's easier to handle
|
||||
// the StyleDirective going forward.
|
||||
if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) {
|
||||
const identifier = info.value === true
|
||||
? {
|
||||
type: 'Identifier',
|
||||
start: info.end - info.name.length,
|
||||
end: info.end,
|
||||
name: info.name
|
||||
} as any
|
||||
: info.value[0].expression;
|
||||
this.expression = new Expression(component, this, scope, identifier);
|
||||
this.should_cache = false;
|
||||
} else {
|
||||
const raw_expression = nodes_to_template_literal(info.value);
|
||||
this.expression = new Expression(component, this, scope, raw_expression);
|
||||
this.should_cache = raw_expression.expressions.length > 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
import { TemplateNode, ConstTag as ConstTagType } from '../../../interfaces';
|
||||
import Component from '../../Component';
|
||||
import ConstTag from '../ConstTag';
|
||||
import map_children from './map_children';
|
||||
import { INodeAllowConstTag, INode } from '../interfaces';
|
||||
import check_graph_for_cycles from '../../utils/check_graph_for_cycles';
|
||||
import compiler_errors from '../../compiler_errors';
|
||||
|
||||
export default function get_const_tags(children: TemplateNode[], component: Component, node: INodeAllowConstTag, parent: INode): [ConstTag[], Array<Exclude<INode, ConstTag>>] {
|
||||
const const_tags: ConstTagType[] = [];
|
||||
const others: Array<Exclude<TemplateNode, ConstTagType>> = [];
|
||||
|
||||
for (const child of children) {
|
||||
if (child.type === 'ConstTag') {
|
||||
const_tags.push(child as ConstTagType);
|
||||
} else {
|
||||
others.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
const consts_nodes = const_tags.map(tag => new ConstTag(component, node, node.scope, tag));
|
||||
const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component);
|
||||
sorted_consts_nodes.forEach(node => node.parse_expression());
|
||||
|
||||
const children_nodes = map_children(component, parent, node.scope, others);
|
||||
|
||||
return [sorted_consts_nodes, children_nodes as Array<Exclude<INode, ConstTag>>];
|
||||
}
|
||||
|
||||
function sort_consts_nodes(consts_nodes: ConstTag[], component: Component) {
|
||||
type ConstNode = {
|
||||
assignees: Set<string>;
|
||||
dependencies: Set<string>;
|
||||
node: ConstTag;
|
||||
};
|
||||
const sorted_consts_nodes: ConstNode[] = [];
|
||||
|
||||
const unsorted_consts_nodes: ConstNode[] = consts_nodes.map(node => {
|
||||
return {
|
||||
assignees: node.assignees,
|
||||
dependencies: node.dependencies,
|
||||
node
|
||||
};
|
||||
});
|
||||
|
||||
const lookup = new Map();
|
||||
|
||||
unsorted_consts_nodes.forEach(node => {
|
||||
node.assignees.forEach(name => {
|
||||
if (!lookup.has(name)) {
|
||||
lookup.set(name, []);
|
||||
}
|
||||
lookup.get(name).push(node);
|
||||
});
|
||||
});
|
||||
|
||||
const cycle = check_graph_for_cycles(unsorted_consts_nodes.reduce((acc, node) => {
|
||||
node.assignees.forEach(v => {
|
||||
node.dependencies.forEach(w => {
|
||||
if (!node.assignees.has(w)) {
|
||||
acc.push([v, w]);
|
||||
}
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, []));
|
||||
|
||||
if (cycle && cycle.length) {
|
||||
const nodeList = lookup.get(cycle[0]);
|
||||
const node = nodeList[0];
|
||||
component.error(node.node, compiler_errors.cyclical_const_tags(cycle));
|
||||
}
|
||||
|
||||
const add_node = (node: ConstNode) => {
|
||||
if (sorted_consts_nodes.includes(node)) return;
|
||||
|
||||
node.dependencies.forEach(name => {
|
||||
if (node.assignees.has(name)) return;
|
||||
const earlier_nodes = lookup.get(name);
|
||||
if (earlier_nodes) {
|
||||
earlier_nodes.forEach(add_node);
|
||||
}
|
||||
});
|
||||
|
||||
sorted_consts_nodes.push(node);
|
||||
};
|
||||
|
||||
unsorted_consts_nodes.forEach(add_node);
|
||||
|
||||
return sorted_consts_nodes.map(node => node.node);
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import ConstTag from '../../../nodes/ConstTag';
|
||||
import Block from '../../Block';
|
||||
import { b, x } from 'code-red';
|
||||
import Renderer from '../../Renderer';
|
||||
|
||||
export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string) {
|
||||
const const_tags_props = [];
|
||||
const_tags.forEach((const_tag, i) => {
|
||||
const name = `#constants_${i}`;
|
||||
const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`);
|
||||
const_tag.contexts.forEach(context => {
|
||||
const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), name => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name })};`);
|
||||
});
|
||||
});
|
||||
return const_tags_props;
|
||||
}
|
||||
|
||||
export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) {
|
||||
const_tags.forEach(const_tag => {
|
||||
const_tag.contexts.forEach(context => {
|
||||
renderer.add_to_context(context.key.name, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import ConstTag from '../../../nodes/ConstTag';
|
||||
|
||||
export function get_const_tags(const_tags: ConstTag[]) {
|
||||
if (const_tags.length === 0) return null;
|
||||
return {
|
||||
type: 'VariableDeclaration',
|
||||
kind: 'let',
|
||||
declarations: const_tags.map(const_tag => {
|
||||
const assignment = const_tag.node.expression;
|
||||
return {
|
||||
type: 'VariableDeclarator',
|
||||
id: assignment.left,
|
||||
init: assignment.right
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import { TemplateElement, TemplateLiteral } from 'estree';
|
||||
import { MustacheTag, Text } from '../../interfaces';
|
||||
|
||||
/**
|
||||
* Transforms a list of Text and MustacheTags into a TemplateLiteral expression.
|
||||
* Start/End positions on the elements of the expression are not set.
|
||||
*/
|
||||
export function nodes_to_template_literal(value: Array<Text | MustacheTag>): TemplateLiteral {
|
||||
const literal: TemplateLiteral = {
|
||||
type: 'TemplateLiteral',
|
||||
expressions: [],
|
||||
quasis: []
|
||||
};
|
||||
|
||||
let quasi: TemplateElement = {
|
||||
type: 'TemplateElement',
|
||||
value: { raw: '', cooked: null },
|
||||
tail: false
|
||||
};
|
||||
|
||||
value.forEach((node) => {
|
||||
if (node.type === 'Text') {
|
||||
quasi.value.raw += node.raw;
|
||||
} else if (node.type === 'MustacheTag') {
|
||||
literal.quasis.push(quasi);
|
||||
literal.expressions.push(node.expression as any);
|
||||
quasi = {
|
||||
type: 'TemplateElement',
|
||||
value: { raw: '', cooked: null },
|
||||
tail: false
|
||||
};
|
||||
}
|
||||
});
|
||||
quasi.tail = true;
|
||||
literal.quasis.push(quasi);
|
||||
return literal;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Pushes all `items` into `array` using `push`, therefore mutating the array.
|
||||
* We do this for memory and perf reasons, and because `array.push(...items)` would
|
||||
* run into a "max call stack size exceeded" error with too many items (~65k).
|
||||
* @param array
|
||||
* @param items
|
||||
*/
|
||||
export function push_array<T>(array: T[], items: T[]): void {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
array.push(items[i]);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
|
||||
<div>before</div>
|
||||
<br>
|
||||
|
||||
<!-- HTML_TAG_START -->a <!-- HTML_TAG_END --><!-- HTML_TAG_START -->b <!-- HTML_TAG_END --><!-- HTML_TAG_START -->c <!-- HTML_TAG_END -->
|
||||
<div>after</div>
|
||||
@ -0,0 +1,6 @@
|
||||
|
||||
<div>before</div>
|
||||
<br>
|
||||
|
||||
<!-- HTML_TAG_START -->a <!-- HTML_TAG_END --><!-- HTML_TAG_START -->b <!-- HTML_TAG_END --><!-- HTML_TAG_START -->c <!-- HTML_TAG_END -->
|
||||
<div>after</div>
|
||||
@ -0,0 +1,11 @@
|
||||
<script>
|
||||
let content = ["a ", "b ", "c "];
|
||||
</script>
|
||||
|
||||
<div>before</div>
|
||||
<br>
|
||||
|
||||
{#each content as c}
|
||||
{@html c}
|
||||
{/each}
|
||||
<div>after</div>
|
||||
@ -1 +1,7 @@
|
||||
<div style:color="red"></div>
|
||||
<div style:color='red'></div>
|
||||
<div style:color=red></div>
|
||||
<div style:color="red{variable}"></div>
|
||||
<div style:color='red{variable}'></div>
|
||||
<div style:color=red{variable}></div>
|
||||
<div style:color={`template${literal}`}></div>
|
||||
@ -0,0 +1,19 @@
|
||||
export default {
|
||||
html: '<div>12 120 70, 30+4=34</div>',
|
||||
async test({ component, target, assert }) {
|
||||
component.promise1 = Promise.resolve({width: 5, height: 6});
|
||||
component.promise2 = Promise.reject({width: 6, height: 7});
|
||||
|
||||
await Promise.resolve();
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>30 300 110, 50+6=56</div>
|
||||
<div>42 420 130, 60+7=67</div>
|
||||
`);
|
||||
|
||||
component.constant = 20;
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
<div>42 840 260, 120+7=127</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
<script>
|
||||
export let promise1 = {width: 3, height: 4};
|
||||
export let promise2 = {width: 5, height: 7};
|
||||
export let constant = 10;
|
||||
|
||||
function calculate(width, height, constant) {
|
||||
return { area: width * height, volume: width * height * constant };
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await promise1 then { width, height }}
|
||||
{@const {area, volume} = calculate(width, height, constant)}
|
||||
{@const perimeter = (width + height) * constant}
|
||||
{@const [_width, _height, sum] = [width * constant, height, width * constant + height]}
|
||||
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
|
||||
{/await}
|
||||
|
||||
{#await promise2 catch { width, height }}
|
||||
{@const {area, volume} = calculate(width, height, constant)}
|
||||
{@const perimeter = (width + height) * constant}
|
||||
{@const [_width, _height, sum] = [width * constant, height, width * constant + height]}
|
||||
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
|
||||
{/await}
|
||||
@ -0,0 +1,19 @@
|
||||
export default {
|
||||
html: '<div>12 120 70, 30+4=34</div>',
|
||||
async test({ component, target, assert }) {
|
||||
component.promise1 = Promise.resolve({width: 5, height: 6});
|
||||
component.promise2 = Promise.reject({width: 6, height: 7});
|
||||
|
||||
await Promise.resolve();
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>30 300 110, 50+6=56</div>
|
||||
<div>42 420 130, 60+7=67</div>
|
||||
`);
|
||||
|
||||
component.constant = 20;
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
<div>42 840 260, 120+7=127</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
<script>
|
||||
export let promise1 = {width: 3, height: 4};
|
||||
export let promise2 = {width: 5, height: 7};
|
||||
export let constant = 10;
|
||||
|
||||
function calculate(width, height, constant) {
|
||||
return { area: width * height, volume: width * height * constant };
|
||||
}
|
||||
</script>
|
||||
|
||||
{#await promise1 then box}
|
||||
{@const {area, volume} = calculate(box.width, box.height, constant)}
|
||||
{@const perimeter = (box.width + box.height) * constant}
|
||||
{@const [width, height, sum] = [box.width * constant, box.height, box.width * constant + box.height]}
|
||||
<div>{area} {volume} {perimeter}, {width}+{height}={sum}</div>
|
||||
{/await}
|
||||
|
||||
{#await promise2 catch box}
|
||||
{@const {area, volume} = calculate(box.width, box.height, constant)}
|
||||
{@const perimeter = (box.width + box.height) * constant}
|
||||
{@const [width, height, sum] = [box.width * constant, box.height, box.width * constant + box.height]}
|
||||
<div>{area} {volume} {perimeter}, {width}+{height}={sum}</div>
|
||||
{/await}
|
||||
@ -0,0 +1,7 @@
|
||||
<script>
|
||||
export let box;
|
||||
</script>
|
||||
|
||||
<slot name="box1" {box} />
|
||||
<slot name="box2" width={box.width} height={box.height} />
|
||||
<slot {box} />
|
||||
@ -0,0 +1,46 @@
|
||||
export default {
|
||||
html: `
|
||||
<div>12 120 70, 30+4=34</div>
|
||||
<div>12 120 70, 30+4=34</div>
|
||||
<div>12 120 70, 30+4=34</div>
|
||||
<div slot="box1">
|
||||
<div>12 120 70, 30+4=34</div>
|
||||
</div>
|
||||
<div slot="box2">
|
||||
<div>12 120 70, 30+4=34</div>
|
||||
</div>
|
||||
<div>12 120 70, 30+4=34</div>
|
||||
<div>12 120 70, 30+4=34</div>
|
||||
`,
|
||||
async test({ component, target, assert }) {
|
||||
component.constant = 20;
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
<div slot="box1">
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
</div>
|
||||
<div slot="box2">
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
</div>
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
`);
|
||||
|
||||
component.box = {width: 5, height: 6};
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
<div slot="box1">
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
</div>
|
||||
<div slot="box2">
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
</div>
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,60 @@
|
||||
<script>
|
||||
import Component from './Component.svelte';
|
||||
export let box = {width: 3, height: 4};
|
||||
export let constant = 10;
|
||||
|
||||
function calculate(width, height, constant) {
|
||||
return { area: width * height, volume: width * height * constant };
|
||||
}
|
||||
</script>
|
||||
|
||||
<Component {box}>
|
||||
<svelte:fragment slot="box1" let:box>
|
||||
{@const {area, volume} = calculate(box.width, box.height, constant)}
|
||||
{@const perimeter = (box.width + box.height) * constant}
|
||||
{@const [width, height, sum] = [box.width * constant, box.height, box.width * constant + box.height]}
|
||||
<div>{area} {volume} {perimeter}, {width}+{height}={sum}</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="box2" let:width let:height>
|
||||
{@const {area, volume} = calculate(width, height, constant)}
|
||||
{@const perimeter = (width + height) * constant}
|
||||
{@const [_width, _height, sum] = [width * constant, height, width * constant + height]}
|
||||
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment let:box={{width, height}}>
|
||||
{@const {area, volume} = calculate(width, height, constant)}
|
||||
{@const perimeter = (width + height) * constant}
|
||||
{@const [_width, _height, sum] = [width * constant, height, width * constant + height]}
|
||||
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
|
||||
</svelte:fragment>
|
||||
</Component>
|
||||
|
||||
<Component {box} let:box>
|
||||
<div slot="box1" let:box>
|
||||
{@const {area, volume} = calculate(box.width, box.height, constant)}
|
||||
{@const perimeter = (box.width + box.height) * constant}
|
||||
{@const [width, height, sum] = [box.width * constant, box.height, box.width * constant + box.height]}
|
||||
<div>{area} {volume} {perimeter}, {width}+{height}={sum}</div>
|
||||
</div>
|
||||
|
||||
<div slot="box2" let:width let:height>
|
||||
{@const {area, volume} = calculate(width, height, constant)}
|
||||
{@const perimeter = (width + height) * constant}
|
||||
{@const [_width, _height, sum] = [width * constant, height, width * constant + height]}
|
||||
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
|
||||
</div>
|
||||
|
||||
{@const {area, volume} = calculate(box.width, box.height, constant)}
|
||||
{@const perimeter = (box.width + box.height) * constant}
|
||||
{@const [width, height, sum] = [box.width * constant, box.height, box.width * constant + box.height]}
|
||||
<div>{area} {volume} {perimeter}, {width}+{height}={sum}</div>
|
||||
</Component>
|
||||
|
||||
<Component {box} let:box={{width, height}}>
|
||||
{@const {area, volume} = calculate(width, height, constant)}
|
||||
{@const perimeter = (width + height) * constant}
|
||||
{@const [_width, _height, sum] = [width * constant, height, width * constant + height]}
|
||||
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
|
||||
</Component>
|
||||
@ -0,0 +1,12 @@
|
||||
export default {
|
||||
html: `
|
||||
<div>7</div>
|
||||
`,
|
||||
async test({ component, target, assert }) {
|
||||
component.a = 5;
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>9</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
export let value = 4;
|
||||
export let a = 3;
|
||||
export let b = 4;
|
||||
</script>
|
||||
|
||||
{#each [value] as n}
|
||||
{@const ab = a + b}
|
||||
<div>{ab}</div>
|
||||
{/each}
|
||||
@ -0,0 +1,47 @@
|
||||
export default {
|
||||
html: `
|
||||
<p>#FF0000</p>
|
||||
<p>#00FF00</p>
|
||||
<p>#0000FF</p>
|
||||
`,
|
||||
async test({ component, target, assert }) {
|
||||
component.constant = 20;
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>#FF0000</p>
|
||||
<p>#00FF00</p>
|
||||
<p>#0000FF</p>
|
||||
`);
|
||||
|
||||
component.tags = [
|
||||
{
|
||||
name: 'Red',
|
||||
color: '#FF0000'
|
||||
},
|
||||
{
|
||||
name: 'Green',
|
||||
color: '#00FF00'
|
||||
},
|
||||
{
|
||||
name: 'Blue',
|
||||
color: '#0000FF'
|
||||
},
|
||||
{
|
||||
name: 'Black',
|
||||
color: '#000000'
|
||||
},
|
||||
{
|
||||
name: 'White',
|
||||
color: '#FFFFFF'
|
||||
}
|
||||
];
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>#FF0000</p>
|
||||
<p>#00FF00</p>
|
||||
<p>#0000FF</p>
|
||||
<p>#000000</p>
|
||||
<p>#FFFFFF</p>
|
||||
`);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
<script>
|
||||
export let tags = [
|
||||
{
|
||||
name: 'Red',
|
||||
color: '#FF0000'
|
||||
},
|
||||
{
|
||||
name: 'Green',
|
||||
color: '#00FF00'
|
||||
},
|
||||
{
|
||||
name: 'Blue',
|
||||
color: '#0000FF'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
{#each tags as tag}
|
||||
{@const tagColor = tags.find(t => t.name === tag.name).color}
|
||||
<p>{tagColor}</p>
|
||||
{/each}
|
||||
@ -0,0 +1,30 @@
|
||||
export default {
|
||||
html: `
|
||||
<div>12 120 70, 30+4=34</div>
|
||||
<div>35 350 120, 50+7=57</div>
|
||||
<div>48 480 140, 60+8=68</div>
|
||||
`,
|
||||
async test({ component, target, assert }) {
|
||||
component.constant = 20;
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
<div>35 700 240, 100+7=107</div>
|
||||
<div>48 960 280, 120+8=128</div>
|
||||
`);
|
||||
|
||||
component.boxes = [
|
||||
{width: 3, height: 4},
|
||||
{width: 4, height: 5},
|
||||
{width: 5, height: 6},
|
||||
{width: 6, height: 7}
|
||||
];
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
<div>20 400 180, 80+5=85</div>
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
<div>42 840 260, 120+7=127</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
<script>
|
||||
export let boxes = [
|
||||
{width: 3, height: 4},
|
||||
{width: 5, height: 7},
|
||||
{width: 6, height: 8},
|
||||
];
|
||||
export let constant = 10;
|
||||
|
||||
function calculate(width, height, constant) {
|
||||
return { area: width * height, volume: width * height * constant };
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each boxes as { width, height }}
|
||||
{@const {area, volume} = calculate(width, height, constant)}
|
||||
{@const perimeter = (width + height) * constant}
|
||||
{@const [_width, _height, sum] = [width * constant, height, width * constant + height]}
|
||||
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
|
||||
{/each}
|
||||
@ -0,0 +1,30 @@
|
||||
export default {
|
||||
html: `
|
||||
<div>12 120 70, 30+4=34</div>
|
||||
<div>35 350 120, 50+7=57</div>
|
||||
<div>48 480 140, 60+8=68</div>
|
||||
`,
|
||||
async test({ component, target, assert }) {
|
||||
component.constant = 20;
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
<div>35 700 240, 100+7=107</div>
|
||||
<div>48 960 280, 120+8=128</div>
|
||||
`);
|
||||
|
||||
component.boxes = [
|
||||
{width: 3, height: 4},
|
||||
{width: 4, height: 5},
|
||||
{width: 5, height: 6},
|
||||
{width: 6, height: 7}
|
||||
];
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>12 240 140, 60+4=64</div>
|
||||
<div>20 400 180, 80+5=85</div>
|
||||
<div>30 600 220, 100+6=106</div>
|
||||
<div>42 840 260, 120+7=127</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
<script>
|
||||
export let boxes = [
|
||||
{width: 3, height: 4},
|
||||
{width: 5, height: 7},
|
||||
{width: 6, height: 8},
|
||||
];
|
||||
export let constant = 10;
|
||||
|
||||
function calculate(width, height, constant) {
|
||||
return { area: width * height, volume: width * height * constant };
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each boxes as box}
|
||||
{@const {area, volume} = calculate(box.width, box.height, constant)}
|
||||
{@const perimeter = (box.width + box.height) * constant}
|
||||
{@const [width, height, sum] = [box.width * constant, box.height, box.width * constant + box.height]}
|
||||
<div>{area} {volume} {perimeter}, {width}+{height}={sum}</div>
|
||||
{/each}
|
||||
@ -0,0 +1,12 @@
|
||||
export default {
|
||||
html: `
|
||||
<div>4 ^ 4 = 256</div>
|
||||
`,
|
||||
async test({ component, target, assert }) {
|
||||
component.value = 3;
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<div>3 ^ 4 = 81</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue