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
5 changes: 5 additions & 0 deletions .changeset/loud-toys-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rspack/core": patch
---

feat: Support `new URL("./foo", import.meta.url)`
2 changes: 2 additions & 0 deletions crates/rspack_core/src/dependency/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub enum DependencyType {
DynamicImport,
// cjs require
CjsRequire,
// new URL("./foo", import.meta.url)
NewUrl,
// import.meta.webpackHot.accept
ImportMetaHotAccept,
// import.meta.webpackHot.decline
Expand Down
2 changes: 2 additions & 0 deletions crates/rspack_plugin_javascript/src/dependency/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod commonjs;
pub use commonjs::*;
mod url;
pub use url::*;
mod esm;
pub use esm::*;
mod hmr;
Expand Down
136 changes: 136 additions & 0 deletions crates/rspack_plugin_javascript/src/dependency/url/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use rspack_core::{
create_javascript_visitor, runtime_globals, CodeGeneratable, CodeGeneratableContext,
CodeGeneratableResult, Dependency, DependencyCategory, DependencyId, DependencyType, ErrorSpan,
JsAstPath, ModuleDependency, ModuleIdentifier,
};
use swc_core::common::Spanned;
use swc_core::ecma::utils::{member_expr, quote_ident, quote_str};
use swc_core::ecma::{ast::*, atoms::JsWord};

#[derive(Debug, Eq, Clone)]
pub struct URLDependency {
id: Option<DependencyId>,
parent_module_identifier: Option<ModuleIdentifier>,
request: JsWord,
span: Option<ErrorSpan>,
#[allow(unused)]
ast_path: JsAstPath,
}

// Do not edit this, as it is used to uniquely identify the dependency.
impl PartialEq for URLDependency {
fn eq(&self, other: &Self) -> bool {
self.parent_module_identifier == other.parent_module_identifier && self.request == other.request
}
}

// Do not edit this, as it is used to uniquely identify the dependency.
impl std::hash::Hash for URLDependency {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.parent_module_identifier.hash(state);
self.request.hash(state);
self.category().hash(state);
self.dependency_type().hash(state);
}
}

impl URLDependency {
pub fn new(request: JsWord, span: Option<ErrorSpan>, ast_path: JsAstPath) -> Self {
Self {
id: None,
parent_module_identifier: None,
request,
span,
ast_path,
}
}
}

impl Dependency for URLDependency {
fn id(&self) -> Option<DependencyId> {
self.id
}
fn set_id(&mut self, id: Option<DependencyId>) {
self.id = id;
}
fn parent_module_identifier(&self) -> Option<&ModuleIdentifier> {
self.parent_module_identifier.as_ref()
}

fn set_parent_module_identifier(&mut self, module_identifier: Option<ModuleIdentifier>) {
self.parent_module_identifier = module_identifier;
}

fn category(&self) -> &DependencyCategory {
&DependencyCategory::Url
}

fn dependency_type(&self) -> &DependencyType {
&DependencyType::NewUrl
}
}

impl ModuleDependency for URLDependency {
fn request(&self) -> &str {
&self.request
}

fn user_request(&self) -> &str {
&self.request
}

fn span(&self) -> Option<&ErrorSpan> {
self.span.as_ref()
}
}

impl CodeGeneratable for URLDependency {
fn generate(
&self,
code_generatable_context: &mut CodeGeneratableContext,
) -> rspack_error::Result<CodeGeneratableResult> {
let CodeGeneratableContext { compilation, .. } = code_generatable_context;
let mut code_gen = CodeGeneratableResult::default();

if let Some(id) = self.id() {
if let Some(module_id) = compilation
.module_graph
.module_graph_module_by_dependency_id(&id)
.map(|m| m.id(&compilation.chunk_graph).to_string())
{
code_gen.visitors.push(
create_javascript_visitor!(exact &self.ast_path, visit_mut_new_expr(n: &mut NewExpr) {
let Some(args) = &mut n.args else { return };

if let (Some(first), Some(second)) = (args.first(), args.get(1)) {
let path_span = first.span();
let meta_span = second.span();

let require_call = CallExpr {
span: path_span,
callee: Callee::Expr(quote_ident!(runtime_globals::REQUIRE).into()),
args: vec![ExprOrSpread {
spread: None,
expr: quote_str!(&*module_id).into(),
}],
type_args: None,
};

args[0] = ExprOrSpread {
spread: None,
expr: require_call.into(),
};

args[1] = ExprOrSpread {
spread: None,
expr: member_expr!(meta_span, self.location),
};
}
}),
);
}
}

Ok(code_gen)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ impl<'a, 'b> VisitMutAstPath for DependencyVisitor<'a, 'b> {
impl_ast_node_interceptor!(module_decl, ModuleDecl);
impl_ast_node_interceptor!(module_item, ModuleItem);
impl_ast_node_interceptor!(call_expr, CallExpr);
impl_ast_node_interceptor!(new_expr, NewExpr);
impl_ast_node_interceptor!(lit, Lit);
impl_ast_node_interceptor!(str, Str);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ use rspack_regex::RspackRegex;
use sugar_path::SugarPath;
use swc_core::common::{pass::AstNodePath, Mark, SyntaxContext};
use swc_core::ecma::ast::{
BinExpr, BinaryOp, CallExpr, Callee, Expr, Lit, MemberProp, ModuleDecl, Tpl,
BinExpr, BinaryOp, CallExpr, Callee, Expr, ExprOrSpread, Ident, Lit, MemberExpr, MemberProp,
MetaPropExpr, MetaPropKind, ModuleDecl, NewExpr, Tpl,
};
use swc_core::ecma::atoms::js_word;
use swc_core::ecma::utils::{quote_ident, quote_str};
use swc_core::ecma::visit::{AstParentNodeRef, VisitAstPath, VisitWithPath};
use swc_core::quote;

use super::{as_parent_path, is_require_context_call};
use crate::dependency::{
CommonJSRequireDependency, EsmDynamicImportDependency, EsmExportDependency, EsmImportDependency,
URLDependency,
};
pub const WEBPACK_HASH: &str = "__webpack_hash__";
pub const WEBPACK_PUBLIC_PATH: &str = "__webpack_public_path__";
Expand Down Expand Up @@ -124,6 +127,52 @@ impl DependencyScanner<'_> {
}
}
}

// new URL("./foo.png", import.meta.url);
fn add_new_url(&mut self, new_expr: &NewExpr, ast_path: &AstNodePath<AstParentNodeRef<'_>>) {
if let Expr::Ident(Ident {
sym: js_word!("URL"),
..
}) = &*new_expr.callee
{
if let Some(args) = &new_expr.args {
if let (Some(first), Some(second)) = (args.first(), args.get(1)) {
if let (
ExprOrSpread {
spread: None,
expr: box Expr::Lit(Lit::Str(path)),
},
// import.meta.url
ExprOrSpread {
spread: None,
expr:
box Expr::Member(MemberExpr {
obj:
box Expr::MetaProp(MetaPropExpr {
kind: MetaPropKind::ImportMeta,
..
}),
prop:
MemberProp::Ident(Ident {
sym: js_word!("url"),
..
}),
..
}),
},
) = (first, second)
{
self.add_dependency(box URLDependency::new(
path.value.clone(),
Some(new_expr.span.into()),
as_parent_path(ast_path),
))
}
}
}
}
}

fn add_export(
&mut self,
module_decl: &ModuleDecl,
Expand Down Expand Up @@ -237,6 +286,15 @@ impl VisitAstPath for DependencyScanner<'_> {
node.visit_children_with_path(self, ast_path);
}

fn visit_new_expr<'ast: 'r, 'r>(
&mut self,
node: &'ast NewExpr,
ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
) {
self.add_new_url(node, &*ast_path);
node.visit_children_with_path(self, ast_path);
}

fn visit_expr<'ast: 'r, 'r>(
&mut self,
expr: &'ast Expr,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
(function() {
var __webpack_modules__ = {

}
// The module cache
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
// Check if module is in cache
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
var module = (__webpack_module_cache__[moduleId] = {
// no module.loaded needed
exports: {}
});
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;

}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = __webpack_modules__;
// webpack/runtime/on_chunk_loaded
(function() {
var deferred = [];
__webpack_require__.O = function (result, chunkIds, fn, priority) {
if (chunkIds) {
priority = priority || 0;
for (var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--)
deferred[i] = deferred[i - 1];
deferred[i] = [chunkIds, fn, priority];
return;
}
var notFulfilled = Infinity;
for (var i = 0; i < deferred.length; i++) {
var [chunkIds, fn, priority] = deferred[i];
var fulfilled = true;
for (var j = 0; j < chunkIds.length; j++) {
if (
(priority & (1 === 0) || notFulfilled >= priority) &&
Object.keys(__webpack_require__.O).every(function (key) {
__webpack_require__.O[key](chunkIds[j]);
})
) {
chunkIds.splice(j--, 1);
} else {
fulfilled = false;
if (priority < notFulfilled) notFulfilled = priority;
}
}
if (fulfilled) {
deferred.splice(i--, 1);
var r = fn();
if (r !== undefined) result = r;
}
}
return result;
};

})();
// webpack/runtime/has_own_property
(function() {
__webpack_require__.o = function (obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
};

})();
// webpack/runtime/jsonp_chunk_loading
(function() {
var installedChunks = {"runtime": 0,};
__webpack_require__.O.j = function (chunkId) {
installedChunks[chunkId] === 0;
};
// install a JSONP callback for chunk loading
var webpackJsonpCallback = function (parentChunkLoadingFunction, data) {
var [chunkIds, moreModules, runtime] = data;
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId,
chunkId,
i = 0;
if (chunkIds.some(id => installedChunks[id] !== 0)) {
for (moduleId in moreModules) {
if (__webpack_require__.o(moreModules, moduleId)) {
__webpack_require__.m[moduleId] = moreModules[moduleId];
}
}
if (runtime) var result = runtime(__webpack_require__);
}
if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (
__webpack_require__.o(installedChunks, chunkId) &&
installedChunks[chunkId]
) {
installedChunks[chunkId][0]();
}
installedChunks[chunkId] = 0;
}
return __webpack_require__.O(result);
};

var chunkLoadingGlobal = (self["webpackChunkwebpack"] =
self["webpackChunkwebpack"] || []);
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind(
null,
chunkLoadingGlobal.push.bind(chunkLoadingGlobal)
);

})();

})();
Loading