Skip to main content

alder_runtime/
dev.rs

1//! In-process development module replacement. Only compiler-produced bundles
2//! enter the loader; no filesystem/network JavaScript loader is exposed.
3use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
4
5use deno_core::{
6    ModuleLoadOptions, ModuleLoadReferrer, ModuleLoadResponse, ModuleLoader, ModuleSource,
7    ModuleSourceCode, ModuleSpecifier, ModuleType, OpState, ResolutionKind, op2,
8};
9use deno_error::JsErrorBox;
10use tokio::sync::{Mutex, mpsc};
11
12#[derive(Debug)]
13pub enum DevEvent {
14    Build { server: String },
15    Error { message: String },
16    Shutdown,
17}
18
19pub(crate) struct DevState {
20    receiver: Mutex<mpsc::Receiver<DevEvent>>,
21    modules: RefCell<BTreeMap<String, String>>,
22    revision: std::cell::Cell<u64>,
23}
24
25#[derive(serde::Serialize)]
26pub(crate) struct Message {
27    kind: &'static str,
28    revision: u64,
29    module: Option<String>,
30    message: Option<String>,
31}
32
33#[op2]
34#[serde]
35pub(crate) async fn op_alder_dev_event(state: Rc<RefCell<OpState>>) -> Result<Message, JsErrorBox> {
36    let state = state
37        .borrow()
38        .try_borrow::<Rc<DevState>>()
39        .cloned()
40        .ok_or_else(|| JsErrorBox::generic("No Alder development session is active"))?;
41    let event = state
42        .receiver
43        .lock()
44        .await
45        .recv()
46        .await
47        .unwrap_or(DevEvent::Shutdown);
48    let mut result = Message {
49        kind: "shutdown",
50        revision: state.revision.get(),
51        module: None,
52        message: None,
53    };
54    match event {
55        DevEvent::Build { server } => {
56            let revision = state.revision.get() + 1;
57            state.revision.set(revision);
58            let module = format!("alder://dev/{revision}.mjs");
59            let mut modules = state.modules.borrow_mut();
60            modules.clear();
61            modules.insert(module.clone(), server);
62            result.kind = "build";
63            result.revision = revision;
64            result.module = Some(module);
65        }
66        DevEvent::Error { message } => {
67            result.kind = "error";
68            result.message = Some(message);
69        }
70        DevEvent::Shutdown => {}
71    }
72    Ok(result)
73}
74
75impl ModuleLoader for DevState {
76    fn resolve(
77        &self,
78        specifier: &str,
79        referrer: &str,
80        _: ResolutionKind,
81    ) -> Result<ModuleSpecifier, JsErrorBox> {
82        deno_core::resolve_import(specifier, referrer).map_err(JsErrorBox::from_err)
83    }
84
85    fn load(
86        &self,
87        specifier: &ModuleSpecifier,
88        _: Option<&ModuleLoadReferrer>,
89        _: ModuleLoadOptions,
90    ) -> ModuleLoadResponse {
91        let result = self
92            .modules
93            .borrow()
94            .get(specifier.as_str())
95            .cloned()
96            .ok_or_else(|| {
97                JsErrorBox::generic("Development module was not supplied by the Alder compiler")
98            })
99            .map(|code| {
100                ModuleSource::new(
101                    ModuleType::JavaScript,
102                    ModuleSourceCode::String(code.into()),
103                    specifier,
104                    None,
105                )
106            });
107        ModuleLoadResponse::Sync(result)
108    }
109}
110
111pub async fn execute_dev(
112    receiver: mpsc::Receiver<DevEvent>,
113    hostname: String,
114    port: u16,
115) -> Result<i32, deno_core::error::AnyError> {
116    let state = Rc::new(DevState {
117        receiver: Mutex::new(receiver),
118        modules: RefCell::new(BTreeMap::new()),
119        revision: std::cell::Cell::new(0),
120    });
121    super::execute_inner(
122        include_str!("../js/dev.js").to_owned(),
123        vec![hostname, port.to_string()],
124        None,
125        Some(state),
126        None,
127    )
128    .await
129}