diff --git a/errors/invalid-dynamic-options-type.md b/errors/invalid-dynamic-options-type.md new file mode 100644 index 0000000000000..e5aa443ac58ec --- /dev/null +++ b/errors/invalid-dynamic-options-type.md @@ -0,0 +1,31 @@ +# Invalid options type in a `next/dynamic` call + +#### Why This Error Occurred + +You have an invalid options type in a `next/dynamic` call. The options must be an object literal. + +#### Possible Ways to Fix It + +**Before** + +```jsx +import dynamic from 'next/dynamic' + +const options = { loading: () =>

...

, ssr: false } +const DynamicComponent = dynamic(() => import('../components/hello'), options) +``` + +**After** + +```jsx +import dynamic from 'next/dynamic' + +const DynamicComponent = dynamic(() => import('../components/hello'), { + loading: () =>

...

, + ssr: false, +}) +``` + +### Useful Links + +- [Dynamic Import](https://nextjs.org/docs/advanced-features/dynamic-import) diff --git a/errors/manifest.json b/errors/manifest.json index 0b565a728d15e..d57216c99fdba 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -518,6 +518,10 @@ { "title": "experimental-jest-transformer", "path": "/errors/experimental-jest-transformer.md" + }, + { + "title": "invalid-dynamic-options-type", + "path": "/errors/invalid-dynamic-options-type.md" } ] } diff --git a/packages/next-swc/crates/core/src/next_dynamic.rs b/packages/next-swc/crates/core/src/next_dynamic.rs index 745d5390e09c2..37ea743f97a84 100644 --- a/packages/next-swc/crates/core/src/next_dynamic.rs +++ b/packages/next-swc/crates/core/src/next_dynamic.rs @@ -86,11 +86,28 @@ impl Fold for NextDynamicPatcher { }); return expr; } + if expr.args.len() == 2 { + match &*expr.args[1].expr { + Expr::Object(_) => {}, + _ => { + HANDLER.with(|handler| { + handler + .struct_span_err( + identifier.span, + "next/dynamic options must be an object literal.\nRead more: https://nextjs.org/docs/messages/invalid-dynamic-options-type", + ) + .emit(); + }); + return expr; + } + } + } self.is_next_dynamic_first_arg = true; expr.args[0].expr = expr.args[0].expr.clone().fold_with(self); self.is_next_dynamic_first_arg = false; + if let None = self.dynamically_imported_specifier { return expr; } diff --git a/packages/next-swc/crates/core/tests/errors.rs b/packages/next-swc/crates/core/tests/errors.rs index c6eeaee2c1b4a..d27f7f5003d41 100644 --- a/packages/next-swc/crates/core/tests/errors.rs +++ b/packages/next-swc/crates/core/tests/errors.rs @@ -1,5 +1,8 @@ -use next_swc::disallow_re_export_all_in_page::disallow_re_export_all_in_page; +use next_swc::{ + disallow_re_export_all_in_page::disallow_re_export_all_in_page, next_dynamic::next_dynamic, +}; use std::path::PathBuf; +use swc_common::FileName; use swc_ecma_transforms_testing::test_fixture_allowing_error; use swc_ecmascript::parser::{EsConfig, Syntax}; use testing::fixture; @@ -22,3 +25,19 @@ fn re_export_all_in_page(input: PathBuf) { &output, ); } + +#[fixture("tests/errors/next-dynamic/**/input.js")] +fn next_dynamic_errors(input: PathBuf) { + let output = input.parent().unwrap().join("output.js"); + test_fixture_allowing_error( + syntax(), + &|_tr| { + next_dynamic( + FileName::Real(PathBuf::from("/some-project/src/some-file.js")), + Some("/some-project/src".into()), + ) + }, + &input, + &output, + ); +} diff --git a/packages/next-swc/crates/core/tests/errors/next-dynamic/no-arguments/input.js b/packages/next-swc/crates/core/tests/errors/next-dynamic/no-arguments/input.js new file mode 100644 index 0000000000000..16d43edbb6921 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-dynamic/no-arguments/input.js @@ -0,0 +1,3 @@ +import dynamic from 'next/dynamic' + +const DynamicComponent = dynamic() diff --git a/packages/next-swc/crates/core/tests/errors/next-dynamic/no-arguments/output.js b/packages/next-swc/crates/core/tests/errors/next-dynamic/no-arguments/output.js new file mode 100644 index 0000000000000..16d43edbb6921 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-dynamic/no-arguments/output.js @@ -0,0 +1,3 @@ +import dynamic from 'next/dynamic' + +const DynamicComponent = dynamic() diff --git a/packages/next-swc/crates/core/tests/errors/next-dynamic/no-arguments/output.stderr b/packages/next-swc/crates/core/tests/errors/next-dynamic/no-arguments/output.stderr new file mode 100644 index 0000000000000..b5d30ef53c63a --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-dynamic/no-arguments/output.stderr @@ -0,0 +1,6 @@ +error: next/dynamic requires at least one argument + --> input.js:3:26 + | +3 | const DynamicComponent = dynamic() + | ^^^^^^^ + diff --git a/packages/next-swc/crates/core/tests/errors/next-dynamic/options-as-variable/input.js b/packages/next-swc/crates/core/tests/errors/next-dynamic/options-as-variable/input.js new file mode 100644 index 0000000000000..2200e61ac1045 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-dynamic/options-as-variable/input.js @@ -0,0 +1,7 @@ +import dynamic from 'next/dynamic' + +const options = { loading: () =>

...

, ssr: false } +const DynamicComponentWithCustomLoading = dynamic( + () => import('../components/hello'), + options +) diff --git a/packages/next-swc/crates/core/tests/errors/next-dynamic/options-as-variable/output.js b/packages/next-swc/crates/core/tests/errors/next-dynamic/options-as-variable/output.js new file mode 100644 index 0000000000000..84408fea7b66d --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-dynamic/options-as-variable/output.js @@ -0,0 +1,7 @@ +import dynamic from 'next/dynamic'; + +const options = { loading: () =>

...

, ssr: false }; +const DynamicComponentWithCustomLoading = dynamic( + () => import('../components/hello'), + options +); diff --git a/packages/next-swc/crates/core/tests/errors/next-dynamic/options-as-variable/output.stderr b/packages/next-swc/crates/core/tests/errors/next-dynamic/options-as-variable/output.stderr new file mode 100644 index 0000000000000..bd5f7a7ac0691 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-dynamic/options-as-variable/output.stderr @@ -0,0 +1,7 @@ +error: next/dynamic options must be an object literal. +Read more: https://nextjs.org/docs/messages/invalid-dynamic-options-type + --> input.js:4:43 + | +4 | const DynamicComponentWithCustomLoading = dynamic( + | ^^^^^^^ + diff --git a/packages/next-swc/crates/core/tests/errors/next-dynamic/too-many-arguments/input.js b/packages/next-swc/crates/core/tests/errors/next-dynamic/too-many-arguments/input.js new file mode 100644 index 0000000000000..b313c3ab6ac5b --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-dynamic/too-many-arguments/input.js @@ -0,0 +1,7 @@ +import dynamic from 'next/dynamic' + +const DynamicComponentWithCustomLoading = dynamic( + () => import('../components/hello'), + { loading: () =>

...

}, + "3rd" +) diff --git a/packages/next-swc/crates/core/tests/errors/next-dynamic/too-many-arguments/output.js b/packages/next-swc/crates/core/tests/errors/next-dynamic/too-many-arguments/output.js new file mode 100644 index 0000000000000..b313c3ab6ac5b --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-dynamic/too-many-arguments/output.js @@ -0,0 +1,7 @@ +import dynamic from 'next/dynamic' + +const DynamicComponentWithCustomLoading = dynamic( + () => import('../components/hello'), + { loading: () =>

...

}, + "3rd" +) diff --git a/packages/next-swc/crates/core/tests/errors/next-dynamic/too-many-arguments/output.stderr b/packages/next-swc/crates/core/tests/errors/next-dynamic/too-many-arguments/output.stderr new file mode 100644 index 0000000000000..08bd41dd777a6 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-dynamic/too-many-arguments/output.stderr @@ -0,0 +1,6 @@ +error: next/dynamic only accepts 2 arguments + --> input.js:3:43 + | +3 | const DynamicComponentWithCustomLoading = dynamic( + | ^^^^^^^ +