diff --git a/.quixos-subtree-source.json b/.quixos-subtree-source.json index 055b4c4..f8234fd 100644 --- a/.quixos-subtree-source.json +++ b/.quixos-subtree-source.json @@ -1,7 +1,7 @@ { "version": 1, "sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos", - "sourceCommit": "7fd5e15105cef21d2b1c3da860c53c5033f66256", + "sourceCommit": "fc13e9b8411c2210b12db5e558e2b4fa7785505e", "sourcePath": "quixos-instance/quixos-nix-helpers", "exportName": "quixos-nix-helpers", "mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git" diff --git a/browser-source.mjs b/browser-source.mjs new file mode 100644 index 0000000..e07ed5e --- /dev/null +++ b/browser-source.mjs @@ -0,0 +1,34 @@ +import {createRequire} from "node:module"; +import path from "node:path"; + +// Resolve build dependencies from the package's locked node_modules, not from +// the helper's own Nix-store location. This program runs only during the build. +const require = createRequire(path.join(process.cwd(), "package.json")); +const {build} = require("esbuild"); +const options = JSON.parse(process.argv[2]); +const platform = new Map([ + ["react", "/__quixos/platform/react/v18.mjs"], + ["react/jsx-runtime", "/__quixos/platform/react-jsx-runtime/v18.mjs"], + ["react/jsx-dev-runtime", "/__quixos/platform/react-jsx-dev-runtime/v18.mjs"], + ["@quixos/web-studio-react-runtime", "/__quixos/platform/web-studio-react-runtime/v1.mjs"], +]); +await build({...options, plugins: [{name: "quixos-browser-source", setup(api) { + api.onResolve({filter: /\?browser-source$/}, ({path: specifier, resolveDir}) => { + if (!specifier.startsWith("./") && !specifier.startsWith("../")) throw new Error("Browser source imports must name a relative compiled module"); + return {path: path.resolve(resolveDir, specifier.slice(0, -"?browser-source".length)), namespace: "browser-source"}; + }); + api.onLoad({filter: /.*/, namespace: "browser-source"}, async ({path: entry}) => { + const browser = await build({entryPoints: [entry], bundle: true, write: false, outfile: "component.mjs", + format: "esm", platform: "browser", target: "es2022", metafile: true, + plugins: [{name: "quixos-platform", setup(browserApi) { + browserApi.onResolve({filter: /.*/}, ({path: name}) => platform.has(name) ? {path: platform.get(name), external: true} : undefined); + }}]}); + const js = browser.outputFiles.find(file => file.path.endsWith(".mjs")); + if (!js) throw new Error("Browser source build produced no JavaScript"); + const css = browser.outputFiles.find(file => file.path.endsWith(".css")); + if (css && Object.values(browser.metafile.outputs).some(output => output.exports.includes("styles"))) throw new Error("Use either imported CSS or an explicit styles export, not both"); + if (browser.outputFiles.some(file => !file.path.endsWith(".mjs") && !file.path.endsWith(".css"))) throw new Error("Browser assets must be embedded, not emitted as unserved files"); + const source = js.text + (css ? `\nexport const styles = ${JSON.stringify(css.text)};\n` : ""); + return {contents: `export default ${JSON.stringify(source)};`, loader: "js", watchFiles: Object.keys(browser.metafile.inputs)}; + }); +}}]}); diff --git a/quixos-package-helpers.nix b/quixos-package-helpers.nix index 23c9162..b4a5fe7 100644 --- a/quixos-package-helpers.nix +++ b/quixos-package-helpers.nix @@ -539,7 +539,6 @@ EOF # An exact, compiler-produced BindingSchema JSON artifact and a backend. # Other language helpers can consume the same schema with their own generator/runtime. bindings ? null, - bindingOptions ? { }, # A dedicated entrypoint calling SDK serveMigration; never start the # normal package server in the isolated migration execution boundary. migrationEntrypoint ? null, @@ -593,14 +592,19 @@ EOF resources = bindingConfig.resources or [ ]; }); bindingOutput = if bindingConfig == null then "src/gen/qx.ts" else bindingConfig.output or "src/gen/qx.ts"; + authoringCheck = builtins.fromJSON (builtins.readFile "${packageRoot}/quixos.check.json"); bindingOptionsFile = if bindingConfig == null then null else - pkgs.writeText "qx-typescript-options.json" (builtins.toJSON (bindingConfig.options or bindingOptions)); + pkgs.writeText "qx-typescript-options.json" (builtins.toJSON (authoringCheck.options or { })); bindingCommand = if bindingConfig == null then "" else '' mkdir -p ${lib.escapeShellArg (dirOf bindingOutput)} ${bindingConfig.generator}/bin/quixos-codegen-ts \ ${lib.escapeShellArg (toString bindingSchema)} \ ${lib.escapeShellArg bindingConfig.packageRevisionId} \ ${lib.escapeShellArg bindingOutput} ${bindingOptionsFile} + ${lib.optionalString (installServer != null && descriptorPath != null) '' + ${bindingConfig.generator}/bin/quixos-qx package-descriptor ${lib.escapeShellArg (toString bindingSchema)} \ + ${lib.escapeShellArg bindingConfig.packageRevisionId} > ${lib.escapeShellArg descriptorPath} + ''} ''; generateBindings = pkgs.writeShellApplication { name = "qx-generate-bindings"; @@ -625,7 +629,14 @@ EOF bundleCommand = if bundle == null then "" - else '' + else if bundleConfig.browserSources or false then '' + ${nodejs}/bin/node ${./browser-source.mjs} ${lib.escapeShellArg (builtins.toJSON { + entryPoints = [ bundleConfig.entry ]; bundle = true; platform = bundlePlatform; + target = bundleTarget; format = bundleFormat; alias = bundleAliases; + preserveSymlinks = bundleConfig.preserveSymlinks or true; + banner.js = bundleBanner; outfile = bundleOutfile; + })} + '' else '' esbuild ${lib.escapeShellArg bundleConfig.entry} \ --bundle \ --platform=${bundlePlatform} \ @@ -693,6 +704,7 @@ EOF runHook preBuild ${exportsFor (callOption buildEnv)} ${bindingCommand} + ${lib.optionalString (bindingConfig != null && bundle != null) "${bindingConfig.generator}/bin/quixos-qx bundle-policy src"} ${lib.optionalString (bindingConfig != null) "yarn exec tsc --noEmit"} ${lib.optionalString (bindingConfig != null) "cp ${lib.escapeShellArg bindingOutput} .qx-checked-bindings"} ${buildCommand}