Skip to content

Commit f0c76d0

Browse files
committed
feat: support import.meta.hot hmr alias
1 parent df598e4 commit f0c76d0

9 files changed

Lines changed: 112 additions & 17 deletions

File tree

crates/rspack_plugin_javascript/src/parser_plugin/hot_module_replacement_plugin.rs

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use rspack_core::{BoxDependency, DependencyRange};
1+
use rspack_core::{BoxDependency, ConstDependency, DependencyRange};
22
use rspack_util::SpanExt;
33
use swc_atoms::Atom;
4-
use swc_experimental_ecma_ast::{CallExpr, GetSpan, MemberExpr, Span};
4+
use swc_experimental_ecma_ast::{CallExpr, GetSpan, MemberExpr, Span, UnaryExpr};
55

66
use crate::{
77
dependency::{
@@ -16,6 +16,37 @@ use crate::{
1616

1717
type CreateDependency = fn(Atom, DependencyRange) -> BoxDependency;
1818

19+
fn is_import_meta_hot(for_name: &str) -> bool {
20+
matches!(
21+
for_name,
22+
expr_name::IMPORT_META_HOT | expr_name::IMPORT_META_HOT_ALIAS
23+
)
24+
}
25+
26+
fn is_import_meta_hot_accept(for_name: &str) -> bool {
27+
matches!(
28+
for_name,
29+
expr_name::IMPORT_META_HOT_ACCEPT | expr_name::IMPORT_META_HOT_ALIAS_ACCEPT
30+
)
31+
}
32+
33+
fn is_import_meta_hot_decline(for_name: &str) -> bool {
34+
matches!(
35+
for_name,
36+
expr_name::IMPORT_META_HOT_DECLINE | expr_name::IMPORT_META_HOT_ALIAS_DECLINE
37+
)
38+
}
39+
40+
fn evaluate_typeof_import_meta_hot(for_name: &str) -> Option<&'static str> {
41+
if is_import_meta_hot(for_name) {
42+
Some("object")
43+
} else if is_import_meta_hot_accept(for_name) || is_import_meta_hot_decline(for_name) {
44+
Some("function")
45+
} else {
46+
None
47+
}
48+
}
49+
1950
fn extract_deps(
2051
parser: &mut JavascriptParser,
2152
call_expr: &CallExpr,
@@ -203,6 +234,17 @@ impl ImportMetaHotReplacementParserPlugin {
203234

204235
#[rspack_macros::implemented_javascript_parser_hooks]
205236
impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for ImportMetaHotReplacementParserPlugin {
237+
fn evaluate_typeof(
238+
&self,
239+
_parser: &mut JavascriptParser<'p>,
240+
expr: &'a UnaryExpr<'a>,
241+
for_name: &str,
242+
) -> Option<crate::utils::eval::BasicEvaluatedExpression<'a>> {
243+
evaluate_typeof_import_meta_hot(for_name).map(|res| {
244+
eval::evaluate_to_string(res.to_string(), expr.span.real_lo(), expr.span.real_hi())
245+
})
246+
}
247+
206248
fn evaluate_identifier(
207249
&self,
208250
_parser: &mut JavascriptParser<'p>,
@@ -211,9 +253,9 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for ImportMetaHotReplacementParserPl
211253
start: u32,
212254
end: u32,
213255
) -> Option<crate::utils::eval::BasicEvaluatedExpression<'p>> {
214-
if for_name == expr_name::IMPORT_META_HOT {
256+
if is_import_meta_hot(for_name) {
215257
Some(eval::evaluate_to_identifier(
216-
expr_name::IMPORT_META_HOT.into(),
258+
for_name.into(),
217259
expr_name::IMPORT_META.into(),
218260
Some(true),
219261
start,
@@ -230,7 +272,7 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for ImportMetaHotReplacementParserPl
230272
expr: &MemberExpr,
231273
for_name: &str,
232274
) -> Option<bool> {
233-
if for_name == expr_name::IMPORT_META_HOT {
275+
if is_import_meta_hot(for_name) {
234276
parser.create_hmr_expression_handler(expr.span());
235277
Some(true)
236278
} else {
@@ -244,16 +286,31 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for ImportMetaHotReplacementParserPl
244286
call_expr: &CallExpr,
245287
for_name: &str,
246288
) -> Option<bool> {
247-
if for_name == expr_name::IMPORT_META_HOT_ACCEPT {
289+
if is_import_meta_hot_accept(for_name) {
248290
parser.create_accept_handler(call_expr, |request, range| {
249291
Box::new(ImportMetaHotAcceptDependency::new(request, range))
250292
})
251-
} else if for_name == expr_name::IMPORT_META_HOT_DECLINE {
293+
} else if is_import_meta_hot_decline(for_name) {
252294
parser.create_decline_handler(call_expr, |request, range| {
253295
Box::new(ImportMetaHotDeclineDependency::new(request, range))
254296
})
255297
} else {
256298
None
257299
}
258300
}
301+
302+
fn r#typeof(
303+
&self,
304+
parser: &mut JavascriptParser<'p>,
305+
expr: &UnaryExpr,
306+
for_name: &str,
307+
) -> Option<bool> {
308+
evaluate_typeof_import_meta_hot(for_name).map(|res| {
309+
parser.add_presentational_dependency(Box::new(ConstDependency::new(
310+
expr.span.into(),
311+
format!("'{res}'").into(),
312+
)));
313+
true
314+
})
315+
}
259316
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ pub mod expr_name {
3131
pub const IMPORT_META_HOT: &str = "import.meta.webpackHot";
3232
pub const IMPORT_META_HOT_ACCEPT: &str = "import.meta.webpackHot.accept";
3333
pub const IMPORT_META_HOT_DECLINE: &str = "import.meta.webpackHot.decline";
34+
pub const IMPORT_META_HOT_ALIAS: &str = "import.meta.hot";
35+
pub const IMPORT_META_HOT_ALIAS_ACCEPT: &str = "import.meta.hot.accept";
36+
pub const IMPORT_META_HOT_ALIAS_DECLINE: &str = "import.meta.hot.decline";
3437
pub const IMPORT_META_CONTEXT: &str = "import.meta.webpackContext";
3538
pub const IMPORT_META_GLOB: &str = "import.meta.glob";
3639
}

packages/rspack/module.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ declare namespace Rspack {
229229

230230
interface ImportMeta {
231231
url: string;
232+
hot?: Rspack.Hot;
232233
webpackHot?: Rspack.Hot;
233234
webpackContext: (
234235
request: string,
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
it('should transform import.meta.webpackHot to false', () => {
1+
it("should transform import.meta.webpackHot and import.meta.hot to false", () => {
22
let hot = false;
33
if (import.meta.webpackHot) {
44
hot = true;
5-
import.meta.webpackHot.accept();
6-
}
5+
import.meta.webpackHot.accept();
6+
}
77

88
expect(hot).toBe(false);
9-
})
9+
10+
let hotAlias = false;
11+
if (import.meta.hot) {
12+
hotAlias = true;
13+
import.meta.hot.accept();
14+
}
15+
16+
expect(hotAlias).toBe(false);
17+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "import.meta.hot";
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import value from "./dep";
2+
3+
let typeofGuardMatched = false;
4+
5+
if (typeof import.meta.hot !== "undefined") {
6+
typeofGuardMatched = true;
7+
}
8+
9+
if (import.meta.hot) {
10+
import.meta.hot.accept(["./dep"], () => {});
11+
}
12+
13+
it("should support import.meta.hot as an HMR alias", () => {
14+
expect(value).toBe("import.meta.hot");
15+
expect(typeofGuardMatched).toBe(true);
16+
expect(typeof import.meta.hot).toBe("object");
17+
expect(typeof import.meta.hot.accept).toBe("function");
18+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { HotModuleReplacementPlugin } = require('@rspack/core');
2+
3+
/** @type {import("@rspack/core").Configuration} */
4+
module.exports = {
5+
devtool: false,
6+
plugins: [new HotModuleReplacementPlugin()],
7+
};

website/docs/en/api/runtime-api/hmr.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import WebpackLicense from '@components/WebpackLicense';
1010

1111
Rspack provides the same interface as webpack for implementing HMR.
1212

13-
If you've enabled Hot Module Replacement via the [HotModuleReplacementPlugin](/plugins/webpack/hot-module-replacement-plugin), its interface is exposed on the `module.hot` and `import.meta.webpackHot` objects.
13+
If you've enabled Hot Module Replacement via the [HotModuleReplacementPlugin](/plugins/webpack/hot-module-replacement-plugin), its interface is exposed on the `module.hot`, `import.meta.webpackHot`, and `import.meta.hot` objects.
1414

15-
In ESM, use `import.meta.webpackHot` instead of `module.hot`.
15+
In ESM, use `import.meta.webpackHot` or `import.meta.hot` instead of `module.hot`.
1616

1717
## Example
1818

@@ -33,7 +33,7 @@ if (import.meta.webpackHot) {
3333
}
3434
```
3535
36-
The following methods are supported by `module.hot` and `import.meta.webpackHot`.
36+
The following methods are supported by `module.hot`, `import.meta.webpackHot`, and `import.meta.hot`.
3737
3838
## Module API
3939

website/docs/zh/api/runtime-api/hmr.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import WebpackLicense from '@components/WebpackLicense';
1010

1111
Rspack 提供了与 webpack 相同的接口来实现模块热替换。
1212

13-
如果你通过 [HotModuleReplacementPlugin](/plugins/webpack/hot-module-replacement-plugin) 启用了模块热替换,它的接口将暴露在 `module.hot``import.meta.webpackHot` 对象下。
13+
如果你通过 [HotModuleReplacementPlugin](/plugins/webpack/hot-module-replacement-plugin) 启用了模块热替换,它的接口将暴露在 `module.hot``import.meta.webpackHot``import.meta.hot` 对象下。
1414

15-
注意,在 ESM 中请使用 `import.meta.webpackHot` 来代替 `module.hot`
15+
注意,在 ESM 中请使用 `import.meta.webpackHot` `import.meta.hot` 来代替 `module.hot`
1616

1717
## 示例
1818

@@ -33,7 +33,7 @@ if (import.meta.webpackHot) {
3333
}
3434
```
3535
36-
`module.hot``import.meta.webpackHot` 支持以下方法。
36+
`module.hot``import.meta.webpackHot``import.meta.hot` 支持以下方法。
3737
3838
## 模块 API
3939

0 commit comments

Comments
 (0)