Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions crates/mako/src/ast/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use swc_core::ecma::ast::{
MetaPropExpr, MetaPropKind, Module, ModuleItem,
};

use crate::module::{ModuleAst, ModuleSystem};

pub fn is_remote_or_data(url: &str) -> bool {
let lower_url = url.to_lowercase();
// ref:
Expand Down Expand Up @@ -148,3 +150,22 @@ pub fn require_ensure(source: String) -> Expr {
}],
)
}

pub fn get_module_system(ast: &ModuleAst) -> ModuleSystem {
match ast {
ModuleAst::Script(module) => {
let is_esm = module
.ast
.body
.iter()
.any(|s| matches!(s, ModuleItem::ModuleDecl(_)));
if is_esm {
ModuleSystem::ESModule
} else {
ModuleSystem::CommonJS
}
}
crate::module::ModuleAst::Css(_) => ModuleSystem::Custom,
crate::module::ModuleAst::None => ModuleSystem::Custom,
}
}
5 changes: 5 additions & 0 deletions crates/mako/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use colored::Colorize;
use thiserror::Error;

use crate::ast::file::{Content, File, JsContent};
use crate::ast::utils::get_module_system;
use crate::compiler::{Compiler, Context};
use crate::generate::chunk_pot::util::hash_hashmap;
use crate::module::{Module, ModuleAst, ModuleId, ModuleInfo};
Expand Down Expand Up @@ -183,6 +184,7 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(
let raw = file.get_content_raw();
let info = ModuleInfo {
file,
module_system: get_module_system(&ast),
ast,
external: Some(external_name),
is_async,
Expand All @@ -207,6 +209,7 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(
let raw = file.get_content_raw();
let info = ModuleInfo {
file,
module_system: get_module_system(&ast),
ast,
raw,
..Default::default()
Expand All @@ -232,6 +235,7 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(

ModuleInfo {
file,
module_system: get_module_system(&ast),
ast,
is_ignored: true,
..Default::default()
Expand Down Expand Up @@ -312,6 +316,7 @@ __mako_require__.loadScript('{}', (e) => e.type === 'load' ? resolve() : reject(
let info = ModuleInfo {
file,
deps,
module_system: get_module_system(&ast),
ast,
resolved_resource: parent_resource,
source_map_chain,
Expand Down
6 changes: 5 additions & 1 deletion crates/mako/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use tracing::debug;

use crate::ast::comments::Comments;
use crate::ast::file::win_path;
use crate::config::{Config, ModuleIdStrategy, OutputMode};
use crate::config::{Config, Mode, ModuleIdStrategy, OutputMode};
use crate::generate::chunk_graph::ChunkGraph;
use crate::generate::optimize_chunk::OptimizeChunksInfo;
use crate::module_graph::ModuleGraph;
Expand Down Expand Up @@ -258,6 +258,10 @@ impl Compiler {

let mut config = config;

if config.mode == Mode::Production && config.experimental.imports_checker {
plugins.push(Arc::new(plugins::imports_checker::ImportsChecker {}));
}

if let Some(progress) = &config.progress {
plugins.push(Arc::new(plugins::progress::ProgressPlugin::new(
plugins::progress::ProgressPluginOptions {
Expand Down
1 change: 1 addition & 0 deletions crates/mako/src/config/experimental.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct ExperimentalConfig {
#[serde(deserialize_with = "deserialize_detect_loop")]
pub detect_circular_dependence: Option<DetectCircularDependence>,
pub central_ensure: bool,
pub imports_checker: bool,
}

#[derive(Deserialize, Serialize, Debug)]
Expand Down
3 changes: 2 additions & 1 deletion crates/mako/src/config/mako.config.default.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"ignores": ["node_modules"],
"graphviz": false
},
"centralEnsure": true
"centralEnsure": true,
"importsChecker": false
},
"useDefineForClassFields": true,
"emitDecoratorMetadata": false,
Expand Down
9 changes: 9 additions & 0 deletions crates/mako/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ pub struct Dependency {
pub span: Option<Span>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ModuleSystem {
CommonJS,
ESModule,
Custom,
}

bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Default)]
pub struct ResolveTypeFlags: u16 {
Expand Down Expand Up @@ -192,11 +199,13 @@ pub struct ModuleInfo {
pub resolved_resource: Option<ResolverResource>,
/// The transformed source map chain of this module
pub source_map_chain: Vec<Vec<u8>>,
pub module_system: ModuleSystem,
}

impl Default for ModuleInfo {
fn default() -> Self {
Self {
module_system: ModuleSystem::CommonJS,
ast: ModuleAst::None,
file: Default::default(),
deps: Default::default(),
Expand Down
1 change: 1 addition & 0 deletions crates/mako/src/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod graphviz;
pub mod hmr_runtime;
pub mod ignore;
pub mod import;
pub mod imports_checker;
pub mod invalid_webpack_syntax;
pub mod manifest;
pub mod minifish;
Expand Down
122 changes: 122 additions & 0 deletions crates/mako/src/plugins/imports_checker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
mod collect_exports;
mod collect_imports;

use std::collections::{HashMap, HashSet};
use std::sync::{Arc, RwLockReadGuard};

use anyhow::Result;
use collect_exports::CollectExports;
use collect_imports::CollectImports;
use swc_core::ecma::visit::VisitWith;
use tracing::error;

use crate::compiler::{Compiler, Context};
use crate::module::{ModuleId, ModuleSystem};
use crate::module_graph::ModuleGraph;
use crate::plugin::Plugin;

pub struct ImportsChecker {}

fn pick_no_export_specifiers_with_imports_info(
module_id: &ModuleId,
module_graph: &RwLockReadGuard<ModuleGraph>,
specifiers: &mut HashSet<String>,
) {
if !specifiers.is_empty() {
let dep_module = module_graph.get_module(module_id).unwrap();
if let Some(info) = &dep_module.info {
match info.module_system {
ModuleSystem::ESModule => {
let mut exports_star_sources: Vec<String> = vec![];
let ast = &info.ast.as_script().unwrap().ast;
ast.visit_with(&mut CollectExports {
specifiers,
exports_star_sources: &mut exports_star_sources,
});
exports_star_sources.into_iter().for_each(|source| {
if let Some(id) =
module_graph.get_dependency_module_by_source(module_id, &source)
{
pick_no_export_specifiers_with_imports_info(
id,
module_graph,
specifiers,
);
}
})
}
ModuleSystem::CommonJS | ModuleSystem::Custom => {
specifiers.clear();
}
}
}
}
}
impl Plugin for ImportsChecker {
fn name(&self) -> &str {
"imports_checker"
}
fn after_build(&self, context: &Arc<Context>, _compiler: &Compiler) -> Result<()> {
let mut modules_imports_map: HashMap<&ModuleId, HashMap<String, HashSet<String>>> =
HashMap::new();

let module_graph = context.module_graph.read().unwrap();
let modules = module_graph.modules();

for m in modules {
if let Some(info) = &m.info {
if !info.file.is_under_node_modules
&& matches!(info.module_system, ModuleSystem::ESModule)
{
// 收集 imports
let ast = &info.ast.as_script().unwrap().ast;
let mut import_specifiers: HashMap<String, HashSet<String>> = HashMap::new();

ast.visit_with(&mut CollectImports {
imports_specifiers_with_source: &mut import_specifiers,
});
modules_imports_map.insert(&m.id, import_specifiers);
}
}
}
// 收集 exports
modules_imports_map
.iter_mut()
.for_each(|(module_id, import_specifiers)| {
import_specifiers
.iter_mut()
.for_each(|(source, specifiers)| {
if let Some(dep_module_id) =
module_graph.get_dependency_module_by_source(module_id, source)
{
pick_no_export_specifiers_with_imports_info(
dep_module_id,
&module_graph,
specifiers,
);
}
})
});
let mut should_panic = false;
modules_imports_map
.into_iter()
.for_each(|(module_id, import_specifiers)| {
import_specifiers
.into_iter()
.filter(|(_, specifiers)| !specifiers.is_empty())
.for_each(|(source, specifiers)| {
should_panic = true;
specifiers.iter().for_each(|specifier| {
error!(
"'{}' is undefined: import from '{}' in '{}'",
specifier, source, module_id.id
);
})
});
});
if should_panic {
panic!("dependency check error!");
};
Comment on lines +117 to +119
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

避免使用 panic!,改用适当的错误处理机制

在生产环境中使用 panic! 可能导致程序崩溃,影响用户体验。建议使用错误返回值或日志记录来处理错误,以提供更好的稳定性和可维护性。

可以考虑修改为:

 if should_panic {
-    panic!("dependency check error!");
+    return Err(anyhow::anyhow!("Dependency check error"));
 };

并在函数签名中调整返回类型:

-fn after_build(&self, context: &Arc<Context>, _compiler: &Compiler) -> Result<()> {
+fn after_build(&self, context: &Arc<Context>, _compiler: &Compiler) -> Result<(), anyhow::Error> {

Committable suggestion skipped: line range outside the PR's diff.

Ok(())
}
}
68 changes: 68 additions & 0 deletions crates/mako/src/plugins/imports_checker/collect_exports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::collections::HashSet;

use swc_core::ecma::ast::*;
use swc_core::ecma::visit::Visit;

pub struct CollectExports<'a> {
pub specifiers: &'a mut HashSet<String>,
pub exports_star_sources: &'a mut Vec<String>,
}

impl<'a> Visit for CollectExports<'a> {
fn visit_module_decl(&mut self, node: &ModuleDecl) {
match &node {
// export const a = 1
ModuleDecl::ExportDecl(ExportDecl { decl, .. }) => match decl {
Decl::Fn(FnDecl { ident, .. }) => {
self.specifiers.remove(&ident.sym.to_string());
}
Decl::Class(ClassDecl { ident, .. }) => {
self.specifiers.remove(&ident.sym.to_string());
}
Decl::Var(box VarDecl { decls, .. }) => decls.iter().for_each(|decl| {
if let Pat::Ident(ident) = &decl.name {
self.specifiers.remove(&ident.sym.to_string());
}
}),
_ => {}
},
// export default function
ModuleDecl::ExportDefaultDecl(_) => {
self.specifiers.remove(&"default".to_string());
}
// export default 1
ModuleDecl::ExportDefaultExpr(_) => {
self.specifiers.remove(&"default".to_string());
}
// export * from 'b'
ModuleDecl::ExportAll(all) => {
let source = all.src.value.to_string();
self.exports_star_sources.push(source);
}
// export {a, b} || export {default as c} from 'd' || export a from 'b'
ModuleDecl::ExportNamed(named) => {
named
.specifiers
.iter()
.for_each(|specifier| match &specifier {
ExportSpecifier::Named(named) => {
if let Some(ModuleExportName::Ident(ident)) = &named.exported {
self.specifiers.remove(&ident.sym.to_string());
} else if let ModuleExportName::Ident(ident) = &named.orig {
self.specifiers.remove(&ident.sym.to_string());
}
}
ExportSpecifier::Namespace(name_spacing) => {
if let ModuleExportName::Ident(ident) = &name_spacing.name {
self.specifiers.remove(&ident.sym.to_string());
}
}
ExportSpecifier::Default(default) => {
self.specifiers.remove(&default.exported.sym.to_string());
}
})
}
_ => {}
}
}
}
Loading