diff --git a/packages/next-swc/crates/core/src/react_server_components.rs b/packages/next-swc/crates/core/src/react_server_components.rs index b172ea47fc4e..db5b5c01ac6a 100644 --- a/packages/next-swc/crates/core/src/react_server_components.rs +++ b/packages/next-swc/crates/core/src/react_server_components.rs @@ -1,3 +1,4 @@ +use regex::Regex; use serde::Deserialize; use swc_core::{ @@ -63,7 +64,7 @@ impl VisitMut for ReactServerComponents { return; } } else { - self.assert_client_graph(&imports); + self.assert_client_graph(&imports, module); } module.visit_mut_children_with(self) } @@ -276,7 +277,7 @@ impl ReactServerComponents { } } - fn assert_client_graph(&self, imports: &Vec) { + fn assert_client_graph(&self, imports: &Vec, module: &Module) { for import in imports { let source = import.source.0.clone(); if self.invalid_client_imports.contains(&source) { @@ -294,6 +295,104 @@ impl ReactServerComponents { }) } } + + // Assert `getServerSideProps` and `getStaticProps` exports. + let is_layout_or_page = Regex::new(r"/(page|layout)\.(ts|js)x?$") + .unwrap() + .is_match(&self.filepath); + if is_layout_or_page { + let mut span = DUMMY_SP; + let mut has_get_server_side_props = false; + let mut has_get_static_props = false; + + 'matcher: for export in &module.body { + match export { + ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => { + for specifier in &export.specifiers { + if let ExportSpecifier::Named(named) = specifier { + match &named.orig { + ModuleExportName::Ident(i) => { + if i.sym == *"getServerSideProps" { + has_get_server_side_props = true; + span = named.span; + break 'matcher; + } + if i.sym == *"getStaticProps" { + has_get_static_props = true; + span = named.span; + break 'matcher; + } + } + ModuleExportName::Str(s) => { + if s.value == *"getServerSideProps" { + has_get_server_side_props = true; + span = named.span; + break 'matcher; + } + if s.value == *"getStaticProps" { + has_get_static_props = true; + span = named.span; + break 'matcher; + } + } + } + } + } + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => match &export.decl { + Decl::Fn(f) => { + if f.ident.sym == *"getServerSideProps" { + has_get_server_side_props = true; + span = f.ident.span; + break 'matcher; + } + if f.ident.sym == *"getStaticProps" { + has_get_static_props = true; + span = f.ident.span; + break 'matcher; + } + } + Decl::Var(v) => { + for decl in &v.decls { + if let Pat::Ident(i) = &decl.name { + if i.sym == *"getServerSideProps" { + has_get_server_side_props = true; + span = i.span; + break 'matcher; + } + if i.sym == *"getStaticProps" { + has_get_static_props = true; + span = i.span; + break 'matcher; + } + } + } + } + _ => {} + }, + _ => {} + } + } + + if has_get_server_side_props || has_get_static_props { + HANDLER.with(|handler| { + handler + .struct_span_err( + span, + format!( + "`{}` is not allowed in Client Components.", + if has_get_server_side_props { + "getServerSideProps" + } else { + "getStaticProps" + } + ) + .as_str(), + ) + .emit() + }) + } + } } } diff --git a/packages/next-swc/crates/core/tests/errors.rs b/packages/next-swc/crates/core/tests/errors.rs index 3b6996807e5d..5571406e6a22 100644 --- a/packages/next-swc/crates/core/tests/errors.rs +++ b/packages/next-swc/crates/core/tests/errors.rs @@ -83,7 +83,7 @@ fn react_server_components_client_graph_errors(input: PathBuf) { syntax(), &|tr| { server_components( - FileName::Real(PathBuf::from("/some-project/src/some-file.js")), + FileName::Real(PathBuf::from("/some-project/src/page.js")), next_swc::react_server_components::Config::WithOptions( next_swc::react_server_components::Options { is_server: false }, ), diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/input.js b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/input.js new file mode 100644 index 000000000000..54ca0a245d2f --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/input.js @@ -0,0 +1,6 @@ +export function getServerSideProps (){ +} + +export default function () { + return null; +} diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/output.js b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/output.js new file mode 100644 index 000000000000..c2a54dc35918 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/output.js @@ -0,0 +1,4 @@ +export function getServerSideProps() {} +export default function() { + return null; +} diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/output.stderr b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/output.stderr new file mode 100644 index 000000000000..8c649c3e39f8 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/output.stderr @@ -0,0 +1,6 @@ + + x `getServerSideProps` is not allowed in Client Components. + ,-[input.js:1:1] + 1 | export function getServerSideProps (){ + : ^^^^^^^^^^^^^^^^^^ + `---- diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/input.js b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/input.js new file mode 100644 index 000000000000..7b8b6b06871d --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/input.js @@ -0,0 +1,6 @@ +export function getStaticProps (){ +} + +export default function () { + return null; +} diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/output.js b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/output.js new file mode 100644 index 000000000000..26b9fcd3f86b --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/output.js @@ -0,0 +1,4 @@ +export function getStaticProps() {} +export default function() { + return null; +} diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/output.stderr b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/output.stderr new file mode 100644 index 000000000000..67cae2d6ee78 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/output.stderr @@ -0,0 +1,6 @@ + + x `getStaticProps` is not allowed in Client Components. + ,-[input.js:1:1] + 1 | export function getStaticProps (){ + : ^^^^^^^^^^^^^^ + `---- diff --git a/packages/next/build/webpack/loaders/next-flight-loader/index.ts b/packages/next/build/webpack/loaders/next-flight-loader/index.ts index c05e8ea4b43e..1f938f1256a0 100644 --- a/packages/next/build/webpack/loaders/next-flight-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-flight-loader/index.ts @@ -1,9 +1,5 @@ -import path from 'path' import { RSC_MODULE_TYPES } from '../../../../shared/lib/constants' -import { - checkExports, - getRSCModuleType, -} from '../../../analysis/get-page-static-info' +import { getRSCModuleType } from '../../../analysis/get-page-static-info' import { parse } from '../../../swc' import { getModuleBuildInfo } from '../get-module-build-info' @@ -15,16 +11,6 @@ function transformServer(source: string, isESModule: boolean) { ) } -function containsPath(parent: string, child: string) { - const relation = path.relative(parent, child) - return !!relation && !relation.startsWith('..') && !path.isAbsolute(relation) -} - -const isPageOrLayoutFile = (filePath: string) => { - const filename = path.basename(filePath) - return /[\\/]?(page|layout)\.(js|ts)x?$/.test(filename) -} - export default async function transformSource( this: any, source: string, @@ -44,32 +30,12 @@ export default async function transformSource( }) const rscType = getRSCModuleType(source) - const createError = (name: string) => - new Error( - `${name} is not supported in client components.\nFrom: ${this.resourcePath}` - ) - const appDir = path.join(this.rootContext, 'app') - const isUnderAppDir = containsPath(appDir, this.resourcePath) - const isResourcePageOrLayoutFile = isPageOrLayoutFile(this.resourcePath) - // If client entry has any gSSP/gSP data fetching methods, error - function errorForInvalidDataFetching(onError: (error: any) => void) { - if (isUnderAppDir && isResourcePageOrLayoutFile) { - const { ssg, ssr } = checkExports(swcAST) - if (ssg) { - onError(createError('getStaticProps')) - } - if (ssr) { - onError(createError('getServerSideProps')) - } - } - } // Assign the RSC meta information to buildInfo. // Exclude next internal files which are not marked as client files buildInfo.rsc = { type: rscType } if (buildInfo.rsc?.type === RSC_MODULE_TYPES.client) { - errorForInvalidDataFetching(this.emitError) return callback(null, source, sourceMap) } diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index fcc5c80c7ade..10efc212720a 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -1077,7 +1077,7 @@ describe('app dir', () => { }) if (isDev) { - it.skip('should throw an error when getServerSideProps is used', async () => { + it('should throw an error when getServerSideProps is used', async () => { const pageFile = 'app/client-with-errors/get-server-side-props/page.js' const content = await next.readFile(pageFile) @@ -1102,11 +1102,11 @@ describe('app dir', () => { expect(res.status).toBe(500) expect(await res.text()).toContain( - 'getServerSideProps is not supported in client components' + '`getServerSideProps` is not allowed in Client Components' ) }) - it.skip('should throw an error when getStaticProps is used', async () => { + it('should throw an error when getStaticProps is used', async () => { const pageFile = 'app/client-with-errors/get-static-props/page.js' const content = await next.readFile(pageFile) const uncomment = content.replace( @@ -1129,7 +1129,7 @@ describe('app dir', () => { expect(res.status).toBe(500) expect(await res.text()).toContain( - 'getStaticProps is not supported in client components' + '`getStaticProps` is not allowed in Client Components' ) }) }