Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add freeze_module hooks #2092

Open
wants to merge 13 commits into
base: v2-dev
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
use std::sync::Arc;

use farmfe_core::{
config::config_regex::ConfigRegex,
context::CompilationContext,
error::{CompilationError, Result},
module::{ModuleId, ModuleMetaData, ModuleType},
plugin::PluginFreezeModuleHookParam,
serde::{Deserialize, Serialize},
swc_common::comments::SingleThreadedComments,
swc_ecma_ast::EsVersion,
swc_ecma_parser::{EsSyntax, Syntax},
};
use farmfe_toolkit::{
css::{codegen_css_stylesheet, parse_css_stylesheet, ParseCssModuleResult},
html::{codegen_html_document, parse_html_document},
script::{codegen_module, parse_module, CodeGenCommentsConfig, ParseScriptModuleResult},
};
use napi::{bindgen_prelude::FromNapiValue, NapiRaw};

use crate::{
new_js_plugin_hook,
plugin_adapters::js_plugin_adapter::thread_safe_js_plugin_hook::ThreadSafeJsPluginHook,
};

#[napi(object)]
pub struct JsPluginFreezeModuleHookFilters {
pub module_types: Vec<String>,
pub resolved_paths: Vec<String>,
}

pub struct PluginFreezeModuleHookFilters {
pub module_types: Vec<ModuleType>,
pub resolved_paths: Vec<ConfigRegex>,
}

impl From<JsPluginFreezeModuleHookFilters> for PluginFreezeModuleHookFilters {
fn from(value: JsPluginFreezeModuleHookFilters) -> Self {
Self {
module_types: value.module_types.into_iter().map(|ty| ty.into()).collect(),
resolved_paths: value
.resolved_paths
.into_iter()
.map(|p| ConfigRegex::new(&p))
.collect(),
}
}
}

pub struct JsPluginFreezeModuleHook {
tsfn: ThreadSafeJsPluginHook,
filters: PluginFreezeModuleHookFilters,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(crate = "farmfe_core::serde", rename_all = "camelCase")]
pub struct PluginFreezeModuleHookResult {
content: String,
source_map: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(crate = "farmfe_core::serde", rename_all = "camelCase")]
struct CompatiblePluginFreezeModuleHookParams {
module_id: ModuleId,
module_type: ModuleType,
content: String,
}

fn format_module_metadata_to_code(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can extract this method as a util which can be shared between different hook

param: &mut PluginFreezeModuleHookParam,
context: &Arc<CompilationContext>,
) -> Result<Option<String>> {
let source_map_enabled = !context.config.sourcemap.is_false();

Ok(match &mut *param.module.meta {
ModuleMetaData::Script(ref mut script_module_meta_data) => {
let cm = context.meta.get_module_source_map(&param.module.id);
let mut src_map = vec![];
let comments = std::mem::take(&mut script_module_meta_data.comments);
let single_threaded_comments = SingleThreadedComments::from(comments);

let code = codegen_module(
&script_module_meta_data.ast,
EsVersion::latest(),
cm.clone(),
if source_map_enabled {
Some(&mut src_map)
} else {
None
},
false,
Some(CodeGenCommentsConfig {
comments: &single_threaded_comments,
config: &context.config.comments,
}),
)
.map_err(|err| CompilationError::GenericError(err.to_string()))?;
// write back the comments
script_module_meta_data.comments = single_threaded_comments.into();
// append source map
if source_map_enabled {
let map = cm.build_source_map(&src_map);
let mut src_map = vec![];
map.to_writer(&mut src_map).map_err(|err| {
CompilationError::GenericError(format!("failed to write source map: {err:?}"))
})?;
param
.module
.source_map_chain
.push(Arc::new(String::from_utf8(src_map).unwrap()));
}

Some(String::from_utf8_lossy(&code).to_string())
}
ModuleMetaData::Css(ref mut css_module_meta_data) => {
let cm = context.meta.get_module_source_map(&param.module.id);
let (code, map) = codegen_css_stylesheet(
&css_module_meta_data.ast,
false,
if source_map_enabled {
Some(cm.clone())
} else {
None
},
);

if let Some(map) = map {
param.module.source_map_chain.push(Arc::new(map));
}

Some(code)
}
ModuleMetaData::Html(ref mut html_module_meta_data) => {
Some(codegen_html_document(&html_module_meta_data.ast, false))
}
ModuleMetaData::Custom(_) => None,
})
}

fn convert_code_to_metadata(
params: &mut PluginFreezeModuleHookParam,
code: Arc<String>,
source_map: Option<String>,
context: &Arc<CompilationContext>,
) -> Result<()> {
params.module.content = code.clone();

if let Some(source_map) = source_map {
params.module.source_map_chain.push(Arc::new(source_map));
}

let filename = params.module.id.to_string();

match &mut *params.module.meta {
ModuleMetaData::Script(ref mut script_module_meta_data) => {
let ParseScriptModuleResult {
ast,
comments,
source_map,
} = parse_module(
&params.module.id,
code,
match params.module.module_type {
ModuleType::Js | ModuleType::Ts => Syntax::Es(Default::default()),
ModuleType::Jsx | ModuleType::Tsx => Syntax::Es(EsSyntax {
jsx: true,
..Default::default()
}),
_ => Syntax::Es(Default::default()),
},
Default::default(),
)?;

context
.meta
.set_module_source_map(&params.module.id, source_map);

script_module_meta_data.ast = ast;
script_module_meta_data.comments = comments.into()
}
ModuleMetaData::Css(ref mut css_module_meta_data) => {
let ParseCssModuleResult {
ast,
comments,
source_map,
} = parse_css_stylesheet(&filename, code)?;

context
.meta
.set_module_source_map(&params.module.id, source_map);

css_module_meta_data.ast = ast;
css_module_meta_data.comments = comments.into();
}
ModuleMetaData::Html(ref mut html_module_meta_data) => {
let v = parse_html_document(&filename, code)?;

html_module_meta_data.ast = v;
}
ModuleMetaData::Custom(_) => {
return Ok(());
}
}

Ok(())
}

impl JsPluginFreezeModuleHook {
new_js_plugin_hook!(
PluginFreezeModuleHookFilters,
JsPluginFreezeModuleHookFilters,
CompatiblePluginFreezeModuleHookParams,
PluginFreezeModuleHookResult
);

pub fn call(
&self,
param: &mut PluginFreezeModuleHookParam,
ctx: Arc<CompilationContext>,
) -> Result<Option<()>> {
if self
.filters
.module_types
.contains(&param.module.module_type)
|| self
.filters
.resolved_paths
.iter()
.any(|m| m.is_match(param.module.id.to_string().as_str()))
{
let Some(result) = format_module_metadata_to_code(param, &ctx)? else {
return Ok(None);
};

let Some(result) = self
.tsfn
.call::<CompatiblePluginFreezeModuleHookParams, PluginFreezeModuleHookResult>(
CompatiblePluginFreezeModuleHookParams {
module_id: param.module.id.clone(),
module_type: param.module.module_type.clone(),
content: result,
},
ctx.clone(),
None,
)?
else {
return Ok(None);
};

convert_code_to_metadata(param, Arc::new(result.content), result.source_map, &ctx)?;

return Ok(None);
}

Ok(None)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ pub mod transform_html;
pub mod update_finished;
pub mod update_modules;
pub mod write_plugin_cache;
pub mod process_module;
pub mod process_module;
pub mod freeze_module;
18 changes: 18 additions & 0 deletions crates/node/src/plugin_adapters/js_plugin_adapter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use self::hooks::{
plugin_cache_loaded::JsPluginPluginCacheLoadedHook,
// render_resource_pot::JsPluginRenderResourcePotHook,
process_module::JsPluginProcessModuleHook,
freeze_module::JsPluginFreezeModuleHook,
render_start::JsPluginRenderStartHook,
resolve::JsPluginResolveHook,
transform::JsPluginTransformHook,
Expand Down Expand Up @@ -60,6 +61,7 @@ pub struct JsPluginAdapter {
js_transform_html_hook: Option<JsPluginTransformHtmlHook>,
js_update_finished_hook: Option<JsPluginUpdateFinishedHook>,
js_process_module_hook: Option<JsPluginProcessModuleHook>,
js_freeze_module_hook: Option<JsPluginFreezeModuleHook>,
}

impl JsPluginAdapter {
Expand Down Expand Up @@ -97,6 +99,8 @@ impl JsPluginAdapter {
get_named_property::<JsObject>(env, &js_plugin_object, "updateFinished").ok();
let process_module_obj =
get_named_property::<JsObject>(env, &js_plugin_object, "processModule").ok();
let freeze_module_obj =
get_named_property::<JsObject>(env, &js_plugin_object, "freezeModule").ok();

Ok(Self {
name,
Expand Down Expand Up @@ -126,6 +130,8 @@ impl JsPluginAdapter {
.map(|obj| JsPluginUpdateFinishedHook::new(env, obj)),
js_process_module_hook: process_module_obj
.map(|obj| JsPluginProcessModuleHook::new(env, obj)),
js_freeze_module_hook: freeze_module_obj
.map(|obj| JsPluginFreezeModuleHook::new(env, obj)),
})
}

Expand Down Expand Up @@ -261,6 +267,18 @@ impl Plugin for JsPluginAdapter {
Ok(None)
}

fn freeze_module(
&self,
param: &mut farmfe_core::plugin::PluginFreezeModuleHookParam,
context: &Arc<CompilationContext>,
) -> Result<Option<()>> {
if let Some(ref js_freeze_module_hook) = self.js_freeze_module_hook {
return js_freeze_module_hook.call(param, context.clone());
}

Ok(None)
}

fn build_end(&self, context: &Arc<CompilationContext>) -> Result<Option<()>> {
if let Some(js_build_end_hook) = &self.js_build_end_hook {
js_build_end_hook.call(EmptyPluginHookParam {}, context.clone())?;
Expand Down
4 changes: 3 additions & 1 deletion examples/lib/farm.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export default defineConfig({
input: {
index: "./index.ts"
},
output: {
targetEnv: 'node'
},
persistentCache: false,
resolve: {
alias: {
Expand All @@ -20,5 +23,4 @@ export default defineConfig({
// tsConfigPath: './tsconfig.json'
// })
// ]
plugins: ['@farmfe/plugin-dts']
});
4 changes: 3 additions & 1 deletion examples/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ export const a: number = 1;
import type { UserInfo } from '@/test2.ts';
// 我是奥特曼
export function b<T extends strin2222g>(name: string, userInfo: UserInfo): T {
return name + names + '123' as T;
return name + names as T;
}

console.log(b<string>('123', { name: '123' }))
16 changes: 14 additions & 2 deletions examples/refactor-react/farm.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,20 @@ export default defineConfig({
about: path.resolve(__dirname, 'about.html'),
},
progress: false,
output: {
publicPath: "/aaa/",
persistentCache: false,
sourcemap: false,
// output: {
// publicPath: "/aaa/",
// filename: '[ext]/[name]-[hash].[ext]',
// assetsFilename: 'assets/[name]-[hash].[ext]',
// },
partialBundling: {
groups: [
{
name: "vendor-react",
test: ["node_modules/"],
},
],
},
},
});
4 changes: 2 additions & 2 deletions js-plugins/tailwindcss/farm.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ export default defineConfig({
persistentCache: false,
progress: false
},
plugins: ['@farmfe/plugin-dts']
// plugins: [dts()]
// plugins: ['@farmfe/plugin-dts']
plugins: [dts()]
});
4 changes: 4 additions & 0 deletions packages/core/binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export interface JsPluginProcessModuleHookFilters {
moduleTypes: Array<string>
resolvedPaths: Array<string>
}
export interface JsPluginFreezeModuleHookFilters {
moduleTypes: Array<string>
resolvedPaths: Array<string>
}
export interface WatchDiffResult {
add: Array<string>
remove: Array<string>
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,6 @@ const FarmConfigSchema = z
export function parseUserConfig(config: UserConfig): UserConfig {
try {
const parsed = FarmConfigSchema.parse(config);
// TODO type not need `as UserConfig`
return parsed as UserConfig;
} catch (err) {
const validationError = fromZodError(err);
Expand Down
Loading
Loading