mirror of
https://github.com/penpot/penpot.git
synced 2026-09-30 07:46:16 +00:00
✨ Make export follow the active renderer only (#11910)
Drop the separate :wasm-export flag and wasm-export/v1 feature. Single export, clipboard PNG, plugins, and batch :is-wasm now key off render-wasm/v1 alone. The exporter trusts :is-wasm for headless WASM and always keeps a worker pool ready.
This commit is contained in:
parent
63baf86152
commit
ec5a1edbed
@ -57,7 +57,6 @@
|
|||||||
"text-editor/v2"
|
"text-editor/v2"
|
||||||
"text-editor-wasm/v1"
|
"text-editor-wasm/v1"
|
||||||
"render-wasm/v1"
|
"render-wasm/v1"
|
||||||
"wasm-export/v1"
|
|
||||||
"variants/v1"})
|
"variants/v1"})
|
||||||
|
|
||||||
;; A set of features enabled by default
|
;; A set of features enabled by default
|
||||||
@ -83,8 +82,7 @@
|
|||||||
"text-editor/v2"
|
"text-editor/v2"
|
||||||
"text-editor-wasm/v1"
|
"text-editor-wasm/v1"
|
||||||
"tokens/numeric-input"
|
"tokens/numeric-input"
|
||||||
"render-wasm/v1"
|
"render-wasm/v1"})
|
||||||
"wasm-export/v1"})
|
|
||||||
|
|
||||||
;; Features that are mainly backend only or there are a proper
|
;; Features that are mainly backend only or there are a proper
|
||||||
;; fallback when frontend reports no support for it
|
;; fallback when frontend reports no support for it
|
||||||
|
|||||||
@ -184,9 +184,6 @@
|
|||||||
:stroke-path
|
:stroke-path
|
||||||
:stroke-per-side
|
:stroke-per-side
|
||||||
|
|
||||||
;; Exporter only: uses render-wasm for export instead of browser
|
|
||||||
;; renderer.
|
|
||||||
:wasm-export
|
|
||||||
:custom-shortcuts
|
:custom-shortcuts
|
||||||
:remote-media-processing
|
:remote-media-processing
|
||||||
|
|
||||||
|
|||||||
@ -34,11 +34,10 @@
|
|||||||
:public-uri (str (cf/get :public-uri))
|
:public-uri (str (cf/get :public-uri))
|
||||||
:internal-uri (str (cf/get-internal-uri))
|
:internal-uri (str (cf/get-internal-uri))
|
||||||
:version (:full cf/version))
|
:version (:full cf/version))
|
||||||
(when (contains? cf/flags :wasm-export)
|
(l/info :msg "headless wasm export available"
|
||||||
(l/info :msg "headless wasm export enabled (experimental)"
|
:wasm-dir wasm/artifact-dir
|
||||||
:wasm-dir wasm/artifact-dir
|
:workers (cf/get :wasm-worker-pool-max)
|
||||||
:workers (cf/get :wasm-worker-pool-max)
|
:image-cache-size (cf/get :wasm-worker-image-cache-size))
|
||||||
:image-cache-size (cf/get :wasm-worker-image-cache-size)))
|
|
||||||
(p/do
|
(p/do
|
||||||
(bwr/init)
|
(bwr/init)
|
||||||
(redis/init)
|
(redis/init)
|
||||||
|
|||||||
@ -71,11 +71,8 @@
|
|||||||
(let [{:keys [resource total headless run]} (prepare cmd auth-token params)]
|
(let [{:keys [resource total headless run]} (prepare cmd auth-token params)]
|
||||||
(->> (jobs/create! {:profile-id profile-id
|
(->> (jobs/create! {:profile-id profile-id
|
||||||
:cmd cmd
|
:cmd cmd
|
||||||
;; What the renderer will actually do, not what the
|
;; Backend used for admission and cancel UI: headless
|
||||||
;; client asked for: `is-wasm` alone still renders in
|
;; WASM when `:is-wasm` is set, otherwise browser.
|
||||||
;; the browser without the `wasm-export` flag, or for
|
|
||||||
;; svg, and the backend decides both the admission cap
|
|
||||||
;; and whether the client offers to cancel.
|
|
||||||
:backend (if headless "wasm" "browser")
|
:backend (if headless "wasm" "browser")
|
||||||
:total total
|
:total total
|
||||||
:name (:name resource)
|
:name (:name resource)
|
||||||
|
|||||||
@ -164,9 +164,8 @@
|
|||||||
(defn prepare-exports
|
(defn prepare-exports
|
||||||
[exports token is-wasm]
|
[exports token is-wasm]
|
||||||
(letfn [(process-group [[part1 :as group]]
|
(letfn [(process-group [[part1 :as group]]
|
||||||
;; The browser renders a partition as a single DOM page, so it is
|
;; Browser path renders one DOM page per partition; headless WASM
|
||||||
;; chunked to bound that page. A wasm export is headless, so
|
;; takes the whole group in one render.
|
||||||
;; it does not need to be chunked, and can be rendered as a single partition.
|
|
||||||
(if (rd/headless? {:is-wasm is-wasm :type (:type part1)})
|
(if (rd/headless? {:is-wasm is-wasm :type (:type part1)})
|
||||||
[(build-render group)]
|
[(build-render group)]
|
||||||
(sequence (comp (partition-all default-partition-size)
|
(sequence (comp (partition-all default-partition-size)
|
||||||
|
|||||||
@ -9,7 +9,6 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.config :as cf]
|
|
||||||
[app.renderer.bitmap :as rb]
|
[app.renderer.bitmap :as rb]
|
||||||
[app.renderer.pdf :as rp]
|
[app.renderer.pdf :as rp]
|
||||||
[app.renderer.svg :as rs]
|
[app.renderer.svg :as rs]
|
||||||
@ -42,7 +41,7 @@
|
|||||||
(defn headless?
|
(defn headless?
|
||||||
"Whether `params` renders with render-wasm rather than a browser."
|
"Whether `params` renders with render-wasm rather than a browser."
|
||||||
[{:keys [is-wasm]}]
|
[{:keys [is-wasm]}]
|
||||||
(and is-wasm (contains? cf/flags :wasm-export)))
|
(boolean is-wasm))
|
||||||
|
|
||||||
(defn render
|
(defn render
|
||||||
[{:keys [type is-wasm] :as params} on-object]
|
[{:keys [type is-wasm] :as params} on-object]
|
||||||
@ -52,7 +51,6 @@
|
|||||||
(when is-wasm
|
(when is-wasm
|
||||||
(l/info :hint "render"
|
(l/info :hint "render"
|
||||||
:type type
|
:type type
|
||||||
:wasm-export (contains? cf/flags :wasm-export)
|
|
||||||
:backend (if headless? "wasm" "browser")))
|
:backend (if headless? "wasm" "browser")))
|
||||||
(if headless?
|
(if headless?
|
||||||
(rw/render params on-object)
|
(rw/render params on-object)
|
||||||
|
|||||||
@ -16,9 +16,8 @@
|
|||||||
the idle watchdog guarantees a wedged worker gives its slot back.
|
the idle watchdog guarantees a wedged worker gives its slot back.
|
||||||
|
|
||||||
Workers run the same bundle as the main thread; `app.core/start` branches on
|
Workers run the same bundle as the main thread; `app.core/start` branches on
|
||||||
`isMainThread`. Without the `wasm-export` flag no worker is spawned at all;
|
`isMainThread`. There is always at least one worker, since a headless render
|
||||||
with it there is always at least one, since a headless render has nowhere
|
has nowhere else to go."
|
||||||
else to go."
|
|
||||||
(:require
|
(:require
|
||||||
["generic-pool" :as gp]
|
["generic-pool" :as gp]
|
||||||
["node:path" :as path]
|
["node:path" :as path]
|
||||||
@ -104,32 +103,27 @@
|
|||||||
|
|
||||||
(defn capacity
|
(defn capacity
|
||||||
"How many renders can run at once, and so how many headless jobs the
|
"How many renders can run at once, and so how many headless jobs the
|
||||||
scheduler may admit. Zero exactly when headless export is off, which is also
|
scheduler may admit."
|
||||||
when no job is headless, so a headless job always has a worker to wait for."
|
|
||||||
[]
|
[]
|
||||||
(if (contains? cf/flags :wasm-export)
|
;; Clamped rather than rejected: a bad value should not stop the exporter
|
||||||
;; Clamped rather than rejected: a bad value should not stop the exporter
|
;; from booting, and a headless render has no other backend to fall back to.
|
||||||
;; from booting, and a headless render has no other backend to fall back to.
|
(max 1 (cf/get :wasm-worker-pool-max 2)))
|
||||||
(max 1 (cf/get :wasm-worker-pool-max 2))
|
|
||||||
0))
|
|
||||||
|
|
||||||
(defn init
|
(defn init
|
||||||
[]
|
[]
|
||||||
(let [configured (cf/get :wasm-worker-pool-max 2)
|
(let [configured (cf/get :wasm-worker-pool-max 2)
|
||||||
max-workers (capacity)]
|
max-workers (capacity)
|
||||||
(when (and (pos? max-workers) (not= configured max-workers))
|
opts #js {:max max-workers
|
||||||
|
:min (min max-workers (cf/get :wasm-worker-pool-min 1))
|
||||||
|
:testOnBorrow true
|
||||||
|
:evictionRunIntervalMillis 30000
|
||||||
|
:numTestsPerEvictionRun 2
|
||||||
|
:idleTimeoutMillis 300000}]
|
||||||
|
(when (not= configured max-workers)
|
||||||
(l/warn :hint "wasm-worker-pool-max raised to the minimum of one"
|
(l/warn :hint "wasm-worker-pool-max raised to the minimum of one"
|
||||||
:configured configured))
|
:configured configured))
|
||||||
(if (pos? max-workers)
|
(l/info :hint "initializing render worker pool" :opts opts)
|
||||||
(let [opts #js {:max max-workers
|
(reset! pool (gp/createPool worker-pool-factory opts))
|
||||||
:min (min max-workers (cf/get :wasm-worker-pool-min 1))
|
|
||||||
:testOnBorrow true
|
|
||||||
:evictionRunIntervalMillis 30000
|
|
||||||
:numTestsPerEvictionRun 2
|
|
||||||
:idleTimeoutMillis 300000}]
|
|
||||||
(l/info :hint "initializing render worker pool" :opts opts)
|
|
||||||
(reset! pool (gp/createPool worker-pool-factory opts)))
|
|
||||||
(l/info :hint "render worker pool disabled, wasm export is off"))
|
|
||||||
(p/resolved nil)))
|
(p/resolved nil)))
|
||||||
|
|
||||||
(defn stop
|
(defn stop
|
||||||
|
|||||||
@ -511,44 +511,26 @@ test("BUG 12384 - Export crashing when exporting a board", async ({ page }) => {
|
|||||||
await workspace.setupEmptyFile();
|
await workspace.setupEmptyFile();
|
||||||
await workspace.mockRPC(/get\-file\?/, "design/get-file-12384.json");
|
await workspace.mockRPC(/get\-file\?/, "design/get-file-12384.json");
|
||||||
|
|
||||||
let hasExportRequestBeenIntercepted = false;
|
// Single export with render-wasm runs in the browser; it must not hit the
|
||||||
|
// exporter API. Fail the test if that path is taken by mistake.
|
||||||
await workspace.page.route("**/api/export", (route) => {
|
await workspace.page.route("**/api/export", (route) => {
|
||||||
if (hasExportRequestBeenIntercepted) {
|
throw new Error("unexpected /api/export request under render-wasm");
|
||||||
route.continue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasExportRequestBeenIntercepted = true;
|
|
||||||
const payload = route.request().postData();
|
|
||||||
const parsedPayload = JSON.parse(payload);
|
|
||||||
|
|
||||||
expect(parsedPayload["~:exports"]).toHaveLength(1);
|
|
||||||
expect(parsedPayload["~:exports"][0]["~:file-id"]).toBe(
|
|
||||||
"~ufa6ce865-34dd-80ac-8006-fe0dab5539a7",
|
|
||||||
);
|
|
||||||
expect(parsedPayload["~:exports"][0]["~:page-id"]).toBe(
|
|
||||||
"~ufa6ce865-34dd-80ac-8006-fe0dab5539a8",
|
|
||||||
);
|
|
||||||
|
|
||||||
route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
contentType: "application/json",
|
|
||||||
response: {},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await workspace.goToWorkspace({
|
await workspace.goToWorkspace({
|
||||||
fileId: "fa6ce865-34dd-80ac-8006-fe0dab5539a7",
|
fileId: "fa6ce865-34dd-80ac-8006-fe0dab5539a7",
|
||||||
pageId: "fa6ce865-34dd-80ac-8006-fe0dab5539a8",
|
pageId: "fa6ce865-34dd-80ac-8006-fe0dab5539a8",
|
||||||
});
|
});
|
||||||
|
await workspace.waitForFirstRender();
|
||||||
|
|
||||||
await workspace.clickLeafLayer("Board");
|
await workspace.clickLeafLayer("Board");
|
||||||
|
|
||||||
let exportRequest = workspace.page.waitForRequest("**/api/export");
|
const [download] = await Promise.all([
|
||||||
|
workspace.page.waitForEvent("download"),
|
||||||
|
workspace.rightSidebar
|
||||||
|
.getByRole("button", { name: "Export 1 element" })
|
||||||
|
.click(),
|
||||||
|
]);
|
||||||
|
|
||||||
await workspace.rightSidebar
|
expect(download.suggestedFilename()).toBe("Board.png");
|
||||||
.getByRole("button", { name: "Export 1 element" })
|
|
||||||
.click();
|
|
||||||
|
|
||||||
await exportRequest;
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.exports.wasm :as wasm.exports]
|
[app.main.data.exports.wasm :as wasm.exports]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
@ -255,9 +254,9 @@
|
|||||||
(def ^:private wasm-export-types #{:jpeg :webp :png :pdf :svg})
|
(def ^:private wasm-export-types #{:jpeg :webp :png :pdf :svg})
|
||||||
|
|
||||||
(defn- wasm-export-enabled?
|
(defn- wasm-export-enabled?
|
||||||
|
"True when the active renderer is render-wasm."
|
||||||
[state]
|
[state]
|
||||||
(and (contains? cf/flags :wasm-export)
|
(features/active-feature? state "render-wasm/v1"))
|
||||||
(features/active-feature? state "render-wasm/v1")))
|
|
||||||
|
|
||||||
(defn- use-wasm-export?
|
(defn- use-wasm-export?
|
||||||
"Whether to take the client-side WASM export path for `export`."
|
"Whether to take the client-side WASM export path for `export`."
|
||||||
|
|||||||
@ -1170,8 +1170,7 @@
|
|||||||
(rx/of (ntf/info (tr "workspace.clipboard.copying")))
|
(rx/of (ntf/info (tr "workspace.clipboard.copying")))
|
||||||
|
|
||||||
;; Call exporter to get image URI, then fetch blob and resolve the deferred.
|
;; Call exporter to get image URI, then fetch blob and resolve the deferred.
|
||||||
(->> (if (and (features/active-feature? state "render-wasm/v1")
|
(->> (if (features/active-feature? state "render-wasm/v1")
|
||||||
(contains? cf/flags :wasm-export))
|
|
||||||
(rx/of {:uri (wasm.exports/export-image-uri export)})
|
(rx/of {:uri (wasm.exports/export-image-uri export)})
|
||||||
(rp/cmd! :export
|
(rp/cmd! :export
|
||||||
{:exports [export]
|
{:exports [export]
|
||||||
|
|||||||
@ -33,7 +33,6 @@
|
|||||||
[app.common.types.shape.shadow :as ctss]
|
[app.common.types.shape.shadow :as ctss]
|
||||||
[app.common.types.text :as txt]
|
[app.common.types.text :as txt]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
|
||||||
[app.main.data.exports.assets :as de]
|
[app.main.data.exports.assets :as de]
|
||||||
[app.main.data.exports.wasm :as wasm.exports]
|
[app.main.data.exports.wasm :as wasm.exports]
|
||||||
[app.main.data.persistence :as dwp]
|
[app.main.data.persistence :as dwp]
|
||||||
@ -49,6 +48,7 @@
|
|||||||
[app.main.data.workspace.texts :as dwt]
|
[app.main.data.workspace.texts :as dwt]
|
||||||
[app.main.data.workspace.tokens.application :as dwta]
|
[app.main.data.workspace.tokens.application :as dwta]
|
||||||
[app.main.data.workspace.variants :as dwv]
|
[app.main.data.workspace.variants :as dwv]
|
||||||
|
[app.main.features :as features]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.plugins.exports :as exports]
|
[app.plugins.exports :as exports]
|
||||||
@ -1603,65 +1603,67 @@
|
|||||||
(u/not-valid plugin-id :export "Plugin doesn't have 'content:read' permission")
|
(u/not-valid plugin-id :export "Plugin doesn't have 'content:read' permission")
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(if (and (contains? cf/flags :wasm-export)
|
(let [export-type (:type value :png)
|
||||||
(contains? #{:jpeg :webp :png} (:type value :png)))
|
wasm-enabled? (features/active-feature? @st/state "render-wasm/v1")]
|
||||||
;; New export with wasm
|
(if (and wasm-enabled?
|
||||||
(let [uri (wasm.exports/export-image-uri
|
(contains? #{:jpeg :webp :png :pdf :svg} export-type))
|
||||||
{:file-id file-id
|
;; Render in the browser with render-wasm.
|
||||||
:page-id page-id
|
(let [export {:file-id file-id
|
||||||
:object-id id
|
:page-id page-id
|
||||||
:type (:type value :png)
|
:object-id id
|
||||||
:scale (:scale value 1)})]
|
:type export-type
|
||||||
(js/Promise.
|
:scale (:scale value 1)}
|
||||||
(fn [resolve reject]
|
uri (case export-type
|
||||||
(->> (http/send!
|
:pdf (wasm.exports/export-pdf-uri export)
|
||||||
{:method :get
|
:svg (wasm.exports/export-svg-uri export)
|
||||||
:uri uri
|
(wasm.exports/export-image-uri export))]
|
||||||
:response-type :blob
|
(js/Promise.
|
||||||
:omit-default-headers true})
|
(fn [resolve reject]
|
||||||
(rx/map :body)
|
(->> (http/send!
|
||||||
(rx/mapcat #(.arrayBuffer %))
|
{:method :get
|
||||||
(rx/map #(js/Uint8Array. %))
|
:uri uri
|
||||||
(rx/tap #(st/emit! (se/event plugin-id "export-shapes" :method "wasm")))
|
:response-type :blob
|
||||||
(rx/subs! resolve reject)))))
|
:omit-default-headers true})
|
||||||
|
(rx/map :body)
|
||||||
;; Old export through exporter
|
(rx/mapcat #(.arrayBuffer %))
|
||||||
(let [shape (u/locate-shape file-id page-id id)
|
(rx/map #(js/Uint8Array. %))
|
||||||
payload
|
(rx/tap #(st/emit! (se/event plugin-id "export-shapes" :method "wasm")))
|
||||||
{:cmd :export-shapes
|
(rx/subs! resolve reject)))))
|
||||||
:profile-id (:profile-id @st/state)
|
|
||||||
:wait true
|
|
||||||
:is-wasm false
|
|
||||||
:exports [(de/normalize-export {:file-id file-id
|
|
||||||
:page-id page-id
|
|
||||||
:object-id id
|
|
||||||
:name (:name shape)
|
|
||||||
:type (:type value :png)
|
|
||||||
:suffix (:suffix value "")
|
|
||||||
:scale (:scale value 1)})]}]
|
|
||||||
(js/Promise.
|
|
||||||
(fn [resolve reject]
|
|
||||||
;; The exporter renders the file from its persisted
|
|
||||||
;; state, so flush pending local changes and wait until
|
|
||||||
;; they are saved before invoking it. Otherwise it may
|
|
||||||
;; export a stale/empty shape. (The wasm export above
|
|
||||||
;; renders locally and does not need this.)
|
|
||||||
(st/emit! ::dwp/force-persist)
|
|
||||||
(->> (rx/concat
|
|
||||||
(->> (dwp/wait-persisted 5000)
|
|
||||||
(rx/ignore))
|
|
||||||
(rp/cmd! :export payload))
|
|
||||||
(rx/mapcat (fn [{:keys [uri]}]
|
|
||||||
(->> (http/send! {:method :get
|
|
||||||
:uri uri
|
|
||||||
:response-type :blob
|
|
||||||
:omit-default-headers true})
|
|
||||||
(rx/map :body))))
|
|
||||||
(rx/mapcat #(.arrayBuffer %))
|
|
||||||
(rx/map #(js/Uint8Array. %))
|
|
||||||
(rx/tap #(st/emit! (se/event plugin-id "export-shapes" :method "exporter")))
|
|
||||||
(rx/subs! resolve reject)))))))))
|
|
||||||
|
|
||||||
|
;; Render via the exporter service.
|
||||||
|
(let [shape (u/locate-shape file-id page-id id)
|
||||||
|
payload
|
||||||
|
{:cmd :export-shapes
|
||||||
|
:profile-id (:profile-id @st/state)
|
||||||
|
:wait true
|
||||||
|
:is-wasm wasm-enabled?
|
||||||
|
:exports [(de/normalize-export {:file-id file-id
|
||||||
|
:page-id page-id
|
||||||
|
:object-id id
|
||||||
|
:name (:name shape)
|
||||||
|
:type export-type
|
||||||
|
:suffix (:suffix value "")
|
||||||
|
:scale (:scale value 1)})]}]
|
||||||
|
(js/Promise.
|
||||||
|
(fn [resolve reject]
|
||||||
|
;; Exporter reads persisted file state; flush first
|
||||||
|
;; so the export is not stale. Browser WASM above
|
||||||
|
;; renders from local state and needs no flush.
|
||||||
|
(st/emit! ::dwp/force-persist)
|
||||||
|
(->> (rx/concat
|
||||||
|
(->> (dwp/wait-persisted 5000)
|
||||||
|
(rx/ignore))
|
||||||
|
(rp/cmd! :export payload))
|
||||||
|
(rx/mapcat (fn [{:keys [uri]}]
|
||||||
|
(->> (http/send! {:method :get
|
||||||
|
:uri uri
|
||||||
|
:response-type :blob
|
||||||
|
:omit-default-headers true})
|
||||||
|
(rx/map :body))))
|
||||||
|
(rx/mapcat #(.arrayBuffer %))
|
||||||
|
(rx/map #(js/Uint8Array. %))
|
||||||
|
(rx/tap #(st/emit! (se/event plugin-id "export-shapes" :method "exporter")))
|
||||||
|
(rx/subs! resolve reject))))))))))
|
||||||
|
|
||||||
;; Interactions
|
;; Interactions
|
||||||
:addInteraction
|
:addInteraction
|
||||||
|
|||||||
@ -78,11 +78,11 @@ flowchart TB
|
|||||||
## Export wiring (single vs multiple)
|
## Export wiring (single vs multiple)
|
||||||
|
|
||||||
The client-side WASM export — rendering in the browser through the vector path
|
The client-side WASM export — rendering in the browser through the vector path
|
||||||
(`render_shape_pdf` / `render_shape_pixels`) — is wired **only for single
|
(`render_shape_pdf` / `render_shape_pixels` / `render_shape_svg`) — is wired
|
||||||
exports** (`request-simple-export` in `frontend/.../exports/assets.cljs`), and
|
**only for single exports** (`request-simple-export` in
|
||||||
only when render-wasm is active and the `enable-wasm-export` flag is set. The
|
`frontend/.../exports/assets.cljs`), and only when the active renderer is
|
||||||
exporter service reads the same flag to decide whether it can serve the
|
render-wasm (`render-wasm/v1`). The exporter service takes the headless WASM
|
||||||
headless WASM path.
|
path when the client sends `:is-wasm true` (same condition).
|
||||||
|
|
||||||
**Multiple/batch export** (`request-multiple-export`) always runs **server-side**
|
**Multiple/batch export** (`request-multiple-export`) always runs **server-side**
|
||||||
via the `:export-shapes` command; it merely passes an `:is-wasm` hint so the
|
via the `:export-shapes` command; it merely passes an `:is-wasm` hint so the
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user