Feat: updates to docs, extension

This commit is contained in:
Jonathan Kelley 2021-01-29 11:57:52 -05:00
parent 2e626aea51
commit a2406b33d6
25 changed files with 917 additions and 508 deletions

View file

@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": false "rust-analyzer.inlayHints.enable": true
} }

View file

@ -6,3 +6,8 @@ Dioxus
VDoms VDoms
Tauri Tauri
webview webview
polyfills
transpiling
fullstack
Serverless
vnode

View file

@ -10,7 +10,7 @@ members = [
"packages/router", "packages/router",
"packages/ssr", "packages/ssr",
"packages/webview", "packages/webview",
# Not a rust package, :(. One day, maybe "packages/livehost",
"packages/vscode-ext", "packages/vscode-ext",
# "packages/macro", # "packages/macro",
# TODO @Jon, share the validation code # TODO @Jon, share the validation code

View file

@ -9,13 +9,15 @@
Dioxus is a new approach for creating performant cross platform user experiences in Rust. In Dioxus, the UI is represented as a tree of Virtual Nodes not bound to any specific renderer. Instead, external renderers can leverage Dioxus' virtual DOM and event system as a source of truth for rendering to a medium of their choice. Developers experienced with building react-based experiences should feel comfortable with Dioxus. Dioxus is a new approach for creating performant cross platform user experiences in Rust. In Dioxus, the UI is represented as a tree of Virtual Nodes not bound to any specific renderer. Instead, external renderers can leverage Dioxus' virtual DOM and event system as a source of truth for rendering to a medium of their choice. Developers experienced with building react-based experiences should feel comfortable with Dioxus.
Dioxus is unique in the space of UI for Rust. Dioxus supports a renderer approach called "broadcasting" where two VDoms with separate renderers can sync their UI states remotely. Our goal as a framework is to work towards "Dioxus Liveview" where a server and client work in tandem, eliminating the need for frontend-specific APIs altogether. Dioxus was built in a way to facilitate powerful external renderers - especially designed for the web, servers, desktop, and hybrid approaches like Dioxus Liveview.
Dioxus is supported by Dioxus Labs, a company providing end-to-end services for building, testing, deploying, and managing Dioxus apps on all supported platforms.
## Features ## Features
Dioxus' goal is to be the most advanced UI system for Rust, targeting isomorphism and hybrid approaches. Our goal is to eliminate context-switching for cross-platform development - both in UI patterns and programming language. Hooks and components should work *everywhere* without compromise. Dioxus' goal is to be the most advanced UI system for Rust, targeting isomorphism and hybrid approaches. Our goal is to eliminate context-switching for cross-platform development - both in UI patterns and programming language. Hooks and components should work *everywhere* without compromise.
Dioxus Core supports: Dioxus Core supports:
- [ ] Hooks - [ ] Hooks for component state
- [ ] Concurrent rendering - [ ] Concurrent rendering
- [ ] Context subscriptions - [ ] Context subscriptions
- [ ] State management integrations - [ ] State management integrations
@ -33,7 +35,7 @@ On top of these, we have several projects you can find in the `packages` folder.
- [ ] `dioxus-android`: Android apps - [ ] `dioxus-android`: Android apps
- [ ] `dioxus-magic`: AR/VR Apps - [ ] `dioxus-magic`: AR/VR Apps
## Hello World ## Components
Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API. Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
```rust ```rust
@ -42,7 +44,7 @@ struct MyProps {
name: String name: String
} }
fn Example(ctx: &Context<MyProps>) -> VNode { async fn Example(ctx: &Context<MyProps>) -> VNode {
html! { <div> "Hello {ctx.props.name}!" </div> } html! { <div> "Hello {ctx.props.name}!" </div> }
} }
``` ```
@ -52,36 +54,67 @@ Here, the `Context` object is used to access hook state, create subscriptions, a
```rust ```rust
// A very terse component! // A very terse component!
#[fc] #[fc]
fn Example(ctx: &Context, name: String) -> VNode { async fn Example(ctx: &Context, name: String) -> VNode {
html! { <div> "Hello {name}!" </div> } html! { <div> "Hello {name}!" </div> }
} }
// or // or
#[functional_component] #[functional_component]
static Example: FC = |ctx, name: String| html! { <div> "Hello {:?name}!" </div> }; static Example: FC = |ctx, name: String| async html! { <div> "Hello {name}!" </div> };
``` ```
The final output of components must be a tree of VNodes. We provide an html macro for using JSX-style syntax to write these, though, you could use any macro, DSL, templating engine, or the constructors directly. The final output of components must be a tree of VNodes. We provide an html macro for using JSX-style syntax to write these, though, you could use any macro, DSL, templating engine, or the constructors directly.
## Concurrency ## Concurrency
In Dioxus, components are asynchronous and can their rendering can be paused at any time by awaiting a future. Hooks can combine this functionality with the Context and Subscription APIs to craft dynamic and efficient user experiences.
Dioxus, using React as a reference, provides the ability to have asynchronous components. With Dioxus, this is a valid component:
```rust ```rust
async fn user_data(ctx: &Context<()>) -> VNode { async fn user_data(ctx: &Context<()>) -> VNode {
let Profile { name, birthday, .. } = use_context::<UserContext>(ctx).fetch_data().await; let Profile { name, birthday, .. } = fetch_data().await;
html! { html! {
<div> <div>
{"Hello, {:?name}!"} {"Hello, {name}!"}
{if birthday === std::Instant::now() {html! {"Happy birthday!"}}} {if birthday === std::Instant::now() {html! {"Happy birthday!"}}}
</div> </div>
} }
} }
``` ```
Asynchronous components are powerful but can also be easy to misuse as they pause rendering for the component and its children. Refer to the concurrent guide for information on how to best use async components. Asynchronous components are powerful but can also be easy to misuse as they pause rendering for the component and its children. Refer to the concurrent guide for information on how to best use async components.
## Liveview
With the Context, Subscription, and Asynchronous APIs, we've built Dioxus Liveview: a coupling of frontend and backend to deliver user experiences that do not require dedicated API development. Instead of building and maintaining frontend-specific API endpoints, components can directly access databases, server caches, and other services directly from the component.
These set of features are still experimental. Currently, we're still working on making these components more ergonomic
```rust
async fn live_component(ctx: &Context<()>) -> VNode {
use_live_component(
ctx,
// Rendered via the client
#[cfg(target_arch = "wasm32")]
|| html! { <div> {"Loading data from server..."} </div> },
// Renderered on the server
#[cfg(not(target_arch = "wasm32"))]
|| html! { <div> {"Server Data Loaded!"} </div> },
)
}
```
## Dioxus LiveHost
Dioxus LiveHost is a paid service dedicated to hosting your Dioxus Apps - whether they be server-rendered, wasm-only, or a liveview. LiveHost enables a wide set of features:
- Versioned fronted/backend with unique links
- Builtin CI/CD for all supported Dioxus platforms (mac, windows, server, wasm, etc)
- Managed and pluggable storage database backends
- Serverless support for minimal latency
- Analytics
- Lighthouse optimization
- On-premise support (see license terms)
For small teams, LiveHost is free. Check out the pricing page to see if Dioxus LiveHost is good your team.
## Examples ## Examples
We use the dedicated `dioxus-cli` to build and test dioxus web-apps. This can run examples, tests, build web workers, launch development servers, bundle, and more. It's general purpose, but currently very tailored to Dioxus for liveview and bundling. If you've not used it before, `cargo install --path pacakages/dioxus-cli` will get it installed. This CLI tool should feel like using `cargo` but with 1st party support for assets, bundling, and other important dioxus-specific features. We use the dedicated `dioxus-cli` to build and test dioxus web-apps. This can run examples, tests, build web workers, launch development servers, bundle, and more. It's general purpose, but currently very tailored to Dioxus for liveview and bundling. If you've not used it before, `cargo install --path pacakages/dioxus-cli` will get it installed. This CLI tool should feel like using `cargo` but with 1st party support for assets, bundling, and other important dioxus-specific features.
@ -97,5 +130,5 @@ Alternatively, `trunk` works but can't run examples.
- twitter-clone: A full-featured Twitter clone showcasing dioxus-liveview, state management patterns, and hooks. `cargo run --example twitter` - twitter-clone: A full-featured Twitter clone showcasing dioxus-liveview, state management patterns, and hooks. `cargo run --example twitter`
## Documentation ## Documentation
We have a pretty robust

View file

@ -1,21 +1,22 @@
# Dioxus Chapter 0 - Intro + Index # Dioxus Chapter 0 - Intro + Index
## Guides ## Guides
------------------ ------------------
| Chapter | Description | | Chapter | Description |
| -------------- | ------------------------------------------ | | ------------------ | ------------------------------------------ |
| 1-hello-world | Intro to Dioxus | | 1-hello-world | Intro to Dioxus |
| 2-utilities | Tools to make writing apps easier | | 2-utilities | Tools to make writing apps easier |
| 3-hello-world | Html + functional_component macro | | 3-vnode-macros | VNodes and the html! macro |
| 4-hello-world | Renderer + event integration using web_sys | | 4-hooks | Renderer + event integration using web_sys |
| 5-hello-world | Virtual DOM, patching/diffing | | 5-context-api | Virtual DOM, patching/diffing |
| 6-hello-world | Standard bundle of useful hooks | | 6-subscription API | Standard bundle of useful hooks |
| 7-hello-world | Html + functional_component macro | | 7-hello-world | Html + functional_component macro |
| 8-hello-world | Renderer + event integration using web_sys | | 8-hello-world | Blessed state management solutions |
| 9-hello-world | Renderer + event integration using web_sys | | 9-hello-world | Renderer + event integration using web_sys |
| 10-hello-world | Renderer + event integration using web_sys | | 10-hello-world | Renderer + event integration using web_sys |
## Development ## Development
------------------ ------------------
| Package | Use | | Package | Use |
@ -30,3 +31,7 @@
| redux | Reducer-style state management | | redux | Reducer-style state management |
| bearly | Simple and idiomatic state management | | bearly | Simple and idiomatic state management |
## Books
---------------
Book of patterns

32
docs/12-liveview.md Normal file
View file

@ -0,0 +1,32 @@
# Dioxus Liveview
Liveview is a configuration where a server and a client work together to render a Dioxus app. Liveview monomorphizes a web application, eliminating the need for frontend-specific APIs.
This is a developer-friendly alternative to the JAM-stack (Javascript + API + Markdown), combining the WASM-compatibility and async performance of Rust.
## Why liveview?
### No APIs necessary!
Because Liveview combines the server and the client, you'll find dedicated APIs unnecessary. You'll still want to implement a datafetching service for Live-apps, but this can be implemented as a crate and shared between apps. This approach was designed to let you model out your data requirements without needing to maintain a public versioned API.
You can find more information to data modeling and fetching for LiveApps in the "Book of Dioxus Patterns".
### Ease of deployment
There is no hassle for deploying Dioxus liveview apps - simply upload the binary to your hosting provider of choice. There simply is no need to deal with intermediate APIs. An app's frontend and backend are tightly coupled to each other, meaning that the backed and frontend are always up to date.
This is especially powerful for small teams, where fast iteration, versioned unique prototypes, and simple deployments are important. As your app grows, Dioxus will happily grow with you, serving up to 100K RPS (thanks to async Rust performance).
### Power of server rendering combined with the reactive nature of JavaScript
Liveview apps are backed by a server and enhanced with web-assembly. This completely eliminates the need to write Javascript to add dynamic content to webpages. Apps are written in only **one** language and require essentially 0 knowledge of build systems, transpiling or ECMAScript versions. Minimum browser support is guaranteed to cover 95% of users, and the Dioxus-CLI can be configured to generate polyfills for even more support.
### Support with LiveHost by Dioxus-Labs
The Dioxus-CLI comes ready for use with premium Dioxus-Labs services, like LiveHost. Simply link up your git repo to the Dioxus LiveHost and start deploying your fullstack Dioxus app today. LiveHost supports:
- Versioned fronted/backend with unique links
- Builtin CI/CD
- Managed and pluggable storage database backends
- Serverless support for minimal latency
- Site Analytics
- Lighthouse optimization
- On-premise support (see license terms)
Dioxus LiveHost is a Dioxus LiveApp management service with all supported features in a single binary. For OSS, we permit free usage and deployment of LiveHost to your favorite hosting provider.

View file

@ -20,15 +20,27 @@ anyhow = "*"
async-std = { version = "1.9.0", features = ["attributes"] } async-std = { version = "1.9.0", features = ["attributes"] }
tide = { version = "0.15.0" } tide = { version = "0.15.0" }
# For the livewview example
tide-websockets = "0.1.0"
serde_millis = "0.1"
serde_json = "1"
serde = { version = "1", features = ['derive'] }
# For the doc generator # For the doc generator
pulldown-cmark = { version = "0.8.0", default-features = false } pulldown-cmark = { version = "0.8.0", default-features = false }
dioxus-webview = { path = "../packages/webview", version = "0.0.0" } dioxus-webview = { path = "../packages/webview", version = "0.0.0" }
dioxus-hooks = { path = "../packages/hooks", version = "0.0.0" } dioxus-hooks = { path = "../packages/hooks", version = "0.0.0" }
# Shared functionality is done as a lib
[lib] [lib]
path = "common.rs" path = "common.rs"
# ================================
# Examples are manually keyed in
# ================================
[[example]] [[example]]
path = "hello_web.rs" path = "hello_web.rs"
name = "hello_web" name = "hello_web"
@ -52,3 +64,11 @@ name = "fc_macro"
[[example]] [[example]]
path = "webview.rs" path = "webview.rs"
name = "webview" name = "webview"
[[example]]
path = "blah.rs"
name = "blah"
[[example]]
path = "live.rs"
name = "live"

9
examples/blah.rs Normal file
View file

@ -0,0 +1,9 @@
use dioxus::prelude::*;
fn main() {
let g = html! {
<div>
<style> </style>
</div>
};
}

65
examples/live.rs Normal file
View file

@ -0,0 +1,65 @@
use dioxus::prelude::*;
use tide::{with_state, Request};
use tide_websockets::{WebSocket, WebSocketConnection};
fn main() {
let mut g = Context { props: &() };
LiveComponent(&mut g);
}
#[cfg(not(target_arch = "wasm32"))]
async fn server() -> tide::Result<()> {
// Build the API
let mut app = tide::with_state(());
app.at("/app").get(WebSocket::new(live_handler));
// Launch the server
app.listen("127.0.0.1:8080").await?;
Ok(())
}
async fn live_handler(req: Request<()>, stream: WebSocketConnection) -> tide::Result<()> {
Ok(())
}
static LiveComponent: FC<()> = |ctx| {
use_live_component(
ctx,
#[cfg(target_arch = "wasm32")]
|| {
// Always wait on the context's live component API
// suspend the component until this promise arrives, or fall back
let g = &LiveComponent;
html! {
<div>
{"Hello world!"}
</div>
}
},
#[cfg(not(target_arch = "wasm32"))]
|| {
// actually use the code originally specified in the component
// this gives use the function pointer. We don't necessarily get to hash the same pointer between two binaries
// Some key will need to be made, likely based on the function parameter
let g = &LiveComponent;
html! {
<div>
{"Hello world!"}
</div>
}
},
)
};
/// This hooks connects with the LiveContext at the top of the app.
fn use_live_component<T>(ctx: &mut Context<T>, b: fn() -> VNode) -> VNode {
todo!()
}
/// LiveContext is a special, limited form of the context api that disallows the "Context API"
/// Its purpose is to shield the original Context where calls to use_context will fail. Instead of panicing and confusing
/// users, we simply disallow usage of "use_context" and "childen". In effect, only serialiable props can be allowed.
///
/// In the future, we might try to lift these restrictions (esp children since they are virtual) but are limited via the web connection
struct LiveContext {}

View file

@ -26,5 +26,7 @@ fn main() {
} }
}); });
let f = 10_i32;
println!("hello {}", f);
app.launch(()); app.launch(());
} }

View file

@ -1,6 +1,6 @@
use std::future::Future; use std::future::Future;
use dioxus_core::prelude::*; use dioxus_core::{prelude::*, virtual_dom::Properties};
// use virtual_dom_rs::Closure; // use virtual_dom_rs::Closure;
// Stop-gap while developing // Stop-gap while developing
@ -13,9 +13,11 @@ pub fn main() {
// let output = renderer.render(); // let output = renderer.render();
} }
#[derive(PartialEq)]
struct Props { struct Props {
name: String, name: String,
} }
impl Properties for Props {}
fn root(ctx: &mut Context<Props>) -> VNode { fn root(ctx: &mut Context<Props>) -> VNode {
// the regular html syntax // the regular html syntax
@ -39,10 +41,17 @@ fn root(ctx: &mut Context<Props>) -> VNode {
} }
let mut node_1: IterableNodes = ("Hello world!").into(); let mut node_1: IterableNodes = ("Hello world!").into();
node_1.first().insert_space_before_text(); node_1.first().insert_space_before_text();
let mut node_2 = VNode::element("button"); let mut node_2 = VNode::element("button");
let node_3 = VNode::Component(VComponent {}); let node_3 = VNode::Component(VComponent::from_fn(
Head,
Props {
name: "".to_string(),
},
));
{ {
// let closure = Closure::wrap(Box::new(|_| {}) as Box<FnMut(_)>); // let closure = Closure::wrap(Box::new(|_| {}) as Box<FnMut(_)>);
// let closure_rc = std::rc::Rc::new(closure); // let closure_rc = std::rc::Rc::new(closure);
@ -67,17 +76,13 @@ fn root(ctx: &mut Context<Props>) -> VNode {
fn Head(ctx: &mut Context<Props>) -> VNode { fn Head(ctx: &mut Context<Props>) -> VNode {
html! { html! {
<head> <head> "Head Section" </head>
"Head Section"
</head>
} }
} }
fn Body(ctx: &mut Context<Props>) -> VNode { fn Body(ctx: &mut Context<Props>) -> VNode {
html! { html! {
<body> <body> {"Footer Section"}</body>
{"Footer Section"}
</body>
} }
} }

View file

@ -6,8 +6,6 @@
//! </div> //! </div>
//! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust. //! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust.
//! //!
//!
//! Dioxus is an efficient virtual dom implementation for building interactive user interfaces in Rust.
//! This crate aims to maintain a uniform hook-based, renderer-agnostic UI framework for cross-platform development. //! This crate aims to maintain a uniform hook-based, renderer-agnostic UI framework for cross-platform development.
//! //!
//! ## Components //! ## Components
@ -15,14 +13,38 @@
//! ``` //! ```
//! use dioxus_core::prelude::*; //! use dioxus_core::prelude::*;
//! //!
//! fn Example(ctx: Context<()>) -> VNode { //! #[derive(Properties)]
//! html! { <div> "Hello world!" </div> } //! struct Props { name: String }
//!
//! fn Example(ctx: &mut Context<Props>) -> VNode {
//! html! { <div> "Hello {ctx.props.name}!" </div> }
//! } //! }
//! ``` //! ```
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used //! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
//! and what properties can be used to specify it in the VNode output. All components in Dioxus are hook-based, which might be more //! and what properties can be used to specify it in the VNode output. Component state in Dioxus is managed by hooks - if you're new
//! complex than other approaches that use traits + lifecycle events. Alternatively, we provide a "lifecycle hook" if you want more //! to hooks, check out the hook guide in the official guide.
//! granualar control with behavior similar to other UI frameworks. //!
//! Components can also be crafted as static closures, enabling type inference without all the type signature noise:
//! ```
//! use dioxus_core::prelude::*;
//!
//! #[derive(Properties)]
//! struct Props { name: String }
//!
//! static Example: FC<Props> = |ctx| {
//! html! { <div> "Hello {ctx.props.name}!" </div> }
//! }
//! ```
//!
//! If the properties struct is too noisy for you, we also provide a macro that converts variadic functions into components automatically.
//! ```
//! use dioxus_core::prelude::*;
//!
//! #[functional_component]
//! static Example: FC = |ctx, name: String| {
//! html! { <div> "Hello {name}!" </div> }
//! }
//! ```
//! //!
//! ## Hooks //! ## Hooks
//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component. Instead of //! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component. Instead of
@ -177,7 +199,12 @@ pub mod virtual_dom {
} }
} }
pub trait Properties {} pub trait Properties: PartialEq + 'static {
/*
Props cannot contain references (as of today) :(
Use a smart pointer
*/
}
// Auto derive for pure components // Auto derive for pure components
impl Properties for () {} impl Properties for () {}
@ -187,8 +214,9 @@ pub mod virtual_dom {
} }
/// Virtual Node Support /// Virtual Node Support
/// VNodes represent lazily-constructed VDom trees that support diffing and event handlers.
/// ///
/// /// These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
/// ///
pub mod nodes { pub mod nodes {
pub use vcomponent::VComponent; pub use vcomponent::VComponent;
@ -528,13 +556,52 @@ pub mod nodes {
/// Virtual Components for custom user-defined components /// Virtual Components for custom user-defined components
/// Only supports the functional syntax /// Only supports the functional syntax
mod vcomponent { mod vcomponent {
use crate::{
prelude::{Context, FC},
virtual_dom::Properties,
};
use std::{any::TypeId, fmt, future::Future};
use super::VNode;
#[derive(PartialEq)] #[derive(PartialEq)]
pub struct VComponent {} pub struct VComponent {
props_id: TypeId,
// callerIDs are unsafely coerced to function pointers
// This is okay because #1, we store the props_id and verify and 2# the html! macro rejects components not made this way
//
// Manually constructing the VComponent is not possible from 3rd party crates
}
impl VComponent {
/// Construct a VComponent directly from a function component
/// This should be *very* fast - we store the function pointer and props type ID. It should also be small on the stack
pub fn from_fn<P: Properties>(f: FC<P>, props: P) -> Self {
// Props needs to be static
let props_id = std::any::TypeId::of::<P>();
// Cast the caller down
Self { props_id }
}
/// Construct a VComponent directly from a concurrent component
pub fn from_asyncfn<P: Properties, F>(f: fn(&mut Context<P>) -> F, props: P) -> Self
where
F: Future<Output = VNode>,
{
// Props needs to be static
let props_id = std::any::TypeId::of::<P>();
// Cast the caller down
Self { props_id }
}
}
// ----------------------------------------------- // -----------------------------------------------
// Allow debug/display adherent to the HTML spec // Allow debug/display adherent to the HTML spec
// ----------------------------------------------- // -----------------------------------------------
use std::fmt;
impl fmt::Debug for VComponent { impl fmt::Debug for VComponent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO: @JON Implement how components should be formatted when spit out to html // TODO: @JON Implement how components should be formatted when spit out to html

View file

@ -0,0 +1,57 @@
use dioxus_core::prelude::*;
fn main() {
let mut s = Context { props: &() };
let g = Component(&mut s);
}
struct CompState {
tasks: Vec<()>,
}
enum Actions {
Add,
MoveUp,
MoveDown,
Remvoe,
}
static Component: FC<()> = |ctx| {
let (tasks, dispatch) = use_reducer(
ctx,
|| CompState { tasks: Vec::new() },
|state, action: Actions| match action {
Actions::Add => state,
Actions::MoveUp => state,
Actions::MoveDown => state,
Actions::Remvoe => state,
},
);
let tasklist = { (0..10).map(|f| html! { <li></li> }) }.collect::<Vec<_>>();
html! {
<div>
<div>
<h1>"Tasks: "</h1>
<ul>
{tasklist}
</ul>
</div>
<div>
<button onclick=|_| dispatch(Action::Add)>{"Add"}</button>
<button onclick=|_| dispatch(Action::MoveUp)>{"MoveUp"}</button>
<button onclick=|_| dispatch(Action::MoveDown)>{"MoveDown"}</button>
<button onclick=|_| dispatch(Action::Remvoe)>{"Remvoe"}</button>
</div>
</div>
}
};
fn use_reducer<Props, State, Action>(
ctx: &mut Context<Props>,
init: fn() -> State,
reducer: fn(State, Action) -> State,
) -> (State, impl Fn(Action)) {
let ii = init();
(ii, |_| {})
}

View file

@ -0,0 +1,9 @@
[package]
name = "dioxus-livehost"
version = "0.0.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View file

@ -57,6 +57,11 @@
"vscode-languageserver-types": "3.15.1" "vscode-languageserver-types": "3.15.1"
} }
}, },
"vscode-languageserver-textdocument": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz",
"integrity": "sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA=="
},
"vscode-languageserver-types": { "vscode-languageserver-types": {
"version": "3.15.1", "version": "3.15.1",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz",

View file

@ -14,7 +14,8 @@
}, },
"dependencies": { "dependencies": {
"vscode-html-languageservice": "^3.0.3", "vscode-html-languageservice": "^3.0.3",
"vscode-languageclient": "^6.1.3" "vscode-languageclient": "^6.1.3",
"vscode-languageserver-textdocument": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/vscode": "^1.43.0" "@types/vscode": "^1.43.0"

View file

@ -3,8 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { TextDocument, Position, Range } from 'vscode-languageclient'; import { TextDocument } from "vscode-languageserver-textdocument";
import { LanguageService, TokenType } from 'vscode-html-languageservice'; import { Position, Range } from "vscode-languageclient";
import { LanguageService, TokenType } from "vscode-html-languageservice";
export interface LanguageRange extends Range { export interface LanguageRange extends Range {
languageId: string | undefined; languageId: string | undefined;
@ -12,14 +13,17 @@ export interface LanguageRange extends Range {
} }
export interface HTMLDocumentRegions { export interface HTMLDocumentRegions {
getEmbeddedDocument(languageId: string, ignoreAttributeValues?: boolean): TextDocument; getEmbeddedDocument(
languageId: string,
ignoreAttributeValues?: boolean
): TextDocument;
getLanguageRanges(range: Range): LanguageRange[]; getLanguageRanges(range: Range): LanguageRange[];
getLanguageAtPosition(position: Position): string | undefined; getLanguageAtPosition(position: Position): string | undefined;
getLanguagesInDocument(): string[]; getLanguagesInDocument(): string[];
getImportedScripts(): string[]; getImportedScripts(): string[];
} }
export const CSS_STYLE_RULE = '__'; export const CSS_STYLE_RULE = "__";
interface EmbeddedRegion { interface EmbeddedRegion {
languageId: string | undefined; languageId: string | undefined;
@ -28,6 +32,7 @@ interface EmbeddedRegion {
attributeValue?: boolean; attributeValue?: boolean;
} }
// Check if the request is coming from inside a special region
export function isInsideStyleRegion( export function isInsideStyleRegion(
languageService: LanguageService, languageService: LanguageService,
documentText: string, documentText: string,
@ -39,7 +44,10 @@ export function isInsideStyleRegion(
while (token !== TokenType.EOS) { while (token !== TokenType.EOS) {
switch (token) { switch (token) {
case TokenType.Styles: case TokenType.Styles:
if (offset >= scanner.getTokenOffset() && offset <= scanner.getTokenEnd()) { if (
offset >= scanner.getTokenOffset() &&
offset <= scanner.getTokenEnd()
) {
return true; return true;
} }
} }
@ -55,7 +63,7 @@ export function getCSSVirtualContent(
): string { ): string {
let regions: EmbeddedRegion[] = []; let regions: EmbeddedRegion[] = [];
let scanner = languageService.createScanner(documentText); let scanner = languageService.createScanner(documentText);
let lastTagName: string = ''; let lastTagName: string = "";
let lastAttributeName: string | null = null; let lastAttributeName: string | null = null;
let languageIdFromType: string | undefined = undefined; let languageIdFromType: string | undefined = undefined;
let importedScripts: string[] = []; let importedScripts: string[] = [];
@ -66,41 +74,47 @@ export function getCSSVirtualContent(
case TokenType.StartTag: case TokenType.StartTag:
lastTagName = scanner.getTokenText(); lastTagName = scanner.getTokenText();
lastAttributeName = null; lastAttributeName = null;
languageIdFromType = 'javascript'; languageIdFromType = "javascript";
break; break;
case TokenType.Styles: case TokenType.Styles:
regions.push({ regions.push({
languageId: 'css', languageId: "css",
start: scanner.getTokenOffset(), start: scanner.getTokenOffset(),
end: scanner.getTokenEnd() end: scanner.getTokenEnd(),
}); });
break; break;
case TokenType.Script: case TokenType.Script:
regions.push({ regions.push({
languageId: languageIdFromType, languageId: languageIdFromType,
start: scanner.getTokenOffset(), start: scanner.getTokenOffset(),
end: scanner.getTokenEnd() end: scanner.getTokenEnd(),
}); });
break; break;
case TokenType.AttributeName: case TokenType.AttributeName:
lastAttributeName = scanner.getTokenText(); lastAttributeName = scanner.getTokenText();
break; break;
case TokenType.AttributeValue: case TokenType.AttributeValue:
if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') { if (
lastAttributeName === "src" &&
lastTagName.toLowerCase() === "script"
) {
let value = scanner.getTokenText(); let value = scanner.getTokenText();
if (value[0] === "'" || value[0] === '"') { if (value[0] === "'" || value[0] === '"') {
value = value.substr(1, value.length - 1); value = value.substr(1, value.length - 1);
} }
importedScripts.push(value); importedScripts.push(value);
} else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { } else if (
lastAttributeName === "type" &&
lastTagName.toLowerCase() === "script"
) {
if ( if (
/["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test( /["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test(
scanner.getTokenText() scanner.getTokenText()
) )
) { ) {
languageIdFromType = 'javascript'; languageIdFromType = "javascript";
} else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) {
languageIdFromType = 'typescript'; languageIdFromType = "typescript";
} else { } else {
languageIdFromType = undefined; languageIdFromType = undefined;
} }
@ -118,7 +132,7 @@ export function getCSSVirtualContent(
languageId: attributeLanguageId, languageId: attributeLanguageId,
start, start,
end, end,
attributeValue: true attributeValue: true,
}); });
} }
} }
@ -129,28 +143,31 @@ export function getCSSVirtualContent(
} }
let content = documentText let content = documentText
.split('\n') .split("\n")
.map(line => { .map((line) => {
return ' '.repeat(line.length); return " ".repeat(line.length);
}).join('\n'); })
.join("\n");
regions.forEach(r => { regions.forEach((r) => {
if (r.languageId === 'css') { if (r.languageId === "css") {
content = content.slice(0, r.start) + documentText.slice(r.start, r.end) + content.slice(r.end); content =
content.slice(0, r.start) +
documentText.slice(r.start, r.end) +
content.slice(r.end);
} }
}); });
return content; return content;
} }
export function getDocumentRegions( export function getDocumentRegions(
languageService: LanguageService, languageService: LanguageService,
document: TextDocument document: TextDocument
): HTMLDocumentRegions { ): HTMLDocumentRegions {
let regions: EmbeddedRegion[] = []; let regions: EmbeddedRegion[] = [];
let scanner = languageService.createScanner(document.getText()); let scanner = languageService.createScanner(document.getText());
let lastTagName: string = ''; let lastTagName: string = "";
let lastAttributeName: string | null = null; let lastAttributeName: string | null = null;
let languageIdFromType: string | undefined = undefined; let languageIdFromType: string | undefined = undefined;
let importedScripts: string[] = []; let importedScripts: string[] = [];
@ -161,41 +178,47 @@ export function getDocumentRegions(
case TokenType.StartTag: case TokenType.StartTag:
lastTagName = scanner.getTokenText(); lastTagName = scanner.getTokenText();
lastAttributeName = null; lastAttributeName = null;
languageIdFromType = 'javascript'; languageIdFromType = "javascript";
break; break;
case TokenType.Styles: case TokenType.Styles:
regions.push({ regions.push({
languageId: 'css', languageId: "css",
start: scanner.getTokenOffset(), start: scanner.getTokenOffset(),
end: scanner.getTokenEnd() end: scanner.getTokenEnd(),
}); });
break; break;
case TokenType.Script: case TokenType.Script:
regions.push({ regions.push({
languageId: languageIdFromType, languageId: languageIdFromType,
start: scanner.getTokenOffset(), start: scanner.getTokenOffset(),
end: scanner.getTokenEnd() end: scanner.getTokenEnd(),
}); });
break; break;
case TokenType.AttributeName: case TokenType.AttributeName:
lastAttributeName = scanner.getTokenText(); lastAttributeName = scanner.getTokenText();
break; break;
case TokenType.AttributeValue: case TokenType.AttributeValue:
if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') { if (
lastAttributeName === "src" &&
lastTagName.toLowerCase() === "script"
) {
let value = scanner.getTokenText(); let value = scanner.getTokenText();
if (value[0] === "'" || value[0] === '"') { if (value[0] === "'" || value[0] === '"') {
value = value.substr(1, value.length - 1); value = value.substr(1, value.length - 1);
} }
importedScripts.push(value); importedScripts.push(value);
} else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { } else if (
lastAttributeName === "type" &&
lastTagName.toLowerCase() === "script"
) {
if ( if (
/["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test( /["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test(
scanner.getTokenText() scanner.getTokenText()
) )
) { ) {
languageIdFromType = 'javascript'; languageIdFromType = "javascript";
} else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) {
languageIdFromType = 'typescript'; languageIdFromType = "typescript";
} else { } else {
languageIdFromType = undefined; languageIdFromType = undefined;
} }
@ -213,7 +236,7 @@ export function getDocumentRegions(
languageId: attributeLanguageId, languageId: attributeLanguageId,
start, start,
end, end,
attributeValue: true attributeValue: true,
}); });
} }
} }
@ -223,13 +246,14 @@ export function getDocumentRegions(
token = scanner.scan(); token = scanner.scan();
} }
return { return {
getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range), getLanguageRanges: (range: Range) =>
getLanguageRanges(document, regions, range),
getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) => getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) =>
getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues), getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues),
getLanguageAtPosition: (position: Position) => getLanguageAtPosition: (position: Position) =>
getLanguageAtPosition(document, regions, position), getLanguageAtPosition(document, regions, position),
getLanguagesInDocument: () => getLanguagesInDocument(document, regions), getLanguagesInDocument: () => getLanguagesInDocument(document, regions),
getImportedScripts: () => importedScripts getImportedScripts: () => importedScripts,
}; };
} }
@ -241,7 +265,9 @@ function getLanguageRanges(
let result: LanguageRange[] = []; let result: LanguageRange[] = [];
let currentPos = range ? range.start : Position.create(0, 0); let currentPos = range ? range.start : Position.create(0, 0);
let currentOffset = range ? document.offsetAt(range.start) : 0; let currentOffset = range ? document.offsetAt(range.start) : 0;
let endOffset = range ? document.offsetAt(range.end) : document.getText().length; let endOffset = range
? document.offsetAt(range.end)
: document.getText().length;
for (let region of regions) { for (let region of regions) {
if (region.end > currentOffset && region.start < endOffset) { if (region.end > currentOffset && region.start < endOffset) {
let start = Math.max(region.start, currentOffset); let start = Math.max(region.start, currentOffset);
@ -250,7 +276,7 @@ function getLanguageRanges(
result.push({ result.push({
start: currentPos, start: currentPos,
end: startPos, end: startPos,
languageId: 'html' languageId: "html",
}); });
} }
let end = Math.min(region.end, endOffset); let end = Math.min(region.end, endOffset);
@ -260,7 +286,7 @@ function getLanguageRanges(
start: startPos, start: startPos,
end: endPos, end: endPos,
languageId: region.languageId, languageId: region.languageId,
attributeValue: region.attributeValue attributeValue: region.attributeValue,
}); });
} }
currentOffset = end; currentOffset = end;
@ -272,7 +298,7 @@ function getLanguageRanges(
result.push({ result.push({
start: currentPos, start: currentPos,
end: endPos, end: endPos,
languageId: 'html' languageId: "html",
}); });
} }
return result; return result;
@ -291,7 +317,7 @@ function getLanguagesInDocument(
} }
} }
} }
result.push('html'); result.push("html");
return result; return result;
} }
@ -310,7 +336,7 @@ function getLanguageAtPosition(
break; break;
} }
} }
return 'html'; return "html";
} }
function getEmbeddedDocument( function getEmbeddedDocument(
@ -321,10 +347,13 @@ function getEmbeddedDocument(
): TextDocument { ): TextDocument {
let currentPos = 0; let currentPos = 0;
let oldContent = document.getText(); let oldContent = document.getText();
let result = ''; let result = "";
let lastSuffix = ''; let lastSuffix = "";
for (let c of contents) { for (let c of contents) {
if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) { if (
c.languageId === languageId &&
(!ignoreAttributeValues || !c.attributeValue)
) {
result = substituteWithWhitespace( result = substituteWithWhitespace(
result, result,
currentPos, currentPos,
@ -344,30 +373,35 @@ function getEmbeddedDocument(
oldContent.length, oldContent.length,
oldContent, oldContent,
lastSuffix, lastSuffix,
'' ""
);
return TextDocument.create(
document.uri,
languageId,
document.version,
result
); );
return TextDocument.create(document.uri, languageId, document.version, result);
} }
function getPrefix(c: EmbeddedRegion) { function getPrefix(c: EmbeddedRegion) {
if (c.attributeValue) { if (c.attributeValue) {
switch (c.languageId) { switch (c.languageId) {
case 'css': case "css":
return CSS_STYLE_RULE + '{'; return CSS_STYLE_RULE + "{";
} }
} }
return ''; return "";
} }
function getSuffix(c: EmbeddedRegion) { function getSuffix(c: EmbeddedRegion) {
if (c.attributeValue) { if (c.attributeValue) {
switch (c.languageId) { switch (c.languageId) {
case 'css': case "css":
return '}'; return "}";
case 'javascript': case "javascript":
return ';'; return ";";
} }
} }
return ''; return "";
} }
function substituteWithWhitespace( function substituteWithWhitespace(
@ -382,7 +416,7 @@ function substituteWithWhitespace(
result += before; result += before;
for (let i = start + before.length; i < end; i++) { for (let i = start + before.length; i < end; i++) {
let ch = oldContent[i]; let ch = oldContent[i];
if (ch === '\n' || ch === '\r') { if (ch === "\n" || ch === "\r") {
// only write new lines, skip the whitespace // only write new lines, skip the whitespace
accumulatedWS = 0; accumulatedWS = 0;
result += ch; result += ch;
@ -390,7 +424,7 @@ function substituteWithWhitespace(
accumulatedWS++; accumulatedWS++;
} }
} }
result = append(result, ' ', accumulatedWS - after.length); result = append(result, " ", accumulatedWS - after.length);
result += after; result += after;
return result; return result;
} }
@ -411,5 +445,5 @@ function getAttributeLanguage(attributeName: string): string | null {
if (!match) { if (!match) {
return null; return null;
} }
return match[1] ? 'css' : 'javascript'; return match[1] ? "css" : "javascript";
} }

View file

@ -3,11 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */ * ------------------------------------------------------------------------------------------ */
import * as path from 'path'; import * as path from "path";
import { commands, CompletionList, ExtensionContext, Uri, workspace } from 'vscode'; import {
import { getLanguageService } from 'vscode-html-languageservice'; commands,
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'; CompletionList,
import { getCSSVirtualContent, isInsideStyleRegion } from './embeddedSupport'; ExtensionContext,
Uri,
workspace,
} from "vscode";
import { getLanguageService } from "vscode-html-languageservice";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind,
} from "vscode-languageclient";
import { isInsideHtmlMacro } from "./rustSupport";
// import { getCSSVirtualContent, isInsideStyleRegion } from "./embeddedSupport";
let client: LanguageClient; let client: LanguageClient;
@ -15,10 +27,12 @@ const htmlLanguageService = getLanguageService();
export function activate(context: ExtensionContext) { export function activate(context: ExtensionContext) {
// The server is implemented in node // The server is implemented in node
let serverModule = context.asAbsolutePath(path.join('server', 'out', 'server.js')); let serverModule = context.asAbsolutePath(
path.join("server", "out", "server.js")
);
// The debug options for the server // The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] }; let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] };
// If the extension is launched in debug mode then the debug server options are used // If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used // Otherwise the run options are used
@ -27,54 +41,98 @@ export function activate(context: ExtensionContext) {
debug: { debug: {
module: serverModule, module: serverModule,
transport: TransportKind.ipc, transport: TransportKind.ipc,
options: debugOptions options: debugOptions,
} },
}; };
const virtualDocumentContents = new Map<string, string>(); const virtualDocumentContents = new Map<string, string>();
workspace.registerTextDocumentContentProvider('embedded-content', { workspace.registerTextDocumentContentProvider("embedded-content", {
provideTextDocumentContent: uri => { provideTextDocumentContent: (uri) => {
const originalUri = uri.path.slice(1).slice(0, -4); const originalUri = uri.path.slice(1).slice(0, -4);
console.error(originalUri);
const decodedUri = decodeURIComponent(originalUri); const decodedUri = decodeURIComponent(originalUri);
return virtualDocumentContents.get(decodedUri); return virtualDocumentContents.get(decodedUri);
} },
}); });
let clientOptions: LanguageClientOptions = { let clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'html1' }], documentSelector: [{ scheme: "file", language: "rust" }],
middleware: { middleware: {
provideCompletionItem: async (document, position, context, token, next) => { provideCompletionItem: async (
// If not in `<style>`, do not perform request forwarding document,
if (!isInsideStyleRegion(htmlLanguageService, document.getText(), document.offsetAt(position))) { position,
context,
token,
next
) => {
/*
1: Find the occurences of the html! macro using regex
2: Check if any of the occurences match the cursor offset
3: If so, direct the captured block to the html to the rsx language service
*/
const docSrc = document.getText();
const offset = document.offsetAt(position);
const matches = docSrc.matchAll(macroRegex);
// Lazily loop through matches, abort early if the cursor is after the match
// let start = 0;
// let end = 0;
let matchBody: string | undefined = undefined;
for (const match of matches) {
// // Check if the cursor is inbetween the previous end and the new start
// // This means the cursor is between html! invocations and we should bail early
// if (offset > end && offset < match.index) {
// // Ensure the match
// // defer to the "next?" symbol
// return await next(document, position, context, token);
// }
// Otherwise, move the counters forward
const start = match.index;
const end = start + match.length;
// Ensure the cursor is within the match
// Break if so
if (offset >= start && offset <= end) {
matchBody = match[1];
break;
}
}
// If we looped through all the matches and the match wasn't defined, then bail
if (matchBody === undefined) {
return await next(document, position, context, token); return await next(document, position, context, token);
} }
// If we're inside the style region, then provide CSS completions with the CSS provider
const originalUri = document.uri.toString(); const originalUri = document.uri.toString();
virtualDocumentContents.set(originalUri, getCSSVirtualContent(htmlLanguageService, document.getText())); virtualDocumentContents.set(originalUri, matchBody);
// getCSSVirtualContent(htmlLanguageService, document.getText())
const vdocUriString = `embedded-content://css/${encodeURIComponent( const vdocUriString = `embedded-content://html/${encodeURIComponent(
originalUri originalUri
)}.css`; )}.html`;
const vdocUri = Uri.parse(vdocUriString); const vdocUri = Uri.parse(vdocUriString);
return await commands.executeCommand<CompletionList>( return await commands.executeCommand<CompletionList>(
'vscode.executeCompletionItemProvider', "vscode.executeCompletionItemProvider",
vdocUri, vdocUri,
position, position,
context.triggerCharacter context.triggerCharacter
); );
} },
} },
}; };
// Create the language client and start the client. // Create the language client and start the client.
client = new LanguageClient( client = new LanguageClient(
'languageServerExample', "languageServerExample",
'Language Server Example', "Language Server Example",
serverOptions, serverOptions,
clientOptions clientOptions
); );
// Start the client. This will also launch the server // Start the client. This will also launch the server
client.start(); client.start();
} }
@ -85,3 +143,5 @@ export function deactivate(): Thenable<void> | undefined {
} }
return client.stop(); return client.stop();
} }
const macroRegex = /html! {([\s\S]*?)}/g;

View file

@ -0,0 +1,8 @@
const macroRegex = /html! {([\s\S]*?)}/g;
export function isInsideHtmlMacro(
match: RegExpMatchArray,
cursor: number
): boolean {
return false;
}

View file

@ -1,8 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "commonjs", "module": "commonjs",
"target": "es2019", "target": "ESNext",
"lib": ["ES2019"], "lib": ["ESNext"],
"outDir": "out", "outDir": "out",
"rootDir": "src", "rootDir": "src",
"sourceMap": true "sourceMap": true

View file

@ -16,26 +16,9 @@
"node": "*" "node": "*"
}, },
"activationEvents": [ "activationEvents": [
"onLanguage:html1" "onLanguage:rust"
], ],
"main": "./client/out/extension", "main": "./client/out/extension",
"contributes": {
"languages": [
{
"id": "rsx",
"extensions": [
".rs"
]
}
],
"grammars": [
{
"language": "rsx",
"scopeName": "text.rsx.basic",
"path": "./syntaxes/rsx.tmLanguage.json"
}
]
},
"scripts": { "scripts": {
"vscode:prepublish": "cd client && npm install && cd .. && npm run compile", "vscode:prepublish": "cd client && npm install && cd .. && npm run compile",
"compile": "tsc -b", "compile": "tsc -b",

View file

@ -3,9 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */ * ------------------------------------------------------------------------------------------ */
import { getLanguageService } from 'vscode-html-languageservice'; import { getLanguageService } from "vscode-html-languageservice";
import { createConnection, InitializeParams, ProposedFeatures, TextDocuments, TextDocumentSyncKind } from 'vscode-languageserver'; import {
import { TextDocument } from 'vscode-languageserver-textdocument'; createConnection,
InitializeParams,
ProposedFeatures,
TextDocuments,
TextDocumentSyncKind,
} from "vscode-languageserver";
import { TextDocument } from "vscode-languageserver-textdocument";
// Create a connection for the server. The connection uses Node's IPC as a transport. // Create a connection for the server. The connection uses Node's IPC as a transport.
// Also include all preview / proposed LSP features. // Also include all preview / proposed LSP features.
@ -23,13 +29,13 @@ connection.onInitialize((_params: InitializeParams) => {
textDocumentSync: TextDocumentSyncKind.Full, textDocumentSync: TextDocumentSyncKind.Full,
// Tell the client that the server supports code completion // Tell the client that the server supports code completion
completionProvider: { completionProvider: {
resolveProvider: false resolveProvider: false,
} },
} },
}; };
}); });
connection.onInitialized(() => { }); connection.onInitialized(() => {});
connection.onCompletion(async (textDocumentPosition, token) => { connection.onCompletion(async (textDocumentPosition, token) => {
const document = documents.get(textDocumentPosition.textDocument.uri); const document = documents.get(textDocumentPosition.textDocument.uri);

View file

@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2019", "target": "ESNext",
"lib": ["ES2019"], "lib": ["ESNext"],
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"sourceMap": true, "sourceMap": true,

View file

@ -1,9 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "commonjs", "module": "commonjs",
"target": "es2019", "target": "ESNext",
"lib": [ "lib": [
"ES2019" "ESNext"
], ],
"outDir": "out", "outDir": "out",
"rootDir": "src", "rootDir": "src",