diff --git a/examples/leptos-0.7-ssr/.gitignore b/examples/leptos-0.7-ssr/.gitignore new file mode 100644 index 00000000..5e2c0cf5 --- /dev/null +++ b/examples/leptos-0.7-ssr/.gitignore @@ -0,0 +1,225 @@ +# tmp files +*~* + +env*/ +/target +gen/ + +# Ignore Cargo.lock because this is a lib crate +Cargo.lock + +# Generated by Cargo +# will have compiled files and executables +/target/ +pkg + +# These are backup files generated by rustfmt +**/*.rs.bk + +# node e2e test tools and outputs +node_modules/ +test-results/ +../end2end/playwright-report/ +playwright/.cache/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/examples/leptos-0.7-ssr/Cargo.toml b/examples/leptos-0.7-ssr/Cargo.toml new file mode 100644 index 00000000..03c54905 --- /dev/null +++ b/examples/leptos-0.7-ssr/Cargo.toml @@ -0,0 +1,134 @@ +[package] +name = "leptos-7-ssr-example" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +hydrate = [ + "leptos/hydrate", + "dep:wasm-bindgen", + "dep:console_error_panic_hook", +] +ssr = [ + "dep:roadster", + "dep:leptos-7-example-entity", + "dep:leptos-7-example-migration", + "dep:axum", + "dep:tokio", + "dep:tokio-util", + "dep:tower", + "dep:tower-http", + "dep:leptos_axum", + "leptos/ssr", + "dep:tracing", +] + +[dependencies] +roadster = { version = "0.6", path = "../..", optional = true, default-features = false, features = ["http", "db-sql", "cli"] } +tokio = { workspace = true, optional = true } +tokio-util = { workspace = true, optional = true } +anyhow = { workspace = true } +tracing = { workspace = true, optional = true } +async-trait = { workspace = true } +clap = { workspace = true } + +# Http API +axum = { workspace = true, optional = true } + +# DB +leptos-7-example-entity = { path = "entity", optional = true } +leptos-7-example-migration = { path = "migration", optional = true } + +serde = { workspace = true, features = ["derive"] } + +# Leptos +leptos = "0.7.0-gamma3" +console_error_panic_hook = { version = "0.1", optional = true } +leptos_axum = { version = "0.7.0-gamma3", optional = true } +leptos_meta = { version = "0.7.0-gamma3" } +leptos_router = { version = "0.7.0-gamma3" } +leptos_config = { version = "0.7.0-gamma3" } +tower = { workspace = true, features = ["full"], optional = true } +tower-http = { workspace = true, features = ["full"], optional = true } +wasm-bindgen = { version = "=0.2.93", optional = true } + +# Defines a size-optimized profile for the WASM bundle in release mode +# Commented out here because profiles are ignored for packages that aren't the workspace root +#[profile.wasm-release] +#inherits = "release" +#opt-level = 'z' +#lto = true +#codegen-units = 1 +#panic = "abort" + +[package.metadata.leptos] +# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name +output-name = "leptos-7-ssr-example" + +# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. +site-root = "target/site" + +# The site-root relative folder where all compiled output (JS, WASM and CSS) is written +# Defaults to pkg +site-pkg-dir = "pkg" + +# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to //app.css +style-file = "style/main.scss" +# Assets source dir. All files found here will be copied and synchronized to site-root. +# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir. +# +# Optional. Env: LEPTOS_ASSETS_DIR. +assets-dir = "public" + +# The port to use for automatic reload monitoring +reload-port = 3001 + +# [Optional] Command to use when running end2end tests. It will run in the end2end dir. +# [Windows] for non-WSL use "npx.cmd playwright test" +# This binary name can be checked in Powershell with Get-Command npx +end2end-cmd = "npx playwright test" +end2end-dir = "end2end" + +# The browserlist query used for optimizing the CSS. +browserquery = "defaults" + +# The features to use when compiling the bin target +# +# Optional. Can be over-ridden with the command line parameter --bin-features +bin-features = ["ssr"] + +# If the --no-default-features flag should be used when compiling the bin target +# +# Optional. Defaults to false. +bin-default-features = false + +# The features to use when compiling the lib target +# +# Optional. Can be over-ridden with the command line parameter --lib-features +lib-features = ["hydrate"] + +# If the --no-default-features flag should be used when compiling the lib target +# +# Optional. Defaults to false. +lib-default-features = false + +# The profile to use for the lib target when compiling for release +# +# Optional. Defaults to "release". +#lib-profile-release = "wasm-release" + +[build-dependencies] +vergen = { workspace = true } +vergen-gitcl = { workspace = true } + +#[[bin]] +#name = "leptos_ssr_example" +#path = "./src/main.rs" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "leptos_7_ssr_example" +path = "./src/lib.rs" diff --git a/examples/leptos-0.7-ssr/README.md b/examples/leptos-0.7-ssr/README.md new file mode 100644 index 00000000..9a0a2c93 --- /dev/null +++ b/examples/leptos-0.7-ssr/README.md @@ -0,0 +1,19 @@ +# Leptos SSR Roadster example + +This is an example for how to use [Leptos](https://github.com/leptos-rs/leptos) v0.7 in SSR mode with Roadster. + +Feel free to explore the project structure, but the best place to start with your application code is in `src/app.rs`. + +## Running your project + +```bash +# Start the database and redis (for sidekiq). Note: change the credentials when deploying to prod +docker run -d -p 5432:5432 -e POSTGRES_USER=roadster -e POSTGRES_DB=example_dev -e POSTGRES_PASSWORD=roadster postgres:15.3-alpine +docker run -d -p 6379:6379 redis:7.2-alpine +# From the root Roadster directory, cd into the example dir +cd examples/leptos-ssr +# Run the app +ROADSTER__ENVIRONMENT=development cargo leptos watch -- --config-dir "$(pwd)/config" +# Alternatively, you can put the ROADSTER__ENVIRONMENT=development in a `.env` file and simply run +cargo leptos watch -- --config-dir "$(pwd)/config" +``` diff --git a/examples/leptos-0.7-ssr/build.rs b/examples/leptos-0.7-ssr/build.rs new file mode 100644 index 00000000..04121895 --- /dev/null +++ b/examples/leptos-0.7-ssr/build.rs @@ -0,0 +1,9 @@ +use vergen::Emitter; +use vergen_gitcl::GitclBuilder; + +fn main() -> Result<(), Box> { + let gitcl = GitclBuilder::default().sha(true).build()?; + Emitter::default().add_instructions(&gitcl)?.emit()?; + + Ok(()) +} diff --git a/examples/leptos-0.7-ssr/config/default.toml b/examples/leptos-0.7-ssr/config/default.toml new file mode 100644 index 00000000..81dd6235 --- /dev/null +++ b/examples/leptos-0.7-ssr/config/default.toml @@ -0,0 +1,23 @@ +[app] +name = "Leptos 0.7 Example" + +[tracing] +level = "debug" + +[database] +auto-migrate = true +connect-timeout = 5000 +acquire-timeout = 5000 +idle-timeout = 60 +min-connections = 0 +max-connections = 10 + +[service.http] +# Listen on any ipv4 or ipv6 addr, useful to allow connections from LAN for local dev +# host = "[::]" +# Listen only on localhost +host = "127.0.0.1" +port = 3000 + +[service.http.middleware.request-response-logging] +content-types-res = ["application/json"] diff --git a/examples/leptos-0.7-ssr/config/development.toml b/examples/leptos-0.7-ssr/config/development.toml new file mode 100644 index 00000000..45b39ab0 --- /dev/null +++ b/examples/leptos-0.7-ssr/config/development.toml @@ -0,0 +1,8 @@ +[tracing] +format = "pretty" + +[auth.jwt] +secret = "secret-dev" + +[database] +uri = "postgres://roadster:roadster@localhost:5432/example_dev" diff --git a/examples/leptos-0.7-ssr/config/test.toml b/examples/leptos-0.7-ssr/config/test.toml new file mode 100644 index 00000000..97faa597 --- /dev/null +++ b/examples/leptos-0.7-ssr/config/test.toml @@ -0,0 +1,8 @@ +[tracing] +format = "pretty" + +[auth.jwt] +secret = "secret-test" + +[database] +uri = "postgres://roadster:roadster@localhost:5433/example_test" diff --git a/examples/leptos-0.7-ssr/end2end/package-lock.json b/examples/leptos-0.7-ssr/end2end/package-lock.json new file mode 100644 index 00000000..260e8eb1 --- /dev/null +++ b/examples/leptos-0.7-ssr/end2end/package-lock.json @@ -0,0 +1,167 @@ +{ + "name": "end2end", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "end2end", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.44.1", + "@types/node": "^20.12.12", + "typescript": "^5.4.5" + } + }, + "node_modules/@playwright/test": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", + "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", + "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + } + }, + "dependencies": { + "@playwright/test": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", + "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", + "dev": true, + "requires": { + "playwright": "1.44.1" + } + }, + "@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "playwright": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", + "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.44.1" + } + }, + "playwright-core": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "dev": true + }, + "typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/examples/leptos-0.7-ssr/end2end/package.json b/examples/leptos-0.7-ssr/end2end/package.json new file mode 100644 index 00000000..a80ac597 --- /dev/null +++ b/examples/leptos-0.7-ssr/end2end/package.json @@ -0,0 +1,15 @@ +{ + "name": "end2end", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.44.1", + "@types/node": "^20.12.12", + "typescript": "^5.4.5" + } +} diff --git a/examples/leptos-0.7-ssr/end2end/playwright.config.ts b/examples/leptos-0.7-ssr/end2end/playwright.config.ts new file mode 100644 index 00000000..aee2d46f --- /dev/null +++ b/examples/leptos-0.7-ssr/end2end/playwright.config.ts @@ -0,0 +1,105 @@ +import type { PlaywrightTestConfig } from "@playwright/test"; +import { devices, defineConfig } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + }, + }, + + { + name: "firefox", + use: { + ...devices["Desktop Firefox"], + }, + }, + + { + name: "webkit", + use: { + ...devices["Desktop Safari"], + }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { + // ...devices['Pixel 5'], + // }, + // }, + // { + // name: 'Mobile Safari', + // use: { + // ...devices['iPhone 12'], + // }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}); diff --git a/examples/leptos-0.7-ssr/end2end/tests/example.spec.ts b/examples/leptos-0.7-ssr/end2end/tests/example.spec.ts new file mode 100644 index 00000000..0139fc30 --- /dev/null +++ b/examples/leptos-0.7-ssr/end2end/tests/example.spec.ts @@ -0,0 +1,9 @@ +import { test, expect } from "@playwright/test"; + +test("homepage has title and heading text", async ({ page }) => { + await page.goto("http://localhost:3000/"); + + await expect(page).toHaveTitle("Welcome to Leptos"); + + await expect(page.locator("h1")).toHaveText("Welcome to Leptos!"); +}); diff --git a/examples/leptos-0.7-ssr/end2end/tsconfig.json b/examples/leptos-0.7-ssr/end2end/tsconfig.json new file mode 100644 index 00000000..e075f973 --- /dev/null +++ b/examples/leptos-0.7-ssr/end2end/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/examples/leptos-0.7-ssr/entity/Cargo.toml b/examples/leptos-0.7-ssr/entity/Cargo.toml new file mode 100644 index 00000000..5119063d --- /dev/null +++ b/examples/leptos-0.7-ssr/entity/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "leptos-7-example-entity" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "entity" +path = "src/lib.rs" + +[dependencies] +sea-orm = { workspace = true } diff --git a/examples/leptos-0.7-ssr/entity/src/lib.rs b/examples/leptos-0.7-ssr/entity/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/leptos-0.7-ssr/entity/src/lib.rs @@ -0,0 +1 @@ + diff --git a/examples/leptos-0.7-ssr/migration/Cargo.toml b/examples/leptos-0.7-ssr/migration/Cargo.toml new file mode 100644 index 00000000..0f3eb906 --- /dev/null +++ b/examples/leptos-0.7-ssr/migration/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "leptos-7-example-migration" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "migration" +path = "src/lib.rs" + +[dependencies] +tokio = { workspace = true } + +[dependencies.sea-orm-migration] +workspace = true +features = [ + # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. + # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. + # e.g. + "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature + "sqlx-postgres", # `DATABASE_DRIVER` feature +] diff --git a/examples/leptos-0.7-ssr/migration/README.md b/examples/leptos-0.7-ssr/migration/README.md new file mode 100644 index 00000000..3b438d89 --- /dev/null +++ b/examples/leptos-0.7-ssr/migration/README.md @@ -0,0 +1,41 @@ +# Running Migrator CLI + +- Generate a new migration file + ```sh + cargo run -- generate MIGRATION_NAME + ``` +- Apply all pending migrations + ```sh + cargo run + ``` + ```sh + cargo run -- up + ``` +- Apply first 10 pending migrations + ```sh + cargo run -- up -n 10 + ``` +- Rollback last applied migrations + ```sh + cargo run -- down + ``` +- Rollback last 10 applied migrations + ```sh + cargo run -- down -n 10 + ``` +- Drop all tables from the database, then reapply all migrations + ```sh + cargo run -- fresh + ``` +- Rollback all applied migrations, then reapply all migrations + ```sh + cargo run -- refresh + ``` +- Rollback all applied migrations + ```sh + cargo run -- reset + ``` +- Check the status of all migrations + ```sh + cargo run -- status + ``` diff --git a/examples/leptos-0.7-ssr/migration/src/lib.rs b/examples/leptos-0.7-ssr/migration/src/lib.rs new file mode 100644 index 00000000..2c605afb --- /dev/null +++ b/examples/leptos-0.7-ssr/migration/src/lib.rs @@ -0,0 +1,12 @@ +pub use sea_orm_migration::prelude::*; + +mod m20220101_000001_create_table; + +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![Box::new(m20220101_000001_create_table::Migration)] + } +} diff --git a/examples/leptos-0.7-ssr/migration/src/m20220101_000001_create_table.rs b/examples/leptos-0.7-ssr/migration/src/m20220101_000001_create_table.rs new file mode 100644 index 00000000..02dab03a --- /dev/null +++ b/examples/leptos-0.7-ssr/migration/src/m20220101_000001_create_table.rs @@ -0,0 +1,15 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +} diff --git a/examples/leptos-0.7-ssr/migration/src/main.rs b/examples/leptos-0.7-ssr/migration/src/main.rs new file mode 100644 index 00000000..f054deaf --- /dev/null +++ b/examples/leptos-0.7-ssr/migration/src/main.rs @@ -0,0 +1,6 @@ +use sea_orm_migration::prelude::*; + +#[tokio::main] +async fn main() { + cli::run_cli(migration::Migrator).await; +} diff --git a/examples/leptos-0.7-ssr/public/favicon.ico b/examples/leptos-0.7-ssr/public/favicon.ico new file mode 100644 index 00000000..2ba8527c Binary files /dev/null and b/examples/leptos-0.7-ssr/public/favicon.ico differ diff --git a/examples/leptos-0.7-ssr/src/app.rs b/examples/leptos-0.7-ssr/src/app.rs new file mode 100644 index 00000000..349f1f89 --- /dev/null +++ b/examples/leptos-0.7-ssr/src/app.rs @@ -0,0 +1,54 @@ +use leptos::prelude::*; +use leptos_meta::{provide_meta_context, MetaTags}; + +/// The static HTML shell of the app. +pub fn shell(options: LeptosOptions) -> impl IntoView { + view! { + + + + + + + + "Leptos 0.7 Example" + + + + + + + + + + } +} + +#[component] +pub fn App() -> impl IntoView { + // Provides context that manages stylesheets, titles, meta tags, etc. + provide_meta_context(); + + // Use the `view!` macro to build you UI using JSX-like syntax. + view! { + + } + + // Alternatively, you can use the builder syntax. + // ( + // HomePage(), + // ) +} + +/// Renders the home page of your application. +#[component] +fn HomePage() -> impl IntoView { + // Creates a reactive value to update the button + let (count, set_count) = signal(0); + let on_click = move |_| set_count.update(|count| *count += 1); + + view! { +

"Welcome to Leptos!"

+ + } +} diff --git a/examples/leptos-0.7-ssr/src/app_state.rs b/examples/leptos-0.7-ssr/src/app_state.rs new file mode 100644 index 00000000..6ccc4c32 --- /dev/null +++ b/examples/leptos-0.7-ssr/src/app_state.rs @@ -0,0 +1,10 @@ +use axum::extract::FromRef; +use leptos::prelude::*; +use roadster::app::context::AppContext; + +#[derive(Clone, FromRef)] +pub struct AppState { + pub app_context: AppContext, + pub leptos_config: ConfFile, + pub leptos_options: LeptosOptions, +} diff --git a/examples/leptos-0.7-ssr/src/cli/mod.rs b/examples/leptos-0.7-ssr/src/cli/mod.rs new file mode 100644 index 00000000..62be39f2 --- /dev/null +++ b/examples/leptos-0.7-ssr/src/cli/mod.rs @@ -0,0 +1,42 @@ +use crate::app_state::AppState; +use crate::server::Server; +use async_trait::async_trait; +use clap::{Parser, Subcommand}; +use roadster::api::cli::RunCommand; +use roadster::error::RoadsterResult; + +/// Leptos SSR Example: Commands specific to managing the `leptos-7-ssr-example` app are provided in +/// the CLI as well. Subcommands not listed under the `roadster` subcommand are specific to +/// `leptos-7-ssr-example`. +#[derive(Debug, Parser)] +#[command(version, about)] +#[non_exhaustive] +pub struct AppCli { + #[command(subcommand)] + pub command: Option, +} + +#[async_trait] +impl RunCommand for AppCli { + #[allow(clippy::disallowed_types)] + async fn run(&self, app: &Server, cli: &AppCli, state: &AppState) -> RoadsterResult { + if let Some(command) = self.command.as_ref() { + command.run(app, cli, state).await + } else { + Ok(false) + } + } +} + +/// App specific subcommands +/// +/// Note: This doc comment doesn't appear in the CLI `--help` message. +#[derive(Debug, Subcommand)] +pub enum AppCommand {} + +#[async_trait] +impl RunCommand for AppCommand { + async fn run(&self, _app: &Server, _cli: &AppCli, _state: &AppState) -> RoadsterResult { + Ok(false) + } +} diff --git a/examples/leptos-0.7-ssr/src/lib.rs b/examples/leptos-0.7-ssr/src/lib.rs new file mode 100644 index 00000000..50d1f4dc --- /dev/null +++ b/examples/leptos-0.7-ssr/src/lib.rs @@ -0,0 +1,14 @@ +pub mod app; +#[cfg(feature = "ssr")] +pub mod app_state; +#[cfg(feature = "ssr")] +pub mod cli; +#[cfg(feature = "ssr")] +pub mod server; + +#[cfg(feature = "hydrate")] +#[wasm_bindgen::prelude::wasm_bindgen] +pub fn hydrate() { + console_error_panic_hook::set_once(); + leptos::mount::hydrate_body(app::App); +} diff --git a/examples/leptos-0.7-ssr/src/main.rs b/examples/leptos-0.7-ssr/src/main.rs new file mode 100644 index 00000000..303d797c --- /dev/null +++ b/examples/leptos-0.7-ssr/src/main.rs @@ -0,0 +1,20 @@ +#[cfg(feature = "ssr")] +use roadster::error::RoadsterResult; + +#[cfg(feature = "ssr")] +#[tokio::main] +async fn main() -> RoadsterResult<()> { + use leptos_7_ssr_example::server::Server; + use roadster::app; + + app::run(Server).await?; + + Ok(()) +} + +#[cfg(not(feature = "ssr"))] +pub fn main() { + // no client-side main function + // unless we want this to work with e.g., Trunk for a purely client-side app + // see lib.rs for hydration function instead +} diff --git a/examples/leptos-0.7-ssr/src/server/mod.rs b/examples/leptos-0.7-ssr/src/server/mod.rs new file mode 100644 index 00000000..30953a24 --- /dev/null +++ b/examples/leptos-0.7-ssr/src/server/mod.rs @@ -0,0 +1,93 @@ +use crate::app::{shell, App}; +use crate::app_state::AppState; +use anyhow::anyhow; +use async_trait::async_trait; +use axum::Router; +use leptos::prelude::*; +use leptos_axum::{generate_route_list, LeptosRoutes}; +use migration::Migrator; +use roadster::app::context::AppContext; +use roadster::app::metadata::AppMetadata; +use roadster::app::App as RoadsterApp; +use roadster::config::environment::Environment; +use roadster::config::AppConfig; +use roadster::error::RoadsterResult; +use roadster::service::http::service::HttpService; +use roadster::service::registry::ServiceRegistry; + +const BASE: &str = "/api"; + +#[derive(Default)] +pub struct Server; + +#[async_trait] +impl RoadsterApp for Server { + type Cli = crate::cli::AppCli; + type M = Migrator; + + fn metadata(&self, _config: &AppConfig) -> RoadsterResult { + Ok(AppMetadata::builder() + .version(env!("VERGEN_GIT_SHA").to_string()) + .build()) + } + + async fn provide_state(&self, app_context: AppContext) -> RoadsterResult { + let leptos_config = leptos_config(&app_context)?; + let leptos_options = leptos_config.leptos_options.clone(); + + let state = AppState { + app_context, + leptos_config, + leptos_options, + }; + Ok(state) + } + + async fn services( + &self, + registry: &mut ServiceRegistry, + state: &AppState, + ) -> RoadsterResult<()> { + let state = state.clone(); + assert_eq!( + state.leptos_options.site_addr, + state + .app_context + .config() + .service + .http + .custom + .address + .socket_addr()?, + "Leptos address does not match the Roadster http address." + ); + + registry + .register_builder( + HttpService::builder(Some(BASE), &state).router(leptos_routes(&state)), + ) + .await?; + + Ok(()) + } +} + +fn leptos_config(context: &AppContext) -> anyhow::Result { + let mut config = + get_configuration(Some("./examples/leptos-0.7-ssr/Cargo.toml")).map_err(|e| anyhow!(e))?; + config.leptos_options.site_addr = context.config().service.http.custom.address.socket_addr()?; + config.leptos_options.env = match context.config().environment { + Environment::Production => Env::PROD, + _ => Env::DEV, + }; + Ok(config) +} + +pub fn leptos_routes(state: &AppState) -> Router { + let state = state.clone(); + Router::::new() + .leptos_routes(&state.clone(), generate_route_list(App), move || { + shell(state.leptos_options.clone()) + }) + .fallback(leptos_axum::file_and_error_handler::(shell)) +} diff --git a/examples/leptos-0.7-ssr/style/main.scss b/examples/leptos-0.7-ssr/style/main.scss new file mode 100644 index 00000000..e4538e15 --- /dev/null +++ b/examples/leptos-0.7-ssr/style/main.scss @@ -0,0 +1,4 @@ +body { + font-family: sans-serif; + text-align: center; +} \ No newline at end of file diff --git a/examples/leptos-ssr/Cargo.toml b/examples/leptos-ssr/Cargo.toml index d631b39c..a4a40dc9 100644 --- a/examples/leptos-ssr/Cargo.toml +++ b/examples/leptos-ssr/Cargo.toml @@ -62,10 +62,6 @@ wasm-bindgen = "=0.2.93" #codegen-units = 1 #panic = "abort" -# Todo: PR on Leptos to allow providing these settings differently, either -# 1. In a builder so we can use our own config file approach -# 2. Implement Roadster's config file approach for Leptos directly -# Todo: Can we set Leptos's environment variables based on our config files instead? [package.metadata.leptos] # The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name output-name = "leptos-ssr-example" diff --git a/examples/leptos-ssr/config/default.toml b/examples/leptos-ssr/config/default.toml index dff06b88..3e88a25a 100644 --- a/examples/leptos-ssr/config/default.toml +++ b/examples/leptos-ssr/config/default.toml @@ -18,3 +18,6 @@ max-connections = 10 # Listen only on localhost host = "127.0.0.1" port = 3000 + +[service.http.middleware.request-response-logging] +content-types-res = ["application/json"] diff --git a/examples/leptos-ssr/src/app.rs b/examples/leptos-ssr/src/app.rs index efa13083..b2af2394 100644 --- a/examples/leptos-ssr/src/app.rs +++ b/examples/leptos-ssr/src/app.rs @@ -1,61 +1,32 @@ -use leptos::html::main; use leptos::*; use leptos_meta::*; -use leptos_router::*; #[component] pub fn App() -> impl IntoView { // Provides context that manages stylesheets, titles, meta tags, etc. provide_meta_context(); - // Builder syntax -- provides better autocomplete support compared to the `view!` macro - ( - StylesheetProps::builder() - .id("leptos") - .href("/pkg/leptos-ssr-example.css") - .build(), - TitleProps::builder().text("Welcome to Leptos").build(), - RouterProps::builder() - .children(ToChildren::to_children(|| { - main() - .child(|| { - RoutesProps::builder() - .children(ToChildren::to_children(|| { - RouteProps::builder() - .path("") - .view(HomePage) - .build() - .into_view() - .into() - })) - .build() - .into_view() - }) - .into_view() - .into() - })) - .build(), - ) + // Use the `view!` macro to build you UI using JSX-like syntax. + view! { + // injects a stylesheet into the document + // id=leptos means cargo-leptos will hot-reload this stylesheet + + + // sets the document title + + + <HomePage/> + } - // Alternatively, the `view!` macro can be used instead. This may be preferred as it's similar - // to JSX syntax. However, auto-complete support is not as good. - // view! { - // // injects a stylesheet into the document <head> - // // id=leptos means cargo-leptos will hot-reload this stylesheet - // <Stylesheet id="leptos" href="/pkg/leptos-ssr-example.css"/> - // - // // sets the document title - // <Title text="Welcome to Leptos"/> - // - // // content for this welcome page - // <Router> - // <main> - // <Routes> - // <Route path="" view=HomePage/> - // </Routes> - // </main> - // </Router> - // } + // Alternatively, you can use the builder syntax. + // ( + // StylesheetProps::builder() + // .id("leptos") + // .href("/pkg/leptos-ssr-example.css") + // .build(), + // TitleProps::builder().text("Welcome to Leptos").build(), + // HomePage(), + // ) } /// Renders the home page of your application.