From 18e965d2efa9409106b8443c241bf147ed5d0a3b Mon Sep 17 00:00:00 2001 From: Nicholas Cunningham Date: Wed, 5 Apr 2023 14:07:46 -0600 Subject: [PATCH] feat(nextjs): add support for experimental appDir (#16132) --- .../packages/next/generators/application.json | 6 ++++ .../application/application.spec.ts | 20 ++++++++++++- .../files/app/api/hello/route.ts__tmpl__ | 4 +++ .../application/files/app/favicon.ico | Bin 0 -> 15086 bytes .../files/app/global.__stylesExt____tmpl__ | 1 + .../application/files/app/layout.tsx__tmpl__ | 22 ++++++++++++++ .../files/app/page.module.__style__ | 1 + .../application/files/app/page.tsx__tmpl__ | 28 ++++++++++++++++++ .../files/{ => common}/.babelrc__tmpl__ | 0 .../files/{ => common}/index.d.ts__tmpl__ | 0 .../files/{ => common}/next-env.d.ts__tmpl__ | 0 .../files/{ => common}/next.config.js__tmpl__ | 20 +++++++++++++ .../files/{ => common}/public/.gitkeep | 0 .../specs/__fileName__.spec.tsx__tmpl__ | 0 .../files/{ => common}/tsconfig.json__tmpl__ | 0 .../application/files/pages/favicon.ico | Bin 0 -> 15086 bytes .../lib/create-application-files.ts | 18 ++++++++++- .../application/lib/normalize-options.ts | 3 ++ .../src/generators/application/schema.d.ts | 1 + .../src/generators/application/schema.json | 6 ++++ 20 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 packages/next/src/generators/application/files/app/api/hello/route.ts__tmpl__ create mode 100644 packages/next/src/generators/application/files/app/favicon.ico create mode 100644 packages/next/src/generators/application/files/app/global.__stylesExt____tmpl__ create mode 100644 packages/next/src/generators/application/files/app/layout.tsx__tmpl__ create mode 100644 packages/next/src/generators/application/files/app/page.module.__style__ create mode 100644 packages/next/src/generators/application/files/app/page.tsx__tmpl__ rename packages/next/src/generators/application/files/{ => common}/.babelrc__tmpl__ (100%) rename packages/next/src/generators/application/files/{ => common}/index.d.ts__tmpl__ (100%) rename packages/next/src/generators/application/files/{ => common}/next-env.d.ts__tmpl__ (100%) rename packages/next/src/generators/application/files/{ => common}/next.config.js__tmpl__ (85%) rename packages/next/src/generators/application/files/{ => common}/public/.gitkeep (100%) rename packages/next/src/generators/application/files/{ => common}/specs/__fileName__.spec.tsx__tmpl__ (100%) rename packages/next/src/generators/application/files/{ => common}/tsconfig.json__tmpl__ (100%) create mode 100644 packages/next/src/generators/application/files/pages/favicon.ico diff --git a/docs/generated/packages/next/generators/application.json b/docs/generated/packages/next/generators/application.json index da58d772463b2..5ad1db11b4e52 100644 --- a/docs/generated/packages/next/generators/application.json +++ b/docs/generated/packages/next/generators/application.json @@ -124,6 +124,12 @@ "default": false, "description": "Do not add dependencies to `package.json`.", "x-priority": "internal" + }, + "appDir": { + "type": "boolean", + "default": false, + "description": "Enable experimental app directory for the project", + "x-prompt": "Do you want to use experimental app/ in this project?" } }, "required": [], diff --git a/packages/next/src/generators/application/application.spec.ts b/packages/next/src/generators/application/application.spec.ts index 12a2885d1b46f..dad0f6e8cd13e 100644 --- a/packages/next/src/generators/application/application.spec.ts +++ b/packages/next/src/generators/application/application.spec.ts @@ -1,7 +1,6 @@ import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import { getProjects, - NxJsonConfiguration, readJson, readProjectConfiguration, Tree, @@ -418,4 +417,23 @@ describe('app', () => { expect(tree.exists('apps/my-app/public/.gitkeep')).toBe(true); }); + + describe('--appDir', () => { + it('should generate app directory instead of pages', async () => { + await applicationGenerator(tree, { + name: 'testApp', + style: 'css', + appDir: true, + }); + + expect(tree.exists('apps/testApp/pages/styles.css')).toBeFalsy(); + + expect(tree.exists('apps/testApp/app/global.css')); + expect(tree.exists('apps/testApp/app/page.tsx')); + expect(tree.exists('apps/testApp/app/layout.tsx')); + expect(tree.exists('apps/testApp/app/api/hello/route.ts')); + expect(tree.exists('apps/testApp/app/page.module.css')); + expect(tree.exists('apps/testApp/app/favicon.ico')); + }); + }); }); diff --git a/packages/next/src/generators/application/files/app/api/hello/route.ts__tmpl__ b/packages/next/src/generators/application/files/app/api/hello/route.ts__tmpl__ new file mode 100644 index 0000000000000..e5655e266b078 --- /dev/null +++ b/packages/next/src/generators/application/files/app/api/hello/route.ts__tmpl__ @@ -0,0 +1,4 @@ +export async function GET(request: Request) { + return new Response('Hello, from API!') + } + \ No newline at end of file diff --git a/packages/next/src/generators/application/files/app/favicon.ico b/packages/next/src/generators/application/files/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..317ebcb2336e0833a22dddf0ab287849f26fda57 GIT binary patch literal 15086 zcmeI332;U^%p|z7g|#(P)qFEA@4f!_@qOK2 z_lJl}!lhL!VT_U|uN7%8B2iKH??xhDa;*`g{yjTFWHvXn;2s{4R7kH|pKGdy(7z!K zgftM+Ku7~24TLlh(!g)gz|foI94G^t2^IO$uvX$3(OR0<_5L2sB)lMAMy|+`xodJ{ z_Uh_1m)~h?a;2W{dmhM;u!YGo=)OdmId_B<%^V^{ovI@y`7^g1_V9G}*f# zNzAtvou}I!W1#{M^@ROc(BZ! z+F!!_aR&Px3_reO(EW+TwlW~tv*2zr?iP7(d~a~yA|@*a89IUke+c472NXM0wiX{- zl`UrZC^1XYyf%1u)-Y)jj9;MZ!SLfd2Hl?o|80Su%Z?To_=^g_Jt0oa#CT*tjx>BI z16wec&AOWNK<#i0Qd=1O$fymLRoUR*%;h@*@v7}wApDl^w*h}!sYq%kw+DKDY)@&A z@9$ULEB3qkR#85`lb8#WZw=@})#kQig9oqy^I$dj&k4jU&^2(M3q{n1AKeGUKPFbr z1^<)aH;VsG@J|B&l>UtU#Ejv3GIqERzYgL@UOAWtW<{p#zy`WyJgpCy8$c_e%wYJL zyGHRRx38)HyjU3y{-4z6)pzb>&Q1pR)B&u01F-|&Gx4EZWK$nkUkOI|(D4UHOXg_- zw{OBf!oWQUn)Pe(=f=nt=zkmdjpO^o8ZZ9o_|4tW1ni+Un9iCW47*-ut$KQOww!;u z`0q)$s6IZO!~9$e_P9X!hqLxu`fpcL|2f^I5d4*a@Dq28;@2271v_N+5HqYZ>x;&O z05*7JT)mUe&%S0@UD)@&8SmQrMtsDfZT;fkdA!r(S=}Oz>iP)w=W508=Rc#nNn7ym z1;42c|8($ALY8#a({%1#IXbWn9-Y|0eDY$_L&j{63?{?AH{);EzcqfydD$@-B`Y3<%IIj7S7rK_N}je^=dEk%JQ4c z!tBdTPE3Tse;oYF>cnrapWq*o)m47X1`~6@(!Y29#>-#8zm&LXrXa(3=7Z)ElaQqj z-#0JJy3Fi(C#Rx(`=VXtJ63E2_bZGCz+QRa{W0e2(m3sI?LOcUBx)~^YCqZ{XEPX)C>G>U4tfqeH8L(3|pQR*zbL1 zT9e~4Tb5p9_G}$y4t`i*4t_Mr9QYvL9C&Ah*}t`q*}S+VYh0M6GxTTSXI)hMpMpIq zD1ImYqJLzbj0}~EpE-aH#VCH_udYEW#`P2zYmi&xSPs_{n6tBj=MY|-XrA;SGA_>y zGtU$?HXm$gYj*!N)_nQ59%lQdXtQZS3*#PC-{iB_sm+ytD*7j`D*k(P&IH2GHT}Eh z5697eQECVIGQAUe#eU2I!yI&%0CP#>%6MWV z@zS!p@+Y1i1b^QuuEF*13CuB zu69dve5k7&Wgb+^s|UB08Dr3u`h@yM0NTj4h7MnHo-4@xmyr7(*4$rpPwsCDZ@2be zRz9V^GnV;;?^Lk%ynzq&K(Aix`mWmW`^152Hoy$CTYVehpD-S1-W^#k#{0^L`V6CN+E z!w+xte;2vu4AmVNEFUOBmrBL>6MK@!O2*N|2=d|Y;oN&A&qv=qKn73lDD zI(+oJAdgv>Yr}8(&@ZuAZE%XUXmX(U!N+Z_sjL<1vjy1R+1IeHt`79fnYdOL{$ci7 z%3f0A*;Zt@ED&Gjm|OFTYBDe%bbo*xXAQsFz+Q`fVBH!N2)kaxN8P$c>sp~QXnv>b zwq=W3&Mtmih7xkR$YA)1Yi?avHNR6C99!u6fh=cL|KQ&PwF!n@ud^n(HNIImHD!h87!i*t?G|p0o+eelJ?B@A64_9%SBhNaJ64EvKgD&%LjLCYnNfc; znj?%*p@*?dq#NqcQFmmX($wms@CSAr9#>hUR^=I+=0B)vvGX%T&#h$kmX*s=^M2E!@N9#m?LhMvz}YB+kd zG~mbP|D(;{s_#;hsKK9lbVK&Lo734x7SIFJ9V_}2$@q?zm^7?*XH94w5Qae{7zOMUF z^?%F%)c1Y)Q?Iy?I>knw*8gYW#ok|2gdS=YYZLiD=CW|Nj;n^x!=S#iJ#`~Ld79+xXpVmUK^B(xO_vO!btA9y7w3L3-0j-y4 z?M-V{%z;JI`bk7yFDcP}OcCd*{Q9S5$iGA7*E1@tfkyjAi!;wP^O71cZ^Ep)qrQ)N z#wqw0_HS;T7x3y|`P==i3hEwK%|>fZ)c&@kgKO1~5<5xBSk?iZV?KI6&i72H6S9A* z=U(*e)EqEs?Oc04)V-~K5AUmh|62H4*`UAtItO$O(q5?6jj+K^oD!04r=6#dsxp?~}{`?&sXn#q2 zGuY~7>O2=!u@@Kfu7q=W*4egu@qPMRM>(eyYyaIE<|j%d=iWNdGsx%c!902v#ngNg z@#U-O_4xN$s_9?(`{>{>7~-6FgWpBpqXb`Ydc3OFL#&I}Irse9F_8R@4zSS*Y*o*B zXL?6*Aw!AfkNCgcr#*yj&p3ZDe2y>v$>FUdKIy_2N~}6AbHc7gA3`6$g@1o|dE>vz z4pl(j9;kyMsjaw}lO?(?Xg%4k!5%^t#@5n=WVc&JRa+XT$~#@rldvN3S1rEpU$;XgxVny7mki3 z-Hh|jUCHrUXuLr!)`w>wgO0N%KTB-1di>cj(x3Bav`7v z3G7EIbU$z>`Nad7Rk_&OT-W{;qg)-GXV-aJT#(ozdmnA~Rq3GQ_3mby(>q6Ocb-RgTUhTN)))x>m&eD;$J5Bg zo&DhY36Yg=J=$Z>t}RJ>o|@hAcwWzN#r(WJ52^g$lh^!63@hh+dR$&_dEGu&^CR*< z!oFqSqO@>xZ*nC2oiOd0eS*F^IL~W-rsrO`J`ej{=ou_q^_(<$&-3f^J z&L^MSYWIe{&pYq&9eGaArA~*kA diff --git a/packages/next/src/generators/application/files/app/layout.tsx__tmpl__ b/packages/next/src/generators/application/files/app/layout.tsx__tmpl__ new file mode 100644 index 0000000000000..498a427d892ab --- /dev/null +++ b/packages/next/src/generators/application/files/app/layout.tsx__tmpl__ @@ -0,0 +1,22 @@ +import Head from 'next/head'; +import './global.<%= stylesExt %>'; + +export const metadata = { + title: 'Nx Next App', + description: 'Generated by create-nx-workspace', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + Welcome to <%= name %>! + + {children} + + ) +} diff --git a/packages/next/src/generators/application/files/app/page.module.__style__ b/packages/next/src/generators/application/files/app/page.module.__style__ new file mode 100644 index 0000000000000..40d88f3d797b7 --- /dev/null +++ b/packages/next/src/generators/application/files/app/page.module.__style__ @@ -0,0 +1 @@ +<%- pageStyleContent %> diff --git a/packages/next/src/generators/application/files/app/page.tsx__tmpl__ b/packages/next/src/generators/application/files/app/page.tsx__tmpl__ new file mode 100644 index 0000000000000..4d4e80e033070 --- /dev/null +++ b/packages/next/src/generators/application/files/app/page.tsx__tmpl__ @@ -0,0 +1,28 @@ +<% if (styledModule && styledModule !== 'styled-jsx') { + var wrapper = 'StyledPage'; +%>import styled from '<%= styledModule %>';<% } else { + var wrapper = 'div'; +%> + <%- style !== 'styled-jsx' ? `import styles from './page.module.${style}';` : '' %> +<% } +%> + +<% if (styledModule && styledModule !== 'styled-jsx') { %> +const StyledPage = styled.div`<%- pageStyleContent %>`; +<% }%> + +export async function Index() { + /* + * Replace the elements below with your own. + * + * Note: The corresponding styles are in the ./<%= fileName %>.<%= style %> file. + */ + return ( + <<%= wrapper %><% if (!styledModule) {%> className={styles.page}<% } %>> + <%- styledModule === 'styled-jsx' ? `` : `` %> + <%- appContent %> + > + ); +}; + +export default Index; \ No newline at end of file diff --git a/packages/next/src/generators/application/files/.babelrc__tmpl__ b/packages/next/src/generators/application/files/common/.babelrc__tmpl__ similarity index 100% rename from packages/next/src/generators/application/files/.babelrc__tmpl__ rename to packages/next/src/generators/application/files/common/.babelrc__tmpl__ diff --git a/packages/next/src/generators/application/files/index.d.ts__tmpl__ b/packages/next/src/generators/application/files/common/index.d.ts__tmpl__ similarity index 100% rename from packages/next/src/generators/application/files/index.d.ts__tmpl__ rename to packages/next/src/generators/application/files/common/index.d.ts__tmpl__ diff --git a/packages/next/src/generators/application/files/next-env.d.ts__tmpl__ b/packages/next/src/generators/application/files/common/next-env.d.ts__tmpl__ similarity index 100% rename from packages/next/src/generators/application/files/next-env.d.ts__tmpl__ rename to packages/next/src/generators/application/files/common/next-env.d.ts__tmpl__ diff --git a/packages/next/src/generators/application/files/next.config.js__tmpl__ b/packages/next/src/generators/application/files/common/next.config.js__tmpl__ similarity index 85% rename from packages/next/src/generators/application/files/next.config.js__tmpl__ rename to packages/next/src/generators/application/files/common/next.config.js__tmpl__ index 8cef6587befb4..69c955d9d1223 100644 --- a/packages/next/src/generators/application/files/next.config.js__tmpl__ +++ b/packages/next/src/generators/application/files/common/next.config.js__tmpl__ @@ -17,6 +17,11 @@ const nextConfig = { // See: https://github.com/gregberge/svgr svgr: false, }, + <% if(appDir) { %> + experimental: { + appDir: true + }, + <% } %> }; module.exports = withLess(withNx(nextConfig)); @@ -32,6 +37,11 @@ const nextConfig = { // See: https://github.com/gregberge/svgr svgr: false, }, + <% if(appDir) { %> + experimental: { + appDir: true + }, + <% } %> }; module.exports = withStylus(withNx(nextConfig)); @@ -50,6 +60,11 @@ const nextConfig = { // See: https://github.com/gregberge/svgr svgr: false, }, + <% if(appDir) { %> + experimental: { + appDir: true + }, + <% } %> }; module.exports = withNx(nextConfig); @@ -64,6 +79,11 @@ const nextConfig = { // See: https://github.com/gregberge/svgr svgr: false, }, + <% if(appDir) { %> + experimental: { + appDir: true + }, + <% } %> }; module.exports = withNx(nextConfig); diff --git a/packages/next/src/generators/application/files/public/.gitkeep b/packages/next/src/generators/application/files/common/public/.gitkeep similarity index 100% rename from packages/next/src/generators/application/files/public/.gitkeep rename to packages/next/src/generators/application/files/common/public/.gitkeep diff --git a/packages/next/src/generators/application/files/specs/__fileName__.spec.tsx__tmpl__ b/packages/next/src/generators/application/files/common/specs/__fileName__.spec.tsx__tmpl__ similarity index 100% rename from packages/next/src/generators/application/files/specs/__fileName__.spec.tsx__tmpl__ rename to packages/next/src/generators/application/files/common/specs/__fileName__.spec.tsx__tmpl__ diff --git a/packages/next/src/generators/application/files/tsconfig.json__tmpl__ b/packages/next/src/generators/application/files/common/tsconfig.json__tmpl__ similarity index 100% rename from packages/next/src/generators/application/files/tsconfig.json__tmpl__ rename to packages/next/src/generators/application/files/common/tsconfig.json__tmpl__ diff --git a/packages/next/src/generators/application/files/pages/favicon.ico b/packages/next/src/generators/application/files/pages/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..317ebcb2336e0833a22dddf0ab287849f26fda57 GIT binary patch literal 15086 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