Compare commits

..

2 Commits

Author SHA1 Message Date
Tim Neutkens
c48571a799 Publish Canary
- @now/next@2.2.1-canary.0
2019-12-16 12:49:02 +01:00
Joe Haddad
6eeb6983d9 [now-next] Support for next export (#3431)
* Add Support for `next export`

* Add some test cases

* tests require canary next.js

* bump

* fix test cases

* Update packages/now-next/src/index.ts

Co-Authored-By: Joe Haddad <joe.haddad@zeit.co>

* Update packages/now-next/src/index.ts

Co-Authored-By: Joe Haddad <joe.haddad@zeit.co>
2019-12-16 12:46:39 +01:00
17 changed files with 295 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@now/next",
"version": "2.2.0",
"version": "2.2.1-canary.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",

View File

@@ -1,16 +1,3 @@
import { ChildProcess, fork } from 'child_process';
import {
pathExists,
readFile,
unlink as unlinkFile,
writeFile,
lstatSync,
} from 'fs-extra';
import os from 'os';
import path from 'path';
import resolveFrom from 'resolve-from';
import semver from 'semver';
import {
BuildOptions,
Config,
@@ -20,6 +7,7 @@ import {
FileBlob,
FileFsRef,
Files,
getLambdaOptionsFromFunction,
getNodeVersion,
getSpawnOptions,
glob,
@@ -30,10 +18,24 @@ import {
Route,
runNpmInstall,
runPackageJsonScript,
getLambdaOptionsFromFunction,
} from '@now/build-utils';
import {
convertRedirects,
convertRewrites,
} from '@now/routing-utils/dist/superstatic';
import nodeFileTrace, { NodeFileTraceReasons } from '@zeit/node-file-trace';
import { ChildProcess, fork } from 'child_process';
import {
lstatSync,
pathExists,
readFile,
unlink as unlinkFile,
writeFile,
} from 'fs-extra';
import os from 'os';
import path from 'path';
import resolveFrom from 'resolve-from';
import semver from 'semver';
import createServerlessConfig from './create-serverless-config';
import nextLegacyVersions from './legacy-versions';
import {
@@ -43,10 +45,14 @@ import {
excludeFiles,
ExperimentalTraceVersion,
getDynamicRoutes,
getExportIntent,
getExportStatus,
getNextConfig,
getPathsInside,
getPrerenderManifest,
getRoutes,
getRoutesManifest,
getSourceFilePathFromPage,
isDynamicRoute,
normalizePackageJson,
normalizePage,
@@ -54,15 +60,8 @@ import {
stringMap,
syncEnvVars,
validateEntrypoint,
getSourceFilePathFromPage,
getRoutesManifest,
} from './utils';
import {
convertRedirects,
convertRewrites,
} from '@now/routing-utils/dist/superstatic';
interface BuildParamsMeta {
isDev: boolean | undefined;
env?: EnvConfig;
@@ -355,6 +354,100 @@ export const build = async ({
}
}
const exportIntent = await getExportIntent(entryPath);
if (exportIntent) {
const { trailingSlash } = exportIntent;
const userExport = await getExportStatus(entryPath);
if (!userExport) {
await writePackageJson(entryPath, {
...pkg,
scripts: {
...pkg.scripts,
'now-automatic-next-export': `next export --outdir "${path.resolve(
entryPath,
'out'
)}"`,
},
});
await runPackageJsonScript(entryPath, 'now-automatic-next-export', {
...spawnOpts,
env,
});
}
const resultingExport = await getExportStatus(entryPath);
if (!resultingExport) {
throw new Error(
'Exporting Next.js app failed. Please check your build logs and contact us if this continues.'
);
}
if (resultingExport.success !== true) {
throw new Error(
'Export of Next.js app failed. Please check your build logs.'
);
}
const outDirectory = resultingExport.outDirectory;
debug(`next export should use trailing slash: ${trailingSlash}`);
// This handles pages, `public/`, and `static/`.
const filesAfterBuild = await glob('**', outDirectory);
const output: Files = { ...filesAfterBuild };
// Strip `.html` extensions from build output
Object.entries(output)
.filter(([name]) => name.endsWith('.html'))
.forEach(([name, value]) => {
const cleanName = name.slice(0, -5);
delete output[name];
output[cleanName] = value;
if (value.type === 'FileBlob' || value.type === 'FileFsRef') {
value.contentType = value.contentType || 'text/html; charset=utf-8';
}
});
return {
output,
routes: [
// TODO: low priority: handle trailingSlash
// redirects take the highest priority
...redirects,
// Before we handle static files we need to set proper caching headers
{
// This ensures we only match known emitted-by-Next.js files and not
// user-emitted files which may be missing a hash in their filename.
src: path.join(
'/',
entryDirectory,
'_next/static/(?:[^/]+/pages|chunks|runtime|css|media)/.+'
),
// Next.js assets contain a hash or entropy in their filenames, so they
// are guaranteed to be unique and cacheable indefinitely.
headers: { 'cache-control': 'public,max-age=31536000,immutable' },
continue: true,
},
{
src: path.join('/', entryDirectory, '_next(?!/data(?:/|$))(?:/.*)?'),
},
// Next.js pages, `static/` folder, reserved assets, and `public/`
// folder
{ handle: 'filesystem' },
...rewrites,
// Dynamic routes
// TODO: do we want to do this?: ...dynamicRoutes,
],
watch: [],
childProcesses: [],
};
}
if (isLegacy) {
debug('Running npm install --production...');
await runNpmInstall(

View File

@@ -624,6 +624,73 @@ export type NextPrerenderedRoutes = {
};
};
export async function getExportIntent(
entryPath: string
): Promise<false | { trailingSlash: boolean }> {
const pathExportMarker = path.join(entryPath, '.next', 'export-marker.json');
const hasExportMarker: boolean = await fs
.access(pathExportMarker, fs.constants.F_OK)
.then(() => true)
.catch(() => false);
if (!hasExportMarker) {
return false;
}
const manifest: {
version: 1;
exportTrailingSlash: boolean;
hasExportPathMap: boolean;
} = JSON.parse(await fs.readFile(pathExportMarker, 'utf8'));
switch (manifest.version) {
case 1: {
if (manifest.hasExportPathMap !== true) {
return false;
}
return { trailingSlash: manifest.exportTrailingSlash };
}
default: {
return false;
}
}
}
export async function getExportStatus(
entryPath: string
): Promise<false | { success: boolean; outDirectory: string }> {
const pathExportDetail = path.join(entryPath, '.next', 'export-detail.json');
const hasExportDetail: boolean = await fs
.access(pathExportDetail, fs.constants.F_OK)
.then(() => true)
.catch(() => false);
if (!hasExportDetail) {
return false;
}
const manifest: {
version: 1;
success: boolean;
outDirectory: string;
} = JSON.parse(await fs.readFile(pathExportDetail, 'utf8'));
switch (manifest.version) {
case 1: {
return {
success: !!manifest.success,
outDirectory: manifest.outDirectory,
};
}
default: {
return false;
}
}
}
export async function getPrerenderManifest(
entryPath: string
): Promise<NextPrerenderedRoutes> {

View File

@@ -0,0 +1,5 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
};

View File

@@ -0,0 +1,12 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/_next/static/testing-build-id/pages/index.js",
"responseHeaders": {
"cache-control": "public,max-age=31536000,immutable"
}
}
]
}

View File

@@ -0,0 +1,10 @@
{
"scripts": {
"build": "next build && next export"
},
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1 @@
export default () => 'Hi';

View File

@@ -0,0 +1,5 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
};

View File

@@ -0,0 +1,14 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/",
"mustContain": "Hi There"
},
{
"path": "/about",
"mustContain": "Hi on About"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"scripts": {
"build": "next build && next export"
},
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,7 @@
function About() {
return <div>Hi on About</div>;
}
About.getInitialProps = () => ({});
export default About;

View File

@@ -0,0 +1,7 @@
function Home() {
return <div>Hi There</div>;
}
Home.getInitialProps = () => ({});
export default Home;

View File

@@ -0,0 +1,6 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
exportPathMap: d => d,
};

View File

@@ -0,0 +1,14 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"probes": [
{
"path": "/",
"mustContain": "Hi There"
},
{
"path": "/about",
"mustContain": "Hi on About"
}
]
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "canary",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1,7 @@
function About() {
return <div>Hi on About</div>;
}
About.getInitialProps = () => ({});
export default About;

View File

@@ -0,0 +1,7 @@
function Home() {
return <div>Hi There</div>;
}
Home.getInitialProps = () => ({});
export default Home;