diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 916edea..5292056 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,16 +8,53 @@ on: pull_request: jobs: - test: + test-sketch: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: erlef/setup-beam@v1 with: - otp-version: "26.0.2" - gleam-version: "1.0.0" - rebar3-version: "3" + otp-version: '26.0.2' + gleam-version: '1.4.1' + rebar3-version: '3' # elixir-version: "1.15.4" - run: gleam deps download + working-directory: sketch - run: gleam test + working-directory: sketch - run: gleam format --check src test + working-directory: sketch + + test-sketch-css: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: erlef/setup-beam@v1 + with: + otp-version: '26.0.2' + gleam-version: '1.4.1' + rebar3-version: '3' + # elixir-version: "1.15.4" + - run: gleam deps download + working-directory: sketch_css + - run: gleam test + working-directory: sketch_css + - run: gleam format --check src test + working-directory: sketch_css + + test-sketch-lustr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: erlef/setup-beam@v1 + with: + otp-version: '26.0.2' + gleam-version: '1.4.1' + rebar3-version: '3' + # elixir-version: "1.15.4" + - run: gleam deps download + working-directory: sketch_lustre + - run: gleam test + working-directory: sketch_lustre + - run: gleam format --check src test + working-directory: sketch_lustre diff --git a/e2e/shared_view/src/shared_view.gleam b/e2e/shared_view/src/shared_view.gleam index 1c7fd5e..612f883 100644 --- a/e2e/shared_view/src/shared_view.gleam +++ b/e2e/shared_view/src/shared_view.gleam @@ -17,10 +17,8 @@ pub type Msg { } /// Defines the standard app, used everywhere in Lustre applications. -/// For cross-technologies applications, only persistent cache can be used, -/// because BEAM does not support ephemeral cache at the moment. pub fn app() { - let assert Ok(cache) = sketch.cache(strategy: sketch.Persistent) + let assert Ok(cache) = sketch.cache(strategy: sketch.Ephemeral) sketch_lustre.node() |> sketch_lustre.compose(view, cache) |> lustre.simple(fn(_) { 0 }, update, _) diff --git a/e2e/ssr/manifest.toml b/e2e/ssr/manifest.toml index 9f0b92c..1fbc2cc 100644 --- a/e2e/ssr/manifest.toml +++ b/e2e/ssr/manifest.toml @@ -16,15 +16,14 @@ packages = [ { name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" }, { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, - { name = "lustre", version = "4.3.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "A0010C17CE8C847A2B979CE9FE68FB94AE9D75247D1173594C10B7C2FD3C83F2" }, + { name = "lustre", version = "4.3.2", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "7F4CE2DB524A882F97C94AA3D320986B6CAC415146D5F98DE4782E1B0B59098F" }, { name = "mist", version = "1.2.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "109B4D64E68C104CC23BB3CC5441ECD479DD7444889DA01113B75C6AF0F0E17B" }, - { name = "plinth", version = "0.4.10", build_tools = ["gleam"], requirements = ["conversation", "gleam_javascript", "gleam_json", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "01561989B292CEF20C864B42E9A32FD15F3F3E46E7B0A2142727A3C556B16EF3" }, + { name = "plinth", version = "0.4.11", build_tools = ["gleam"], requirements = ["conversation", "gleam_javascript", "gleam_json", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "698B53538B02B261ABF30203F0B487B9C3BE2A1B40E858176ED0918AA1B61965" }, { name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" }, { name = "shared_view", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre", "sketch", "sketch_lustre"], source = "local", path = "../shared_view" }, - { name = "sketch", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], source = "local", path = "../../sketch" }, + { name = "sketch", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], source = "local", path = "../../sketch" }, { name = "sketch_lustre", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre", "plinth", "sketch"], source = "local", path = "../../sketch_lustre" }, { name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" }, - { name = "xxhash", version = "0.3.1", build_tools = ["mix"], requirements = [], otp_app = "xxhash", source = "hex", outer_checksum = "14FEF02F550C1B393DE52912F7EF4CE53B5749C63DB70FFDC6582474E625513C" }, ] [requirements] diff --git a/e2e/ssr/src/ssr.gleam b/e2e/ssr/src/ssr.gleam index 376002f..5b5f42e 100644 --- a/e2e/ssr/src/ssr.gleam +++ b/e2e/ssr/src/ssr.gleam @@ -8,7 +8,7 @@ import sketch import sketch/lustre as sketch_lustre pub fn main() { - let assert Ok(cache) = sketch.cache(strategy: sketch.Persistent) + let assert Ok(cache) = sketch.cache(strategy: sketch.Ephemeral) let assert Ok(_) = fn(_) { greet(cache) } |> mist.new() diff --git a/sketch/README.md b/sketch/README.md index 871fce4..665618b 100644 --- a/sketch/README.md +++ b/sketch/README.md @@ -60,8 +60,7 @@ import sketch import sketch/lustre as sketch_lustre pub fn main() { - // Initialise the cache. Two strategies can be used in browser, only one - // on server-side. + // Initialise the cache. Two strategies can be used. Ephemeral caches are designed as throw-away caches. let assert Ok(cache) = sketch.cache(strategy: sketch.Ephemeral) // Select the output of the generated stylesheet. sketch_lustre.node() diff --git a/sketch/src/sketch.gleam b/sketch/src/sketch.gleam index aaa582f..3c66ff7 100644 --- a/sketch/src/sketch.gleam +++ b/sketch/src/sketch.gleam @@ -4,6 +4,7 @@ import gleam/float import gleam/int import gleam/list +import gleam/pair import gleam/result import gleam/string import sketch/internals/cache/setup as cache @@ -33,33 +34,22 @@ pub fn class(styles: List(style.Style)) -> Class { style.class(styles) } -@target(erlang) /// Render the content in the cache in proper CSS stylesheet. pub fn render(cache: Cache) { - let assert BeamCache(cache) = cache - cache.render(cache) -} - -@target(javascript) -pub fn render(cache: Cache) { - let assert JsCache(cache) = cache - style.render(cache) + case cache { + BeamCache(cache:) -> cache.render(cache) + JsCache(cache:) -> style.render(cache) + } } -@target(javascript) /// Convert a `Class` to its proper class name, to use it anywhere in your /// application. It can have the form `class1` or `class1 class2` in case of /// classes composition. pub fn class_name(class: Class, cache: Cache) -> #(Cache, String) { - let assert JsCache(cache) = cache - let #(cache, class_name) = style.class_name(class, cache) - #(JsCache(cache), class_name) -} - -@target(erlang) -pub fn class_name(class: Class, cache: Cache) -> #(Cache, String) { - let assert BeamCache(cache) = cache - #(BeamCache(cache), cache.class_name(class, cache)) + case cache { + JsCache(c) -> style.class_name(class, c) |> pair.map_first(JsCache) + BeamCache(c) -> cache.class_name(class, c) |> pair.map_first(BeamCache) + } } /// Strategy for the Cache. Two strategies are available as of now: ephemeral @@ -67,7 +57,7 @@ pub fn class_name(class: Class, cache: Cache) -> #(Cache, String) { /// generation wil rely on hashing function. It means two class names will be /// identical if their content are identical. /// In the second case, the cache is persistent, meaning it will keep the -/// memories of the generated classes. On BEAM, only Persistent is allowed. +/// memories of the generated classes. pub type Strategy { Ephemeral Persistent @@ -76,6 +66,7 @@ pub type Strategy { @target(javascript) /// Create a cache, managing the styles. You can instanciate as much cache as /// you want, if you need to manage different stylesheets. +/// Instanciating an `Ephemeral` _always_ succeed. pub fn cache(strategy strategy: Strategy) { Ok(case strategy { Ephemeral -> JsCache(style.ephemeral()) @@ -84,8 +75,13 @@ pub fn cache(strategy strategy: Strategy) { } @target(erlang) -pub fn cache(strategy _strategy: Strategy) { - cache.persistent() |> result.map(BeamCache) +pub fn cache(strategy strategy: Strategy) { + case strategy { + Ephemeral -> Ok(BeamCache(cache.ephemeral())) + Persistent -> + cache.persistent() + |> result.map(BeamCache) + } } // Properties diff --git a/sketch/src/sketch/internals/cache/setup.gleam b/sketch/src/sketch/internals/cache/setup.gleam index 76ba02c..d9bf217 100644 --- a/sketch/src/sketch/internals/cache/setup.gleam +++ b/sketch/src/sketch/internals/cache/setup.gleam @@ -4,34 +4,53 @@ import gleam/bool import gleam/erlang/process.{type Subject} import gleam/list import gleam/otp/actor +import gleam/pair import gleam/result import sketch/internals/cache/state import sketch/internals/style /// Manages the styles. Can be instanciated with [`create_cache`](#create_cache). pub opaque type Cache { - Cache(subject: Subject(state.Request)) + PersistentCache(subject: Subject(state.Request)) + EphemeralCache(cache: style.Cache) +} + +@target(erlang) +pub fn ephemeral() { + EphemeralCache(style.ephemeral()) } @target(erlang) pub fn persistent() -> Result(Cache, Nil) { let assert Ok(subject) = actor.start(style.persistent(), state.loop) - Ok(Cache(subject)) + Ok(PersistentCache(subject)) } @target(erlang) pub fn render(cache: Cache) -> String { - let Cache(subject) = cache - process.try_call(subject, state.Render, 1000) - |> result.nil_error() - |> result.unwrap("") + case cache { + EphemeralCache(cache:) -> style.render(cache) + PersistentCache(subject:) -> { + process.try_call(subject, state.Render, 1000) + |> result.nil_error() + |> result.unwrap("") + } + } } @target(erlang) -pub fn class_name(class: style.Class, cache: Cache) -> String { - let style.Class(string_representation: _, content: c) = class - let Cache(subject) = cache - use <- bool.guard(when: list.is_empty(c), return: "") - process.try_call(subject, state.Fetch(class, _), within: 100) - |> result.unwrap("") +pub fn class_name(class: style.Class, cache: Cache) -> #(Cache, String) { + case cache { + EphemeralCache(cache:) -> { + let #(cache, class_name) = style.class_name(class, cache) + #(EphemeralCache(cache:), class_name) + } + PersistentCache(subject:) -> { + let style.Class(string_representation: _, content: c) = class + use <- bool.guard(when: list.is_empty(c), return: #(cache, "")) + process.try_call(subject, state.Fetch(class, _), within: 100) + |> result.unwrap("") + |> pair.new(cache, _) + } + } } diff --git a/sketch/src/sketch/internals/cache/state.gleam b/sketch/src/sketch/internals/cache/state.gleam index e536777..bbc4930 100644 --- a/sketch/src/sketch/internals/cache/state.gleam +++ b/sketch/src/sketch/internals/cache/state.gleam @@ -2,7 +2,6 @@ import gleam/erlang/process.{type Subject} import gleam/otp/actor -import gleam/string import sketch/internals/style pub type Request { diff --git a/sketch/src/sketch/internals/style.gleam b/sketch/src/sketch/internals/style.gleam index 237c033..980ca39 100644 --- a/sketch/src/sketch/internals/style.gleam +++ b/sketch/src/sketch/internals/style.gleam @@ -8,10 +8,9 @@ import gleam/string import sketch/internals/class import sketch/internals/string as sketch_string +@external(erlang, "erlang", "phash2") @external(javascript, "../../xxhash.ffi.mjs", "xxHash32") -fn xx_hash32(content: String) -> Int { - 0 -} +fn xx_hash32(content: String) -> Int pub type Class { Class(string_representation: String, content: List(Style))