Skip to main content

turbopack_ecmascript/webpack/
parse.rs

1use anyhow::Result;
2use swc_core::ecma::ast::{
3    ArrowExpr, CallExpr, Callee, Expr, ExprStmt, FnExpr, Module, ModuleItem, Program, Script, Stmt,
4};
5use turbo_rcstr::rcstr;
6use turbo_tasks::Vc;
7use turbo_tasks_fs::FileSystemPath;
8use turbopack_core::{compile_time_info::CompileTimeInfo, source::Source};
9
10use crate::{
11    EcmascriptInputTransforms, EcmascriptModuleAssetType,
12    parse::{ParseResult, parse},
13    utils::unparen,
14};
15
16#[turbo_tasks::value(shared, serialization = "skip")]
17#[derive(Debug)]
18pub enum WebpackRuntime {
19    Webpack5 { context_path: FileSystemPath },
20    None,
21}
22
23fn iife(stmt: &Stmt) -> Option<&Vec<Stmt>> {
24    if let Stmt::Expr(ExprStmt { expr, .. }) = &stmt
25        && let Expr::Call(CallExpr {
26            callee: Callee::Expr(callee),
27            args,
28            ..
29        }) = unparen(expr)
30    {
31        if !args.is_empty() {
32            return None;
33        }
34        return get_fn_body(callee);
35    }
36    None
37}
38
39fn program_iife(program: &Program) -> Option<&Vec<Stmt>> {
40    match program {
41        Program::Module(Module { body, .. }) => {
42            if let [ModuleItem::Stmt(stmt)] = &body[..] {
43                return iife(stmt);
44            }
45        }
46        Program::Script(Script { body, .. }) => {
47            if let [stmt] = &body[..] {
48                return iife(stmt);
49            }
50        }
51    }
52    None
53}
54
55fn is_webpack_require_decl(stmt: &Stmt) -> bool {
56    if let Some(decl) = stmt.as_decl()
57        && let Some(fn_decl) = decl.as_fn_decl()
58    {
59        return &*fn_decl.ident.sym == "__webpack_require__";
60    }
61    false
62}
63
64fn get_fn_body(expr: &Expr) -> Option<&Vec<Stmt>> {
65    let expr = unparen(expr);
66    if let Some(FnExpr { function, .. }) = expr.as_fn_expr()
67        && let Some(body) = &function.body
68    {
69        return Some(&body.stmts);
70    }
71    if let Some(ArrowExpr { body, .. }) = expr.as_arrow()
72        && let Some(block) = body.as_block_stmt()
73    {
74        return Some(&block.stmts);
75    }
76    None
77}
78
79#[turbo_tasks::function]
80pub async fn webpack_runtime(
81    source: Vc<Box<dyn Source>>,
82    transforms: Vc<EcmascriptInputTransforms>,
83    compile_time_info: Vc<CompileTimeInfo>,
84) -> Result<Vc<WebpackRuntime>> {
85    let node_env = compile_time_info
86        .await?
87        .defines
88        .read_process_env(rcstr!("NODE_ENV"))
89        .owned()
90        .await?
91        .unwrap_or_else(|| rcstr!("development"));
92    let parsed = parse(
93        source,
94        EcmascriptModuleAssetType::Ecmascript,
95        transforms,
96        node_env,
97        false,
98        false,
99    )
100    .await?;
101    match &*parsed {
102        ParseResult::Ok { program, .. } => {
103            if let Some(stmts) = program_iife(program)
104                && stmts.iter().any(is_webpack_require_decl)
105            {
106                return Ok(WebpackRuntime::Webpack5 {
107                    context_path: source.ident().await?.path.parent(),
108                }
109                .cell());
110            }
111        }
112        ParseResult::Unparsable { .. } | ParseResult::NotFound => {}
113    }
114    Ok(WebpackRuntime::None.cell())
115}