From a4c7272747d05207417eda900cc03aca1f72e8a8 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Sun, 18 Sep 2022 14:52:16 +0200 Subject: [PATCH 1/3] port page and layout level api assertions to swc --- .../core/src/react_server_components.rs | 112 +++++++++++++++++- packages/next-swc/crates/core/tests/errors.rs | 2 +- .../get-server-side-props/input.js | 6 + .../get-server-side-props/output.js | 4 + .../get-server-side-props/output.stderr | 6 + .../client-graph/get-static-props/input.js | 6 + .../client-graph/get-static-props/output.js | 4 + .../get-static-props/output.stderr | 6 + .../loaders/next-flight-loader/index.ts | 36 +----- test/e2e/app-dir/index.test.ts | 4 +- 10 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/input.js create mode 100644 packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/output.js create mode 100644 packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-server-side-props/output.stderr create mode 100644 packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/input.js create mode 100644 packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/output.js create mode 100644 packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/get-static-props/output.stderr 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 b172ea47fc4ed7c..5d2b9fccb7c4d1a 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,113 @@ 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: loop { + for export in &module.body { + match export { + ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => { + for specifier in &export.specifiers { + match specifier { + ExportSpecifier::Named(named) => 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 { + match &decl.name { + Pat::Ident(i) => { + 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; + } + } + _ => {} + } + } + } + _ => {} + } + } + _ => {} + } + } + break; + } + + 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 3b6996807e5d19b..5571406e6a22b2c 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 000000000000000..54ca0a245d2fdf2 --- /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 000000000000000..c2a54dc35918f42 --- /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 000000000000000..8c649c3e39f8a4c --- /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 000000000000000..7b8b6b06871d53c --- /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 000000000000000..26b9fcd3f86bce3 --- /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 000000000000000..67cae2d6ee7892a --- /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 c05e8ea4b43e6ec..1f938f1256a03fd 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 e56701d81b996d6..fec1da46d67ffa3 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -1086,7 +1086,7 @@ 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' ) }) @@ -1113,7 +1113,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' ) }) } From 15cdb8c465ec3fba10851b26bba039fc59263567 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Sun, 18 Sep 2022 15:01:32 +0200 Subject: [PATCH 2/3] enable tests --- test/e2e/app-dir/index.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index fec1da46d67ffa3..576d4b695168a5c 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -1061,7 +1061,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) @@ -1090,7 +1090,7 @@ describe('app dir', () => { ) }) - 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( From 1a6c76ff93011f881fd722ad499da13f1069cf8b Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Sun, 18 Sep 2022 17:13:49 +0200 Subject: [PATCH 3/3] fix linter errors --- .../core/src/react_server_components.rs | 111 ++++++++---------- 1 file changed, 51 insertions(+), 60 deletions(-) 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 5d2b9fccb7c4d1a..db5b5c01ac6a4a1 100644 --- a/packages/next-swc/crates/core/src/react_server_components.rs +++ b/packages/next-swc/crates/core/src/react_server_components.rs @@ -305,82 +305,73 @@ impl ReactServerComponents { let mut has_get_server_side_props = false; let mut has_get_static_props = false; - 'matcher: loop { - for export in &module.body { - match export { - ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => { - for specifier in &export.specifiers { - match specifier { - ExportSpecifier::Named(named) => 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; - } + '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; } - 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; - } + 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" { + } + 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 = f.ident.span; + span = i.span; break 'matcher; } - if f.ident.sym == *"getStaticProps" { + if i.sym == *"getStaticProps" { has_get_static_props = true; - span = f.ident.span; + span = i.span; break 'matcher; } } - Decl::Var(v) => { - for decl in &v.decls { - match &decl.name { - Pat::Ident(i) => { - 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; - } - } - _ => {} - } - } - } - _ => {} } } _ => {} - } + }, + _ => {} } - break; } if has_get_server_side_props || has_get_static_props {