From ae63881e3897144e3cd616747c1a76a13e2b7451 Mon Sep 17 00:00:00 2001 From: sorrycc Date: Wed, 9 Oct 2024 17:29:40 +0800 Subject: [PATCH 1/3] feat: add resolve_id plugin hook --- crates/binding/src/js_hook.rs | 11 +++++ crates/binding/src/js_plugin.rs | 27 ++++++++++- crates/mako/src/lib.rs | 2 +- crates/mako/src/plugin.rs | 26 +++++++++- crates/mako/src/resolve.rs | 19 +++++++- crates/mako/src/resolve/resolution.rs | 63 +++++++++++++++++++++++++ crates/mako/src/resolve/resource.rs | 2 +- e2e/fixtures/plugins/expect.js | 3 ++ e2e/fixtures/plugins/plugins.config.js | 11 ++++- e2e/fixtures/plugins/resolve_id_mock.js | 1 + e2e/fixtures/plugins/src/index.tsx | 1 + packages/mako/binding.d.ts | 4 ++ 12 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 crates/mako/src/resolve/resolution.rs create mode 100644 e2e/fixtures/plugins/resolve_id_mock.js diff --git a/crates/binding/src/js_hook.rs b/crates/binding/src/js_hook.rs index e36f21c82..beab970a2 100644 --- a/crates/binding/src/js_hook.rs +++ b/crates/binding/src/js_hook.rs @@ -58,12 +58,15 @@ pub struct JsHooks { pub _on_generate_file: Option, #[napi(ts_type = "() => Promise;")] pub build_start: Option, + #[napi(ts_type = "(source: string, importer: string) => Promise<{ id: string }>;")] + pub resolve_id: Option, } pub struct TsFnHooks { pub build_start: Option>, pub generate_end: Option>, pub load: Option>>, + pub resolve_id: Option>>, pub _on_generate_file: Option>, } @@ -79,6 +82,9 @@ impl TsFnHooks { load: hooks.load.as_ref().map(|hook| unsafe { ThreadsafeFunction::from_napi_value(env.raw(), hook.raw()).unwrap() }), + resolve_id: hooks.resolve_id.as_ref().map(|hook| unsafe { + ThreadsafeFunction::from_napi_value(env.raw(), hook.raw()).unwrap() + }), _on_generate_file: hooks._on_generate_file.as_ref().map(|hook| unsafe { ThreadsafeFunction::from_napi_value(env.raw(), hook.raw()).unwrap() }), @@ -99,3 +105,8 @@ pub struct LoadResult { #[napi(js_name = "type")] pub content_type: String, } + +#[napi(object, use_nullable = true)] +pub struct ResolveIdResult { + pub id: String, +} diff --git a/crates/binding/src/js_plugin.rs b/crates/binding/src/js_plugin.rs index 83a7792a0..41b056830 100644 --- a/crates/binding/src/js_plugin.rs +++ b/crates/binding/src/js_plugin.rs @@ -1,6 +1,7 @@ +use std::path::PathBuf; use std::sync::Arc; -use crate::js_hook::{LoadResult, TsFnHooks, WriteFile}; +use crate::js_hook::{LoadResult, ResolveIdResult, TsFnHooks, WriteFile}; pub struct JsPlugin { pub hooks: TsFnHooks, @@ -9,6 +10,7 @@ use anyhow::{anyhow, Result}; use mako::ast::file::{Content, JsContent}; use mako::compiler::Context; use mako::plugin::{Plugin, PluginGenerateEndParams, PluginLoadParam}; +use mako::resolve::{Resolution, ResolvedResource, ResolverResource}; impl Plugin for JsPlugin { fn name(&self) -> &str { @@ -47,6 +49,29 @@ impl Plugin for JsPlugin { Ok(None) } + fn resolve_id( + &self, + source: &str, + importer: &str, + _context: &Arc, + ) -> Result> { + if let Some(hook) = &self.hooks.resolve_id { + let x: Option = + hook.call((source.to_string(), importer.to_string()))?; + if let Some(x) = x { + return Ok(Some(ResolverResource::Resolved(ResolvedResource( + Resolution { + path: PathBuf::from(x.id), + query: None, + fragment: None, + package_json: None, + }, + )))); + } + } + Ok(None) + } + fn generate_end(&self, param: &PluginGenerateEndParams, _context: &Arc) -> Result<()> { if let Some(hook) = &self.hooks.generate_end { hook.call(serde_json::to_value(param)?)? diff --git a/crates/mako/src/lib.rs b/crates/mako/src/lib.rs index c4a9d15e5..2d3444c18 100644 --- a/crates/mako/src/lib.rs +++ b/crates/mako/src/lib.rs @@ -15,7 +15,7 @@ mod module; mod module_graph; pub mod plugin; mod plugins; -mod resolve; +pub mod resolve; pub mod share; pub mod stats; pub mod utils; diff --git a/crates/mako/src/plugin.rs b/crates/mako/src/plugin.rs index f33b568fc..1b4d27883 100644 --- a/crates/mako/src/plugin.rs +++ b/crates/mako/src/plugin.rs @@ -53,6 +53,15 @@ pub trait Plugin: Any + Send + Sync { Ok(None) } + fn resolve_id( + &self, + _source: &str, + _importer: &str, + _context: &Arc, + ) -> Result> { + Ok(None) + } + fn next_build(&self, _next_build_param: &NextBuildParam) -> bool { true } @@ -208,7 +217,6 @@ impl PluginDriver { Ok(None) } - #[allow(dead_code)] pub fn transform_js( &self, param: &PluginTransformJsParam, @@ -233,7 +241,6 @@ impl PluginDriver { Ok(()) } - #[allow(dead_code)] pub fn before_resolve( &self, param: &mut Vec, @@ -245,6 +252,21 @@ impl PluginDriver { Ok(()) } + pub fn resolve_id( + &self, + source: &str, + importer: &str, + context: &Arc, + ) -> Result> { + for plugin in &self.plugins { + let ret = plugin.resolve_id(source, importer, context)?; + if ret.is_some() { + return Ok(ret); + } + } + Ok(None) + } + pub fn before_generate(&self, context: &Arc) -> Result<()> { for plugin in &self.plugins { plugin.generate_begin(context)?; diff --git a/crates/mako/src/resolve.rs b/crates/mako/src/resolve.rs index 19a62d2fe..4a74f4462 100644 --- a/crates/mako/src/resolve.rs +++ b/crates/mako/src/resolve.rs @@ -10,8 +10,10 @@ use regex::Captures; use thiserror::Error; use tracing::debug; +mod resolution; mod resource; -pub(crate) use resource::{ExternalResource, ResolvedResource, ResolverResource}; +pub use resolution::Resolution; +pub use resource::{ExternalResource, ResolvedResource, ResolverResource}; use crate::ast::file::parse_path; use crate::compiler::Context; @@ -49,6 +51,14 @@ pub fn resolve( crate::mako_profile_function!(); crate::mako_profile_scope!("resolve", &dep.source); + // plugin first + if let Some(resolved) = context + .plugin_driver + .resolve_id(&dep.source, path, context)? + { + return Ok(resolved); + } + if dep.source.starts_with("virtual:") { return Ok(ResolverResource::Virtual(PathBuf::from(&dep.source))); } @@ -244,7 +254,12 @@ fn do_resolve( // TODO: 临时方案,需要改成删除文件时删 resolve cache 里的内容 // 比如把 util.ts 改名为 util.tsx,目前应该是还有问题的 if resolution.path().exists() { - Ok(ResolverResource::Resolved(ResolvedResource(resolution))) + Ok(ResolverResource::Resolved(ResolvedResource(Resolution { + package_json: resolution.package_json().cloned(), + path: resolution.clone().into_path_buf(), + query: resolution.query().map(|q| q.to_string()), + fragment: resolution.fragment().map(|f| f.to_string()), + }))) } else { Err(anyhow!(ResolveError { path: source.to_string(), diff --git a/crates/mako/src/resolve/resolution.rs b/crates/mako/src/resolve/resolution.rs new file mode 100644 index 000000000..52c93dfd1 --- /dev/null +++ b/crates/mako/src/resolve/resolution.rs @@ -0,0 +1,63 @@ +use std::fmt; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use oxc_resolver::PackageJson; + +#[derive(Clone)] +pub struct Resolution { + pub path: PathBuf, + pub query: Option, + pub fragment: Option, + pub package_json: Option>, +} + +impl Resolution { + /// Returns the path without query and fragment + pub fn path(&self) -> &Path { + &self.path + } + + /// Returns the path without query and fragment + pub fn into_path_buf(self) -> PathBuf { + self.path + } + + /// Returns the path query `?query`, contains the leading `?` + pub fn query(&self) -> Option<&str> { + self.query.as_deref() + } + + /// Returns the path fragment `#fragment`, contains the leading `#` + pub fn fragment(&self) -> Option<&str> { + self.fragment.as_deref() + } + + /// Returns serialized package_json + pub fn package_json(&self) -> Option<&Arc> { + self.package_json.as_ref() + } + + /// Returns the full path with query and fragment + pub fn full_path(&self) -> PathBuf { + let mut path = self.path.clone().into_os_string(); + if let Some(query) = &self.query { + path.push(query); + } + if let Some(fragment) = &self.fragment { + path.push(fragment); + } + PathBuf::from(path) + } +} + +impl fmt::Debug for Resolution { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resolution") + .field("path", &self.path) + .field("query", &self.query) + .field("fragment", &self.fragment) + .field("package_json", &self.package_json.as_ref().map(|p| &p.path)) + .finish() + } +} diff --git a/crates/mako/src/resolve/resource.rs b/crates/mako/src/resolve/resource.rs index 7de6e08af..6e9219481 100644 --- a/crates/mako/src/resolve/resource.rs +++ b/crates/mako/src/resolve/resource.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use oxc_resolver::Resolution; +use crate::resolve::Resolution; #[derive(Debug, Clone)] pub struct ExternalResource { diff --git a/e2e/fixtures/plugins/expect.js b/e2e/fixtures/plugins/expect.js index 848af158a..0b9f2e6e7 100644 --- a/e2e/fixtures/plugins/expect.js +++ b/e2e/fixtures/plugins/expect.js @@ -8,3 +8,6 @@ assert(content.includes(`children: "foo.bar"`), `jsx in foo.bar works`); assert(content.includes(`children: ".bar"`), `jsx in hoo.bar works`); assert(content.includes(`children: ".haha"`), `plugin in node_modules works`); assert(content.includes(`children: ".hoo"`), `relative plugin works`); + +// resolve_id hook +assert(content.includes(`resolve_id mocked`), `resolve_id hook works`); diff --git a/e2e/fixtures/plugins/plugins.config.js b/e2e/fixtures/plugins/plugins.config.js index 5331b5b42..f06c67aa1 100644 --- a/e2e/fixtures/plugins/plugins.config.js +++ b/e2e/fixtures/plugins/plugins.config.js @@ -19,5 +19,14 @@ module.exports = [ }; } } - } + }, + { + async resolveId(source, importer) { + if (source === 'resolve_id') { + console.log('resolveId', source, importer); + return { id: require('path').join(__dirname, 'resolve_id_mock.js') }; + } + return null; + } + }, ]; diff --git a/e2e/fixtures/plugins/resolve_id_mock.js b/e2e/fixtures/plugins/resolve_id_mock.js new file mode 100644 index 000000000..39e08b0a5 --- /dev/null +++ b/e2e/fixtures/plugins/resolve_id_mock.js @@ -0,0 +1 @@ +console.log('resolve_id mocked'); diff --git a/e2e/fixtures/plugins/src/index.tsx b/e2e/fixtures/plugins/src/index.tsx index a936a668a..ef1713285 100644 --- a/e2e/fixtures/plugins/src/index.tsx +++ b/e2e/fixtures/plugins/src/index.tsx @@ -2,3 +2,4 @@ console.log(require('./foo.bar')); console.log(require('./hoo.bar')); console.log(require('./foo.haha')); console.log(require('./foo.hoo')); +console.log(require('resolve_id')); diff --git a/packages/mako/binding.d.ts b/packages/mako/binding.d.ts index 1972f583e..f22f5540b 100644 --- a/packages/mako/binding.d.ts +++ b/packages/mako/binding.d.ts @@ -51,6 +51,7 @@ export interface JsHooks { }) => void; onGenerateFile?: (path: string, content: Buffer) => Promise; buildStart?: () => Promise; + resolveId?: (source: string, importer: string) => Promise<{ id: string }>; } export interface WriteFile { path: string; @@ -60,6 +61,9 @@ export interface LoadResult { content: string; type: string; } +export interface ResolveIdResult { + id: string; +} export interface BuildParams { root: string; config: { From b133fa32f8314b9df3365c37a319c0706c73532c Mon Sep 17 00:00:00 2001 From: sorrycc Date: Wed, 9 Oct 2024 17:39:55 +0800 Subject: [PATCH 2/3] chore: update docs --- docs/config.md | 1 + docs/config.zh-CN.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/config.md b/docs/config.md index 44205841e..a23b66808 100644 --- a/docs/config.md +++ b/docs/config.md @@ -544,6 +544,7 @@ Specify the plugins to use. }; }) => void; load?: (filePath: string) => Promise<{ content: string, type: 'css'|'js'|'jsx'|'ts'|'tsx' }>; + resolveId?: (id: string, importer: string) => Promise<{ id: string }>; } ``` diff --git a/docs/config.zh-CN.md b/docs/config.zh-CN.md index 152377c31..ef4bb0956 100644 --- a/docs/config.zh-CN.md +++ b/docs/config.zh-CN.md @@ -544,6 +544,7 @@ e.g. }; }) => void; load?: (filePath: string) => Promise<{ content: string, type: 'css'|'js'|'jsx'|'ts'|'tsx' }>; + resolveId?: (id: string, importer: string) => Promise<{ id: string }>; } ``` From fd5afd137ee28c4a3142666e5423dea3d5b488c3 Mon Sep 17 00:00:00 2001 From: sorrycc Date: Thu, 10 Oct 2024 10:54:41 +0800 Subject: [PATCH 3/3] feat: add external --- crates/binding/src/js_hook.rs | 1 + crates/binding/src/js_plugin.rs | 9 ++++++++- docs/config.md | 2 +- docs/config.zh-CN.md | 2 +- e2e/fixtures/plugins/expect.js | 1 + e2e/fixtures/plugins/plugins.config.js | 7 +++++-- e2e/fixtures/plugins/src/index.tsx | 1 + packages/mako/binding.d.ts | 1 + 8 files changed, 19 insertions(+), 5 deletions(-) diff --git a/crates/binding/src/js_hook.rs b/crates/binding/src/js_hook.rs index beab970a2..444b47681 100644 --- a/crates/binding/src/js_hook.rs +++ b/crates/binding/src/js_hook.rs @@ -109,4 +109,5 @@ pub struct LoadResult { #[napi(object, use_nullable = true)] pub struct ResolveIdResult { pub id: String, + pub external: Option, } diff --git a/crates/binding/src/js_plugin.rs b/crates/binding/src/js_plugin.rs index 41b056830..6d6b47ee5 100644 --- a/crates/binding/src/js_plugin.rs +++ b/crates/binding/src/js_plugin.rs @@ -10,7 +10,7 @@ use anyhow::{anyhow, Result}; use mako::ast::file::{Content, JsContent}; use mako::compiler::Context; use mako::plugin::{Plugin, PluginGenerateEndParams, PluginLoadParam}; -use mako::resolve::{Resolution, ResolvedResource, ResolverResource}; +use mako::resolve::{ExternalResource, Resolution, ResolvedResource, ResolverResource}; impl Plugin for JsPlugin { fn name(&self) -> &str { @@ -59,6 +59,13 @@ impl Plugin for JsPlugin { let x: Option = hook.call((source.to_string(), importer.to_string()))?; if let Some(x) = x { + if let Some(true) = x.external { + return Ok(Some(ResolverResource::External(ExternalResource { + source: source.to_string(), + external: source.to_string(), + script: None, + }))); + } return Ok(Some(ResolverResource::Resolved(ResolvedResource( Resolution { path: PathBuf::from(x.id), diff --git a/docs/config.md b/docs/config.md index a23b66808..869cc77d6 100644 --- a/docs/config.md +++ b/docs/config.md @@ -544,7 +544,7 @@ Specify the plugins to use. }; }) => void; load?: (filePath: string) => Promise<{ content: string, type: 'css'|'js'|'jsx'|'ts'|'tsx' }>; - resolveId?: (id: string, importer: string) => Promise<{ id: string }>; + resolveId?: (id: string, importer: string) => Promise<{ id: string, external: bool }>; } ``` diff --git a/docs/config.zh-CN.md b/docs/config.zh-CN.md index ef4bb0956..4e497031c 100644 --- a/docs/config.zh-CN.md +++ b/docs/config.zh-CN.md @@ -544,7 +544,7 @@ e.g. }; }) => void; load?: (filePath: string) => Promise<{ content: string, type: 'css'|'js'|'jsx'|'ts'|'tsx' }>; - resolveId?: (id: string, importer: string) => Promise<{ id: string }>; + resolveId?: (id: string, importer: string) => Promise<{ id: string, external: bool }>; } ``` diff --git a/e2e/fixtures/plugins/expect.js b/e2e/fixtures/plugins/expect.js index 0b9f2e6e7..433942d89 100644 --- a/e2e/fixtures/plugins/expect.js +++ b/e2e/fixtures/plugins/expect.js @@ -11,3 +11,4 @@ assert(content.includes(`children: ".hoo"`), `relative plugin works`); // resolve_id hook assert(content.includes(`resolve_id mocked`), `resolve_id hook works`); +assert(content.includes(`module.exports = resolve_id_external;`), `resolve_id hook with external works`); diff --git a/e2e/fixtures/plugins/plugins.config.js b/e2e/fixtures/plugins/plugins.config.js index f06c67aa1..81e88e0bb 100644 --- a/e2e/fixtures/plugins/plugins.config.js +++ b/e2e/fixtures/plugins/plugins.config.js @@ -22,9 +22,12 @@ module.exports = [ }, { async resolveId(source, importer) { + console.log('resolveId', source, importer); if (source === 'resolve_id') { - console.log('resolveId', source, importer); - return { id: require('path').join(__dirname, 'resolve_id_mock.js') }; + return { id: require('path').join(__dirname, 'resolve_id_mock.js'), external: false }; + } + if (source === 'resolve_id_external') { + return { id: 'resolve_id_external', external: true }; } return null; } diff --git a/e2e/fixtures/plugins/src/index.tsx b/e2e/fixtures/plugins/src/index.tsx index ef1713285..b53b90ae7 100644 --- a/e2e/fixtures/plugins/src/index.tsx +++ b/e2e/fixtures/plugins/src/index.tsx @@ -3,3 +3,4 @@ console.log(require('./hoo.bar')); console.log(require('./foo.haha')); console.log(require('./foo.hoo')); console.log(require('resolve_id')); +console.log(require('resolve_id_external')); diff --git a/packages/mako/binding.d.ts b/packages/mako/binding.d.ts index f22f5540b..138b79371 100644 --- a/packages/mako/binding.d.ts +++ b/packages/mako/binding.d.ts @@ -63,6 +63,7 @@ export interface LoadResult { } export interface ResolveIdResult { id: string; + external: boolean | null; } export interface BuildParams { root: string;