Skip to content

Commit

Permalink
feat(unstable): Workspaces support (#20410)
Browse files Browse the repository at this point in the history
This commit adds unstable workspace support. This is extremely
bare-bones and
minimal first-pass at this.

With this change `deno.json` supports specifying `workspaces` key, that
accepts a list of subdirectories. Each workspace can have its own import
map. It's required to specify a `"name"` and `"version"` properties in the
configuration file for the workspace:

```jsonc
// deno.json
{
  "workspaces": [
     "a",
     "b"
  },
  "imports": {
    "express": "npm:express@5"
   }
}
```
``` jsonc
// a/deno.json
{
  "name": "a",
  "version": "1.0.2",
  "imports": {
    "kleur": "npm:kleur"
  }
}
```
```jsonc
// b/deno.json
{
  "name": "b",
  "version": "0.51.0",
  "imports": {
    "chalk": "npm:chalk"
  }
}
```

`--unstable-workspaces` flag is required to use this feature:
```
$ deno run --unstable-workspaces mod.ts
```

---------

Co-authored-by: David Sherret <dsherret@gmail.com>
  • Loading branch information
bartlomieju and dsherret committed Nov 17, 2023
1 parent 544923a commit 9534e6e
Show file tree
Hide file tree
Showing 38 changed files with 317 additions and 24 deletions.
22 changes: 10 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ winres.workspace = true
[dependencies]
deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_cache_dir = "=0.6.1"
deno_config = "=0.5.0"
deno_config = "=0.6.4"
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_doc = { version = "=0.73.1", features = ["html"] }
deno_emit = "=0.31.3"
deno_graph = "=0.61.0"
deno_doc = { version = "=0.73.2", features = ["html"] }
deno_emit = "=0.31.4"
deno_graph = "=0.61.1"
deno_lint = { version = "=0.52.2", features = ["docs"] }
deno_lockfile.workspace = true
deno_npm = "0.15.2"
Expand Down Expand Up @@ -95,7 +95,7 @@ glob = "0.3.1"
hex.workspace = true
http.workspace = true
hyper.workspace = true
import_map = "=0.15.0"
import_map = { version = "=0.17.0", features = ["ext"] }
indexmap.workspace = true
jsonc-parser = { version = "=0.21.1", features = ["serde"] }
lazy-regex.workspace = true
Expand Down
11 changes: 11 additions & 0 deletions cli/args/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ pub struct Flags {
pub unstable: bool,
pub unstable_bare_node_builtins: bool,
pub unstable_byonm: bool,
pub unstable_workspaces: bool,
pub unstable_features: Vec<String>,
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
pub v8_flags: Vec<String>,
Expand Down Expand Up @@ -871,6 +872,7 @@ pub fn flags_from_vec(args: Vec<String>) -> clap::error::Result<Flags> {
flags.unstable_bare_node_builtins =
matches.get_flag("unstable-bare-node-builtins");
flags.unstable_byonm = matches.get_flag("unstable-byonm");
flags.unstable_workspaces = matches.get_flag("unstable-workspaces");

if matches.get_flag("quiet") {
flags.log_level = Some(Level::Error);
Expand Down Expand Up @@ -984,6 +986,15 @@ fn clap_root() -> Command {
.value_parser(FalseyValueParser::new())
.action(ArgAction::SetTrue)
.global(true),
)
.arg(
Arg::new("unstable-workspaces")
.long("unstable-workspaces")
.help("Enable unstable 'workspaces' feature")
.env("DENO_UNSTABLE_WORKSPACES")
.value_parser(FalseyValueParser::new())
.action(ArgAction::SetTrue)
.global(true),
);

for (flag_name, help, _) in crate::UNSTABLE_GRANULAR_FLAGS {
Expand Down
2 changes: 1 addition & 1 deletion cli/args/import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub async fn resolve_import_map_from_specifier(
import_map_from_value(specifier, value)
}

fn import_map_from_value(
pub fn import_map_from_value(
specifier: &Url,
json_value: serde_json::Value,
) -> Result<ImportMap, AnyError> {
Expand Down
57 changes: 57 additions & 0 deletions cli/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub use deno_config::TsConfig;
pub use deno_config::TsConfigForEmit;
pub use deno_config::TsConfigType;
pub use deno_config::TsTypeLib;
pub use deno_config::WorkspaceConfig;
pub use flags::*;
pub use lockfile::Lockfile;
pub use lockfile::LockfileError;
Expand Down Expand Up @@ -619,6 +620,7 @@ pub struct CliOptions {
maybe_package_json: Option<PackageJson>,
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
overrides: CliOptionOverrides,
maybe_workspace_config: Option<WorkspaceConfig>,
}

impl CliOptions {
Expand Down Expand Up @@ -652,6 +654,20 @@ impl CliOptions {
.with_context(|| "Resolving node_modules folder.")?;
let maybe_vendor_folder =
resolve_vendor_folder(&initial_cwd, &flags, maybe_config_file.as_ref());
let maybe_workspace_config =
if let Some(config_file) = maybe_config_file.as_ref() {
config_file.to_workspace_config()?
} else {
None
};

// TODO(bartlomieju): remove in v1.39 or v1.40.
if let Some(wsconfig) = &maybe_workspace_config {
if !wsconfig.members.is_empty() && !flags.unstable_workspaces {
eprintln!("Use of unstable 'workspaces' feature. The --unstable-workspaces flags must be provided.");
std::process::exit(70);
}
}

if let Some(env_file_name) = &flags.env_file {
if (from_filename(env_file_name)).is_err() {
Expand All @@ -668,6 +684,7 @@ impl CliOptions {
maybe_node_modules_folder,
maybe_vendor_folder,
overrides: Default::default(),
maybe_workspace_config,
})
}

Expand Down Expand Up @@ -813,6 +830,41 @@ impl CliOptions {
&self,
file_fetcher: &FileFetcher,
) -> Result<Option<ImportMap>, AnyError> {
if let Some(workspace_config) = self.maybe_workspace_config.as_ref() {
let base_import_map_config = ::import_map::ext::ImportMapConfig {
base_url: self.maybe_config_file.as_ref().unwrap().specifier.clone(),
import_map_value: workspace_config.base_import_map_value.clone(),
};
let children_configs = workspace_config
.members
.iter()
.map(|member| {
let import_map_value = member.config_file.to_import_map_value();
::import_map::ext::ImportMapConfig {
base_url: member.config_file.specifier.clone(),
import_map_value,
}
})
.collect();

let maybe_import_map = ::import_map::ext::create_synthetic_import_map(
base_import_map_config,
children_configs,
);
if let Some((_import_map_url, import_map)) = maybe_import_map {
log::debug!(
"Workspace config generated this import map {}",
serde_json::to_string_pretty(&import_map).unwrap()
);
return import_map::import_map_from_value(
// TODO(bartlomieju): maybe should be stored on the workspace config?
&self.maybe_config_file.as_ref().unwrap().specifier,
import_map,
)
.map(Some);
}
}

let import_map_specifier = match self.resolve_import_map_specifier()? {
Some(specifier) => specifier,
None => return Ok(None),
Expand Down Expand Up @@ -940,6 +992,7 @@ impl CliOptions {
maybe_config_file: self.maybe_config_file.clone(),
maybe_package_json: self.maybe_package_json.clone(),
maybe_lockfile: self.maybe_lockfile.clone(),
maybe_workspace_config: self.maybe_workspace_config.clone(),
overrides: self.overrides.clone(),
}
}
Expand Down Expand Up @@ -1037,6 +1090,10 @@ impl CliOptions {
&self.maybe_config_file
}

pub fn maybe_workspace_config(&self) -> &Option<WorkspaceConfig> {
&self.maybe_workspace_config
}

pub fn maybe_package_json(&self) -> &Option<PackageJson> {
&self.maybe_package_json
}
Expand Down
59 changes: 55 additions & 4 deletions cli/graph_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::util::sync::TaskQueue;
use crate::util::sync::TaskQueuePermit;

use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
Expand Down Expand Up @@ -269,6 +270,12 @@ impl ModuleGraphBuilder {
options: CreateGraphOptions<'_>,
) -> Result<deno_graph::ModuleGraph, AnyError> {
let maybe_imports = self.options.to_maybe_imports()?;
let maybe_workspace_config = self.options.maybe_workspace_config();
let workspace_members = if let Some(wc) = maybe_workspace_config {
workspace_config_to_workspace_members(wc)?
} else {
vec![]
};

let cli_resolver = self.resolver.clone();
let graph_resolver = cli_resolver.as_graph_resolver();
Expand All @@ -291,8 +298,7 @@ impl ModuleGraphBuilder {
npm_resolver: Some(graph_npm_resolver),
module_analyzer: Some(options.analyzer),
reporter: maybe_file_watcher_reporter,
// todo(dsherret): workspace support
workspace_members: vec![],
workspace_members,
},
)
.await?;
Expand All @@ -312,6 +318,12 @@ impl ModuleGraphBuilder {
) -> Result<Arc<deno_graph::ModuleGraph>, AnyError> {
let mut cache = self.create_graph_loader();
let maybe_imports = self.options.to_maybe_imports()?;
let maybe_workspace_config = self.options.maybe_workspace_config();
let workspace_members = if let Some(wc) = maybe_workspace_config {
workspace_config_to_workspace_members(wc)?
} else {
vec![]
};
let cli_resolver = self.resolver.clone();
let graph_resolver = cli_resolver.as_graph_resolver();
let graph_npm_resolver = cli_resolver.as_graph_npm_resolver();
Expand All @@ -336,8 +348,7 @@ impl ModuleGraphBuilder {
npm_resolver: Some(graph_npm_resolver),
module_analyzer: Some(&analyzer),
reporter: maybe_file_watcher_reporter,
// todo(dsherret): workspace support
workspace_members: vec![],
workspace_members,
},
)
.await?;
Expand Down Expand Up @@ -719,6 +730,46 @@ impl deno_graph::source::Reporter for FileWatcherReporter {
}
}

pub fn workspace_config_to_workspace_members(
workspace_config: &deno_config::WorkspaceConfig,
) -> Result<Vec<deno_graph::WorkspaceMember>, AnyError> {
workspace_config
.members
.iter()
.map(|member| {
workspace_member_config_try_into_workspace_member(member).with_context(
|| {
format!(
"Failed to resolve configuration for '{}' workspace member at '{}'",
member.member_name,
member.config_file.specifier.as_str()
)
},
)
})
.collect()
}

fn workspace_member_config_try_into_workspace_member(
config: &deno_config::WorkspaceMemberConfig,
) -> Result<deno_graph::WorkspaceMember, AnyError> {
let nv = deno_semver::package::PackageNv {
name: config.package_name.clone(),
version: deno_semver::Version::parse_standard(&config.package_version)?,
};
Ok(deno_graph::WorkspaceMember {
base: ModuleSpecifier::from_directory_path(&config.path).unwrap(),
nv,
exports: config
.config_file
.to_exports_config()?
.into_map()
// todo(dsherret): deno_graph should use an IndexMap
.into_iter()
.collect(),
})
}

#[cfg(test)]
mod test {
use std::sync::Arc;
Expand Down
10 changes: 8 additions & 2 deletions cli/module_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::cache::ParsedSourceCache;
use crate::emit::Emitter;
use crate::graph_util::graph_lock_or_exit;
use crate::graph_util::graph_valid_with_cli_options;
use crate::graph_util::workspace_config_to_workspace_members;
use crate::graph_util::FileWatcherReporter;
use crate::graph_util::ModuleGraphBuilder;
use crate::graph_util::ModuleGraphContainer;
Expand Down Expand Up @@ -119,6 +120,12 @@ impl ModuleLoadPreparer {

let mut cache = self.module_graph_builder.create_fetch_cacher(permissions);
let maybe_imports = self.options.to_maybe_imports()?;
let maybe_workspace_config = self.options.maybe_workspace_config();
let workspace_members = if let Some(wc) = maybe_workspace_config {
workspace_config_to_workspace_members(wc)?
} else {
vec![]
};
let graph_resolver = self.resolver.as_graph_resolver();
let graph_npm_resolver = self.resolver.as_graph_npm_resolver();
let maybe_file_watcher_reporter = self
Expand Down Expand Up @@ -152,8 +159,7 @@ impl ModuleLoadPreparer {
npm_resolver: Some(graph_npm_resolver),
module_analyzer: Some(&analyzer),
reporter: maybe_file_watcher_reporter,
// todo(dsherret): workspace support
workspace_members: vec![],
workspace_members,
},
)
.await?;
Expand Down

0 comments on commit 9534e6e

Please sign in to comment.