turbopack_ecmascript/webpack/
parse.rs1use std::borrow::Cow;
2
3use anyhow::Result;
4use swc_core::{
5 common::GLOBALS,
6 ecma::{
7 ast::{
8 ArrowExpr, AssignOp, AssignTarget, BinExpr, BinaryOp, CallExpr, Callee, Expr,
9 ExprOrSpread, ExprStmt, FnExpr, Lit, Module, ModuleItem, Program, Script,
10 SimpleAssignTarget, Stmt,
11 },
12 visit::{Visit, VisitWith},
13 },
14};
15use turbo_rcstr::rcstr;
16use turbo_tasks::Vc;
17use turbo_tasks_fs::FileSystemPath;
18use turbopack_core::{compile_time_info::CompileTimeInfo, source::Source};
19
20use crate::{
21 EcmascriptInputTransforms, EcmascriptModuleAssetType,
22 analyzer::{JsValue, graph::EvalContext},
23 parse::{ParseResult, parse},
24 utils::unparen,
25};
26
27#[turbo_tasks::value(shared, serialization = "skip")]
28#[derive(Debug)]
29pub enum WebpackRuntime {
30 Webpack5 {
31 #[turbo_tasks(trace_ignore)]
34 chunk_request_expr: JsValue,
35 context_path: FileSystemPath,
36 },
37 None,
38}
39
40fn iife(stmt: &Stmt) -> Option<&Vec<Stmt>> {
41 if let Stmt::Expr(ExprStmt { expr, .. }) = &stmt
42 && let Expr::Call(CallExpr {
43 callee: Callee::Expr(callee),
44 args,
45 ..
46 }) = unparen(expr)
47 {
48 if !args.is_empty() {
49 return None;
50 }
51 return get_fn_body(callee);
52 }
53 None
54}
55
56fn program_iife(program: &Program) -> Option<&Vec<Stmt>> {
57 match program {
58 Program::Module(Module { body, .. }) => {
59 if let [ModuleItem::Stmt(stmt)] = &body[..] {
60 return iife(stmt);
61 }
62 }
63 Program::Script(Script { body, .. }) => {
64 if let [stmt] = &body[..] {
65 return iife(stmt);
66 }
67 }
68 }
69 None
70}
71
72fn is_webpack_require_decl(stmt: &Stmt) -> bool {
73 if let Some(decl) = stmt.as_decl()
74 && let Some(fn_decl) = decl.as_fn_decl()
75 {
76 return &*fn_decl.ident.sym == "__webpack_require__";
77 }
78 false
79}
80
81fn get_assign_target_identifier(expr: &AssignTarget) -> Option<Cow<'_, str>> {
82 match expr.as_simple()? {
83 SimpleAssignTarget::Ident(ident) => Some(Cow::Borrowed(&*ident.sym)),
84 SimpleAssignTarget::Member(member) => {
85 if let Some(obj_name) = get_expr_identifier(&member.obj)
86 && let Some(ident) = member.prop.as_ident()
87 {
88 return Some(Cow::Owned(obj_name.into_owned() + "." + &*ident.sym));
89 }
90 None
91 }
92 SimpleAssignTarget::Paren(p) => get_expr_identifier(&p.expr),
93
94 _ => None,
95 }
96}
97
98fn get_expr_identifier(expr: &Expr) -> Option<Cow<'_, str>> {
99 let expr = unparen(expr);
100 if let Some(ident) = expr.as_ident() {
101 return Some(Cow::Borrowed(&*ident.sym));
102 }
103 if let Some(member) = expr.as_member()
104 && let Some(ident) = member.prop.as_ident()
105 && let Some(obj_name) = get_expr_identifier(&member.obj)
106 {
107 return Some(Cow::Owned(obj_name.into_owned() + "." + &*ident.sym));
108 }
109 None
110}
111
112fn get_assignment<'a>(stmts: &'a Vec<Stmt>, property: &str) -> Option<&'a Expr> {
113 for stmt in stmts {
114 if let Some(stmts) = iife(stmt)
115 && let Some(result) = get_assignment(stmts, property)
116 {
117 return Some(result);
118 }
119 if let Some(expr_stmt) = stmt.as_expr()
120 && let Some(assign) = unparen(&expr_stmt.expr).as_assign()
121 && assign.op == AssignOp::Assign
122 && let Some(name) = get_assign_target_identifier(&assign.left)
123 && name == property
124 {
125 return Some(unparen(&assign.right));
126 }
127 }
128 None
129}
130
131fn get_fn_body(expr: &Expr) -> Option<&Vec<Stmt>> {
132 let expr = unparen(expr);
133 if let Some(FnExpr { function, .. }) = expr.as_fn_expr()
134 && let Some(body) = &function.body
135 {
136 return Some(&body.stmts);
137 }
138 if let Some(ArrowExpr { body, .. }) = expr.as_arrow()
139 && let Some(block) = body.as_block_stmt()
140 {
141 return Some(&block.stmts);
142 }
143 None
144}
145
146fn get_javascript_chunk_filename(stmts: &Vec<Stmt>, eval_context: &EvalContext) -> Option<JsValue> {
147 if let Some(expr) = get_assignment(stmts, "__webpack_require__.u")
148 && let Some(stmts) = get_fn_body(expr)
149 && let Some(ret) = stmts.iter().find_map(|stmt| stmt.as_return_stmt())
150 && let Some(expr) = &ret.arg
151 {
152 return Some(eval_context.eval(expr));
153 }
154 None
155}
156
157struct RequirePrefixVisitor {
158 result: Option<Lit>,
159}
160
161impl Visit for RequirePrefixVisitor {
162 fn visit_call_expr(&mut self, call: &CallExpr) {
163 if let Some(expr) = call.callee.as_expr()
164 && let Some(name) = get_expr_identifier(expr)
165 && name == "require"
166 && let [ExprOrSpread { spread: None, expr }] = &call.args[..]
167 && let Some(BinExpr {
168 op: BinaryOp::Add,
169 left,
170 ..
171 }) = expr.as_bin()
172 {
173 self.result = left.as_lit().cloned();
174 return;
175 }
176 call.visit_children_with(self);
177 }
178}
179
180fn get_require_prefix(stmts: &Vec<Stmt>) -> Option<Lit> {
181 if let Some(expr) = get_assignment(stmts, "__webpack_require__.f.require") {
182 let mut visitor = RequirePrefixVisitor { result: None };
183 expr.visit_children_with(&mut visitor);
184 return visitor.result;
185 }
186 None
187}
188
189#[turbo_tasks::function]
190pub async fn webpack_runtime(
191 source: Vc<Box<dyn Source>>,
192 transforms: Vc<EcmascriptInputTransforms>,
193 compile_time_info: Vc<CompileTimeInfo>,
194) -> Result<Vc<WebpackRuntime>> {
195 let node_env = compile_time_info
196 .await?
197 .defines
198 .read_process_env(rcstr!("NODE_ENV"))
199 .owned()
200 .await?
201 .unwrap_or_else(|| rcstr!("development"));
202 let parsed = parse(
203 source,
204 EcmascriptModuleAssetType::Ecmascript,
205 transforms,
206 node_env,
207 false,
208 false,
209 )
210 .await?;
211 match &*parsed {
212 ParseResult::Ok {
213 program,
214 eval_context,
215 globals,
216 ..
217 } => {
218 if let Some(stmts) = program_iife(program)
219 && stmts.iter().any(is_webpack_require_decl)
220 {
221 let chunk_filename = GLOBALS.set(globals, || {
223 get_javascript_chunk_filename(stmts, eval_context)
224 });
225
226 let prefix_path = get_require_prefix(stmts);
227
228 if let (Some(chunk_filename), Some(prefix_path)) = (chunk_filename, prefix_path) {
229 let value = JsValue::concat(vec![
230 JsValue::Constant(prefix_path.into()),
231 chunk_filename,
232 ]);
233
234 return Ok(WebpackRuntime::Webpack5 {
235 chunk_request_expr: value,
236 context_path: source.ident().await?.path.parent(),
237 }
238 .cell());
239 }
240 }
241 }
242 ParseResult::Unparsable { .. } | ParseResult::NotFound => {}
243 }
244 Ok(WebpackRuntime::None.cell())
245}