@sanity/ui-workshop
Version:
An environment for designing, reviewing, and quality-testing React components.
386 lines (379 loc) • 10.4 kB
JavaScript
import cpx from "cpx";
import { writeFile, readFile } from "fs/promises";
import path from "path";
import rimraf from "rimraf";
import { build as build$1, createServer } from "vite";
import { createRequire } from "node:module";
import fs, { accessSync } from "fs";
import globby from "globby";
import mkdirp from "mkdirp";
import react from "@vitejs/plugin-react";
import chokidar from "chokidar";
import express from "express";
function _fileExists(file) {
try {
return accessSync(file), !0;
} catch {
return !1;
}
}
const RUNTIME_FILE_NAMES = ["workshop.runtime.js", "workshop.runtime.jsx", "workshop.runtime.mjs", "workshop.runtime.cjs", "workshop.runtime.ts", "workshop.runtime.tsx"];
function _findRuntimeFile(options) {
const {
packagePath
} = options;
for (const f of RUNTIME_FILE_NAMES) {
const file = path.resolve(packagePath, f);
if (_fileExists(file)) return file;
}
}
const require2 = createRequire(import.meta.url);
async function _loadRuntime(options) {
const {
packagePath
} = options, configPath = _findRuntimeFile({
packagePath
});
if (!configPath)
return;
const {
register
} = await import("esbuild-register/dist/node"), eslintOptions = {
// eslint options
jsx: "automatic",
jsxFactory: "createElement",
jsxFragment: "Fragment",
jsxImportSource: "react",
logLevel: "silent"
}, {
unregister
} = globalThis.__DEV__ ? {
unregister: () => {
}
} : register(eslintOptions), config = require2(configPath);
return unregister(), config?.default || config;
}
const DEFAULT_PATTERN = ["src/**/__workshop__/index.js", "src/**/__workshop__/index.jsx", "src/**/__workshop__/index.ts", "src/**/__workshop__/index.tsx"];
function _getFiles(options) {
const {
cwd,
pattern
} = options;
return {
subscribe(observer) {
return globby(pattern, {
cwd
}).then((files) => {
observer.next(files.map((f) => path.resolve(cwd, f)));
}), {
unsubscribe() {
}
};
}
};
}
function _sanitizeModulePath(modulePath) {
return modulePath.replace(/\.[^/.]+$/, "").replace(/\/index$/, "");
}
function _compileModule(paths) {
if (paths.length === 0)
return `// THIS FILE IS AUTO-GENERATED
export const scopes = []
`;
const sortedPaths = paths.sort(), imports = sortedPaths.map((p, idx) => `import _${idx} from '${_sanitizeModulePath(p)}'`).join(`
`), exports = sortedPaths.map((_p, idx) => ` _${idx}`).join(`,
`);
return ["// THIS FILE IS AUTO-GENERATED", imports, `export const scopes = [
${exports},
]`].join(`
`) + `
`;
}
const HTML$1 = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, viewport-fit=cover"
/>
<style>
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
-webkit-font-smoothing: antialiased;
}
html,
body,
#root {
height: 100%;
margin: 0;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
if (window.parent !== window) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__
}
<\/script>
<script type="module" src="/.workshop/frame/main.tsx"><\/script>
</body>
</html>
`;
async function _writeFrameHTML(options) {
await writeFile(path.resolve(options.outDir, "frame/index.html"), HTML$1);
}
const SCRIPT$1 = `// THIS FILE IS AUTO-GENERATED
import {mountFrame} from '@sanity/ui-workshop'
import {scopes} from '../scopes'
import config from '../../workshop.config'
mountFrame({
config: {...config, scopes},
element: document.getElementById('root'),
})
`;
async function _writeFrameScript(options) {
await writeFile(path.resolve(options.outDir, "frame/main.tsx"), SCRIPT$1);
}
const HTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, viewport-fit=cover"
/>
<style>
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
-webkit-font-smoothing: antialiased;
}
html,
body,
#root {
height: 100%;
margin: 0;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/.workshop/main.tsx"><\/script>
</body>
</html>
`;
async function _writeHTML(options) {
await writeFile(path.resolve(options.outDir, "index.html"), HTML);
}
const SCRIPT = `// THIS FILE IS AUTO-GENERATED
import {mount} from '@sanity/ui-workshop'
import {scopes} from './scopes'
import config from '../workshop.config'
mount({
config: {...config, scopes},
element: document.getElementById('root'),
})
`;
async function _writeScript(options) {
await writeFile(path.resolve(options.outDir, "main.tsx"), SCRIPT);
}
async function buildStaticFiles(options) {
const {
runtimeDir
} = options;
await mkdirp(runtimeDir), await _writeHTML({
outDir: runtimeDir
}), await _writeScript({
outDir: runtimeDir
}), await mkdirp(path.resolve(runtimeDir, "frame")), await _writeFrameHTML({
outDir: runtimeDir
}), await _writeFrameScript({
outDir: runtimeDir
});
}
function createViteConfig(options) {
const {
cwd,
outDir,
runtimeDir
} = options;
return {
build: {
outDir,
rollupOptions: {
input: {
main: path.resolve(runtimeDir, "index.html"),
frame: path.resolve(runtimeDir, "frame/index.html")
}
}
},
optimizeDeps: {
esbuildOptions: {
jsx: "automatic"
}
},
plugins: [react({
babel: {
plugins: [["babel-plugin-react-compiler", {
target: "19"
}]]
}
})],
root: cwd
};
}
function getScopes(options) {
return new Promise((resolve) => {
_getFiles(options).subscribe({
next: resolve
});
});
}
async function build(options) {
const {
cwd
} = options, runtime = await _loadRuntime({
packagePath: cwd
}), runtimeDir = path.resolve(cwd, ".workshop"), outDir = runtime?.build?.outDir || path.resolve(cwd, "dist");
await buildStaticFiles({
runtimeDir
});
const relativeScopes = (await getScopes({
cwd,
pattern: runtime?.pattern || DEFAULT_PATTERN
})).map((f) => path.relative(outDir, f)), code = _compileModule(relativeScopes);
await writeFile(path.resolve(runtimeDir, "scopes.ts"), code);
const baseViteConfig = createViteConfig({
cwd,
outDir,
runtimeDir
});
let viteConfig = runtime?.vite?.(baseViteConfig) || baseViteConfig;
typeof viteConfig == "object" && "then" in viteConfig && (viteConfig = await viteConfig), await build$1(viteConfig), cpx.copySync(path.resolve(outDir, ".workshop", "**/*"), outDir), await rimraf(path.resolve(outDir, ".workshop"));
}
function _watchFiles(options) {
const {
cwd,
pattern
} = options;
return {
subscribe(observer) {
const watcher = chokidar.watch(pattern, {
cwd,
ignoreInitial: !0
});
return watcher.on("all", (event, file) => {
observer.next({
type: event,
file: path.resolve(cwd, file)
});
}), {
unsubscribe() {
watcher.close();
}
};
}
};
}
function _watchScopes(options) {
return {
subscribe(observer) {
const initialFiles$ = _getFiles(options), fileEvent$ = _watchFiles(options);
let files;
initialFiles$.subscribe({
next(initialFiles) {
files = initialFiles, observer.next(files);
}
});
const fileEventSub = fileEvent$.subscribe({
next(event) {
if (event.type === "add" && (files.push(event.file), observer.next(files)), event.type === "unlink") {
const idx = files.indexOf(event.file);
idx && (files.splice(idx, 1), observer.next(files));
}
}
});
return {
unsubscribe() {
fileEventSub.unsubscribe();
}
};
}
};
}
async function createDevServer(options) {
const {
cwd,
outDir,
runtime,
runtimeDir
} = options, baseViteConfig = {
...createViteConfig({
cwd,
outDir,
runtimeDir
}),
appType: "custom",
// don't include HTML middlewares
configFile: !1,
logLevel: "info",
server: {
middlewareMode: !0
}
};
let viteConfig = runtime?.vite?.(baseViteConfig) || baseViteConfig;
typeof viteConfig == "object" && "then" in viteConfig && (viteConfig = await viteConfig);
const vite = await createServer(viteConfig), app = express();
return app.use(vite.middlewares), app.use("*all", async (req, res) => {
const url = req.originalUrl;
let htmlPath = "index.html";
url.includes("/frame/") && (htmlPath = "frame/index.html");
try {
const template = await readFile(path.resolve(runtimeDir, htmlPath), "utf-8"), html = await vite.transformIndexHtml(url, template);
res.status(200).set({
"Content-Type": "text/html"
}).send(html);
} catch (e) {
e instanceof Error ? (vite.ssrFixStacktrace(e), console.log(e.stack), res.status(500).end(e.stack)) : res.status(500).end(String(e));
}
}), app;
}
async function dev(options) {
const {
cwd
} = options, runtime = await _loadRuntime({
packagePath: cwd
}), runtimeDir = path.resolve(cwd, ".workshop"), outDir = path.resolve(cwd, "dist");
await buildStaticFiles({
runtimeDir
});
const scopesSub = _watchScopes({
cwd,
pattern: runtime?.pattern || DEFAULT_PATTERN
}).subscribe({
next: (scopes) => {
const relativeScopes = scopes.map((f) => path.relative(runtimeDir, f)), code = _compileModule(relativeScopes);
fs.writeFileSync(path.resolve(runtimeDir, "scopes.ts"), code);
}
}), app = await createDevServer({
cwd,
outDir,
runtime,
runtimeDir
}), port = runtime?.server?.port || 1337;
app.listen(port, () => {
console.log(`listening on http://localhost:${port}`);
}).on("close", () => {
console.log("server closed"), scopesSub.unsubscribe(), process.exit(1);
});
}
export {
build,
dev
};
//# sourceMappingURL=runtime.js.map