farmfe_core/plugin/
mod.rs

1use std::{any::Any, collections::HashMap, hash::Hash, sync::Arc};
2
3use farmfe_macro_cache_item::cache_item;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7  config::Config,
8  context::CompilationContext,
9  error::Result,
10  module::{
11    module_graph::ModuleGraph, module_group::ModuleGroupGraph, Module, ModuleId, ModuleMetaData,
12    ModuleType,
13  },
14  resource::{
15    resource_pot::{ResourcePot, ResourcePotInfo, ResourcePotMetaData},
16    Resource, ResourceType,
17  },
18  stats::Stats,
19};
20
21pub mod constants;
22pub mod plugin_driver;
23
24pub const DEFAULT_PRIORITY: i32 = 100;
25
26pub trait Plugin: Any + Send + Sync {
27  fn name(&self) -> &str;
28
29  fn priority(&self) -> i32 {
30    DEFAULT_PRIORITY
31  }
32
33  fn config(&self, _config: &mut Config) -> Result<Option<()>> {
34    Ok(None)
35  }
36
37  fn plugin_cache_loaded(
38    &self,
39    _cache: &Vec<u8>,
40    _context: &Arc<CompilationContext>,
41  ) -> Result<Option<()>> {
42    Ok(None)
43  }
44
45  fn build_start(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {
46    Ok(None)
47  }
48
49  fn resolve(
50    &self,
51    _param: &PluginResolveHookParam,
52    _context: &Arc<CompilationContext>,
53    _hook_context: &PluginHookContext,
54  ) -> Result<Option<PluginResolveHookResult>> {
55    Ok(None)
56  }
57
58  fn load(
59    &self,
60    _param: &PluginLoadHookParam,
61    _context: &Arc<CompilationContext>,
62    _hook_context: &PluginHookContext,
63  ) -> Result<Option<PluginLoadHookResult>> {
64    Ok(None)
65  }
66
67  fn transform(
68    &self,
69    _param: &PluginTransformHookParam,
70    _context: &Arc<CompilationContext>,
71  ) -> Result<Option<PluginTransformHookResult>> {
72    Ok(None)
73  }
74
75  fn parse(
76    &self,
77    _param: &PluginParseHookParam,
78    _context: &Arc<CompilationContext>,
79    _hook_context: &PluginHookContext,
80  ) -> Result<Option<ModuleMetaData>> {
81    Ok(None)
82  }
83
84  fn process_module(
85    &self,
86    _param: &mut PluginProcessModuleHookParam,
87    _context: &Arc<CompilationContext>,
88  ) -> Result<Option<()>> {
89    Ok(None)
90  }
91
92  fn analyze_deps(
93    &self,
94    _param: &mut PluginAnalyzeDepsHookParam,
95    _context: &Arc<CompilationContext>,
96  ) -> Result<Option<()>> {
97    Ok(None)
98  }
99
100  fn finalize_module(
101    &self,
102    _param: &mut PluginFinalizeModuleHookParam,
103    _context: &Arc<CompilationContext>,
104  ) -> Result<Option<()>> {
105    Ok(None)
106  }
107
108  /// The module graph should be constructed and finalized here
109  fn build_end(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {
110    Ok(None)
111  }
112
113  fn generate_start(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {
114    Ok(None)
115  }
116
117  /// Some optimization of the module graph should be performed here, for example, tree shaking, scope hoisting
118  fn optimize_module_graph(
119    &self,
120    _module_graph: &mut ModuleGraph,
121    _context: &Arc<CompilationContext>,
122  ) -> Result<Option<()>> {
123    Ok(None)
124  }
125
126  /// Analyze module group based on module graph
127  fn analyze_module_graph(
128    &self,
129    _module_graph: &mut ModuleGraph,
130    _context: &Arc<CompilationContext>,
131    _hook_context: &PluginHookContext,
132  ) -> Result<Option<ModuleGroupGraph>> {
133    Ok(None)
134  }
135
136  /// partial bundling modules to [Vec<ResourcePot>]
137  fn partial_bundling(
138    &self,
139    _modules: &Vec<ModuleId>,
140    _context: &Arc<CompilationContext>,
141    _hook_context: &PluginHookContext,
142  ) -> Result<Option<Vec<ResourcePot>>> {
143    Ok(None)
144  }
145
146  /// process resource graph before render and generating each resource
147  fn process_resource_pots(
148    &self,
149    _resource_pots: &mut Vec<&mut ResourcePot>,
150    _context: &Arc<CompilationContext>,
151  ) -> Result<Option<()>> {
152    Ok(None)
153  }
154
155  fn render_start(
156    &self,
157    _config: &Config,
158    _context: &Arc<CompilationContext>,
159  ) -> Result<Option<()>> {
160    Ok(None)
161  }
162
163  fn render_resource_pot_modules(
164    &self,
165    _resource_pot: &ResourcePot,
166    _context: &Arc<CompilationContext>,
167    _hook_context: &PluginHookContext,
168  ) -> Result<Option<ResourcePotMetaData>> {
169    Ok(None)
170  }
171
172  /// Transform rendered bundled code for the given resource_pot
173  fn render_resource_pot(
174    &self,
175    _resource_pot: &PluginRenderResourcePotHookParam,
176    _context: &Arc<CompilationContext>,
177  ) -> Result<Option<PluginRenderResourcePotHookResult>> {
178    Ok(None)
179  }
180
181  fn augment_resource_hash(
182    &self,
183    _render_pot_info: &ResourcePotInfo,
184    _context: &Arc<CompilationContext>,
185  ) -> Result<Option<String>> {
186    Ok(None)
187  }
188
189  /// Optimize the resource pot, for example, minimize
190  fn optimize_resource_pot(
191    &self,
192    _resource: &mut ResourcePot,
193    _context: &Arc<CompilationContext>,
194  ) -> Result<Option<()>> {
195    Ok(None)
196  }
197
198  /// Generate resources based on the [ResourcePot], return [Resource] and [Option<SourceMap>]
199  fn generate_resources(
200    &self,
201    _resource_pot: &mut ResourcePot,
202    _context: &Arc<CompilationContext>,
203    _hook_context: &PluginHookContext,
204  ) -> Result<Option<PluginGenerateResourcesHookResult>> {
205    Ok(None)
206  }
207
208  /// Process generated resources after the file name of the resource is hashed
209  fn process_generated_resources(
210    &self,
211    _resources: &mut PluginGenerateResourcesHookResult,
212    _context: &Arc<CompilationContext>,
213  ) -> Result<Option<()>> {
214    Ok(None)
215  }
216
217  /// handle entry resource after all resources are generated and processed.
218  /// For example, insert the generated resources into html
219  fn handle_entry_resource(
220    &self,
221    _resource: &mut PluginHandleEntryResourceHookParams,
222    _context: &Arc<CompilationContext>,
223  ) -> Result<Option<()>> {
224    Ok(None)
225  }
226
227  /// Do some finalization work on the generated resources, for example, add hash to the file name,
228  /// or insert the generated resources into html
229  fn finalize_resources(
230    &self,
231    _param: &mut PluginFinalizeResourcesHookParams,
232    _context: &Arc<CompilationContext>,
233  ) -> Result<Option<()>> {
234    Ok(None)
235  }
236
237  fn generate_end(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {
238    Ok(None)
239  }
240
241  fn finish(&self, _stat: &Stats, _context: &Arc<CompilationContext>) -> Result<Option<()>> {
242    Ok(None)
243  }
244
245  /// Called when calling compiler.update(module_paths).
246  /// Useful to do some operations like clearing previous state or ignore some files when performing HMR
247  fn update_modules(
248    &self,
249    _params: &mut PluginUpdateModulesHookParams,
250    _context: &Arc<CompilationContext>,
251  ) -> Result<Option<()>> {
252    Ok(None)
253  }
254
255  /// Called when calling compiler.update(module_paths).
256  /// Useful to do some operations like modifying the module graph
257  fn module_graph_updated(
258    &self,
259    _param: &PluginModuleGraphUpdatedHookParams,
260    _context: &Arc<CompilationContext>,
261  ) -> Result<Option<()>> {
262    Ok(None)
263  }
264
265  /// Called when calling compiler.update(module_paths).
266  /// This hook is called after all compilation work is done, including the resources regeneration and finalization.
267  fn update_finished(&self, _context: &Arc<CompilationContext>) -> Result<Option<()>> {
268    Ok(None)
269  }
270
271  // Called when hit persistent cache. return false to invalidate the cache
272  fn handle_persistent_cached_module(
273    &self,
274    _module: &Module,
275    _context: &Arc<CompilationContext>,
276  ) -> Result<Option<bool>> {
277    Ok(None)
278  }
279
280  fn write_plugin_cache(&self, _context: &Arc<CompilationContext>) -> Result<Option<Vec<u8>>> {
281    Ok(None)
282  }
283}
284
285#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
286#[serde(rename_all = "camelCase")]
287#[cache_item]
288pub enum ResolveKind {
289  /// entry input in the config
290  Entry(String),
291  /// static import, e.g. `import a from './a'`
292  #[default]
293  Import,
294  /// static export, e.g. `export * from './a'`
295  ExportFrom,
296  /// dynamic import, e.g. `import('./a').then(module => console.log(module))`
297  DynamicImport,
298  /// cjs require, e.g. `require('./a')`
299  Require,
300  /// @import of css, e.g. @import './a.css'
301  CssAtImport,
302  /// url() of css, e.g. url('./a.png')
303  CssUrl,
304  /// `<script src="./index.html" />` of html
305  ScriptSrc,
306  /// `<link href="index.css" />` of html
307  LinkHref,
308  /// Hmr update
309  HmrUpdate,
310  /// Custom ResolveKind, e.g. `const worker = new Worker(new Url("worker.js"))` of a web worker
311  Custom(String),
312}
313
314impl ResolveKind {
315  /// dynamic if self is [ResolveKind::DynamicImport] or [ResolveKind::Custom("dynamic:xxx")] (dynamic means the module is loaded dynamically, for example, fetch from network)
316  /// used when analyzing module groups
317  pub fn is_dynamic(&self) -> bool {
318    matches!(self, ResolveKind::DynamicImport)
319      || matches!(self, ResolveKind::Custom(c) if c.starts_with("dynamic:"))
320  }
321
322  pub fn is_export_from(&self) -> bool {
323    matches!(self, ResolveKind::ExportFrom)
324  }
325
326  pub fn is_require(&self) -> bool {
327    matches!(self, ResolveKind::Require)
328  }
329}
330
331impl From<&str> for ResolveKind {
332  fn from(value: &str) -> Self {
333    serde_json::from_str(value).unwrap()
334  }
335}
336
337impl From<ResolveKind> for String {
338  fn from(value: ResolveKind) -> Self {
339    serde_json::to_string(&value).unwrap()
340  }
341}
342
343/// Plugin hook call context, designed for `first type` hook, used to provide info when call plugins from another plugin
344#[derive(Debug, Clone, Serialize, Deserialize, Default)]
345pub struct PluginHookContext {
346  /// if this hook is called by the compiler, its value is [None]
347  /// if this hook is called by other plugins, its value is set by the caller plugins.
348  pub caller: Option<String>,
349  /// meta data passed between plugins
350  pub meta: HashMap<String, String>,
351}
352
353impl PluginHookContext {
354  fn caller_format<T: AsRef<str>>(name: T) -> String {
355    format!("[{}]", name.as_ref())
356  }
357
358  pub fn add_caller<T: AsRef<str>>(&self, name: T) -> Option<String> {
359    match self.caller.as_ref() {
360      Some(c) => Some(format!("{}{}", c, Self::caller_format(name))),
361      None => Some(Self::caller_format(name)),
362    }
363  }
364  pub fn contain_caller<T: AsRef<str>>(&self, name: T) -> bool {
365    if let Some(ref s) = self.caller {
366      s.contains(&Self::caller_format(name))
367    } else {
368      false
369    }
370  }
371}
372
373/// Parameter of the resolve hook
374#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
375#[serde(rename_all = "camelCase")]
376pub struct PluginResolveHookParam {
377  /// the source would like to resolve, for example, './index'
378  pub source: String,
379  /// the start location to resolve `specifier`, being [None] if resolving a entry or resolving a hmr update.
380  pub importer: Option<ModuleId>,
381  /// for example, [ResolveKind::Import] for static import (`import a from './a'`)
382  pub kind: ResolveKind,
383}
384
385#[derive(Debug, Serialize, Deserialize, Clone)]
386#[serde(rename_all = "camelCase", default)]
387pub struct PluginResolveHookResult {
388  /// resolved path, normally a absolute file path.
389  pub resolved_path: String,
390  /// whether this module should be external, if true, the module won't present in the final result
391  pub external: bool,
392  /// whether this module has side effects, affects tree shaking. By default, it's true, means all modules may has side effects.
393  /// use sideEffects field in package.json to mark it as side effects free
394  pub side_effects: bool,
395  /// the query parsed from specifier, for example, query should be `{ inline: "" }` if specifier is `./a.png?inline`
396  /// if you custom plugins, your plugin should be responsible for parsing query
397  /// if you just want a normal query parsing like the example above, [farmfe_toolkit::resolve::parse_query] should be helpful
398  pub query: Vec<(String, String)>,
399  /// the meta data passed between plugins and hooks
400  pub meta: HashMap<String, String>,
401}
402
403impl Default for PluginResolveHookResult {
404  fn default() -> Self {
405    Self {
406      side_effects: true,
407      resolved_path: "unknown".to_string(),
408      external: false,
409      query: vec![],
410      meta: Default::default(),
411    }
412  }
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
416#[serde(rename_all = "camelCase")]
417pub struct PluginLoadHookParam<'a> {
418  /// the module id string
419  pub module_id: String,
420  /// the resolved path from resolve hook
421  pub resolved_path: &'a str,
422  /// the query map
423  pub query: Vec<(String, String)>,
424  /// the meta data passed between plugins and hooks
425  pub meta: HashMap<String, String>,
426}
427
428#[derive(Debug, Serialize, Deserialize)]
429#[serde(rename_all = "camelCase")]
430pub struct PluginLoadHookResult {
431  /// the source content of the module
432  pub content: String,
433  /// the type of the module, for example [ModuleType::Js] stands for a normal javascript file,
434  /// usually end with `.js` extension
435  pub module_type: ModuleType,
436  /// source map of the module
437  pub source_map: Option<String>,
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize)]
441#[serde(rename_all = "camelCase")]
442pub struct PluginTransformHookParam<'a> {
443  /// the module id string
444  pub module_id: String,
445  /// source content after load or transformed result of previous plugin
446  pub content: String,
447  /// module type after load
448  pub module_type: ModuleType,
449  /// resolved path from resolve hook
450  pub resolved_path: &'a str,
451  /// query from resolve hook
452  pub query: Vec<(String, String)>,
453  /// the meta data passed between plugins and hooks
454  pub meta: HashMap<String, String>,
455  /// source map chain of previous plugins
456  pub source_map_chain: Vec<Arc<String>>,
457}
458
459#[derive(Debug, Default, Serialize, Deserialize)]
460#[serde(rename_all = "camelCase", default)]
461pub struct PluginTransformHookResult {
462  /// transformed source content, will be passed to next plugin.
463  pub content: String,
464  /// you can change the module type after transform.
465  pub module_type: Option<ModuleType>,
466  /// transformed source map, all plugins' transformed source map will be stored as a source map chain.
467  pub source_map: Option<String>,
468  /// if true, the previous source map chain will be ignored, and the source map chain will be reset to [source_map] returned by this plugin.
469  pub ignore_previous_source_map: bool,
470}
471
472#[derive(Debug, Serialize, Deserialize)]
473pub struct PluginParseHookParam {
474  /// module id
475  pub module_id: ModuleId,
476  /// resolved path
477  pub resolved_path: String,
478  /// resolved query
479  pub query: Vec<(String, String)>,
480  pub module_type: ModuleType,
481  /// source content(after transform)
482  pub content: Arc<String>,
483}
484
485pub struct PluginProcessModuleHookParam<'a> {
486  pub module_id: &'a ModuleId,
487  pub module_type: &'a ModuleType,
488  pub content: Arc<String>,
489  pub meta: &'a mut ModuleMetaData,
490}
491
492#[derive(Clone)]
493pub struct PluginAnalyzeDepsHookParam<'a> {
494  pub module: &'a Module,
495  /// analyzed deps from previous plugins, you can push new entries to it for your plugin.
496  pub deps: Vec<PluginAnalyzeDepsHookResultEntry>,
497}
498
499#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
500#[cache_item]
501pub struct PluginAnalyzeDepsHookResultEntry {
502  pub source: String,
503  pub kind: ResolveKind,
504}
505
506pub struct PluginFinalizeModuleHookParam<'a> {
507  pub module: &'a mut Module,
508  pub deps: &'a Vec<PluginAnalyzeDepsHookResultEntry>,
509}
510
511#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone)]
512pub struct WatchDiffResult {
513  pub add: Vec<String>,
514  pub remove: Vec<String>,
515}
516
517/// The output after the updating process
518#[derive(Debug, Clone, Default, Serialize, Deserialize)]
519#[serde(rename_all = "camelCase", default)]
520pub struct UpdateResult {
521  pub added_module_ids: Vec<ModuleId>,
522  pub updated_module_ids: Vec<ModuleId>,
523  pub removed_module_ids: Vec<ModuleId>,
524  /// Javascript module map string, the key is the module id, the value is the module function
525  /// This code string should be returned to the client side as MIME type `application/javascript`
526  pub immutable_resources: String,
527  pub mutable_resources: String,
528  pub boundaries: HashMap<String, Vec<Vec<String>>>,
529  pub dynamic_resources_map: Option<HashMap<ModuleId, Vec<(String, ResourceType)>>>,
530  pub extra_watch_result: WatchDiffResult,
531}
532#[derive(Debug, Clone, Serialize, Deserialize)]
533pub enum UpdateType {
534  // added a new module
535  Added,
536  // updated a module
537  Updated,
538  // removed a module
539  Removed,
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize)]
543#[serde(rename_all = "camelCase", default)]
544pub struct PluginUpdateModulesHookParams {
545  pub paths: Vec<(String, UpdateType)>,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize)]
549#[serde(rename_all = "camelCase", default)]
550pub struct PluginModuleGraphUpdatedHookParams {
551  pub added_modules_ids: Vec<ModuleId>,
552  pub removed_modules_ids: Vec<ModuleId>,
553  pub updated_modules_ids: Vec<ModuleId>,
554}
555
556#[derive(Debug, Serialize, Deserialize)]
557pub struct EmptyPluginHookParam {}
558
559#[derive(Debug, Serialize, Deserialize)]
560pub struct EmptyPluginHookResult {}
561
562#[cache_item]
563#[derive(Debug, Clone, Serialize, Deserialize)]
564#[serde(rename_all = "camelCase")]
565pub struct PluginGenerateResourcesHookResult {
566  pub resource: Resource,
567  pub source_map: Option<Resource>,
568}
569
570#[derive(Debug, Clone, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct PluginRenderResourcePotHookParam {
573  pub content: Arc<String>,
574  pub source_map_chain: Vec<Arc<String>>,
575  pub resource_pot_info: ResourcePotInfo,
576}
577
578#[derive(Debug, Serialize, Deserialize)]
579pub struct PluginRenderResourcePotHookResult {
580  pub content: String,
581  pub source_map: Option<String>,
582}
583
584pub struct PluginDriverRenderResourcePotHookResult {
585  pub content: Arc<String>,
586  pub source_map_chain: Vec<Arc<String>>,
587}
588
589pub struct PluginFinalizeResourcesHookParams<'a> {
590  pub resources_map: &'a mut HashMap<String, Resource>,
591  pub config: &'a Config,
592}
593
594pub struct PluginHandleEntryResourceHookParams<'a> {
595  pub resource: &'a mut Resource,
596  pub module_graph: &'a ModuleGraph,
597  pub module_group_graph: &'a ModuleGroupGraph,
598  pub entry_module_id: &'a ModuleId,
599}