Skip to content

Commit 1aadf05

Browse files
authored
feat: Support new URL("./foo", import.meta.url) (#2340)
1 parent 08337f2 commit 1aadf05

File tree

20 files changed

+526
-1
lines changed

20 files changed

+526
-1
lines changed

.changeset/loud-toys-watch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@rspack/core": patch
3+
---
4+
5+
feat: Support `new URL("./foo", import.meta.url)`

crates/rspack_core/src/dependency/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ pub enum DependencyType {
3535
DynamicImport,
3636
// cjs require
3737
CjsRequire,
38+
// new URL("./foo", import.meta.url)
39+
NewUrl,
3840
// import.meta.webpackHot.accept
3941
ImportMetaHotAccept,
4042
// import.meta.webpackHot.decline

crates/rspack_plugin_javascript/src/dependency/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
mod commonjs;
22
pub use commonjs::*;
3+
mod url;
4+
pub use url::*;
35
mod esm;
46
pub use esm::*;
57
mod hmr;
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use rspack_core::{
2+
create_javascript_visitor, runtime_globals, CodeGeneratable, CodeGeneratableContext,
3+
CodeGeneratableResult, Dependency, DependencyCategory, DependencyId, DependencyType, ErrorSpan,
4+
JsAstPath, ModuleDependency, ModuleIdentifier,
5+
};
6+
use swc_core::common::Spanned;
7+
use swc_core::ecma::utils::{member_expr, quote_ident, quote_str};
8+
use swc_core::ecma::{ast::*, atoms::JsWord};
9+
10+
#[derive(Debug, Eq, Clone)]
11+
pub struct URLDependency {
12+
id: Option<DependencyId>,
13+
parent_module_identifier: Option<ModuleIdentifier>,
14+
request: JsWord,
15+
span: Option<ErrorSpan>,
16+
#[allow(unused)]
17+
ast_path: JsAstPath,
18+
}
19+
20+
// Do not edit this, as it is used to uniquely identify the dependency.
21+
impl PartialEq for URLDependency {
22+
fn eq(&self, other: &Self) -> bool {
23+
self.parent_module_identifier == other.parent_module_identifier && self.request == other.request
24+
}
25+
}
26+
27+
// Do not edit this, as it is used to uniquely identify the dependency.
28+
impl std::hash::Hash for URLDependency {
29+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
30+
self.parent_module_identifier.hash(state);
31+
self.request.hash(state);
32+
self.category().hash(state);
33+
self.dependency_type().hash(state);
34+
}
35+
}
36+
37+
impl URLDependency {
38+
pub fn new(request: JsWord, span: Option<ErrorSpan>, ast_path: JsAstPath) -> Self {
39+
Self {
40+
id: None,
41+
parent_module_identifier: None,
42+
request,
43+
span,
44+
ast_path,
45+
}
46+
}
47+
}
48+
49+
impl Dependency for URLDependency {
50+
fn id(&self) -> Option<DependencyId> {
51+
self.id
52+
}
53+
fn set_id(&mut self, id: Option<DependencyId>) {
54+
self.id = id;
55+
}
56+
fn parent_module_identifier(&self) -> Option<&ModuleIdentifier> {
57+
self.parent_module_identifier.as_ref()
58+
}
59+
60+
fn set_parent_module_identifier(&mut self, module_identifier: Option<ModuleIdentifier>) {
61+
self.parent_module_identifier = module_identifier;
62+
}
63+
64+
fn category(&self) -> &DependencyCategory {
65+
&DependencyCategory::Url
66+
}
67+
68+
fn dependency_type(&self) -> &DependencyType {
69+
&DependencyType::NewUrl
70+
}
71+
}
72+
73+
impl ModuleDependency for URLDependency {
74+
fn request(&self) -> &str {
75+
&self.request
76+
}
77+
78+
fn user_request(&self) -> &str {
79+
&self.request
80+
}
81+
82+
fn span(&self) -> Option<&ErrorSpan> {
83+
self.span.as_ref()
84+
}
85+
}
86+
87+
impl CodeGeneratable for URLDependency {
88+
fn generate(
89+
&self,
90+
code_generatable_context: &mut CodeGeneratableContext,
91+
) -> rspack_error::Result<CodeGeneratableResult> {
92+
let CodeGeneratableContext { compilation, .. } = code_generatable_context;
93+
let mut code_gen = CodeGeneratableResult::default();
94+
95+
if let Some(id) = self.id() {
96+
if let Some(module_id) = compilation
97+
.module_graph
98+
.module_graph_module_by_dependency_id(&id)
99+
.map(|m| m.id(&compilation.chunk_graph).to_string())
100+
{
101+
code_gen.visitors.push(
102+
create_javascript_visitor!(exact &self.ast_path, visit_mut_new_expr(n: &mut NewExpr) {
103+
let Some(args) = &mut n.args else { return };
104+
105+
if let (Some(first), Some(second)) = (args.first(), args.get(1)) {
106+
let path_span = first.span();
107+
let meta_span = second.span();
108+
109+
let require_call = CallExpr {
110+
span: path_span,
111+
callee: Callee::Expr(quote_ident!(runtime_globals::REQUIRE).into()),
112+
args: vec![ExprOrSpread {
113+
spread: None,
114+
expr: quote_str!(&*module_id).into(),
115+
}],
116+
type_args: None,
117+
};
118+
119+
args[0] = ExprOrSpread {
120+
spread: None,
121+
expr: require_call.into(),
122+
};
123+
124+
args[1] = ExprOrSpread {
125+
spread: None,
126+
expr: member_expr!(meta_span, self.location),
127+
};
128+
}
129+
}),
130+
);
131+
}
132+
}
133+
134+
Ok(code_gen)
135+
}
136+
}

crates/rspack_plugin_javascript/src/visitors/dependency/code_generation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ impl<'a, 'b> VisitMutAstPath for DependencyVisitor<'a, 'b> {
266266
impl_ast_node_interceptor!(module_decl, ModuleDecl);
267267
impl_ast_node_interceptor!(module_item, ModuleItem);
268268
impl_ast_node_interceptor!(call_expr, CallExpr);
269+
impl_ast_node_interceptor!(new_expr, NewExpr);
269270
impl_ast_node_interceptor!(lit, Lit);
270271
impl_ast_node_interceptor!(str, Str);
271272
}

crates/rspack_plugin_javascript/src/visitors/dependency/scanner.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ use rspack_regex::RspackRegex;
77
use sugar_path::SugarPath;
88
use swc_core::common::{pass::AstNodePath, Mark, SyntaxContext};
99
use swc_core::ecma::ast::{
10-
BinExpr, BinaryOp, CallExpr, Callee, Expr, Lit, MemberProp, ModuleDecl, Tpl,
10+
BinExpr, BinaryOp, CallExpr, Callee, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp,
11+
MetaPropExpr, MetaPropKind, ModuleDecl, NewExpr, Tpl,
1112
};
13+
use swc_core::ecma::atoms::js_word;
1214
use swc_core::ecma::utils::{quote_ident, quote_str};
1315
use swc_core::ecma::visit::{AstParentNodeRef, VisitAstPath, VisitWithPath};
1416
use swc_core::quote;
1517

1618
use super::{as_parent_path, is_require_context_call};
1719
use crate::dependency::{
1820
CommonJSRequireDependency, EsmDynamicImportDependency, EsmExportDependency, EsmImportDependency,
21+
URLDependency,
1922
};
2023
pub const WEBPACK_HASH: &str = "__webpack_hash__";
2124
pub const WEBPACK_PUBLIC_PATH: &str = "__webpack_public_path__";
@@ -124,6 +127,52 @@ impl DependencyScanner<'_> {
124127
}
125128
}
126129
}
130+
131+
// new URL("./foo.png", import.meta.url);
132+
fn add_new_url(&mut self, new_expr: &NewExpr, ast_path: &AstNodePath<AstParentNodeRef<'_>>) {
133+
if let Expr::Ident(Ident {
134+
sym: js_word!("URL"),
135+
..
136+
}) = &*new_expr.callee
137+
{
138+
if let Some(args) = &new_expr.args {
139+
if let (Some(first), Some(second)) = (args.first(), args.get(1)) {
140+
if let (
141+
ExprOrSpread {
142+
spread: None,
143+
expr: box Expr::Lit(Lit::Str(path)),
144+
},
145+
// import.meta.url
146+
ExprOrSpread {
147+
spread: None,
148+
expr:
149+
box Expr::Member(MemberExpr {
150+
obj:
151+
box Expr::MetaProp(MetaPropExpr {
152+
kind: MetaPropKind::ImportMeta,
153+
..
154+
}),
155+
prop:
156+
MemberProp::Ident(Ident {
157+
sym: js_word!("url"),
158+
..
159+
}),
160+
..
161+
}),
162+
},
163+
) = (first, second)
164+
{
165+
self.add_dependency(box URLDependency::new(
166+
path.value.clone(),
167+
Some(new_expr.span.into()),
168+
as_parent_path(ast_path),
169+
))
170+
}
171+
}
172+
}
173+
}
174+
}
175+
127176
fn add_export(
128177
&mut self,
129178
module_decl: &ModuleDecl,
@@ -237,6 +286,15 @@ impl VisitAstPath for DependencyScanner<'_> {
237286
node.visit_children_with_path(self, ast_path);
238287
}
239288

289+
fn visit_new_expr<'ast: 'r, 'r>(
290+
&mut self,
291+
node: &'ast NewExpr,
292+
ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
293+
) {
294+
self.add_new_url(node, &*ast_path);
295+
node.visit_children_with_path(self, ast_path);
296+
}
297+
240298
fn visit_expr<'ast: 'r, 'r>(
241299
&mut self,
242300
expr: &'ast Expr,

crates/rspack_plugin_javascript/tests/fixtures/new_url/inline/expected/main.js

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
(function() {
2+
var __webpack_modules__ = {
3+
4+
}
5+
// The module cache
6+
var __webpack_module_cache__ = {};
7+
function __webpack_require__(moduleId) {
8+
// Check if module is in cache
9+
var cachedModule = __webpack_module_cache__[moduleId];
10+
if (cachedModule !== undefined) {
11+
return cachedModule.exports;
12+
}
13+
// Create a new module (and put it into the cache)
14+
var module = (__webpack_module_cache__[moduleId] = {
15+
// no module.loaded needed
16+
exports: {}
17+
});
18+
// Execute the module function
19+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
20+
// Return the exports of the module
21+
return module.exports;
22+
23+
}
24+
// expose the modules object (__webpack_modules__)
25+
__webpack_require__.m = __webpack_modules__;
26+
// webpack/runtime/on_chunk_loaded
27+
(function() {
28+
var deferred = [];
29+
__webpack_require__.O = function (result, chunkIds, fn, priority) {
30+
if (chunkIds) {
31+
priority = priority || 0;
32+
for (var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--)
33+
deferred[i] = deferred[i - 1];
34+
deferred[i] = [chunkIds, fn, priority];
35+
return;
36+
}
37+
var notFulfilled = Infinity;
38+
for (var i = 0; i < deferred.length; i++) {
39+
var [chunkIds, fn, priority] = deferred[i];
40+
var fulfilled = true;
41+
for (var j = 0; j < chunkIds.length; j++) {
42+
if (
43+
(priority & (1 === 0) || notFulfilled >= priority) &&
44+
Object.keys(__webpack_require__.O).every(function (key) {
45+
__webpack_require__.O[key](chunkIds[j]);
46+
})
47+
) {
48+
chunkIds.splice(j--, 1);
49+
} else {
50+
fulfilled = false;
51+
if (priority < notFulfilled) notFulfilled = priority;
52+
}
53+
}
54+
if (fulfilled) {
55+
deferred.splice(i--, 1);
56+
var r = fn();
57+
if (r !== undefined) result = r;
58+
}
59+
}
60+
return result;
61+
};
62+
63+
})();
64+
// webpack/runtime/has_own_property
65+
(function() {
66+
__webpack_require__.o = function (obj, prop) {
67+
return Object.prototype.hasOwnProperty.call(obj, prop);
68+
};
69+
70+
})();
71+
// webpack/runtime/jsonp_chunk_loading
72+
(function() {
73+
var installedChunks = {"runtime": 0,};
74+
__webpack_require__.O.j = function (chunkId) {
75+
installedChunks[chunkId] === 0;
76+
};
77+
// install a JSONP callback for chunk loading
78+
var webpackJsonpCallback = function (parentChunkLoadingFunction, data) {
79+
var [chunkIds, moreModules, runtime] = data;
80+
// add "moreModules" to the modules object,
81+
// then flag all "chunkIds" as loaded and fire callback
82+
var moduleId,
83+
chunkId,
84+
i = 0;
85+
if (chunkIds.some(id => installedChunks[id] !== 0)) {
86+
for (moduleId in moreModules) {
87+
if (__webpack_require__.o(moreModules, moduleId)) {
88+
__webpack_require__.m[moduleId] = moreModules[moduleId];
89+
}
90+
}
91+
if (runtime) var result = runtime(__webpack_require__);
92+
}
93+
if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
94+
for (; i < chunkIds.length; i++) {
95+
chunkId = chunkIds[i];
96+
if (
97+
__webpack_require__.o(installedChunks, chunkId) &&
98+
installedChunks[chunkId]
99+
) {
100+
installedChunks[chunkId][0]();
101+
}
102+
installedChunks[chunkId] = 0;
103+
}
104+
return __webpack_require__.O(result);
105+
};
106+
107+
var chunkLoadingGlobal = (self["webpackChunkwebpack"] =
108+
self["webpackChunkwebpack"] || []);
109+
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
110+
chunkLoadingGlobal.push = webpackJsonpCallback.bind(
111+
null,
112+
chunkLoadingGlobal.push.bind(chunkLoadingGlobal)
113+
);
114+
115+
})();
116+
117+
})();

0 commit comments

Comments
 (0)