diff --git a/configs/farm-js-plugin.base.config.mjs b/configs/farm-js-plugin.base.config.mjs index 57e9b229a..aa2640c56 100644 --- a/configs/farm-js-plugin.base.config.mjs +++ b/configs/farm-js-plugin.base.config.mjs @@ -1,46 +1,47 @@ -import { builtinModules } from 'module'; +import { builtinModules } from "module"; -const format = process.env.FARM_FORMAT || 'cjs'; -const ext = format === 'esm' ? 'mjs' : 'cjs'; +const format = process.env.FARM_FORMAT || "cjs"; +const ext = format === "esm" ? "mjs" : "cjs"; export function createFarmJsPluginBuildConfig(plugins, options = {}) { return { compilation: { input: { - index: './src/index.ts' + index: "./src/index.ts", }, output: { path: `build/${format}`, entryFilename: `[entryName].${ext}`, - targetEnv: 'node', - format + targetEnv: "node", + format, }, external: [ - '@farmfe/core', + "@farmfe/core", ...builtinModules.map((m) => `^${m}$`), ...builtinModules.map((m) => `^node:${m}$`), - ...(options.external || []) + ...(options.external || []), ], partialBundling: { enforceResources: [ { - name: 'index.js', - test: ['.+'] - } - ] + name: "index.js", + test: [".+"], + }, + ], }, minify: false, sourcemap: false, presetEnv: false, - persistentCache: { - envs: { - FARM_FORMAT: format - } - } + persistentCache: false, + // persistentCache: { + // envs: { + // FARM_FORMAT: format + // } + // } }, server: { - hmr: false + hmr: false, }, - plugins + plugins, }; } diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 9bd4fb564..53c2fa696 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -140,10 +140,10 @@ impl Compiler { } // triggering build stage - let res = { + { #[cfg(feature = "profile")] farmfe_core::puffin::profile_scope!("Build Stage"); - self.build() + self.build()? }; self.context.record_manager.set_build_end_time(); @@ -179,7 +179,7 @@ impl Compiler { self.context.record_manager.set_end_time(); - res + Ok(()) } pub fn context(&self) -> &Arc { diff --git a/crates/compiler/src/update/regenerate_resources/mod.rs b/crates/compiler/src/update/regenerate_resources/mod.rs index 4595e65ff..7e6febd23 100644 --- a/crates/compiler/src/update/regenerate_resources/mod.rs +++ b/crates/compiler/src/update/regenerate_resources/mod.rs @@ -80,64 +80,64 @@ pub fn render_and_generate_update_resource( |resource_pot: &mut ResourcePot| -> farmfe_core::error::Result { let async_modules = context.custom.get(ASYNC_MODULES).unwrap(); let async_modules = async_modules.downcast_ref::>().unwrap(); - if !resource_pot.modules().is_empty() { - let RenderedJsResourcePot { - mut bundle, - rendered_modules, - .. - } = resource_pot_to_runtime_object(resource_pot, &module_graph, async_modules, context)?; - bundle.prepend("("); - bundle.append(")", None); - - let mut rendered_map_chain = vec![]; - - if context.config.sourcemap.enabled(resource_pot.immutable) { - let root = context.config.root.clone(); - let map = bundle - .generate_map(SourceMapOptions { - include_content: Some(true), - remap_source: Some(Box::new(move |src| format!("/{}", relative(&root, src)))), - ..Default::default() - }) - .map_err(|_| CompilationError::GenerateSourceMapError { - id: resource_pot.id.clone(), - })?; - - let mut buf = vec![]; - map.to_writer(&mut buf).expect("failed to write sourcemap"); - rendered_map_chain.push(Arc::new(String::from_utf8(buf).unwrap())); - } - // The hmr result should alway be a js resource - resource_pot.meta = ResourcePotMetaData { - rendered_modules, - rendered_content: Arc::new(bundle.to_string()), - rendered_map_chain, - ..Default::default() - }; - - let (mut update_resources, _) = render_resource_pot_generate_resources( - resource_pot, - context, - &Default::default(), - true, - &mut None, - )?; - - if let Some(map) = update_resources.source_map { - // inline source map - update_resources.resource.bytes.append( - &mut format!( - "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,{}", - base64_encode(&map.bytes) - ) - .into_bytes(), - ); - } - - let code = String::from_utf8(update_resources.resource.bytes).unwrap(); - - return Ok(code); - } + // if !resource_pot.modules().is_empty() { + // let RenderedJsResourcePot { + // mut bundle, + // rendered_modules, + // .. + // } = resource_pot_to_runtime_object(resource_pot, &module_graph, async_modules, context)?; + // bundle.prepend("("); + // bundle.append(")", None); + + // let mut rendered_map_chain = vec![]; + + // if context.config.sourcemap.enabled(resource_pot.immutable) { + // let root = context.config.root.clone(); + // let map = bundle + // .generate_map(SourceMapOptions { + // include_content: Some(true), + // remap_source: Some(Box::new(move |src| format!("/{}", relative(&root, src)))), + // ..Default::default() + // }) + // .map_err(|_| CompilationError::GenerateSourceMapError { + // id: resource_pot.id.clone(), + // })?; + + // let mut buf = vec![]; + // map.to_writer(&mut buf).expect("failed to write sourcemap"); + // rendered_map_chain.push(Arc::new(String::from_utf8(buf).unwrap())); + // } + // // The hmr result should alway be a js resource + // resource_pot.meta = ResourcePotMetaData { + // rendered_modules, + // rendered_content: Arc::new(bundle.to_string()), + // rendered_map_chain, + // ..Default::default() + // }; + + // let (mut update_resources, _) = render_resource_pot_generate_resources( + // resource_pot, + // context, + // &Default::default(), + // true, + // &mut None, + // )?; + + // if let Some(map) = update_resources.source_map { + // // inline source map + // update_resources.resource.bytes.append( + // &mut format!( + // "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,{}", + // base64_encode(&map.bytes) + // ) + // .into_bytes(), + // ); + // } + + // let code = String::from_utf8(update_resources.resource.bytes).unwrap(); + + // return Ok(code); + // } Ok("{}".to_string()) }; diff --git a/crates/core/src/context/mod.rs b/crates/core/src/context/mod.rs index 2ff2c610b..a008effef 100644 --- a/crates/core/src/context/mod.rs +++ b/crates/core/src/context/mod.rs @@ -1,10 +1,14 @@ -use std::{any::Any, path::Path, sync::Arc}; +use std::{ + any::Any, + path::{Path, PathBuf}, + sync::Arc, +}; use dashmap::DashMap; use parking_lot::{Mutex, RwLock}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use swc_common::Globals; +use swc_common::{FileName, Globals, SourceFile, SourceMap}; use crate::{ cache::CacheManager, @@ -207,13 +211,40 @@ impl Default for ContextMetaData { /// Shared script meta data used for [swc] pub struct ScriptContextMetaData { pub globals: Globals, + pub module_source_maps: DashMap, Arc)>, } impl ScriptContextMetaData { pub fn new() -> Self { Self { globals: Globals::new(), + module_source_maps: DashMap::new(), + } + } + + /// create a swc source map from a source + pub fn create_swc_source_map( + &self, + module_id: &ModuleId, + content: Arc, + ) -> (Arc, Arc) { + // if the source map already exists, return it + if let Some(value) = self.module_source_maps.get(module_id) { + return (value.0.clone(), value.1.clone()); } + + let cm = Arc::new(SourceMap::default()); + let sf = cm.new_source_file_from( + Arc::new(FileName::Real(PathBuf::from(module_id.to_string()))), + content, + ); + + // store the source map and source file + self + .module_source_maps + .insert(module_id.clone(), (cm.clone(), sf.clone())); + + (cm, sf) } } diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 393113083..ec666fc02 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -8,9 +8,10 @@ use std::{ use farmfe_core::{ config::{ - config_regex::ConfigRegex, external::ExternalConfig, - partial_bundling::PartialBundlingEnforceResourceConfig, AliasItem, Config, ModuleFormat, - StringOrRegex, TargetEnv, FARM_MODULE_SYSTEM, + config_regex::ConfigRegex, + external::{self, ExternalConfig}, + partial_bundling::PartialBundlingEnforceResourceConfig, + AliasItem, Config, ModuleFormat, StringOrRegex, TargetEnv, FARM_MODULE_SYSTEM, }, context::CompilationContext, enhanced_magic_string::types::{MappingsOptionHires, SourceMapOptions}, @@ -277,11 +278,8 @@ impl Plugin for FarmPluginRuntime { let async_modules = async_modules.downcast_ref::>().unwrap(); let module_graph = context.module_graph.read(); let external_config = ExternalConfig::from(&*context.config); - let RenderedJsResourcePot { - mut bundle, - rendered_modules, - external_modules, - } = resource_pot_to_runtime_object(resource_pot, &module_graph, async_modules, context)?; + let (mut code, map, external_modules) = + resource_pot_to_runtime_object(resource_pot, &module_graph, async_modules, context)?; let mut external_modules_str = None; @@ -290,6 +288,13 @@ impl Plugin for FarmPluginRuntime { &context.config.output.target_env, ); + let external_modules = external_modules + .into_iter() + .map(|id| id.to_string()) + .collect::>(); + let mut external_modules = external_modules.into_iter().collect::>(); + external_modules.sort(); + // inject global externals if !external_modules.is_empty() && context.config.output.target_env == TargetEnv::Node { let mut import_strings = vec![]; @@ -336,10 +341,10 @@ impl Plugin for FarmPluginRuntime { for source in external_modules { let replace_source = external_config - .find_match(&source) - .map(|v| v.source(&source)) + .find_match(&source.to_string()) + .map(|v| v.source(&source.to_string())) // it's maybe from plugin - .unwrap_or(source.clone()); + .unwrap_or(source.to_string()); let source_obj = format!("window['{replace_source}']||{{}}"); external_objs.push(if context.config.output.format == ModuleFormat::EsModule { @@ -356,59 +361,44 @@ impl Plugin for FarmPluginRuntime { external_modules_str = Some(prepend_str); } - let is_target_node_and_cjs = context.config.output.target_env == TargetEnv::Node - && context.config.output.format == ModuleFormat::CommonJs; - - let str = format!( - r#"(function(_){{for(var r in _){{_[r].__farm_resource_pot__={};{farm_global_this}.{FARM_MODULE_SYSTEM}.register(r,_[r])}}}})("#, - if is_target_node_and_cjs { - "'file://'+__filename".to_string() - } else { - // TODO make it final output file name - format!("'{}'", resource_pot.name.to_string() + ".js") - }, - ); - - bundle.prepend(&str); - bundle.append(");", None); - if let Some(external_modules_str) = external_modules_str { - bundle.prepend(&external_modules_str); + code = external_modules_str + code.as_str(); } return Ok(Some(ResourcePotMetaData { - rendered_modules, - rendered_content: Arc::new(bundle.to_string()), - rendered_map_chain: if context.config.sourcemap.enabled(resource_pot.immutable) { - let root = context.config.root.clone(); - let map = bundle - .generate_map(SourceMapOptions { - include_content: Some(true), - remap_source: Some(Box::new(move |src| { - format!("/{}", farmfe_utils::relative(&root, src)) - })), - hires: if context.config.minify.enabled() { - Some(MappingsOptionHires::Boundary) - } else { - None - }, - ..Default::default() - }) - .map_err(|_| CompilationError::GenerateSourceMapError { - id: resource_pot.id.to_string(), - })?; - let mut buf = vec![]; - map - .to_writer(&mut buf) - .map_err(|e| CompilationError::RenderScriptModuleError { - id: resource_pot.id.to_string(), - source: Some(Box::new(e)), - })?; - - vec![Arc::new(String::from_utf8(buf).unwrap())] - } else { - vec![] - }, + rendered_modules: HashMap::new(), + rendered_content: Arc::new(code), + rendered_map_chain: map.map(|m| vec![m]).unwrap_or(vec![]), + // rendered_map_chain: if context.config.sourcemap.enabled(resource_pot.immutable) { + // let root = context.config.root.clone(); + // let map = bundle + // .generate_map(SourceMapOptions { + // include_content: Some(true), + // remap_source: Some(Box::new(move |src| { + // format!("/{}", farmfe_utils::relative(&root, src)) + // })), + // hires: if context.config.minify.enabled() { + // Some(MappingsOptionHires::Boundary) + // } else { + // None + // }, + // ..Default::default() + // }) + // .map_err(|_| CompilationError::GenerateSourceMapError { + // id: resource_pot.id.to_string(), + // })?; + // let mut buf = vec![]; + // map + // .to_writer(&mut buf) + // .map_err(|e| CompilationError::RenderScriptModuleError { + // id: resource_pot.id.to_string(), + // source: Some(Box::new(e)), + // })?; + + // vec![Arc::new(String::from_utf8(buf).unwrap())] + // } else { + // vec![] + // }, ..Default::default() })); } diff --git a/crates/plugin_runtime/src/parse.rs b/crates/plugin_runtime/src/parse.rs deleted file mode 100644 index d5c8500a0..000000000 --- a/crates/plugin_runtime/src/parse.rs +++ /dev/null @@ -1,50 +0,0 @@ -/// This file is not used and is only for reference purposes. -use std::sync::Arc; - -use farmfe_core::{ - context::CompilationContext, - module::ModuleType, - swc_common::BytePos, - swc_ecma_ast::{EsVersion, Expr, Module, ModuleItem, Script, Stmt}, - swc_ecma_parser::{lexer::Lexer, Parser, StringInput}, -}; -use farmfe_toolkit::script::syntax_from_module_type; - -pub struct ParserUtils<'a>(Parser>); - -impl<'a> ParserUtils<'a> { - pub fn parse<'b, T>(source: T, context: &Arc) -> ParserUtils<'b> - where - T: Into<&'b str>, - { - let syntax = - syntax_from_module_type(&ModuleType::Js, context.config.script.parser.clone()).unwrap(); - - let input = StringInput::new(source.into(), BytePos::DUMMY, BytePos::DUMMY); - // let comments = SingleThreadedComments::default(); - let lexer = Lexer::new(syntax, EsVersion::EsNext, input, None); - - let mut parser = Parser::new_from(lexer); - // parser.parse_stmt(top_level) - ParserUtils(parser) - } - - pub fn parse_stmt(mut self, top_level: bool) -> Stmt { - self.0.parse_stmt(top_level).unwrap() - } -} - -macro_rules! impl_parse { - ($name:ident, $r:ty) => { - impl<'a> ParserUtils<'a> { - pub fn $name(mut self) -> $r { - self.0.$name().unwrap() - } - } - }; -} - -impl_parse!(parse_module, Module); -impl_parse!(parse_module_item, ModuleItem); -impl_parse!(parse_script, Script); -impl_parse!(parse_expr, Box); diff --git a/crates/plugin_runtime/src/render_resource_pot/mod.rs b/crates/plugin_runtime/src/render_resource_pot/mod.rs index 76574487a..274e3e9ed 100644 --- a/crates/plugin_runtime/src/render_resource_pot/mod.rs +++ b/crates/plugin_runtime/src/render_resource_pot/mod.rs @@ -1,12 +1,13 @@ use std::{ collections::{HashMap, HashSet}, + path::PathBuf, sync::Arc, }; use farmfe_core::{ cache::cache_store::CacheStoreKey, cache_item, - config::minify::MinifyMode, + config::{minify::MinifyMode, FARM_MODULE_SYSTEM}, context::CompilationContext, deserialize, enhanced_magic_string::{ @@ -19,16 +20,27 @@ use farmfe_core::{ rayon::iter::{IntoParallelIterator, ParallelIterator}, resource::resource_pot::{RenderedModule, ResourcePot}, serialize, + swc_common::DUMMY_SP, + swc_ecma_ast::{ + EsVersion, Expr, ExprOrSpread, KeyValueProp, Lit, ObjectLit, Prop, PropName, PropOrSpread, + }, + swc_ecma_parser::{EsSyntax, Syntax}, +}; +use farmfe_toolkit::{ + common::{build_source_map, collapse_sourcemap, MinifyBuilder, Source}, + html::get_farm_global_this, + script::{codegen_module, parse_module, CodeGenCommentsConfig, ParseScriptModuleResult}, }; -use farmfe_toolkit::common::MinifyBuilder; use farmfe_utils::hash::sha256; use render_module::RenderModuleOptions; +use render_resource_pot_ast::{render_resource_pot_ast, RenderResourcePotAstResult}; use self::render_module::{render_module, RenderModuleResult}; mod render_module; // mod farm_module_system; +mod render_resource_pot_ast; mod source_replacer; mod transform_async_module; mod transform_module_decls; @@ -58,15 +70,15 @@ pub fn resource_pot_to_runtime_object( module_graph: &ModuleGraph, async_modules: &HashSet, context: &Arc, -) -> Result { +) -> Result<(String, Option>, Vec)> { let modules = Mutex::new(vec![]); - let minify_builder = - MinifyBuilder::create_builder(&context.config.minify, Some(MinifyMode::Module)); + // let minify_builder = + // MinifyBuilder::create_builder(&context.config.minify, Some(MinifyMode::Module)); - let is_enabled_minify = |module_id: &ModuleId| { - minify_builder.is_enabled(&module_id.resolved_path(&context.config.root)) - }; + // let is_enabled_minify = |module_id: &ModuleId| { + // minify_builder.is_enabled(&module_id.resolved_path(&context.config.root)) + // }; resource_pot .modules() @@ -76,95 +88,84 @@ pub fn resource_pot_to_runtime_object( .module(m_id) .unwrap_or_else(|| panic!("Module not found: {m_id:?}")); - let mut cache_store_key = None; + // let mut cache_store_key = None; - // enable persistent cache - if context.config.persistent_cache.enabled() { - let content_hash = module.content_hash.clone(); - let store_key = CacheStoreKey { - name: m_id.to_string() + "-resource_pot_to_runtime_object", - key: sha256( - format!( - "resource_pot_to_runtime_object_{}_{}_{}", - content_hash, - m_id.to_string(), - module.used_exports.join(",") - ) - .as_bytes(), - 32, - ), - }; - cache_store_key = Some(store_key.clone()); + // // enable persistent cache + // if context.config.persistent_cache.enabled() { + // let content_hash = module.content_hash.clone(); + // let store_key = CacheStoreKey { + // name: m_id.to_string() + "-resource_pot_to_runtime_object", + // key: sha256( + // format!( + // "resource_pot_to_runtime_object_{}_{}_{}", + // content_hash, + // m_id.to_string(), + // module.used_exports.join(",") + // ) + // .as_bytes(), + // 32, + // ), + // }; + // cache_store_key = Some(store_key.clone()); - // determine whether the cache exists,and store_key not change - if context.cache_manager.custom.has_cache(&store_key.name) - && !context.cache_manager.custom.is_cache_changed(&store_key) - { - if let Some(cache) = context.cache_manager.custom.read_cache(&store_key.name) { - let cached_rendered_script_module = deserialize!(&cache, CacheRenderedScriptModule); - let module = cached_rendered_script_module.to_magic_string(&context); + // // determine whether the cache exists,and store_key not change + // if context.cache_manager.custom.has_cache(&store_key.name) + // && !context.cache_manager.custom.is_cache_changed(&store_key) + // { + // if let Some(cache) = context.cache_manager.custom.read_cache(&store_key.name) { + // let cached_rendered_script_module = deserialize!(&cache, CacheRenderedScriptModule); + // let module = cached_rendered_script_module.to_magic_string(&context); - modules.lock().push(RenderedScriptModule { - module, - id: cached_rendered_script_module.id, - rendered_module: cached_rendered_script_module.rendered_module, - external_modules: cached_rendered_script_module.external_modules, - }); - return Ok(()); - } - } - } + // modules.lock().push(RenderedScriptModule { + // module, + // id: cached_rendered_script_module.id, + // rendered_module: cached_rendered_script_module.rendered_module, + // external_modules: cached_rendered_script_module.external_modules, + // }); + // return Ok(()); + // } + // } + // } let is_async_module = async_modules.contains(m_id); - let RenderModuleResult { - rendered_module, - external_modules, - source_map_chain, - } = render_module(RenderModuleOptions { + let render_module_result = render_module(RenderModuleOptions { module, module_graph, - is_enabled_minify, - minify_builder: &minify_builder, is_async_module, context, })?; - let code = rendered_module.rendered_content.clone(); + // let code = rendered_module.rendered_content.clone(); - // cache the code and sourcemap - if context.config.persistent_cache.enabled() { - let cache_rendered_script_module = CacheRenderedScriptModule::new( - m_id.clone(), - code.clone(), - rendered_module.clone(), - external_modules.clone(), - source_map_chain.clone(), - ); - let bytes = serialize!(&cache_rendered_script_module); - context - .cache_manager - .custom - .write_single_cache(cache_store_key.unwrap(), bytes) - .expect("failed to write resource pot to runtime object cache"); - } + // // cache the code and sourcemap + // if context.config.persistent_cache.enabled() { + // let cache_rendered_script_module = CacheRenderedScriptModule::new( + // m_id.clone(), + // code.clone(), + // rendered_module.clone(), + // external_modules.clone(), + // source_map_chain.clone(), + // ); + // let bytes = serialize!(&cache_rendered_script_module); + // context + // .cache_manager + // .custom + // .write_single_cache(cache_store_key.unwrap(), bytes) + // .expect("failed to write resource pot to runtime object cache"); + // } - let mut module = MagicString::new( - &code, - Some(MagicStringOptions { - filename: Some(m_id.resolved_path_with_query(&context.config.root)), - source_map_chain, - ..Default::default() - }), - ); + // let mut module = MagicString::new( + // &code, + // Some(MagicStringOptions { + // filename: Some(m_id.resolved_path_with_query(&context.config.root)), + // source_map_chain, + // ..Default::default() + // }), + // ); - module.prepend(&format!("{:?}:", m_id.id(context.config.mode.clone()))); - module.append(","); + // module.prepend(&format!("{:?}:", m_id.id(context.config.mode.clone()))); + // module.append(","); - modules.lock().push(RenderedScriptModule { - id: m_id.clone(), - module, - rendered_module, - external_modules, - }); + modules.lock().push(render_module_result); Ok::<(), CompilationError>(()) })?; @@ -172,41 +173,65 @@ pub fn resource_pot_to_runtime_object( // sort props by module id to make sure the order is stable let mut modules = modules.into_inner(); modules.sort_by(|a, b| { - a.id + a.module_id .id(context.config.mode.clone()) - .cmp(&b.id.id(context.config.mode.clone())) + .cmp(&b.module_id.id(context.config.mode.clone())) }); - // insert props to the object lit - let mut bundle = Bundle::new(BundleOptions { - trace_source_map_chain: Some(true), - separator: if context.config.minify.enabled() { - Some('\0') + let RenderResourcePotAstResult { + rendered_resource_pot_ast, + mut external_modules, + merged_sourcemap, + merged_comments, + } = render_resource_pot_ast(modules, &resource_pot.id, context)?; + + // sort external modules by module id to make sure the order is stable + external_modules.sort(); + + let sourcemap_enabled = context.config.sourcemap.enabled(resource_pot.immutable); + + let mut mappings = vec![]; + let code_bytes = codegen_module( + &rendered_resource_pot_ast, + context.config.script.target.clone(), + merged_sourcemap.clone(), + if sourcemap_enabled { + Some(&mut mappings) } else { None }, - ..Default::default() - }); - let mut rendered_modules = HashMap::new(); - let mut external_modules_set = HashSet::new(); + context.config.minify.enabled(), + Some(CodeGenCommentsConfig { + comments: &merged_comments, + // preserve all comments when generate module code. + config: &context.config.comments, + }), + ) + .map_err(|e| CompilationError::RenderScriptModuleError { + id: resource_pot.id.to_string(), + source: Some(Box::new(e)), + })?; - for m in modules { - bundle.add_source(m.module, None).unwrap(); - rendered_modules.insert(m.id, m.rendered_module); - external_modules_set.extend(m.external_modules); - } + let mut map = None; + if sourcemap_enabled { + let sourcemap = build_source_map(merged_sourcemap, &mappings); + // trace sourcemap chain of each module + let sourcemap = collapse_sourcemap(sourcemap, module_graph); + let mut buf = vec![]; + sourcemap + .to_writer(&mut buf) + .map_err(|e| CompilationError::RenderScriptModuleError { + id: resource_pot.id.to_string(), + source: Some(Box::new(e)), + })?; + let sourcemap = Arc::new(String::from_utf8(buf).unwrap()); - let mut external_modules = external_modules_set.into_iter().collect::>(); - external_modules.sort(); + map = Some(sourcemap); + } - bundle.prepend("{"); - bundle.append("}", None); + let code = String::from_utf8(code_bytes).unwrap(); - Ok(RenderedJsResourcePot { - bundle, - rendered_modules, - external_modules, - }) + Ok((code, map, external_modules)) } pub struct RenderedScriptModule { diff --git a/crates/plugin_runtime/src/render_resource_pot/render_module.rs b/crates/plugin_runtime/src/render_resource_pot/render_module.rs index b5f1e4c86..aa45d50f9 100644 --- a/crates/plugin_runtime/src/render_resource_pot/render_module.rs +++ b/crates/plugin_runtime/src/render_resource_pot/render_module.rs @@ -3,10 +3,12 @@ use std::{path::PathBuf, sync::Arc}; use farmfe_core::{ context::CompilationContext, error::CompilationError, - module::{module_graph::ModuleGraph, Module, ModuleId, ModuleSystem}, + module::{module_graph::ModuleGraph, CommentsMetaData, Module, ModuleId, ModuleSystem}, resource::resource_pot::RenderedModule, - swc_common::{comments::SingleThreadedComments, util::take::Take, Mark, SyntaxContext}, - swc_ecma_ast::{ArrowExpr, BlockStmtOrExpr, Expr, ExprStmt}, + swc_common::{ + comments::SingleThreadedComments, util::take::Take, Mark, SourceMap, SyntaxContext, + }, + swc_ecma_ast::{ArrowExpr, BlockStmtOrExpr, Expr, ExprStmt, FnExpr}, }; use farmfe_toolkit::{ common::{build_source_map, create_swc_source_map, MinifyBuilder, Source}, @@ -40,39 +42,39 @@ use super::{ }; pub struct RenderModuleResult { - pub rendered_module: RenderedModule, - pub external_modules: Vec, - pub source_map_chain: Vec>, + pub module_id: ModuleId, + pub rendered_ast: Expr, + pub cm: Arc, + pub comments: CommentsMetaData, + pub external_modules: Vec, } -pub struct RenderModuleOptions<'a, F: Fn(&ModuleId) -> bool> { +pub struct RenderModuleOptions<'a> { pub module: &'a Module, pub module_graph: &'a ModuleGraph, - pub is_enabled_minify: F, - pub minify_builder: &'a MinifyBuilder, pub is_async_module: bool, pub context: &'a Arc, } -pub fn render_module<'a, F: Fn(&ModuleId) -> bool>( - options: RenderModuleOptions<'a, F>, +pub fn render_module<'a>( + options: RenderModuleOptions<'a>, ) -> farmfe_core::error::Result { let RenderModuleOptions { module, module_graph, - is_enabled_minify, - minify_builder, is_async_module, context, } = options; let mut cloned_module = module.meta.as_script().ast.clone(); let (cm, _) = create_swc_source_map(Source { - path: PathBuf::from(module.id.resolved_path_with_query(&context.config.root)), + // path: PathBuf::from(module.id.resolved_path_with_query(&context.config.root)), + path: PathBuf::from(module.id.to_string()), content: module.content.clone(), }); let mut external_modules = vec![]; let comments: SingleThreadedComments = module.meta.as_script().comments.clone().into(); - let minify_enabled = is_enabled_minify(&module.id); + + let mut func_expr = Expr::default(); try_with(cm.clone(), &context.meta.script.globals, || { let (unresolved_mark, top_level_mark) = if module.meta.as_script().unresolved_mark == 0 @@ -141,83 +143,86 @@ pub fn render_module<'a, F: Fn(&ModuleId) -> bool>( } // swc code gen would emit a trailing `;` when is_target_legacy is false. // we can not deal with this situation for now, so we set is_target_legacy to true here, it will be fixed in the future. - wrap_function(&mut cloned_module, is_async_module, true); + let mut expr = wrap_function(cloned_module, is_async_module, true); - if minify_enabled { - minify_js_module( - &mut cloned_module, - cm.clone(), - &comments, - unresolved_mark, - top_level_mark, - minify_builder.minify_options.as_ref().unwrap(), - ); - } + // if minify_enabled { + // minify_js_module( + // &mut cloned_module, + // cm.clone(), + // &comments, + // unresolved_mark, + // top_level_mark, + // minify_builder.minify_options.as_ref().unwrap(), + // ); + // } - cloned_module.visit_mut_with(&mut fixer(Some(&comments))); + expr.visit_mut_with(&mut fixer(Some(&comments))); + func_expr = expr; external_modules = source_replacer.external_modules; })?; - // remove shebang - cloned_module.shebang = None; + // // remove shebang + // cloned_module.shebang = None; - let sourcemap_enabled = context.config.sourcemap.enabled(module.immutable); - // wrap module function - // let wrapped_module = wrap_module_ast(cloned_module); - let mut mappings = vec![]; - let code_bytes = codegen_module( - &cloned_module, - context.config.script.target.clone(), - cm.clone(), - if sourcemap_enabled { - Some(&mut mappings) - } else { - None - }, - context.config.minify.enabled(), - Some(CodeGenCommentsConfig { - comments: &comments, - // preserve all comments when generate module code. - config: &context.config.comments, - }), - ) - .map_err(|e| CompilationError::RenderScriptModuleError { - id: module.id.to_string(), - source: Some(Box::new(e)), - })?; + // let sourcemap_enabled = context.config.sourcemap.enabled(module.immutable); + // // wrap module function + // // let wrapped_module = wrap_module_ast(cloned_module); + // let mut mappings = vec![]; + // let code_bytes = codegen_module( + // &cloned_module, + // context.config.script.target.clone(), + // cm.clone(), + // if sourcemap_enabled { + // Some(&mut mappings) + // } else { + // None + // }, + // context.config.minify.enabled(), + // Some(CodeGenCommentsConfig { + // comments: &comments, + // // preserve all comments when generate module code. + // config: &context.config.comments, + // }), + // ) + // .map_err(|e| CompilationError::RenderScriptModuleError { + // id: module.id.to_string(), + // source: Some(Box::new(e)), + // })?; - let code = Arc::new(String::from_utf8(code_bytes).unwrap()); + // let code = Arc::new(String::from_utf8(code_bytes).unwrap()); - let mut rendered_module = RenderedModule { - id: module.id.clone(), - rendered_content: code.clone(), - rendered_map: None, - rendered_length: code.len(), - original_length: module.content.len(), - }; - let mut source_map_chain = vec![]; + // let mut rendered_module = RenderedModule { + // id: module.id.clone(), + // rendered_content: code.clone(), + // rendered_map: None, + // rendered_length: code.len(), + // original_length: module.content.len(), + // }; + // let mut source_map_chain = vec![]; - if sourcemap_enabled { - let sourcemap = build_source_map(cm, &mappings); - let mut buf = vec![]; - sourcemap - .to_writer(&mut buf) - .map_err(|e| CompilationError::RenderScriptModuleError { - id: module.id.to_string(), - source: Some(Box::new(e)), - })?; - let map = Arc::new(String::from_utf8(buf).unwrap()); - rendered_module.rendered_map = Some(map.clone()); + // if sourcemap_enabled { + // let sourcemap = build_source_map(cm, &mappings); + // let mut buf = vec![]; + // sourcemap + // .to_writer(&mut buf) + // .map_err(|e| CompilationError::RenderScriptModuleError { + // id: module.id.to_string(), + // source: Some(Box::new(e)), + // })?; + // let map = Arc::new(String::from_utf8(buf).unwrap()); + // rendered_module.rendered_map = Some(map.clone()); - source_map_chain = module.source_map_chain.clone(); - source_map_chain.push(map); - } + // source_map_chain = module.source_map_chain.clone(); + // source_map_chain.push(map); + // } Ok(RenderModuleResult { - rendered_module, + module_id: module.id.clone(), + cm, + comments: comments.into(), + rendered_ast: func_expr, external_modules, - source_map_chain, }) } @@ -238,7 +243,7 @@ pub fn render_module<'a, F: Fn(&ModuleId) -> bool>( /// exports.b = b; /// } /// ``` -fn wrap_function(module: &mut SwcModule, is_async_module: bool, is_target_legacy: bool) { +fn wrap_function(mut module: SwcModule, is_async_module: bool, is_target_legacy: bool) -> Expr { let body = module.body.take(); let params = vec![ @@ -284,28 +289,24 @@ fn wrap_function(module: &mut SwcModule, is_async_module: bool, is_target_legacy }) .collect(); - let item = if !is_target_legacy { - ModuleItem::Stmt(Stmt::Expr(ExprStmt { + if !is_target_legacy { + Expr::Arrow(ArrowExpr { span: DUMMY_SP, - expr: Box::new(Expr::Arrow(ArrowExpr { + params: params.into_iter().map(|p| p.pat).collect(), + body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt { span: DUMMY_SP, - params: params.into_iter().map(|p| p.pat).collect(), - body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt { - span: DUMMY_SP, - stmts, - ctxt: SyntaxContext::empty(), - })), - is_async: is_async_module, - is_generator: false, - type_params: None, - return_type: None, + stmts, ctxt: SyntaxContext::empty(), })), - })) + is_async: is_async_module, + is_generator: false, + type_params: None, + return_type: None, + ctxt: SyntaxContext::empty(), + }) } else { - ModuleItem::Stmt(Stmt::Decl(farmfe_core::swc_ecma_ast::Decl::Fn(FnDecl { - ident: " ".into(), - declare: false, + Expr::Fn(FnExpr { + ident: None, function: Box::new(Function { params, decorators: vec![], @@ -321,8 +322,6 @@ fn wrap_function(module: &mut SwcModule, is_async_module: bool, is_target_legacy return_type: None, ctxt: SyntaxContext::empty(), }), - }))) - }; - - module.body.push(item); + }) + } } diff --git a/crates/plugin_runtime/src/render_resource_pot/render_resource_pot_ast.rs b/crates/plugin_runtime/src/render_resource_pot/render_resource_pot_ast.rs new file mode 100644 index 000000000..7c1e73e42 --- /dev/null +++ b/crates/plugin_runtime/src/render_resource_pot/render_resource_pot_ast.rs @@ -0,0 +1,206 @@ +use std::sync::Arc; + +use farmfe_core::{ + config::FARM_MODULE_SYSTEM, + context::CompilationContext, + module::ModuleId, + rayon::{iter::IntoParallelRefMutIterator, prelude::*}, + resource::resource_pot::ResourcePotId, + swc_common::{ + comments::{Comments, SingleThreadedComments}, + BytePos, FilePathMapping, SourceMap, SyntaxContext, DUMMY_SP, + }, + swc_ecma_ast::{ + ComputedPropName, Expr, ExprOrSpread, Ident, IdentName, KeyValueProp, Lit, MemberExpr, + MemberProp, Module as SwcModule, ModuleItem, ObjectLit, Prop, PropName, PropOrSpread, + }, + swc_ecma_parser::{EsSyntax, Syntax}, +}; +use farmfe_toolkit::{ + common::get_swc_sourcemap_filename, + script::parse_stmt, + swc_ecma_visit::{VisitMut, VisitMutWith}, +}; + +use super::render_module::RenderModuleResult; + +pub struct RenderResourcePotAstResult { + pub rendered_resource_pot_ast: SwcModule, + pub external_modules: Vec, + pub merged_sourcemap: Arc, + pub merged_comments: SingleThreadedComments, +} + +pub fn render_resource_pot_ast( + mut render_module_results: Vec, + resource_pot_id: &ResourcePotId, + context: &Arc, +) -> farmfe_core::error::Result { + // if context.config.sourcemap.enabled(resource_pot.immutable) { + let cm = merge_sourcemap(&mut render_module_results); + let comments = merge_comments(&mut render_module_results, cm.clone()); + + let mut rendered_resource_pot_ast = ObjectLit { + span: DUMMY_SP, + props: vec![], + }; + + // insert props to the object lit + for RenderModuleResult { + module_id, + rendered_ast, + .. + } in &mut render_module_results + { + let expr = std::mem::take(rendered_ast); + rendered_resource_pot_ast + .props + .push(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Str(module_id.id(context.config.mode.clone()).into()), + value: Box::new(expr), + })))); + } + + let mut stmt = parse_stmt( + resource_pot_id, + r#"(function (moduleSystem, modules) { + for (var moduleId in modules) { + var module = modules[moduleId]; + moduleSystem.register(moduleId, module); + } + })("farm_module_system", "farm_object_lit");"#, + Syntax::Es(EsSyntax::default()), + cm.clone(), + true, + )?; + + let args = &mut stmt.as_mut_expr().unwrap().expr.as_mut_call().unwrap().args; + + // let global_this = get_farm_global_this( + // &context.config.runtime.namespace, + // &context.config.output.target_env, + // ); + let global_this = if context.config.output.target_env.is_node() { + "global" + } else { + "window" + }; + + // window['hash'].__farm_module_system__; + args[0] = ExprOrSpread { + spread: None, + expr: Box::new(Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: Box::new(Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: Box::new(Expr::Ident(Ident::new( + global_this.into(), + DUMMY_SP, + SyntaxContext::empty(), + ))), + prop: MemberProp::Computed(ComputedPropName { + span: DUMMY_SP, + expr: Box::new(Expr::Lit(Lit::Str( + context.config.runtime.namespace.as_str().into(), + ))), + }), + })), + prop: MemberProp::Ident(IdentName::new(FARM_MODULE_SYSTEM.into(), DUMMY_SP)), + })), // expr: Box::new(Expr::Lit(Lit::Str( + // format!("{global_this}.{FARM_MODULE_SYSTEM}").into(), + // ))), + }; + args[1] = ExprOrSpread { + spread: None, + expr: Box::new(Expr::Object(rendered_resource_pot_ast)), + }; + + Ok(RenderResourcePotAstResult { + rendered_resource_pot_ast: SwcModule { + span: DUMMY_SP, + shebang: None, + body: vec![ModuleItem::Stmt(stmt)], + }, + external_modules: render_module_results + .into_iter() + .map(|item| item.external_modules) + .flatten() + .collect(), + merged_sourcemap: cm, + merged_comments: comments, + }) +} + +fn merge_sourcemap(render_module_results: &mut Vec) -> Arc { + let new_cm = Arc::new(SourceMap::new(FilePathMapping::empty())); + let mut start_poss = vec![]; + + for RenderModuleResult { module_id, cm, .. } in render_module_results.iter() { + let filename = get_swc_sourcemap_filename(module_id); + let content = cm + .get_source_file(&filename) + .unwrap_or_else(|| panic!("no source file found for {:?}", module_id)); + let source_file = new_cm.new_source_file_from(Arc::new(filename), content.src.clone()); + start_poss.push(source_file.start_pos); + } + + // update Span in parallel + render_module_results + .par_iter_mut() + .zip(start_poss.par_iter_mut()) + .for_each(|(res, start_pos)| { + res.rendered_ast.visit_mut_with(&mut SpanUpdater { + start_pos: *start_pos, + }); + }); + + new_cm +} + +struct SpanUpdater { + start_pos: BytePos, +} + +impl VisitMut for SpanUpdater { + fn visit_mut_span(&mut self, node: &mut farmfe_core::swc_common::Span) { + node.lo = self.start_pos + node.lo; + node.hi = self.start_pos + node.hi; + } +} + +fn merge_comments( + render_module_results: &mut Vec, + cm: Arc, +) -> SingleThreadedComments { + let merged_comments = SingleThreadedComments::default(); + + for RenderModuleResult { + module_id, + comments: module_comments, + .. + } in render_module_results + { + let filename = get_swc_sourcemap_filename(module_id); + let source_file = cm + .get_source_file(&filename) + .unwrap_or_else(|| panic!("no source file found for {:?}", module_id)); + let start_pos = source_file.start_pos; + let comments = std::mem::take(module_comments); + + for item in comments.leading { + let byte_pos = start_pos + item.byte_pos; + for comment in item.comment { + merged_comments.add_leading(byte_pos, comment); + } + } + + for item in comments.trailing { + let byte_pos = start_pos + item.byte_pos; + for comment in item.comment { + merged_comments.add_trailing(byte_pos, comment); + } + } + } + + merged_comments +} diff --git a/crates/plugin_runtime/src/render_resource_pot/source_replacer.rs b/crates/plugin_runtime/src/render_resource_pot/source_replacer.rs index ddd3b3601..e52926179 100644 --- a/crates/plugin_runtime/src/render_resource_pot/source_replacer.rs +++ b/crates/plugin_runtime/src/render_resource_pot/source_replacer.rs @@ -34,7 +34,7 @@ pub struct SourceReplacer<'a> { module_graph: &'a ModuleGraph, module_id: ModuleId, mode: Mode, - pub external_modules: Vec, + pub external_modules: Vec, target_env: TargetEnv, } @@ -138,9 +138,10 @@ impl SourceReplacer<'_> { let (id, resolve_kind) = (self.find_real_module_meta_by_source(&source)).unwrap_or_else(|| { + let deps = self.module_graph.dependencies(&self.module_id); panic!( - "Cannot find module id for source {:?} from {:?}", - source, self.module_id + "Cannot find module id for source {:?} from {:?}. current deps {:#?}", + source, self.module_id, deps ) }); // only execute script module @@ -159,7 +160,7 @@ impl SourceReplacer<'_> { return SourceReplaceResult::NotReplaced; } - self.external_modules.push(id.to_string()); + self.external_modules.push(id.clone()); return SourceReplaceResult::NotReplaced; } @@ -192,7 +193,7 @@ impl SourceReplacer<'_> { let dep_module = self.module_graph.module(&id).unwrap(); if dep_module.external { - self.external_modules.push(id.to_string()); + self.external_modules.push(id.clone()); return SourceReplaceResult::NotReplaced; } diff --git a/crates/toolkit/src/common.rs b/crates/toolkit/src/common.rs index 22670bb71..29c6d656c 100644 --- a/crates/toolkit/src/common.rs +++ b/crates/toolkit/src/common.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, path::{Path, PathBuf}, sync::Arc, }; @@ -11,7 +12,9 @@ use farmfe_core::{ minify::{MinifyMode, MinifyOptions}, SourcemapConfig, }, - enhanced_magic_string::collapse_sourcemap::collapse_sourcemap_chain, + enhanced_magic_string::collapse_sourcemap::{collapse_sourcemap_chain, read_source_content}, + module::ModuleId, + rayon::iter::{IntoParallelRefIterator, ParallelIterator}, relative_path::RelativePath, resource::{resource_pot::ResourcePot, Resource, ResourceOrigin, ResourceType}, serde_json::Value, @@ -30,6 +33,16 @@ pub struct Source { pub content: Arc, } +/// get swc source map filename from module id. +/// you can get module id from sourcemap filename too, by +pub fn get_swc_sourcemap_filename(module_id: &ModuleId) -> FileName { + FileName::Real(PathBuf::from(module_id.to_string())) +} + +pub fn get_module_id_from_sourcemap_filename(filename: &str) -> ModuleId { + filename.into() +} + /// create a swc source map from a source pub fn create_swc_source_map(source: Source) -> (Arc, Arc) { let cm = Arc::new(SourceMap::default()); @@ -107,6 +120,88 @@ pub fn build_source_map( cm.build_source_map_with_config(mappings, None, FarmSwcSourceMapConfig::default()) } +pub fn collapse_sourcemap( + sourcemap: sourcemap::SourceMap, + module_graph: &farmfe_core::module::module_graph::ModuleGraph, +) -> sourcemap::SourceMap { + let mut builder = sourcemap::SourceMapBuilder::new(sourcemap.get_file()); + let mut cached_sourcemap = HashMap::>::new(); + + let mut add_token = |src_token: &sourcemap::Token, + dst_token: &sourcemap::Token, + dst_sourcemap: &sourcemap::SourceMap| { + let new_token = builder.add( + src_token.get_dst_line(), + src_token.get_dst_col(), + dst_token.get_src_line(), + dst_token.get_src_col(), + dst_token.get_source(), + dst_token.get_name(), + dst_token.is_range(), + ); + + if !builder.has_source_contents(new_token.src_id) { + if let Some(content) = read_source_content(dst_token.clone(), dst_sourcemap) { + builder.set_source_contents(new_token.src_id, Some(&content)); + } + } + }; + + for token in sourcemap.tokens() { + if let Some(filename) = token.get_source() { + let module_id = get_module_id_from_sourcemap_filename(filename); + if !module_graph.has_module(&module_id) { + add_token(&token, &token, &sourcemap); + continue; + } + + let module = module_graph + .module(&module_id) + .unwrap_or_else(|| panic!("module {} not found in module graph", module_id.to_string())); + + if module.source_map_chain.is_empty() { + add_token(&token, &token, &sourcemap); + continue; + } + + let sourcemap_chain = cached_sourcemap.entry(module_id).or_insert_with(|| { + let mut chain = module + .source_map_chain + .par_iter() + .map(|i| sourcemap::SourceMap::from_slice(i.as_bytes()).unwrap()) + .collect::>(); + // reverse the chain to make the last one the original sourcemap + chain.reverse(); + // filter out the empty sourcemap + chain = chain + .into_iter() + .filter(|map| map.get_token_count() > 0) + .collect(); + chain + }); + + let mut dst_token = token.clone(); + let mut dst_sourcemap = token.sourcemap(); + + // trace the token back to original source file + for orig_map in sourcemap_chain { + if let Some(orig_token) = + orig_map.lookup_token(dst_token.get_src_line(), dst_token.get_src_col()) + { + dst_token = orig_token; + dst_sourcemap = orig_token.sourcemap(); + } + } + + add_token(&token, &dst_token, &dst_sourcemap); + } else { + add_token(&token, &token, &sourcemap); + } + } + + builder.into_sourcemap() +} + pub struct FarmSwcSourceMapConfig { inline_sources_content: bool, } diff --git a/crates/toolkit/src/css/mod.rs b/crates/toolkit/src/css.rs similarity index 100% rename from crates/toolkit/src/css/mod.rs rename to crates/toolkit/src/css.rs diff --git a/crates/toolkit/src/fs/mod.rs b/crates/toolkit/src/fs.rs similarity index 100% rename from crates/toolkit/src/fs/mod.rs rename to crates/toolkit/src/fs.rs diff --git a/crates/toolkit/src/hash/mod.rs b/crates/toolkit/src/hash.rs similarity index 100% rename from crates/toolkit/src/hash/mod.rs rename to crates/toolkit/src/hash.rs diff --git a/crates/toolkit/src/html/mod.rs b/crates/toolkit/src/html.rs similarity index 100% rename from crates/toolkit/src/html/mod.rs rename to crates/toolkit/src/html.rs diff --git a/crates/toolkit/src/script/mod.rs b/crates/toolkit/src/script/mod.rs index 43b42318d..c6505e5ab 100644 --- a/crates/toolkit/src/script/mod.rs +++ b/crates/toolkit/src/script/mod.rs @@ -4,35 +4,32 @@ use swc_ecma_codegen::{ text_writer::{JsWriter, WriteJs}, Emitter, Node, }; -use swc_ecma_parser::{lexer::Lexer, EsSyntax, Parser, StringInput, Syntax, TsSyntax}; +use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax}; use farmfe_core::{ - config::{comments::CommentsConfig, ScriptParserConfig}, - context::CompilationContext, + config::comments::CommentsConfig, error::{CompilationError, Result}, - module::{ModuleSystem, ModuleType}, - plugin::{PluginFinalizeModuleHookParam, ResolveKind}, swc_common::{ comments::{Comments, SingleThreadedComments}, - BytePos, FileName, LineCol, Mark, SourceMap, - }, - swc_ecma_ast::{ - CallExpr, Callee, EsVersion, Expr, Ident, IdentName, Import, MemberProp, Module as SwcModule, - ModuleItem, Stmt, + BytePos, FileName, LineCol, SourceMap, }, + swc_ecma_ast::{EsVersion, Module as SwcModule, Stmt}, }; -use swc_ecma_visit::{Visit, VisitWith}; + use swc_error_reporters::handler::try_with_handler; use crate::common::{create_swc_source_map, minify_comments, Source}; pub use farmfe_toolkit_plugin_types::swc_ast::ParseScriptModuleResult; -use self::swc_try_with::try_with; - +pub mod constant; pub mod defined_idents_collector; +pub mod module_system; pub mod swc_try_with; -pub mod constant; +pub mod utils; + +pub use module_system::*; +pub use utils::*; /// parse the content of a module to [SwcModule] ast. pub fn parse_module( @@ -152,214 +149,3 @@ pub fn codegen_module( Ok(buf) } - -/// Get [ModuleType] from the resolved id's extension, return [ModuleType::Custom(ext)] if the extension is not internally supported. -/// Panic if the id do not has a extension. -pub fn module_type_from_id(id: &str) -> Option { - let path = PathBuf::from(id); - - path.extension().map(|ext| ext.to_str().unwrap().into()) -} - -/// return [None] if module type is not script -pub fn syntax_from_module_type( - module_type: &ModuleType, - config: ScriptParserConfig, -) -> Option { - match module_type { - ModuleType::Js => Some(Syntax::Es(EsSyntax { - jsx: false, - import_attributes: true, - ..config.es_config - })), - ModuleType::Jsx => Some(Syntax::Es(EsSyntax { - jsx: true, - import_attributes: true, - ..config.es_config - })), - ModuleType::Ts => Some(Syntax::Typescript(TsSyntax { - tsx: false, - ..config.ts_config - })), - ModuleType::Tsx => Some(Syntax::Typescript(TsSyntax { - tsx: true, - ..config.ts_config - })), - _ => None, - } -} - -/// Whether the call expr is commonjs require. -/// A call expr is commonjs require if: -/// * callee is an identifier named `require` -/// * arguments is a single string literal -/// * require is a global variable -pub fn is_commonjs_require( - unresolved_mark: Mark, - top_level_mark: Mark, - call_expr: &CallExpr, -) -> bool { - if let Callee::Expr(box Expr::Ident(Ident { ctxt, sym, .. })) = &call_expr.callee { - sym == "require" && (ctxt.outer() == unresolved_mark || ctxt.outer() == top_level_mark) - } else { - false - } -} - -/// Whether the call expr is dynamic import. -pub fn is_dynamic_import(call_expr: &CallExpr) -> bool { - matches!(&call_expr.callee, Callee::Import(Import { .. })) -} - -pub fn module_system_from_deps(deps: Vec) -> ModuleSystem { - let mut module_system = ModuleSystem::Custom(String::from("unknown")); - - for resolve_kind in deps { - if matches!(resolve_kind, ResolveKind::Import) - || matches!(resolve_kind, ResolveKind::DynamicImport) - || matches!(resolve_kind, ResolveKind::ExportFrom) - { - match module_system { - ModuleSystem::EsModule => continue, - ModuleSystem::CommonJs => { - module_system = ModuleSystem::Hybrid; - break; - } - _ => module_system = ModuleSystem::EsModule, - } - } else if matches!(resolve_kind, ResolveKind::Require) { - match module_system { - ModuleSystem::CommonJs => continue, - ModuleSystem::EsModule => { - module_system = ModuleSystem::Hybrid; - break; - } - _ => module_system = ModuleSystem::CommonJs, - } - } - } - - module_system -} - -struct ModuleSystemAnalyzer { - unresolved_mark: Mark, - contain_module_exports: bool, - contain_esm: bool, -} - -impl Visit for ModuleSystemAnalyzer { - fn visit_stmts(&mut self, n: &[Stmt]) { - if self.contain_module_exports || self.contain_esm { - return; - } - - n.visit_children_with(self); - } - - fn visit_member_expr(&mut self, n: &farmfe_core::swc_ecma_ast::MemberExpr) { - if self.contain_module_exports { - return; - } - - if let box Expr::Ident(Ident { sym, ctxt, .. }) = &n.obj { - if sym == "module" && ctxt.outer() == self.unresolved_mark { - if let MemberProp::Ident(IdentName { sym, .. }) = &n.prop { - if sym == "exports" { - self.contain_module_exports = true; - } - } - } else if sym == "exports" && ctxt.outer() == self.unresolved_mark { - self.contain_module_exports = true; - } else { - n.visit_children_with(self); - } - } else { - n.visit_children_with(self); - } - } - - fn visit_module_decl(&mut self, n: &farmfe_core::swc_ecma_ast::ModuleDecl) { - if self.contain_esm { - return; - } - - self.contain_esm = true; - - n.visit_children_with(self); - } -} - -pub fn module_system_from_ast(ast: &SwcModule, module_system: ModuleSystem) -> ModuleSystem { - if module_system != ModuleSystem::Hybrid { - // if the ast contains ModuleDecl, it's a esm module - for item in ast.body.iter() { - if let ModuleItem::ModuleDecl(_) = item { - if module_system == ModuleSystem::CommonJs { - return ModuleSystem::Hybrid; - } else { - return ModuleSystem::EsModule; - } - } - } - } - - module_system -} - -pub fn set_module_system_for_module_meta( - param: &mut PluginFinalizeModuleHookParam, - context: &Arc, -) { - // default to commonjs - let module_system_from_deps_option = if !param.deps.is_empty() { - module_system_from_deps(param.deps.iter().map(|d| d.kind.clone()).collect()) - } else { - ModuleSystem::UnInitial - }; - - // param.module.meta.as_script_mut().module_system = module_system.clone(); - - let ast = ¶m.module.meta.as_script().ast; - - let mut module_system_from_ast: ModuleSystem = ModuleSystem::UnInitial; - { - // try_with(param.module.meta.as_script().comments.into(), globals, op) - - let (cm, _) = create_swc_source_map(Source { - path: PathBuf::from(¶m.module.id.to_string()), - content: param.module.content.clone(), - }); - - try_with(cm, &context.meta.script.globals, || { - let unresolved_mark = Mark::from_u32(param.module.meta.as_script().unresolved_mark); - let mut analyzer = ModuleSystemAnalyzer { - unresolved_mark, - contain_module_exports: false, - contain_esm: false, - }; - - ast.visit_with(&mut analyzer); - - if analyzer.contain_module_exports { - module_system_from_ast = module_system_from_ast.merge(ModuleSystem::CommonJs); - } - - if analyzer.contain_esm { - module_system_from_ast = module_system_from_ast.merge(ModuleSystem::EsModule); - } - }) - .unwrap(); - } - - let mut v = [module_system_from_deps_option, module_system_from_ast] - .into_iter() - .reduce(|a, b| a.merge(b)) - .unwrap_or(ModuleSystem::UnInitial); - - if matches!(v, ModuleSystem::UnInitial) { - v = ModuleSystem::Hybrid; - } - - param.module.meta.as_script_mut().module_system = v; -} diff --git a/crates/toolkit/src/script/module_system.rs b/crates/toolkit/src/script/module_system.rs new file mode 100644 index 000000000..1ae995369 --- /dev/null +++ b/crates/toolkit/src/script/module_system.rs @@ -0,0 +1,169 @@ +use std::{path::PathBuf, sync::Arc}; + +use farmfe_core::{ + context::CompilationContext, + module::ModuleSystem, + plugin::{PluginFinalizeModuleHookParam, ResolveKind}, + swc_common::Mark, + swc_ecma_ast::{Expr, Ident, IdentName, MemberProp, Module as SwcModule, ModuleItem, Stmt}, +}; +use swc_ecma_visit::{Visit, VisitWith}; + +use crate::common::{create_swc_source_map, Source}; + +pub use farmfe_toolkit_plugin_types::swc_ast::ParseScriptModuleResult; + +use crate::script::swc_try_with::try_with; + +pub fn module_system_from_deps(deps: Vec) -> ModuleSystem { + let mut module_system = ModuleSystem::Custom(String::from("unknown")); + + for resolve_kind in deps { + if matches!(resolve_kind, ResolveKind::Import) + || matches!(resolve_kind, ResolveKind::DynamicImport) + || matches!(resolve_kind, ResolveKind::ExportFrom) + { + match module_system { + ModuleSystem::EsModule => continue, + ModuleSystem::CommonJs => { + module_system = ModuleSystem::Hybrid; + break; + } + _ => module_system = ModuleSystem::EsModule, + } + } else if matches!(resolve_kind, ResolveKind::Require) { + match module_system { + ModuleSystem::CommonJs => continue, + ModuleSystem::EsModule => { + module_system = ModuleSystem::Hybrid; + break; + } + _ => module_system = ModuleSystem::CommonJs, + } + } + } + + module_system +} + +struct ModuleSystemAnalyzer { + unresolved_mark: Mark, + contain_module_exports: bool, + contain_esm: bool, +} + +impl Visit for ModuleSystemAnalyzer { + fn visit_stmts(&mut self, n: &[Stmt]) { + if self.contain_module_exports || self.contain_esm { + return; + } + + n.visit_children_with(self); + } + + fn visit_member_expr(&mut self, n: &farmfe_core::swc_ecma_ast::MemberExpr) { + if self.contain_module_exports { + return; + } + + if let box Expr::Ident(Ident { sym, ctxt, .. }) = &n.obj { + if sym == "module" && ctxt.outer() == self.unresolved_mark { + if let MemberProp::Ident(IdentName { sym, .. }) = &n.prop { + if sym == "exports" { + self.contain_module_exports = true; + } + } + } else if sym == "exports" && ctxt.outer() == self.unresolved_mark { + self.contain_module_exports = true; + } else { + n.visit_children_with(self); + } + } else { + n.visit_children_with(self); + } + } + + fn visit_module_decl(&mut self, n: &farmfe_core::swc_ecma_ast::ModuleDecl) { + if self.contain_esm { + return; + } + + self.contain_esm = true; + + n.visit_children_with(self); + } +} + +pub fn module_system_from_ast(ast: &SwcModule, module_system: ModuleSystem) -> ModuleSystem { + if module_system != ModuleSystem::Hybrid { + // if the ast contains ModuleDecl, it's a esm module + for item in ast.body.iter() { + if let ModuleItem::ModuleDecl(_) = item { + if module_system == ModuleSystem::CommonJs { + return ModuleSystem::Hybrid; + } else { + return ModuleSystem::EsModule; + } + } + } + } + + module_system +} + +pub fn set_module_system_for_module_meta( + param: &mut PluginFinalizeModuleHookParam, + context: &Arc, +) { + // default to commonjs + let module_system_from_deps_option = if !param.deps.is_empty() { + module_system_from_deps(param.deps.iter().map(|d| d.kind.clone()).collect()) + } else { + ModuleSystem::UnInitial + }; + + // param.module.meta.as_script_mut().module_system = module_system.clone(); + + let ast = ¶m.module.meta.as_script().ast; + + let mut module_system_from_ast: ModuleSystem = ModuleSystem::UnInitial; + { + // try_with(param.module.meta.as_script().comments.into(), globals, op) + + let (cm, _) = create_swc_source_map(Source { + path: PathBuf::from(¶m.module.id.to_string()), + content: param.module.content.clone(), + }); + + try_with(cm, &context.meta.script.globals, || { + let unresolved_mark = Mark::from_u32(param.module.meta.as_script().unresolved_mark); + let mut analyzer = ModuleSystemAnalyzer { + unresolved_mark, + contain_module_exports: false, + contain_esm: false, + }; + + ast.visit_with(&mut analyzer); + + if analyzer.contain_module_exports { + module_system_from_ast = module_system_from_ast.merge(ModuleSystem::CommonJs); + } + + if analyzer.contain_esm { + module_system_from_ast = module_system_from_ast.merge(ModuleSystem::EsModule); + } + }) + .unwrap(); + } + + let mut v = [module_system_from_deps_option, module_system_from_ast] + .into_iter() + .reduce(|a, b| a.merge(b)) + .unwrap_or(ModuleSystem::UnInitial); + + if matches!(v, ModuleSystem::UnInitial) { + v = ModuleSystem::Hybrid; + } + + param.module.meta.as_script_mut().module_system = v; +} diff --git a/crates/toolkit/src/script/swc_try_with.rs b/crates/toolkit/src/script/swc_try_with.rs index 3b8a89630..8ea437f3a 100644 --- a/crates/toolkit/src/script/swc_try_with.rs +++ b/crates/toolkit/src/script/swc_try_with.rs @@ -4,7 +4,7 @@ use farmfe_core::{ context::CompilationContext, error::{CompilationError, Result}, swc_common::{errors::HANDLER, Globals, Mark, SourceMap, SyntaxContext, GLOBALS}, - swc_ecma_ast::{Ident, Module}, + swc_ecma_ast::Module, }; use swc_ecma_transforms::helpers::{Helpers, HELPERS}; use swc_ecma_transforms_base::resolver; diff --git a/crates/toolkit/src/script/utils.rs b/crates/toolkit/src/script/utils.rs new file mode 100644 index 000000000..826a6b104 --- /dev/null +++ b/crates/toolkit/src/script/utils.rs @@ -0,0 +1,70 @@ +use std::path::PathBuf; + +use swc_ecma_parser::{EsSyntax, Syntax, TsSyntax}; + +use farmfe_core::{ + config::ScriptParserConfig, + module::ModuleType, + swc_common::Mark, + swc_ecma_ast::{CallExpr, Callee, Expr, Ident, Import}, +}; + +pub use farmfe_toolkit_plugin_types::swc_ast::ParseScriptModuleResult; + +/// Get [ModuleType] from the resolved id's extension, return [ModuleType::Custom(ext)] if the extension is not internally supported. +/// Panic if the id do not has a extension. +pub fn module_type_from_id(id: &str) -> Option { + let path = PathBuf::from(id); + + path.extension().map(|ext| ext.to_str().unwrap().into()) +} + +/// return [None] if module type is not script +pub fn syntax_from_module_type( + module_type: &ModuleType, + config: ScriptParserConfig, +) -> Option { + match module_type { + ModuleType::Js => Some(Syntax::Es(EsSyntax { + jsx: false, + import_attributes: true, + ..config.es_config + })), + ModuleType::Jsx => Some(Syntax::Es(EsSyntax { + jsx: true, + import_attributes: true, + ..config.es_config + })), + ModuleType::Ts => Some(Syntax::Typescript(TsSyntax { + tsx: false, + ..config.ts_config + })), + ModuleType::Tsx => Some(Syntax::Typescript(TsSyntax { + tsx: true, + ..config.ts_config + })), + _ => None, + } +} + +/// Whether the call expr is commonjs require. +/// A call expr is commonjs require if: +/// * callee is an identifier named `require` +/// * arguments is a single string literal +/// * require is a global variable +pub fn is_commonjs_require( + unresolved_mark: Mark, + top_level_mark: Mark, + call_expr: &CallExpr, +) -> bool { + if let Callee::Expr(box Expr::Ident(Ident { ctxt, sym, .. })) = &call_expr.callee { + sym == "require" && (ctxt.outer() == unresolved_mark || ctxt.outer() == top_level_mark) + } else { + false + } +} + +/// Whether the call expr is dynamic import. +pub fn is_dynamic_import(call_expr: &CallExpr) -> bool { + matches!(&call_expr.callee, Callee::Import(Import { .. })) +} diff --git a/crates/toolkit/tests/script.rs b/crates/toolkit/tests/script.rs index 93c7b89cb..84d059ba4 100644 --- a/crates/toolkit/tests/script.rs +++ b/crates/toolkit/tests/script.rs @@ -7,7 +7,7 @@ use farmfe_core::{ use farmfe_toolkit::{ fs::read_file_utf8, script::{ - codegen_module, module_type_from_id, parse_module, syntax_from_module_type, + codegen_module, parse_module, utils::module_type_from_id, utils::syntax_from_module_type, CodeGenCommentsConfig, ParseScriptModuleResult, }, }; diff --git a/examples/vite-adapter-vue/farm.config.ts b/examples/vite-adapter-vue/farm.config.ts index 00063c0e4..c12e66ab1 100644 --- a/examples/vite-adapter-vue/farm.config.ts +++ b/examples/vite-adapter-vue/farm.config.ts @@ -22,15 +22,15 @@ function configureVitePluginVue() { // using plugin vue vitePlugin: vue(), // configuring filters for it. Unmatched module paths will be skipped. - filters: ["!node_modules", "node_modules/my-ui"] + filters: ["!node_modules", "node_modules/my-ui"] }; } export default defineConfig({ compilation: { // compilation options here - // persistentCache: false - output:{ + // persistentCache: false, + output: { path: "build", // publicPath: "/vue-public-path/", }, diff --git a/packages/core/src/config/index.ts b/packages/core/src/config/index.ts index 1465e0acb..e92a04b30 100644 --- a/packages/core/src/config/index.ts +++ b/packages/core/src/config/index.ts @@ -717,7 +717,7 @@ export async function readConfigFile( return config; } finally { - fse.unlink(getFilePath(outputPath, fileName)).catch(() => {}); + // fse.unlink(getFilePath(outputPath, fileName)).catch(() => {}); } }