Skip to content
This repository was archived by the owner on May 10, 2021. It is now read-only.

copy dynamic import chunks to functions dirs #174

Merged
merged 1 commit into from
Mar 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions lib/helpers/copyDynamicImportChunks.js
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) => {
Copy link
Contributor

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 this

Copy link
Contributor

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

Copy link
Contributor Author

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 😂

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;
6 changes: 5 additions & 1 deletion lib/helpers/setupNetlifyFunctionForPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -47,8 +48,11 @@ const setupNetlifyFunctionForPage = ({
});
});

// Copy any dynamic import chunks
copyDynamicImportChunks(functionDirectory);
Copy link
Contributor

Choose a reason for hiding this comment

The 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");
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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. ../[chunk]) 😕

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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,
Expand Down
2 changes: 1 addition & 1 deletion tests/configurableDirs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const FUNCTIONS_DIR = "my-functions";
const PUBLISH_DIR = "my-publish";

// Capture the output to verify successful build
let buildOutput;
let runOutput;

beforeAll(
async () => {
Expand Down
50 changes: 50 additions & 0 deletions tests/dynamicImports.test.js
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);
});
});
3 changes: 3 additions & 0 deletions tests/fixtures/components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Header() {
return <h1>header</h1>;
}
3 changes: 3 additions & 0 deletions tests/fixtures/next.config.js-est
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
target: "experimental-serverless-trace",
};
44 changes: 44 additions & 0 deletions tests/fixtures/pages-dynamic-imports/deep/import.js
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;
148 changes: 148 additions & 0 deletions tests/fixtures/pages-dynamic-imports/index.js
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;