Skip to content

Commit

Permalink
refactor: overhaul presets structure (#2446)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed May 16, 2024
1 parent a92f267 commit 944dcde
Show file tree
Hide file tree
Showing 131 changed files with 2,369 additions and 1,661 deletions.
1 change: 1 addition & 0 deletions .github/workflows/autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
cache: "pnpm"
- run: pnpm install
- run: pnpm stub
- run: pnpm gen-presets
- name: Fix lint issues
run: npm run lint:fix
- uses: autofix-ci/action@ea32e3a12414e6d3183163c3424a7d7a8631ad84
Expand Down
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"$schema": "https://biomejs.dev/schemas/1.7.1/schema.json",
"formatter": {
"indentStyle": "space"
"indentStyle": "space",
"ignore": ["**/*.gen.ts"]
},
"linter": {
"enabled": false
Expand Down
12 changes: 11 additions & 1 deletion build.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { defineBuildConfig } from "unbuild";
import { normalize } from "pathe";
import { resolve, normalize } from "pathe";
import { fileURLToPath } from "node:url";

const srcDir = fileURLToPath(new URL("src", import.meta.url));

export default defineBuildConfig({
declaration: true,
Expand All @@ -8,8 +11,15 @@ export default defineBuildConfig({
"src/index",
"src/config",
"src/cli/index",
{ input: "src/presets/", outDir: "dist/presets", format: "esm" },
{ input: "src/runtime/", outDir: "dist/runtime", format: "esm" },
],
alias: {
nitropack: resolve(srcDir, "index.ts"),
"nitropack/": srcDir,
"nitropack/presets": resolve(srcDir, "presets/index.ts"),
"nitropack/presets/": resolve(srcDir, "presets"),
},
rollup: {
output: {
chunkFileNames(chunk) {
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import unjs from "eslint-config-unjs";

export default unjs({
ignores: ["**/.output", "**/.nitro", "**/.netlify", "**/.nuxt"],
ignores: ["**/.output", "**/.nitro", "**/.netlify", "**/.nuxt", "**/*.gen.*"],
rules: {
"unicorn/no-null": 0,
"no-undef": 0,
Expand Down
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
"types": "./dist/runtime/*.d.ts",
"import": "./dist/runtime/*.mjs"
},
"./presets/*": {
"types": "./dist/presets/*.d.ts",
"import": "./dist/presets/*.mjs"
},
"./presets": {
"types": "./dist/presets/index.d.ts",
"import": "./dist/presets/index.mjs"
},
"./dist/runtime/*": {
"types": "./dist/runtime/*.d.ts",
"import": "./dist/runtime/*.mjs"
Expand All @@ -43,14 +51,15 @@
"*.d.ts"
],
"scripts": {
"build": "unbuild",
"build": "pnpm gen-presets && unbuild",
"dev": "pnpm nitro dev playground",
"dev:build": "pnpm nitro build playground",
"dev:start": "node playground/.output/server/index.mjs",
"lint": "eslint --cache . && biome format .",
"lint:fix": "eslint --cache --fix . && biome format . --write",
"nitro": "JITI_ESM_RESOLVE=1 NODE_OPTIONS=\"--enable-source-maps\" jiti ./src/cli/index.ts",
"prepack": "pnpm build",
"gen-presets": "JITI_ESM_RESOLVE=1 pnpm jiti scripts/gen-presets.ts",
"release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",
"stub": "unbuild --stub",
"test": "pnpm lint && pnpm vitest-es run --silent",
Expand Down
1 change: 1 addition & 0 deletions presets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./dist/presets/index";
94 changes: 94 additions & 0 deletions scripts/gen-presets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import createJITI from "jiti";
import { consola } from "consola";
import { kebabCase, camelCase, pascalCase, snakeCase } from "scule";
import { readdirSync, existsSync, writeFileSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type { NitroPreset, NitroPresetMeta } from "nitropack";
import { findTypeExports } from "mlly";

const autoGenHeader = /* ts */ `// Auto-generated using gen-presets script\n`;

// --- Scan presets/ directory ---
const presetsDir = fileURLToPath(new URL("../src/presets", import.meta.url));
const presetDirs: string[] = readdirSync(presetsDir, { withFileTypes: true })
.filter(
(dir) =>
dir.isDirectory() &&
existsSync(resolve(presetsDir, dir.name, "preset.ts"))
)
.map((dir) => dir.name);

// --- Load presets ---
const jitiRequire = createJITI(presetsDir, {
esmResolve: true,
interopDefault: true,
alias: {
"nitropack": fileURLToPath(new URL("../src/index.ts", import.meta.url))
}
});
const allPresets: (NitroPreset & { _meta?: NitroPresetMeta })[] = [];
for (const preset of presetDirs) {
const presetPath = resolve(presetsDir, preset, "preset.ts");
const _presets = jitiRequire(presetPath);
allPresets.push(..._presets);
}

// --- Validate names ---
const _names = new Set<string>();
for (const preset of allPresets) {
if (!preset._meta?.name) {
consola.warn(`Preset ${preset} does not have a name`);
continue;
}
const names = [preset._meta.name, ...(preset._meta.aliases || [])];
for (const name of names) {
if (_names.has(name)) {
if (!preset._meta.compatibility?.date) {
consola.warn(`Preset ${name} is duplicated`);
}
continue;
}
if (kebabCase(name) !== name) {
consola.warn(`Preset ${name} is not kebab-case`);
}
_names.add(name);
}
}
const names = [..._names].sort();
consola.log(names.join(", "));

// --- Generate presets/_all.gen.ts ---
writeFileSync(
resolve(presetsDir, "_all.gen.ts"),
/* ts */ `${autoGenHeader}
${presetDirs
.map((preset) => `import _${camelCase(preset)} from "./${preset}/preset";`)
.join("\n")}
export default [
${presetDirs.map((preset) => ` ..._${camelCase(preset)},`).join("\n")}
] as const;
`
);
// --- Generate presets/_types.gen.ts ---
const presetsWithType = presetDirs.filter((presetDir) => {
const presetPath = resolve(presetsDir, presetDir, "preset.ts");
const content = readFileSync(presetPath, "utf8");
const typeExports = findTypeExports(content);
return typeExports.some(type => type.name === "PresetOptions")
});
writeFileSync(
resolve(presetsDir, "_types.gen.ts"),
/* ts */ `${autoGenHeader}
${presetsWithType.map((preset) => `import { PresetOptions as ${pascalCase(preset)}Options } from "./${preset}/preset";`).join("\n")}
export interface PresetOptions {
${presetsWithType.map((preset) => ` ${camelCase(preset)}: ${pascalCase(preset)}Options;`).join("\n")}
}
export type PresetName = ${names.map((name) => `"${name}"`).join(" | ")};
export type PresetNameInput = ${names.flatMap((name) => [...new Set([kebabCase(name), camelCase(name), snakeCase(name)])].map(n => `"${n}"`)).join(" | ")} | (string & {});
`
);
7 changes: 7 additions & 0 deletions src/cli/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export default defineCommand({
description:
"The build preset to use (you can also use `NITRO_PRESET` environment variable).",
},
compatibilityDate: {
type: "string",
description:
"The date to use for preset compatibility (you can also use `NITRO_COMPATIBILITY_DATE` environment variable).",
},
},
async run({ args }) {
const rootDir = resolve((args.dir || args._dir || ".") as string);
Expand All @@ -30,6 +35,8 @@ export default defineCommand({
dev: false,
minify: args.minify,
preset: args.preset,
}, {
compatibilityDate: args.compatibilityDate || "2024-05-17",
});
await prepare(nitro);
await copyPublicAssets(nitro);
Expand Down
44 changes: 23 additions & 21 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { resolve, join } from "pathe";
import { loadConfig, watchConfig, WatchConfigOptions } from "c12";
import { klona } from "klona/full";
import { camelCase } from "scule";
import { defu } from "defu";
import { resolveModuleExportNames } from "mlly";
import escapeRE from "escape-string-regexp";
Expand All @@ -13,20 +12,19 @@ import { version } from "../package.json";
import {
resolvePath,
resolveFile,
detectTarget,
provideFallbackValues,
} from "./utils";
import type {
NitroConfig,
NitroOptions,
NitroPreset,
NitroRouteConfig,
NitroRouteRules,
NitroRuntimeConfig,
} from "./types";
import { runtimeDir, pkgDir } from "./dirs";
import * as _PRESETS from "./presets";
import { nitroImports } from "./imports";
import { PresetName } from "./presets";
import { resolvePreset } from "./preset";

const NitroDefaults: NitroConfig = {
// General
Expand Down Expand Up @@ -142,6 +140,7 @@ const NitroDefaults: NitroConfig = {
export interface LoadConfigOptions {
watch?: boolean;
c12?: WatchConfigOptions;
compatibilityDate?: string;
}

export async function loadOptions(
Expand All @@ -159,8 +158,13 @@ export async function loadOptions(

// Load configuration and preset
configOverrides = klona(configOverrides);

// @ts-ignore
globalThis.defineNitroConfig = globalThis.defineNitroConfig || ((c) => c);

// Compatibility date
const compatibilityDate = process.env.NITRO_COMPATIBILITY_DATE || opts.compatibilityDate;

const c12Config = await (opts.watch ? watchConfig : loadConfig)(<
WatchConfigOptions
>{
Expand All @@ -173,7 +177,10 @@ export async function loadOptions(
preset: presetOverride,
},
defaultConfig: {
preset: detectTarget({ static: configOverrides.static }),
preset: (await resolvePreset("", {
static: configOverrides.static,
compatibilityDate
}))?._meta?.name
},
defaults: NitroDefaults,
jitiOptions: {
Expand All @@ -182,30 +189,25 @@ export async function loadOptions(
"nitropack/config": "nitropack/config",
},
},
resolve(id: string) {
const presets = _PRESETS as any as Record<string, NitroPreset>;
let matchedPreset = presets[camelCase(id)] || presets[id];
if (!matchedPreset) {
return null;
async resolve (id: string) {
const preset = await resolvePreset(id, {
static: configOverrides.static,
compatibilityDate: compatibilityDate
});
if (preset) {
return {
config: preset,
};
}
if (typeof matchedPreset === "function") {
matchedPreset = matchedPreset();
}
return {
config: matchedPreset,
};
},
...opts.c12,
});
const options = klona(c12Config.config) as NitroOptions;
options._config = configOverrides;
options._c12 = c12Config;

options.preset =
presetOverride ||
((c12Config.layers || []).find((l) => l.config?.preset)?.config
?.preset as string) ||
detectTarget({ static: options.static });
const _presetName = (c12Config.layers || []).find((l) => l.config?._meta?.name)?.config?._meta?.name || presetOverride
options.preset = _presetName as PresetName;

options.rootDir = resolve(options.rootDir || ".");
options.workspaceDir = await findWorkspaceDir(options.rootDir).catch(
Expand Down
57 changes: 55 additions & 2 deletions src/preset.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
import type { NitroPreset } from "./types";
import type { NitroPreset, NitroPresetMeta } from "./types";
import { fileURLToPath } from "node:url";
import { kebabCase } from "scule";
import { provider } from 'std-env'

export function defineNitroPreset<
P extends NitroPreset,
M extends NitroPresetMeta,
>(preset: P, meta?: M): P & { _meta: NitroPresetMeta } {
if (
meta?.url &&
typeof preset !== "function" &&
preset.entry &&
preset.entry.startsWith(".")
) {
preset.entry = fileURLToPath(new URL(preset.entry, meta.url));
}
return { ...preset, _meta: meta } as P & { _meta: M };
}

let _presets: typeof import("./presets")["presets"]

export async function resolvePreset(name: string, opts: { static?: boolean, compatibilityDate?: string }): Promise<(NitroPreset & { _meta?: NitroPresetMeta }) | undefined> {
if (!_presets) {
_presets = (await import("./presets") as typeof import("./presets")).presets;
}

const _name = kebabCase(name) || provider;
const _date = new Date(opts.compatibilityDate || 0);

const matches = _presets.filter((preset) => {
const names = [preset._meta.name, preset._meta.stdName, ...(preset._meta.aliases || [])].filter(Boolean);
if (!names.includes(_name)) {
return false;
}
if (preset._meta.compatibility?.date && new Date(preset._meta.compatibility?.date || 0) > _date) {
return false
}
return true
}).sort((a, b) => {
const aDate = new Date(a._meta.compatibility?.date || 0);
const bDate = new Date(b._meta.compatibility?.date || 0);
return bDate > aDate ? 1 : -1
});

const preset = matches.find(p => (p._meta.static || false) === (opts?.static || false)) || matches[0];

if (typeof preset === 'function') {
return preset();
}

if (!name && !preset) {
return opts?.static ? resolvePreset('static', opts) : resolvePreset('node-server', opts)
}

export function defineNitroPreset(preset: NitroPreset) {
return preset;
}

0 comments on commit 944dcde

Please sign in to comment.