diff --git a/tools/changelog/src/cli.rs b/tools/changelog/src/cli.rs
index 4d1e434d3f7..d15ba731c6a 100644
--- a/tools/changelog/src/cli.rs
+++ b/tools/changelog/src/cli.rs
@@ -81,25 +81,38 @@ impl Cli {
let log_lines = create_log_lines(from_ref, to, package_labels, token)?;
// categorize logs
- let (fixes, features): (Vec<_>, Vec<_>) = log_lines
+ let (breaking_changes, filtered_log_lines): (Vec<_>, Vec<_>) = log_lines
.into_iter()
- .partition(|log_line| log_line.message.to_lowercase().contains("fix"));
+ .partition(|log_line| log_line.is_breaking_change);
+
+ let (fixes, features): (Vec<_>, Vec<_>) =
+ filtered_log_lines
+ .into_iter()
+ .partition(|filtered_log_line| {
+ filtered_log_line.message.to_lowercase().contains("fix")
+ });
// create displayable log lines
let fixes_logs = write_log_lines(fixes)?;
let features_logs = write_log_lines(features)?;
+ let breaking_changes_logs = write_log_lines(breaking_changes)?;
if !skip_file_write {
// create version changelog
- let version_changelog =
- write_changelog_file(&fixes_logs, &features_logs, package, next_version)?;
+ let version_changelog = write_changelog_file(
+ &fixes_logs,
+ &features_logs,
+ &breaking_changes_logs,
+ package,
+ next_version,
+ )?;
// write changelog
write_changelog(&changelog_path, &version_changelog)?;
}
// stdout changelog meant for tag description
- stdout_tag_description_changelog(&fixes_logs, &features_logs)?;
+ stdout_tag_description_changelog(&fixes_logs, &features_logs, &breaking_changes_logs)?;
Ok(())
}
diff --git a/tools/changelog/src/create_log_line.rs b/tools/changelog/src/create_log_line.rs
index 29afafaf54c..f0abb64e0c9 100644
--- a/tools/changelog/src/create_log_line.rs
+++ b/tools/changelog/src/create_log_line.rs
@@ -89,11 +89,15 @@ pub fn create_log_line(
}
return Ok(None);
}
+ let is_breaking_change = issue_labels
+ .iter()
+ .any(|label| label.to_lowercase().contains("breaking change"));
let log_line = LogLine {
message,
user: author_name.to_string(),
issue_id,
+ is_breaking_change,
};
println!("{log_line:?}");
diff --git a/tools/changelog/src/log_line.rs b/tools/changelog/src/log_line.rs
index e8d5596fd7c..b5a4d4af8ba 100644
--- a/tools/changelog/src/log_line.rs
+++ b/tools/changelog/src/log_line.rs
@@ -3,4 +3,5 @@ pub struct LogLine {
pub message: String,
pub user: String,
pub issue_id: String,
+ pub is_breaking_change: bool,
}
diff --git a/tools/changelog/src/stdout_tag_description_changelog.rs b/tools/changelog/src/stdout_tag_description_changelog.rs
index adbb7ab0739..13b6821dec6 100644
--- a/tools/changelog/src/stdout_tag_description_changelog.rs
+++ b/tools/changelog/src/stdout_tag_description_changelog.rs
@@ -2,13 +2,17 @@ use std::io::{stdout, Write};
use anyhow::Result;
-pub fn stdout_tag_description_changelog(fixes_logs: &[u8], features_logs: &[u8]) -> Result<()> {
+pub fn stdout_tag_description_changelog(
+ fixes_logs: &[u8],
+ features_logs: &[u8],
+ breaking_changes_logs: &[u8],
+) -> Result<()> {
let mut tag_changelog = Vec::new();
writeln!(tag_changelog, "# Changelog")?;
writeln!(tag_changelog)?;
- if fixes_logs.is_empty() && features_logs.is_empty() {
+ if fixes_logs.is_empty() && features_logs.is_empty() && breaking_changes_logs.is_empty() {
writeln!(tag_changelog, "No changes")?;
writeln!(tag_changelog)?;
}
@@ -26,6 +30,12 @@ pub fn stdout_tag_description_changelog(fixes_logs: &[u8], features_logs: &[u8])
tag_changelog.extend(features_logs);
}
+ if !breaking_changes_logs.is_empty() {
+ writeln!(tag_changelog, "## 🚨 Breaking changes")?;
+ writeln!(tag_changelog)?;
+ tag_changelog.extend(breaking_changes_logs);
+ }
+
stdout().write_all(&tag_changelog)?;
Ok(())
diff --git a/tools/changelog/src/write_changelog_file.rs b/tools/changelog/src/write_changelog_file.rs
index 4eb95d99943..976d5a65cfb 100644
--- a/tools/changelog/src/write_changelog_file.rs
+++ b/tools/changelog/src/write_changelog_file.rs
@@ -9,7 +9,7 @@ pub fn write_changelog(changelog_path: &str, version_changelog: &[u8]) -> Result
.context(format!("could not open {changelog_path} for reading"))?;
let old_changelog_reader = BufReader::new(old_changelog);
- let changelog_path_new = &format!("../{changelog_path}.new");
+ let changelog_path_new = &format!("{changelog_path}.new");
let mut new_changelog = fs::OpenOptions::new()
.write(true)
diff --git a/tools/changelog/src/write_log_lines.rs b/tools/changelog/src/write_log_lines.rs
index 152dcf0735d..29d6387902d 100644
--- a/tools/changelog/src/write_log_lines.rs
+++ b/tools/changelog/src/write_log_lines.rs
@@ -10,6 +10,7 @@ pub fn write_log_lines(log_lines: Vec
) -> Result> {
message,
user,
issue_id,
+ ..
} in log_lines
{
writeln!(
diff --git a/tools/changelog/src/write_version_changelog.rs b/tools/changelog/src/write_version_changelog.rs
index a5285d75807..806e6f7c384 100644
--- a/tools/changelog/src/write_version_changelog.rs
+++ b/tools/changelog/src/write_version_changelog.rs
@@ -8,6 +8,7 @@ use crate::yew_package::YewPackage;
pub fn write_changelog_file(
fixes_logs: &[u8],
features_logs: &[u8],
+ breaking_changes_logs: &[u8],
package: YewPackage,
next_version: Version,
) -> Result> {
@@ -25,7 +26,7 @@ pub fn write_changelog_file(
)?;
writeln!(version_only_changelog)?;
- if fixes_logs.is_empty() && features_logs.is_empty() {
+ if fixes_logs.is_empty() && features_logs.is_empty() && breaking_changes_logs.is_empty() {
writeln!(version_only_changelog, "No changes")?;
writeln!(version_only_changelog)?;
}
@@ -44,5 +45,12 @@ pub fn write_changelog_file(
writeln!(version_only_changelog)?;
}
+ if !breaking_changes_logs.is_empty() {
+ writeln!(version_only_changelog, "### 🚨 Breaking changes")?;
+ writeln!(version_only_changelog)?;
+ version_only_changelog.extend(breaking_changes_logs);
+ writeln!(version_only_changelog)?;
+ }
+
Ok(version_only_changelog)
}
diff --git a/tools/changelog/tests/generate_yew_changelog_file.rs b/tools/changelog/tests/generate_yew_changelog_file.rs
index c4782cd1901..56a80cb4f0b 100644
--- a/tools/changelog/tests/generate_yew_changelog_file.rs
+++ b/tools/changelog/tests/generate_yew_changelog_file.rs
@@ -17,19 +17,12 @@ impl Drop for FileDeleteOnDrop {
}
}
-#[test]
-fn generate_yew_changelog_file() -> Result<()> {
- // Setup
- let file_delete_on_drop = FileDeleteOnDrop;
-
- fs::copy("tests/test_base.md", "tests/test_changelog.md")?;
-
- // Run
+fn _generate_yew_changelog_file(from: &str, to: &str) -> Result<()> {
let cli_args = Cli {
package: YewPackage::from_str("yew").unwrap(),
new_version_level: NewVersionLevel::Minor,
- from: Some("abeb8bc3f1ffabc8a58bd9ba4430cd091a06335a".to_string()),
- to: "d8ec50150ed27e2835bb1def26d2371a8c2ab750".to_string(),
+ from: Some(from.to_string()),
+ to: to.to_string(),
changelog_path: "tests/test_changelog.md".to_string(),
skip_file_write: false,
skip_get_bump_version: true,
@@ -38,6 +31,27 @@ fn generate_yew_changelog_file() -> Result<()> {
cli_args.run().unwrap();
+ Ok(())
+}
+
+#[test]
+fn generate_yew_changelog_file() -> Result<()> {
+ // Setup
+ let file_delete_on_drop = FileDeleteOnDrop;
+
+ fs::copy("tests/test_base.md", "tests/test_changelog.md")?;
+
+ // Run
+ _generate_yew_changelog_file(
+ "abeb8bc3f1ffabc8a58bd9ba4430cd091a06335a",
+ "d8ec50150ed27e2835bb1def26d2371a8c2ab750",
+ )?;
+
+ _generate_yew_changelog_file(
+ "8086a73a217a099a46138f4363411827b18d1cb0",
+ "934aedbc8815fd77fc6630b644cfea4f9a071236",
+ )?;
+
// Check
let expected = File::open("tests/test_expected.md")?;
let expected_reader_lines = BufReader::new(expected).lines();
@@ -48,13 +62,13 @@ fn generate_yew_changelog_file() -> Result<()> {
let lines = expected_reader_lines.zip(after_reader_lines);
for (i, (expected_line, after_line)) in lines.enumerate() {
- if i == 2 {
- // third line has dynamic things that may break the tests
- let expected_third_line = expected_line?.replace(
+ if i == 2 || i == 13 {
+ // these lines have dynamic things that may break the tests
+ let expected_line_updated = expected_line?.replace(
"date_goes_here",
Utc::now().format("%Y-%m-%d").to_string().as_str(),
);
- assert_eq!(expected_third_line, after_line?);
+ assert_eq!(expected_line_updated, after_line?);
} else {
assert_eq!(expected_line?, after_line?);
}
diff --git a/tools/changelog/tests/test_expected.md b/tools/changelog/tests/test_expected.md
index 09d8f8f92e7..5e186956986 100644
--- a/tools/changelog/tests/test_expected.md
+++ b/tools/changelog/tests/test_expected.md
@@ -2,9 +2,20 @@
## ✨ yew **0.0.0** *(date_goes_here)* Changelog
+### ⚡️ Features
+
+- Incremental performance improvements to element creation. [[@Greg Johnston](https://github.com/Greg Johnston), [#3169](https://github.com/yewstack/yew/pull/3169)]
+
+### 🚨 Breaking changes
+
+- Enable PartialEq for all virtual dom types. [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#3206](https://github.com/yewstack/yew/pull/3206)]
+- Pass hook dependencies as the first function argument. [[@Arniu Tseng](https://github.com/Arniu Tseng), [#2861](https://github.com/yewstack/yew/pull/2861)]
+
+## ✨ yew **0.0.0** *(date_goes_here)* Changelog
+
### 🛠 Fixes
-- Fix defaulted type parameter.. [[@futursolo](https://github.com/futursolo), [#2284](https://github.com/yewstack/yew/pull/2284)]
+- Fix defaulted type parameter.. [[@Kaede Hoshikawa](https://github.com/Kaede Hoshikawa), [#2284](https://github.com/yewstack/yew/pull/2284)]
### ⚡️ Features
diff --git a/website/docs/advanced-topics/server-side-rendering.md b/website/docs/advanced-topics/server-side-rendering.md
index 8fef136efda..6d3789ff33c 100644
--- a/website/docs/advanced-topics/server-side-rendering.md
+++ b/website/docs/advanced-topics/server-side-rendering.md
@@ -147,6 +147,15 @@ one implementation, you may want to use a `PhantomComponent` to fill the
position of the extra component.
:::
+:::warning
+
+The hydration can only succeed if the real DOM matches the expected DOM
+after initial render of the SSR output (static HTML) by browser. If your HTML is
+not spec-compliant, the hydration _may_ fail. Browsers may change the DOM structure
+of the incorrect HTML, causing the actual DOM to be different from the expected DOM.
+For example, [if you have a `` without a ``, the browser may add a `` to the DOM](https://github.com/yewstack/yew/issues/2684)
+:::
+
## Component Lifecycle during hydration
During Hydration, components schedule 2 consecutive renders after it is
diff --git a/website/docs/concepts/suspense.mdx b/website/docs/concepts/suspense.mdx
index 74d6dff1ddd..6c697d82f00 100644
--- a/website/docs/concepts/suspense.mdx
+++ b/website/docs/concepts/suspense.mdx
@@ -136,45 +136,11 @@ fn app() -> Html {
### Use Suspense in Struct Components
It's not possible to suspend a struct component directly. However, you
-can use a function component as a [HOC](../advanced-topics/struct-components/hoc) to
-achieve suspense-based data fetching.
+can use a function component as a [Higher Order Component](../advanced-topics/struct-components/hoc)
+to achieve suspense-based data fetching.
-```rust ,ignore
-use yew::prelude::*;
-
-#[function_component(WithUser)]
-fn with_user() -> HtmlResult
-where T: BaseComponent
-{
- let user = use_user()?;
-
- Ok(html! {})
-}
-
-#[derive(Debug, PartialEq, Properties)]
-pub struct UserContentProps {
- pub user: User,
-}
-
-pub struct BaseUserContent;
-
-impl Component for BaseUserContent {
- type Properties = UserContentProps;
- type Message = ();
-
- fn create(ctx: &Context) -> Self {
- Self
- }
-
- fn view(&self, ctx: &Context) -> Html {
- let name = ctx.props().user.name;
-
- html! {{"Hello, "}{name}{"!"}
}
- }
-}
-
-pub type UserContent = WithUser;
-```
+The [suspense example in the Yew repository](https://github.com/yewstack/yew/tree/master/examples/suspense/src/struct_consumer.rs)
+demonstrates how to use.
## Relevant examples
diff --git a/website/docs/migration-guides/yew/from-0_20_0-to-next.mdx b/website/docs/migration-guides/yew/from-0_20_0-to-next.mdx
index e1f78cce413..9fbc98ce013 100644
--- a/website/docs/migration-guides/yew/from-0_20_0-to-next.mdx
+++ b/website/docs/migration-guides/yew/from-0_20_0-to-next.mdx
@@ -25,11 +25,14 @@ sg --pattern 'use_callback($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_callback(
sg --pattern 'use_memo($CALLBACK,$$$DEPENDENCIES)' --rewrite 'use_memo($$$DEPENDENCIES, $CALLBACK)' -l rs -i
sg --pattern 'use_memo($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_memo($DEPENDENCIES,$$$CALLBACK)' -l rs -i
-sg --pattern 'use_transitive_state($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_transitive_state($DEPENDENCIES,$$$CALLBACK)' -l rs -i
-sg --pattern 'use_transitive_state($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_transitive_state($DEPENDENCIES,$$$CALLBACK)' -l rs -i
+sg --pattern 'use_future_with_deps($CALLBACK,$$$DEPENDENCIES)' --rewrite 'use_effect_with($$$DEPENDENCIES, $CALLBACK)' -l rs -i
+sg --pattern 'use_future_with($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_effect_with($DEPENDENCIES,$$$CALLBACK)' -l rs -i
-sg --pattern 'use_prepared_state($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_prepared_state($DEPENDENCIES,$$$CALLBACK)' -l rs -i
-sg --pattern 'use_prepared_state($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_prepared_state($DEPENDENCIES,$$$CALLBACK)' -l rs -i
+sg --pattern 'use_transitive_state!($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_transitive_state!($DEPENDENCIES,$$$CALLBACK)' -l rs -i
+sg --pattern 'use_transitive_state!($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_transitive_state!($DEPENDENCIES,$$$CALLBACK)' -l rs -i
+
+sg --pattern 'use_prepared_state!($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_prepared_state!($DEPENDENCIES,$$$CALLBACK)' -l rs -i
+sg --pattern 'use_prepared_state!($DEPENDENCIES,,$$$CALLBACK)' --rewrite 'use_prepared_state!($DEPENDENCIES,$$$CALLBACK)' -l rs -i
```
### Reasoning