Skip to content
Open
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
19 changes: 19 additions & 0 deletions crates/rspack_core/src/options/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ define_import_meta_known_properties! {
const DIRNAME = "dirname";
const FILENAME = "filename";
const GLOB = "glob";
const HOT = "hot";
const MAIN = "main";
const RESOLVE = "resolve";
const RSPACK_BASE_URI = "rspackBaseUri";
Expand Down Expand Up @@ -1932,4 +1933,22 @@ mod tests {

assert_eq!(options.rules[0].effect.id, 0);
}

#[test]
fn import_meta_hot_options_are_independent() {
let hot_disabled = ImportMetaOptions::new(HashMap::from_iter([("hot".to_string(), false)]));
assert!(!hot_disabled.is_known_property_enabled(ImportMetaKnownProperties::HOT));
assert!(
hot_disabled.is_known_property_enabled(ImportMetaKnownProperties::WEBPACK_HOT),
"disabling import.meta.hot should not disable import.meta.webpackHot"
);

let webpack_hot_disabled =
ImportMetaOptions::new(HashMap::from_iter([("webpackHot".to_string(), false)]));
assert!(webpack_hot_disabled.is_known_property_enabled(ImportMetaKnownProperties::HOT));
assert!(
!webpack_hot_disabled.is_known_property_enabled(ImportMetaKnownProperties::WEBPACK_HOT),
"disabling import.meta.webpackHot should not disable import.meta.hot"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,48 @@ use crate::{

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

fn is_import_meta_hot(for_name: &str) -> bool {
matches!(
for_name,
expr_name::IMPORT_META_HOT | expr_name::IMPORT_META_HOT_ALIAS
)
}

fn is_import_meta_hot_accept(for_name: &str) -> bool {
matches!(
for_name,
expr_name::IMPORT_META_HOT_ACCEPT | expr_name::IMPORT_META_HOT_ALIAS_ACCEPT
)
}

fn is_import_meta_hot_decline(for_name: &str) -> bool {
matches!(
for_name,
expr_name::IMPORT_META_HOT_DECLINE | expr_name::IMPORT_META_HOT_ALIAS_DECLINE
)
}

fn import_meta_hot_property(for_name: &str) -> Option<ImportMetaKnownProperties> {
match for_name {
expr_name::IMPORT_META_HOT
| expr_name::IMPORT_META_HOT_ACCEPT
| expr_name::IMPORT_META_HOT_DECLINE => Some(ImportMetaKnownProperties::WEBPACK_HOT),
expr_name::IMPORT_META_HOT_ALIAS
| expr_name::IMPORT_META_HOT_ALIAS_ACCEPT
| expr_name::IMPORT_META_HOT_ALIAS_DECLINE => Some(ImportMetaKnownProperties::HOT),
_ => None,
}
}

fn is_import_meta_hot_enabled(parser: &JavascriptParser, for_name: &str) -> bool {
import_meta_hot_property(for_name).is_some_and(|property| {
parser
.javascript_options
.import_meta()
.is_known_property_enabled(property)
})
}

fn extract_deps(
parser: &mut JavascriptParser,
call_expr: &CallExpr,
Expand Down Expand Up @@ -211,14 +253,9 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for ImportMetaHotReplacementParserPl
start: u32,
end: u32,
) -> Option<crate::utils::eval::BasicEvaluatedExpression<'p>> {
if for_name == expr_name::IMPORT_META_HOT
&& parser
.javascript_options
.import_meta()
.is_known_property_enabled(ImportMetaKnownProperties::WEBPACK_HOT)
{
if is_import_meta_hot(for_name) && is_import_meta_hot_enabled(parser, for_name) {
Some(eval::evaluate_to_identifier(
expr_name::IMPORT_META_HOT.into(),
for_name.into(),
expr_name::IMPORT_META.into(),
Some(true),
start,
Expand All @@ -235,12 +272,7 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for ImportMetaHotReplacementParserPl
expr: &MemberExpr,
for_name: &str,
) -> Option<bool> {
if for_name == expr_name::IMPORT_META_HOT
&& parser
.javascript_options
.import_meta()
.is_known_property_enabled(ImportMetaKnownProperties::WEBPACK_HOT)
{
if is_import_meta_hot(for_name) && is_import_meta_hot_enabled(parser, for_name) {
parser.create_hmr_expression_handler(expr.span());
Some(true)
} else {
Expand All @@ -254,19 +286,15 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for ImportMetaHotReplacementParserPl
call_expr: &CallExpr,
for_name: &str,
) -> Option<bool> {
if !parser
.javascript_options
.import_meta()
.is_known_property_enabled(ImportMetaKnownProperties::WEBPACK_HOT)
{
if !is_import_meta_hot_enabled(parser, for_name) {
return None;
}

if for_name == expr_name::IMPORT_META_HOT_ACCEPT {
if is_import_meta_hot_accept(for_name) {
parser.create_accept_handler(call_expr, |request, range| {
Box::new(ImportMetaHotAcceptDependency::new(request, range))
})
} else if for_name == expr_name::IMPORT_META_HOT_DECLINE {
} else if is_import_meta_hot_decline(for_name) {
parser.create_decline_handler(call_expr, |request, range| {
Box::new(ImportMetaHotDeclineDependency::new(request, range))
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,11 @@ impl ImportMetaPlugin {
if let Some(api) = import_meta_runtime_api_from_name(name) {
return Some(api.property);
}
None
match name {
expr_name::IMPORT_META_HOT => Some(ImportMetaKnownProperties::WEBPACK_HOT),
expr_name::IMPORT_META_HOT_ALIAS => Some(ImportMetaKnownProperties::HOT),
_ => None,
}
}

fn preserve_property(&self, property: Option<&str>) -> bool {
Expand Down Expand Up @@ -488,6 +492,12 @@ impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for ImportMetaPlugin {
&& self.runtime_api_enabled(api)
{
evaluated = Some(api.type_of.to_string())
} else if Self::known_property_from_name(for_name)
.is_some_and(|property| self.known_property_enabled(property))
{
// HMR fields are evaluated by HotModuleReplacementPlugin. Keep `typeof`
// unknown here so the member expression can be rewritten to `module.hot`.
return None;
} else if let Some(member_expr) = expr.arg.as_member()
&& let Some(meta_expr) = member_expr.obj.as_meta_prop()
&& meta_expr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub mod expr_name {
pub const IMPORT_META_HOT: &str = "import.meta.webpackHot";
pub const IMPORT_META_HOT_ACCEPT: &str = "import.meta.webpackHot.accept";
pub const IMPORT_META_HOT_DECLINE: &str = "import.meta.webpackHot.decline";
pub const IMPORT_META_HOT_ALIAS: &str = "import.meta.hot";
pub const IMPORT_META_HOT_ALIAS_ACCEPT: &str = "import.meta.hot.accept";
pub const IMPORT_META_HOT_ALIAS_DECLINE: &str = "import.meta.hot.decline";
pub const IMPORT_META_CONTEXT: &str = "import.meta.webpackContext";
pub const IMPORT_META_GLOB: &str = "import.meta.glob";
}
Expand Down
1 change: 1 addition & 0 deletions packages/rspack/module.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ declare namespace Rspack {

interface ImportMeta {
url: string;
hot?: Rspack.Hot;
Comment thread
intellild marked this conversation as resolved.
Comment thread
intellild marked this conversation as resolved.
webpackHot?: Rspack.Hot;
webpackContext: (
request: string,
Expand Down
5 changes: 5 additions & 0 deletions packages/rspack/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,11 @@ export type ImportMetaParserOptions = {
*/
glob?: boolean;

/**
* Enable/disable evaluating import.meta.hot.
*/
hot?: boolean;

/**
* Enable/disable evaluating import.meta.main.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "import.meta.hot";
32 changes: 32 additions & 0 deletions tests/rspack-test/configCases/parsing/import-meta-hot/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import value from "./dep";

const fs = require("fs");

let typeofGuardMatched = false;
let webpackHotTypeofGuardMatched = false;

if (typeof import.meta.hot !== "undefined") {
typeofGuardMatched = true;
}

if (typeof import.meta.webpackHot !== "undefined") {
webpackHotTypeofGuardMatched = true;
}

if (import.meta.hot) {
import.meta.hot.accept(["./dep"], () => {});
}

it("should support import.meta.hot as an HMR alias", () => {
expect(value).toBe("import.meta.hot");
expect(typeofGuardMatched).toBe(true);
expect(webpackHotTypeofGuardMatched).toBe(true);
expect(typeof import.meta.hot).toBe("object");
expect(typeof import.meta.hot.accept).toBe("function");
expect(typeof import.meta.webpackHot).toBe("object");

const source = fs.readFileSync(__filename, "utf-8");
const typeofKeyword = ["type", "of"].join("");
const runtimeTypeof = `${typeofKeyword} module.hot !== "undefined"`;
expect(source.split(runtimeTypeof)).toHaveLength(3);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const fs = require("fs");

it("should disable the hot alias independently from the canonical HMR field", () => {
expect(typeof import.meta.hot).toBe("undefined");
expect(typeof import.meta.webpackHot).toBe("object");
if (import.meta.hot) {
import.meta.hot.accept();
import.meta.hot.decline();
}

const source = fs.readFileSync(__filename, "utf-8");
const importMeta = ["import", "meta"].join(".");
expect(source.split(`${importMeta}.hot`)).toHaveLength(5);
expect(source).not.toContain(`${importMeta}.webpackHot`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
it("should transform import.meta.hot to false without HMR", () => {
let hot = false;
if (import.meta.hot) {
hot = true;
import.meta.hot.accept();
}

expect(hot).toBe(false);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const { HotModuleReplacementPlugin } = require('@rspack/core');

/** @type {import("@rspack/core").Configuration[]} */
module.exports = [
{
entry: './index.js',
devtool: false,
externals: {
fs: 'node-commonjs fs',
},
node: {
__filename: false,
},
plugins: [new HotModuleReplacementPlugin()],
},
{
entry: './production.js',
mode: 'production',
target: 'web',
devServer: {
hot: true,
},
},
{
entry: './parser-options.js',
mode: 'development',
devtool: false,
target: 'node',
experiments: {
outputModule: true,
},
output: {
module: true,
chunkFormat: 'module',
},
module: {
parser: {
javascript: {
importMeta: {
hot: false,
},
},
},
},
plugins: [new HotModuleReplacementPlugin()],
},
];
8 changes: 5 additions & 3 deletions website/docs/en/api/runtime-api/hmr.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import WebpackLicense from '@components/WebpackLicense';

Rspack provides the same interface as webpack for implementing HMR.

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.
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.

In ESM, use `import.meta.webpackHot` instead of `module.hot`.
In ESM, use `import.meta.webpackHot` or `import.meta.hot` instead of `module.hot`.

`import.meta.hot` is available since Rspack 2.1.5.

## Example

Expand All @@ -33,7 +35,7 @@ if (import.meta.webpackHot) {
}
```

The following methods are supported by `module.hot` and `import.meta.webpackHot`.
The following methods are supported by `module.hot`, `import.meta.webpackHot`, and `import.meta.hot`.

## Module API

Expand Down
8 changes: 5 additions & 3 deletions website/docs/zh/api/runtime-api/hmr.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import WebpackLicense from '@components/WebpackLicense';

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

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

注意,在 ESM 中请使用 `import.meta.webpackHot` 来代替 `module.hot`。
注意,在 ESM 中请使用 `import.meta.webpackHot` 或 `import.meta.hot` 来代替 `module.hot`。

`import.meta.hot` 从 Rspack 2.1.5 开始可用。

## 示例

Expand All @@ -33,7 +35,7 @@ if (import.meta.webpackHot) {
}
```

`module.hot` 和 `import.meta.webpackHot` 支持以下方法。
`module.hot`、`import.meta.webpackHot` 和 `import.meta.hot` 支持以下方法。

## 模块 API

Expand Down
Loading