-
Notifications
You must be signed in to change notification settings - Fork 67
copy dynamic import chunks to functions dirs #174
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
const { join } = require("path"); | ||
const { copySync, readdirSync } = require("fs-extra"); | ||
const { logTitle } = require("../helpers/logger"); | ||
const { NEXT_DIST_DIR } = require("../config"); | ||
|
||
// Check if there are dynamic import chunks and copy to the necessary function dir | ||
const copyDynamicImportChunks = (functionPath) => { | ||
const chunksPath = join(NEXT_DIST_DIR, "serverless"); | ||
const files = readdirSync(chunksPath); | ||
const chunkRegex = new RegExp(/^(\.?[-_$~A-Z0-9a-z]+){1,}\.js$/g); | ||
const excludeFiles = ["init-server.js.js", "on-error-server.js.js"]; | ||
files.forEach((file) => { | ||
if (!excludeFiles.includes(file) && chunkRegex.test(file)) { | ||
logTitle("💼 Copying dynamic import chunks to", functionPath); | ||
copySync(join(chunksPath, file), join(functionPath, file), { | ||
overwrite: false, | ||
errorOnExist: true, | ||
}); | ||
} | ||
}); | ||
}; | ||
|
||
module.exports = copyDynamicImportChunks; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ const { | |
FUNCTION_TEMPLATE_PATH, | ||
} = require("../config"); | ||
const getNetlifyFunctionName = require("./getNetlifyFunctionName"); | ||
const copyDynamicImportChunks = require("./copyDynamicImportChunks"); | ||
const { logItem } = require("./logger"); | ||
|
||
// Create a Netlify Function for the page with the given file path | ||
|
@@ -47,8 +48,11 @@ const setupNetlifyFunctionForPage = ({ | |
}); | ||
}); | ||
|
||
// Copy any dynamic import chunks | ||
copyDynamicImportChunks(functionDirectory); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I love how you contained this so we can drop it easily if/when Next fixes this upstream ❤️ |
||
|
||
// Copy page | ||
const nextPageCopyPath = join(functionDirectory, "nextPage.js"); | ||
const nextPageCopyPath = join(functionDirectory, "nextPage", "index.js"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason behind this change is to organize all the page files in the same directory, or is it also required for the fix to work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this is because the chunks are imported relative to a parent dir (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, what jason said :( the lambda generated by next (that we previous renamed to nextPage.js to pair with next_whatever_the_page_name_is.js) is searching for the chunks one level up at |
||
copySync(join(NEXT_DIST_DIR, "serverless", filePath), nextPageCopyPath, { | ||
overwrite: false, | ||
errorOnExist: true, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Test next-on-netlify when config is set from a function in next.config.js | ||
// See: https://github.com/netlify/next-on-netlify/issues/25 | ||
|
||
const { parse, join } = require("path"); | ||
const { existsSync, readdirSync, readFileSync } = require("fs-extra"); | ||
const buildNextApp = require("./helpers/buildNextApp"); | ||
|
||
// The name of this test file (without extension) | ||
const FILENAME = parse(__filename).name; | ||
|
||
// The directory which will be used for testing. | ||
// We simulate a NextJS app within that directory, with pages, and a | ||
// package.json file. | ||
const PROJECT_PATH = join(__dirname, "builds", FILENAME); | ||
|
||
// Capture the output to verify successful build | ||
let buildOutput; | ||
|
||
beforeAll( | ||
async () => { | ||
buildOutput = await buildNextApp() | ||
.forTest(__filename) | ||
.withPages("pages-dynamic-imports") | ||
.withNextConfig("next.config.js-est") | ||
.withPackageJson("package.json") | ||
.withFile("components/Header.js", join("components", "Header.js")) | ||
.build(); | ||
}, | ||
// time out after 180 seconds | ||
180 * 1000 | ||
); | ||
|
||
describe("next-on-netlify", () => { | ||
const functionsDir = join(PROJECT_PATH, "out_functions"); | ||
|
||
test("builds successfully", () => { | ||
expect(buildOutput).toMatch("Next on Netlify"); | ||
expect(buildOutput).toMatch("Success! All done!"); | ||
}); | ||
|
||
test("copies chunk files to ", () => { | ||
const functionFiles = readdirSync(join(functionsDir, "next_index")); | ||
const chunkRegex = new RegExp(/(\.?[-_$~A-Z0-9a-z]+){1,}\.js$/g); | ||
let chunkFileExists; | ||
functionFiles.forEach((file) => { | ||
chunkFileExists = chunkRegex.test(file); | ||
}); | ||
expect(chunkFileExists).toBe(true); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function Header() { | ||
return <h1>header</h1>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
target: "experimental-serverless-trace", | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import Link from "next/link"; | ||
import dynamic from "next/dynamic"; | ||
const Header = dynamic( | ||
() => import(/* webpackChunkName: 'header' */ "../../components/Header"), | ||
{ ssr: true } | ||
); | ||
|
||
const Show = ({ show }) => ( | ||
<div> | ||
<Header /> | ||
<p> | ||
This page uses getInitialProps() to fetch the show with the ID provided in | ||
the URL: /shows/:id | ||
<br /> | ||
Refresh the page to see server-side rendering in action. | ||
<br /> | ||
You can also try changing the ID to any other number between 1-10000. | ||
</p> | ||
|
||
<hr /> | ||
|
||
<h1>Show #{show.id}</h1> | ||
<p>{show.name}</p> | ||
|
||
<hr /> | ||
|
||
<Link href="/"> | ||
<a>Go back home</a> | ||
</Link> | ||
</div> | ||
); | ||
|
||
export const getServerSideProps = async ({ params }) => { | ||
const res = await fetch("https://api.tvmaze.com/shows/42"); | ||
const data = await res.json(); | ||
|
||
return { | ||
props: { | ||
show: data, | ||
}, | ||
}; | ||
}; | ||
|
||
export default Show; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import Link from "next/link"; | ||
import dynamic from "next/dynamic"; | ||
const Header = dynamic( | ||
() => import(/* webpackChunkName: 'header' */ "../components/Header"), | ||
{ ssr: true } | ||
); | ||
|
||
const Index = ({ shows }) => ( | ||
<div> | ||
<img | ||
src="/next-on-netlify.png" | ||
alt="NextJS on Netlify Banner" | ||
style={{ maxWidth: "100%" }} | ||
/> | ||
|
||
<Header /> | ||
|
||
<p> | ||
This is a demo of a NextJS application with Server-Side Rendering (SSR). | ||
<br /> | ||
It is hosted on Netlify. | ||
<br /> | ||
Server-side rendering is handled by Netlify Functions. | ||
<br /> | ||
Minimal configuration is required. | ||
<br /> | ||
Everything is handled by the{" "} | ||
<a href="https://www.npmjs.com/package/next-on-netlify"> | ||
next-on-netlify | ||
</a>{" "} | ||
npm package. | ||
</p> | ||
|
||
<h1>1. Server-Side Rendering Made Easy</h1> | ||
<p> | ||
This page is server-side rendered. | ||
<br /> | ||
It fetches a random list of five TV shows from the TVmaze REST API. | ||
<br /> | ||
Refresh this page to see it change. | ||
</p> | ||
|
||
<ul> | ||
{shows.map(({ id, name }) => ( | ||
<li key={id}> | ||
<Link href="/shows/[id]" as={`/shows/${id}`}> | ||
<a> | ||
#{id}: {name} | ||
</a> | ||
</Link> | ||
</li> | ||
))} | ||
</ul> | ||
|
||
<h1>2. Full Support for Dynamic Pages</h1> | ||
<p> | ||
Dynamic pages, introduced in NextJS 9.2, are fully supported. | ||
<br /> | ||
Click on a show to check out a server-side rendered page with dynamic | ||
routing (/shows/:id). | ||
</p> | ||
|
||
<ul> | ||
{shows.slice(0, 3).map(({ id, name }) => ( | ||
<li key={id}> | ||
<Link href="/shows/[id]" as={`/shows/${id}`}> | ||
<a> | ||
#{id}: {name} | ||
</a> | ||
</Link> | ||
</li> | ||
))} | ||
</ul> | ||
|
||
<h1>3. Catch-All Routes? Included ✔</h1> | ||
<p> | ||
You can even take advantage of{" "} | ||
<a href="https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes"> | ||
NextJS' catch-all routes feature | ||
</a> | ||
. | ||
<br /> | ||
Here are three examples: | ||
</p> | ||
<ul> | ||
<li> | ||
<Link href="/shows/[...params]" as={`/shows/73/whatever/path/you/want`}> | ||
<a>/shows/73/whatever/path/you/want</a> | ||
</Link> | ||
</li> | ||
<li> | ||
<Link href="/shows/[...params]" as={`/shows/94/whatever/path/you`}> | ||
<a>/shows/94/whatever/path/you</a> | ||
</Link> | ||
</li> | ||
<li> | ||
<Link href="/shows/[...params]" as={`/shows/106/whatever/path`}> | ||
<a>/shows/106/whatever/path</a> | ||
</Link> | ||
</li> | ||
</ul> | ||
|
||
<h1>4. Static Pages Stay Static</h1> | ||
<p> | ||
next-on-netlify automatically determines which pages are dynamic and which | ||
ones are static. | ||
<br /> | ||
Only dynamic pages are server-side rendered. | ||
<br /> | ||
Static pages are pre-rendered and served directly by Netlify's CDN. | ||
</p> | ||
|
||
<ul> | ||
<li> | ||
<Link href="/static"> | ||
<a>Static NextJS page: /static</a> | ||
</Link> | ||
</li> | ||
<li> | ||
<Link href="/static/[id]" as="/static/123456789"> | ||
<a>Static NextJS page with dynamic routing: /static/:id</a> | ||
</Link> | ||
</li> | ||
</ul> | ||
|
||
<h1>Want to Learn More?</h1> | ||
<p> | ||
Check out the{" "} | ||
<a href="https://github.com/FinnWoelm/next-on-netlify-demo"> | ||
source code on GitHub | ||
</a> | ||
. | ||
</p> | ||
</div> | ||
); | ||
|
||
Index.getInitialProps = async function () { | ||
// Set a random page between 1 and 100 | ||
const randomPage = Math.floor(Math.random() * 100) + 1; | ||
|
||
// Get the data | ||
const res = await fetch(`https://api.tvmaze.com/shows?page=${randomPage}`); | ||
const data = await res.json(); | ||
|
||
return { shows: data.slice(0, 5) }; | ||
}; | ||
|
||
export default Index; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean we're copying all
chunks
to all functions?I was also worried users can control the
chunks
configuration until I found thisThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we're copying all chunks, we might hit size limits for particularly inefficient sites. I think 50MB is the cap, so if people are doing terrible things like including all of lodash/moment/etc. we might see issues opened down the road
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, it does. i think it should be okay for now and can definitely revisit later if issues arise from this implementation. reading the lambdas for the chunk names should be an "if absolutely necessary" approach imo 😂