Skip to content

Commit

Permalink
feat: add vercel-static and netlify-static presets (#1073)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe authored Apr 15, 2023
1 parent a45623d commit fac5436
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 58 deletions.
3 changes: 2 additions & 1 deletion src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,10 @@ async function _build(nitro: Nitro, rollupConfig: RollupConfig) {
await generateFSTree(nitro.options.output.serverDir)
);
}
await nitro.hooks.callHook("compiled", nitro);
}

await nitro.hooks.callHook("compiled", nitro);

// Show deploy and preview hints
const rOutput = relative(process.cwd(), nitro.options.output.dir);
const rewriteRelativePaths = (input: string) => {
Expand Down
5 changes: 2 additions & 3 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ export async function loadOptions(
// Preset
let presetOverride =
(configOverrides.preset as string) || process.env.NITRO_PRESET;
const defaultPreset = detectTarget() || "node-server";
if (configOverrides.dev) {
presetOverride = "nitro-dev";
}
Expand All @@ -126,7 +125,7 @@ export async function loadOptions(
preset: presetOverride,
},
defaultConfig: {
preset: defaultPreset,
preset: detectTarget() || "node-server",
},
defaults: NitroDefaults,
resolve(id: string) {
Expand All @@ -149,7 +148,7 @@ export async function loadOptions(
options.preset =
presetOverride ||
(layers.find((l) => l.config.preset)?.config.preset as string) ||
defaultPreset;
(detectTarget({ static: !options.build }) ?? "node-server");

options.rootDir = resolve(options.rootDir || ".");
options.workspaceDir = await findWorkspaceDir(options.rootDir).catch(
Expand Down
45 changes: 35 additions & 10 deletions src/presets/netlify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,48 @@ export const netlifyEdge = defineNitroPreset({
},
});

export const netlifyStatic = defineNitroPreset({
extends: "static",
output: {
publicDir: "{{ rootDir }}/dist",
},
commands: {
preview: "npx serve ./static",
},
hooks: {
async compiled(nitro: Nitro) {
await writeHeaders(nitro);
await writeRedirects(nitro);
},
},
});

async function writeRedirects(nitro: Nitro) {
const redirectsPath = join(nitro.options.output.publicDir, "_redirects");
let contents = "/* /.netlify/functions/server 200";
const staticFallback = existsSync(
join(nitro.options.output.publicDir, "404.html")
)
? "/* /404.html 404"
: "";
let contents = nitro.options.build
? "/* /.netlify/functions/server 200"
: staticFallback;

const rules = Object.entries(nitro.options.routeRules).sort(
(a, b) => a[0].split(/\/(?!\*)/).length - b[0].split(/\/(?!\*)/).length
);

// Rewrite static ISR paths to builder functions
for (const [key, value] of rules.filter(
([_, value]) => value.isr !== undefined
)) {
contents = value.isr
? `${key.replace("/**", "/*")}\t/.netlify/builders/server 200\n` +
contents
: `${key.replace("/**", "/*")}\t/.netlify/functions/server 200\n` +
contents;
if (nitro.options.build) {
// Rewrite static ISR paths to builder functions
for (const [key, value] of rules.filter(
([_, value]) => value.isr !== undefined
)) {
contents = value.isr
? `${key.replace("/**", "/*")}\t/.netlify/builders/server 200\n` +
contents
: `${key.replace("/**", "/*")}\t/.netlify/functions/server 200\n` +
contents;
}
}

for (const [key, routeRules] of rules.filter(
Expand Down
112 changes: 70 additions & 42 deletions src/presets/vercel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,30 @@ export const vercelEdge = defineNitroPreset({
},
});

export const vercelStatic = defineNitroPreset({
extends: "static",
output: {
dir: "{{ rootDir }}/.vercel/output",
publicDir: "{{ output.dir }}/static",
},
commands: {
preview: "npx serve ./static",
},
hooks: {
async compiled(nitro: Nitro) {
const buildConfigPath = resolve(nitro.options.output.dir, "config.json");
const buildConfig = generateBuildConfig(nitro);
await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2));
},
},
});

function generateBuildConfig(nitro: Nitro) {
const rules = Object.entries(nitro.options.routeRules).sort(
(a, b) => b[0].split(/\/(?!\*)/).length - a[0].split(/\/(?!\*)/).length
);

return defu(nitro.options.vercel?.config, <VercelBuildConfigV3>{
const config = defu(nitro.options.vercel?.config, <VercelBuildConfigV3>{
version: 3,
overrides: {
// Nitro static prerendered route overrides
Expand Down Expand Up @@ -163,50 +181,60 @@ function generateBuildConfig(nitro: Nitro) {
continue: true,
})),
{ handle: "filesystem" },
// ISR rules
...rules
.filter(
([key, value]) =>
// value.isr === false || (value.isr && key.includes("/**"))
value.isr !== undefined
)
.map(([key, value]) => {
const src = key.replace(/^(.*)\/\*\*/, "(?<url>$1/.*)");
if (value.isr === false) {
// we need to write a rule to avoid route being shadowed by another cache rule elsewhere
return {
src,
dest: "/__nitro",
};
}
],
});

// Early return if we are not building a serverless function
if (!nitro.options.build) {
return config;
}

config.routes.push(
// ISR rules
...rules
.filter(
([key, value]) =>
// value.isr === false || (value.isr && key.includes("/**"))
value.isr !== undefined
)
.map(([key, value]) => {
const src = key.replace(/^(.*)\/\*\*/, "(?<url>$1/.*)");
if (value.isr === false) {
// we need to write a rule to avoid route being shadowed by another cache rule elsewhere
return {
src,
dest:
nitro.options.preset === "vercel-edge"
? "/__nitro?url=$url"
: generateEndpoint(key) + "?url=$url",
dest: "/__nitro",
};
}),
// If we are using an ISR function for /, then we need to write this explicitly
...(nitro.options.routeRules["/"]?.isr
? [
{
src: "(?<url>/)",
dest: "/__nitro-index",
},
]
: []),
// If we are using an ISR function as a fallback, then we do not need to output the below fallback route as well
...(!nitro.options.routeRules["/**"]?.isr
? [
{
src: "/(.*)",
dest: "/__nitro",
},
]
: []),
],
});
}
return {
src,
dest:
nitro.options.preset === "vercel-edge"
? "/__nitro?url=$url"
: generateEndpoint(key) + "?url=$url",
};
}),
// If we are using an ISR function for /, then we need to write this explicitly
...(nitro.options.routeRules["/"]?.isr
? [
{
src: "(?<url>/)",
dest: "/__nitro-index",
},
]
: []),
// If we are using an ISR function as a fallback, then we do not need to output the below fallback route as well
...(!nitro.options.routeRules["/**"]?.isr
? [
{
src: "/(.*)",
dest: "/__nitro",
},
]
: [])
);

return config;
}

function generateEndpoint(url: string) {
Expand Down
13 changes: 11 additions & 2 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,17 @@ const autodetectableProviders: Partial<
cleavr: "cleavr",
};

export function detectTarget() {
return autodetectableProviders[provider];
const autodetectableStaticProviders: Partial<
Record<ProviderName, KebabCase<keyof typeof _PRESETS>>
> = {
netlify: "netlify-static",
vercel: "vercel-static",
};

export function detectTarget(options: { static?: boolean } = {}) {
return options?.static
? autodetectableStaticProviders[provider]
: autodetectableProviders[provider];
}

export async function isDirectory(path: string) {
Expand Down

0 comments on commit fac5436

Please sign in to comment.