import * as fs from "node:fs"; import fsPromise from "node:fs/promises"; import path from "node:path"; import { TsconfigPathsPlugin } from "@esbuild-plugins/tsconfig-paths"; import { init, parse } from "es-module-lexer"; import { defineConfig } from "tsup"; import { workerPlugin } from "./worker-plugin"; const DIST_PATH = "./dist"; const USE_CLIENT_DIRECTIVE = `'use client';\n`; const addDirectivesToChunkFiles = async (readPath: string): Promise => { try { if (!fs.existsSync(readPath)) return; const files = await fsPromise.readdir(readPath); for (const file of files) { if (file.endsWith(".mjs") || file.endsWith(".js")) { const filePath = path.join(readPath, file); const data = await fsPromise.readFile(filePath, "utf8"); if (data.startsWith(USE_CLIENT_DIRECTIVE)) continue; await fsPromise.writeFile(filePath, `${USE_CLIENT_DIRECTIVE}${data}`, "utf8"); } } } catch (err) { // oxlint-disable-next-line no-console console.error("Error:", err); } }; const banner = `/** * Copyright 2025 Aiden Bai, Million Software, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the “Software”), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */`; void (async () => { await init; if (fs.existsSync(DIST_PATH)) { fs.rmSync(DIST_PATH, { recursive: true }); } fs.mkdirSync(DIST_PATH, { recursive: true }); const code = fs.readFileSync("./src/core/index.ts", "utf8"); const [_, allExports] = parse(code); const names: Array = []; for (const exportItem of allExports) { names.push(exportItem.n); } const createFn = (name: string) => `export let ${name}=()=>{}`; const createVar = (name: string) => `export let ${name}=undefined`; let script = ""; for (const name of names) { if (name[0].toLowerCase() === name[0]) { script += `${createFn(name)}\n`; continue; } script += `${createVar(name)}\n`; } setTimeout(() => { for (const ext of ["js", "mjs", "global.js"]) { fs.writeFileSync(`./dist/rsc-shim.${ext}`, script); } }, 500); })(); export default defineConfig([ { entry: ["./src/auto.ts", "./src/install-hook.ts"], outDir: DIST_PATH, banner: { js: banner, }, splitting: false, clean: false, sourcemap: false, format: ["iife"], // Target ES2019 (no `?.`, no `??`) so older babel-loader configs without // `@babel/preset-env` for optional chaining can still parse the bundle // (#287, #336). target: "es2019", platform: "browser", treeshake: true, dts: true, minify: process.env.NODE_ENV === "production" ? "terser" : false, env: { NODE_ENV: process.env.NODE_ENV ?? "development", }, external: [ "react", "react-dom", "next", "next/navigation", "react-router", "react-router-dom", "@remix-run/react", ], esbuildPlugins: [workerPlugin], loader: { ".css": "text", ".worker.js": "text", }, }, { entry: [ "./src/index.ts", "./src/install-hook.ts", "./src/core/all-environments.ts", "./src/lite/index.ts", ], banner: { js: banner, }, outDir: DIST_PATH, splitting: false, clean: false, sourcemap: false, format: ["cjs", "esm"], target: "es2019", platform: "browser", // FIXME: tree shaking removes use client directive // Info: vercel analytics does the same thing- https://github.com/vercel/analytics/blob/main/packages/web/tsup.config.js treeshake: false, dts: true, watch: process.env.NODE_ENV === "development", async onSuccess() { await addDirectivesToChunkFiles(DIST_PATH); await addDirectivesToChunkFiles(path.join(DIST_PATH, "lite")); await addDirectivesToChunkFiles(path.join(DIST_PATH, "core")); }, minify: false, env: { NODE_ENV: process.env.NODE_ENV ?? "development", NPM_PACKAGE_VERSION: JSON.parse( fs.readFileSync(path.join(__dirname, "../scan", "package.json"), "utf8"), ).version, }, external: [ "react", "react-dom", "next", "next/navigation", "react-router", "react-router-dom", "@remix-run/react", "preact", "@preact/signals", ], loader: { ".css": "text", }, esbuildPlugins: [ workerPlugin, TsconfigPathsPlugin({ tsconfig: path.resolve(__dirname, "./tsconfig.json"), }), ], }, { entry: ["./src/cli.mts"], outDir: DIST_PATH, banner: { js: banner, }, splitting: false, clean: false, sourcemap: false, format: ["cjs"], target: "esnext", platform: "node", minify: false, env: { NODE_ENV: process.env.NODE_ENV ?? "development", NPM_PACKAGE_VERSION: JSON.parse( fs.readFileSync(path.join(__dirname, "../scan", "package.json"), "utf8"), ).version, }, watch: process.env.NODE_ENV === "development", }, { entry: [ "./src/react-component-name/index.ts", "./src/react-component-name/vite.ts", "./src/react-component-name/webpack.ts", "./src/react-component-name/esbuild.ts", "./src/react-component-name/rspack.ts", "./src/react-component-name/rolldown.ts", "./src/react-component-name/rollup.ts", "./src/react-component-name/astro.ts", "./src/react-component-name/loader.ts", ], outDir: `${DIST_PATH}/react-component-name`, splitting: false, sourcemap: false, clean: false, format: ["cjs", "esm"], target: "esnext", external: [ "unplugin", "estree-walker", "@rollup/pluginutils", "@babel/types", "@babel/parser", "@babel/traverse", "@babel/generator", "@babel/core", "rollup", "webpack", "esbuild", "rspack", "vite", ], dts: true, minify: false, treeshake: true, env: { NODE_ENV: process.env.NODE_ENV || "development", }, outExtension: ({ format }) => ({ js: format === "esm" ? ".mjs" : ".js", }), esbuildOptions: (options, context) => { options.mainFields = ["module", "main"]; options.conditions = ["import", "require", "node", "default"]; options.format = context.format === "esm" ? "esm" : "cjs"; options.preserveSymlinks = true; }, }, ]);