diff --git a/.changeset/purple-gorillas-do.md b/.changeset/purple-gorillas-do.md new file mode 100644 index 00000000..77212b27 --- /dev/null +++ b/.changeset/purple-gorillas-do.md @@ -0,0 +1,5 @@ +--- +"@ladle/react": minor +--- + +Add `--base` and `--mode` options à la `vite build` diff --git a/packages/ladle/lib/cli/apply-cli-config.js b/packages/ladle/lib/cli/apply-cli-config.js new file mode 100644 index 00000000..85a7b285 --- /dev/null +++ b/packages/ladle/lib/cli/apply-cli-config.js @@ -0,0 +1,31 @@ +import debug from "./debug.js"; +import path from "path"; +import loadConfig from "./load-config.js"; + +/** + * @param params {import("../shared/types").CLIParams} + */ +export default async function applyCLIConfig(params) { + debug(`CLI theme: ${params.theme}`); + debug(`CLI stories: ${params.stories}`); + debug(`CLI port: ${params.port || "undefined"}`); + debug(`CLI out: ${params.outDir || "undefined"}`); + params.config = params.config || ".ladle"; + const configFolder = path.isAbsolute(params.config) + ? params.config + : path.join(process.cwd(), params.config); + const config = await loadConfig(configFolder); + config.addons.theme.defaultState = + params.theme || config.addons.theme.defaultState; + config.stories = params.stories || config.stories; + config.viteConfig = params.viteConfig || config.viteConfig; + config.outDir = params.outDir || config.outDir; + config.port = params.port || config.port; + config.previewPort = params.previewPort || config.previewPort; + config.base = params.base || config.base; + config.mode = params.mode || config.mode; + debug(`Final config:\n${JSON.stringify(config, null, " ")}`); + process.env["VITE_PUBLIC_LADLE_THEME"] = config.addons.theme.defaultState; + process.env["VITE_PUBLIC_STORIES"] = config.stories; + return { configFolder, config }; +} diff --git a/packages/ladle/lib/cli/build.js b/packages/ladle/lib/cli/build.js index 329a4adb..ced1d399 100755 --- a/packages/ladle/lib/cli/build.js +++ b/packages/ladle/lib/cli/build.js @@ -4,37 +4,19 @@ import path from "path"; import { promises as fs } from "fs"; import { globby } from "globby"; import viteProd from "./vite-prod.js"; -import loadConfig from "./load-config.js"; import debug from "./debug.js"; import { getMetaJsonString } from "./vite-plugin/generate/get-meta-json.js"; import { getEntryData } from "./vite-plugin/parse/get-entry-data.js"; import getFolderSize from "./get-folder-size.js"; +import applyCLIConfig from "./apply-cli-config.js"; /** - * @param params {import("../shared/types").BuildParams} + * @param params {import("../shared/types").CLIParams} */ const build = async (params = {}) => { const startTime = performance.now(); debug("Starting build command"); - debug(`CLI theme: ${params.theme}`); - debug(`CLI stories: ${params.stories}`); - debug(`CLI out: ${params.outDir ? params.outDir : "undefined"}`); - params.config = params.config || ".ladle"; - const configFolder = path.isAbsolute(params.config) - ? params.config - : path.join(process.cwd(), params.config); - const config = await loadConfig(configFolder); - - // CLI flags override default and custom config files - config.addons.theme.defaultState = params.theme - ? params.theme - : config.addons.theme.defaultState; - config.stories = params.stories ? params.stories : config.stories; - config.viteConfig = params.viteConfig ? params.viteConfig : config.viteConfig; - config.outDir = params.outDir ? params.outDir : config.outDir; - debug(`Final config:\n${JSON.stringify(config, null, " ")}`); - process.env["VITE_PUBLIC_LADLE_THEME"] = config.addons.theme.defaultState; - process.env["VITE_PUBLIC_STORIES"] = config.stories; + const { configFolder, config } = await applyCLIConfig(params); await viteProd(config, configFolder); const entryData = await getEntryData(await globby([config.stories])); const jsonContent = getMetaJsonString(entryData); diff --git a/packages/ladle/lib/cli/cli.js b/packages/ladle/lib/cli/cli.js index c5220cc9..93ee52a3 100755 --- a/packages/ladle/lib/cli/cli.js +++ b/packages/ladle/lib/cli/cli.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { program } from "commander"; +import { Command } from "commander"; import serve from "./serve.js"; import build from "./build.js"; import preview from "./preview.js"; @@ -9,6 +9,7 @@ import preview from "./preview.js"; */ const strToInt = (n) => parseInt(n, 10); +const program = new Command("ladle"); program.showHelpAfterError().showSuggestionAfterError(); program @@ -18,11 +19,10 @@ program .option("-p, --port [number]", "port to serve the application", strToInt) .option("--stories [string]", "glob to find stories") .option("--theme [string]", "theme light, dark or auto") - .option( - "--config [string]", - "folder where Ladle configs are located, default .ladle", - ) + .option("--config [string]", "folder where config is located, default .ladle") .option("--viteConfig [string]", "file with Vite configuration") + .option("--base [string]", "base URL path for build output") + .option("--mode [string]", "Vite mode") .action(async (params) => { await serve({ ...params, serve: params }); }); @@ -35,6 +35,8 @@ program .option("--theme [string]", "theme light, dark or auto") .option("--config [string]", "folder where config is located, default .ladle") .option("--viteConfig [string]", "file with Vite configuration") + .option("--base [string]", "base URL path for build output") + .option("--mode [string]", "Vite mode") .action(async (params) => { const success = await build({ ...params, build: params }); if (success) { @@ -50,6 +52,8 @@ program .option("-p, --port [number]", "port to serve the application", strToInt) .option("--config [string]", "folder where config is located, default .ladle") .option("--viteConfig [string]", "file with Vite configuration") + .option("--base [string]", "base URL path for build output") + .option("--mode [string]", "Vite mode") .action(async (params) => { await preview({ ...params, previewPort: params.port }); }); diff --git a/packages/ladle/lib/cli/preview.js b/packages/ladle/lib/cli/preview.js index f0b8e65e..a97daec0 100644 --- a/packages/ladle/lib/cli/preview.js +++ b/packages/ladle/lib/cli/preview.js @@ -1,27 +1,15 @@ #!/usr/bin/env node -import path from "path"; import vitePreview from "./vite-preview.js"; -import loadConfig from "./load-config.js"; import debug from "./debug.js"; +import applyCLIConfig from "./apply-cli-config.js"; /** - * @param params {import("../shared/types").DevParams} + * @param params {import("../shared/types").CLIParams} */ const preview = async (params = {}) => { debug("Starting preview command"); - debug(`CLI port: ${params.port ? params.port : "undefined"}`); - params.config = params.config || ".ladle"; - const configFolder = path.isAbsolute(params.config) - ? params.config - : path.join(process.cwd(), params.config); - const config = await loadConfig(configFolder); - config.previewPort = params.previewPort - ? params.previewPort - : config.previewPort; - config.outDir = params.outDir ? params.outDir : config.outDir; - config.viteConfig = params.viteConfig ? params.viteConfig : config.viteConfig; - debug(`Final config:\n${JSON.stringify(config, null, " ")}`); + const { configFolder, config } = await applyCLIConfig(params); await vitePreview(config, configFolder); }; diff --git a/packages/ladle/lib/cli/serve.js b/packages/ladle/lib/cli/serve.js index 275ec3f1..f4bb48cb 100755 --- a/packages/ladle/lib/cli/serve.js +++ b/packages/ladle/lib/cli/serve.js @@ -1,33 +1,15 @@ #!/usr/bin/env node -import path from "path"; import viteDev from "./vite-dev.js"; -import loadConfig from "./load-config.js"; import debug from "./debug.js"; +import applyCLIConfig from "./apply-cli-config.js"; /** - * @param params {import("../shared/types").DevParams} + * @param params {import("../shared/types").CLIParams} */ const serve = async (params = {}) => { debug("Starting serve command"); - debug(`CLI theme: ${params.theme}`); - debug(`CLI stories: ${params.stories}`); - debug(`CLI port: ${params.port ? params.port : "undefined"}`); - params.config = params.config || ".ladle"; - const configFolder = path.isAbsolute(params.config) - ? params.config - : path.join(process.cwd(), params.config); - const config = await loadConfig(configFolder); - // CLI flags override default and custom config files - config.addons.theme.defaultState = params.theme - ? params.theme - : config.addons.theme.defaultState; - config.stories = params.stories ? params.stories : config.stories; - config.port = params.port ? params.port : config.port; - config.viteConfig = params.viteConfig ? params.viteConfig : config.viteConfig; - debug(`Final config:\n${JSON.stringify(config, null, " ")}`); - process.env["VITE_PUBLIC_LADLE_THEME"] = config.addons.theme.defaultState; - process.env["VITE_PUBLIC_STORIES"] = config.stories; + const { configFolder, config } = await applyCLIConfig(params); await viteDev(config, configFolder); }; diff --git a/packages/ladle/lib/cli/vite-base.js b/packages/ladle/lib/cli/vite-base.js index 62d9d497..100a653d 100644 --- a/packages/ladle/lib/cli/vite-base.js +++ b/packages/ladle/lib/cli/vite-base.js @@ -80,6 +80,7 @@ const getBaseViteConfig = async (ladleConfig, configFolder, viteConfig) => { */ const config = { ...viteConfig, + base: ladleConfig.base, configFile: false, cacheDir: userViteConfig.cacheDir ? userViteConfig.cacheDir diff --git a/packages/ladle/lib/cli/vite-dev.js b/packages/ladle/lib/cli/vite-dev.js index f5cbaf68..b98b1eb7 100644 --- a/packages/ladle/lib/cli/vite-dev.js +++ b/packages/ladle/lib/cli/vite-dev.js @@ -28,7 +28,7 @@ const bundler = async (config, configFolder) => { * @type {import('vite').InlineConfig} */ const viteConfig = await getBaseViteConfig(config, configFolder, { - mode: "development", + mode: config.mode || "development", server: { port: config.port, hmr: { @@ -48,10 +48,17 @@ const bundler = async (config, configFolder) => { const jsonContent = getMetaJsonObject(entryData); res.json(jsonContent); }); + // When `middlewareMode` is true, vite's own base middleware won't redirect requests, + // so we need to do that ourselves. + const { base } = viteConfig; + if (base && base !== "/") { + app.get("/", (_, res) => res.redirect(base)); + app.get("/index.html", (_, res) => res.redirect(base)); + } app.use(vite.middlewares); const serverUrl = `${vite.config.server.https ? "https" : "http"}://${ vite.config.server.host || "localhost" - }:${port}`; + }:${port}${vite.config.base || ""}`; app.listen(port, async () => { console.log( boxen(`🥄 Ladle.dev served at ${serverUrl}`, { diff --git a/packages/ladle/lib/cli/vite-preview.js b/packages/ladle/lib/cli/vite-preview.js index 42a06f49..02fdfdae 100644 --- a/packages/ladle/lib/cli/vite-preview.js +++ b/packages/ladle/lib/cli/vite-preview.js @@ -14,7 +14,7 @@ const vitePreview = async (config, configFolder) => { * @type {import('vite').InlineConfig} */ const viteConfig = await getBaseViteConfig(config, configFolder, { - mode: "production", + mode: config.mode || "production", build: { outDir: path.join(process.cwd(), config.outDir), emptyOutDir: true, diff --git a/packages/ladle/lib/cli/vite-prod.js b/packages/ladle/lib/cli/vite-prod.js index d3792694..61d7cdd4 100644 --- a/packages/ladle/lib/cli/vite-prod.js +++ b/packages/ladle/lib/cli/vite-prod.js @@ -12,7 +12,7 @@ const viteProd = async (config, configFolder) => { * @type {import('vite').InlineConfig} */ const viteConfig = await getBaseViteConfig(config, configFolder, { - mode: "production", + mode: config.mode || "production", build: { outDir: path.join(process.cwd(), config.outDir), emptyOutDir: true, diff --git a/packages/ladle/lib/shared/default-config.js b/packages/ladle/lib/shared/default-config.js index ae6ea23b..48edef70 100644 --- a/packages/ladle/lib/shared/default-config.js +++ b/packages/ladle/lib/shared/default-config.js @@ -8,6 +8,7 @@ export default { port: 61000, previewPort: 8080, outDir: "build", + base: undefined, // enable/disable addons and their default state addons: { control: { diff --git a/packages/ladle/lib/shared/types.ts b/packages/ladle/lib/shared/types.ts index e4c0f97b..ab145230 100644 --- a/packages/ladle/lib/shared/types.ts +++ b/packages/ladle/lib/shared/types.ts @@ -122,11 +122,7 @@ export type GeneratedStories = { }; }; -export type DevParams = Partial & { - theme?: ThemeState; - config?: string; -}; -export type BuildParams = Partial & { +export type CLIParams = Partial & { theme?: ThemeState; config?: string; }; @@ -143,6 +139,8 @@ export type Config = { port: number; previewPort: number; outDir: string; + base?: string; + mode?: string; addons: { control: { enabled: boolean; diff --git a/packages/website/docs/cli.md b/packages/website/docs/cli.md index c9fa2e21..624c134e 100644 --- a/packages/website/docs/cli.md +++ b/packages/website/docs/cli.md @@ -14,24 +14,27 @@ Options: Commands: serve|dev [options] start developing build [options] build static production app - preview [options] start a server to preview the build + preview [options] start a server to preview the build in outDir help [command] display help for command ``` ## Serve command ```bash -Usage: ladle serve [options] +Usage: ladle serve|dev [options] start developing Options: - -p, --port [number] port to serve the application - --stories [string] glob to find stories - --theme [string] theme light, dark or auto - --config [string] folder where Ladle configs are located, default .ladle - --viteConfig [string] file with Vite configuration - -h, --help display help for command + -p, --port [number] port to serve the application + --stories [string] glob to find stories + --theme [string] theme light, dark or auto + --config [string] folder where config is located, default .ladle + --viteConfig [string] file with Vite configuration + --base [string] base URL path for build output + --mode [string] Vite mode + -h, --help display help for command + ``` ## Build command @@ -42,12 +45,15 @@ Usage: ladle build [options] build static production app Options: - -o, --outDir output directory - --stories [string] glob to find stories - --theme [string] theme light, dark or auto - --config [string] folder where config is located, default .ladle - --viteConfig [string] file with Vite configuration - -h, --help display help for command + -o, --outDir output directory + --stories [string] glob to find stories + --theme [string] theme light, dark or auto + --config [string] folder where config is located, default .ladle + --viteConfig [string] file with Vite configuration + --base [string] base URL path for build output + --mode [string] Vite mode + -h, --help display help for command + ``` ## Preview command @@ -62,5 +68,8 @@ Options: -p, --port [number] port to serve the application --config [string] folder where config is located, default .ladle --viteConfig [string] file with Vite configuration + --base [string] base URL path for build output + --mode [string] Vite mode -h, --help display help for command + ``` diff --git a/packages/website/docs/config.md b/packages/website/docs/config.md index 4a551f55..7b5e9ab0 100644 --- a/packages/website/docs/config.md +++ b/packages/website/docs/config.md @@ -79,6 +79,28 @@ export default { }; ``` +### base + +Base path for building the output; useful for e.g. hosting your project's storybook on GitHub Pages: + +```tsx +export default { + base: "/my-project/", +}; +``` + +### mode + +Vite [mode](https://vitejs.dev/guide/env-and-mode.html#modes). If not set, defaults to `development` when developing and `production` for building static output. + +This also affects [Vite's .env file loading](https://vitejs.dev/guide/env-and-mode.html#env-files), as well as anything else setting `mode` affects. + +```tsx +export default { + mode: "my-custom-mode", +}; +``` + ### addons You can enable or disable all Ladle addons (the buttons in the left bottom corner). You can also control their default state.