From 0f8b28b5b91120f36b5d6cd0947bddf2ccb9256e Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Wed, 20 Dec 2023 16:23:45 -0300 Subject: [PATCH] Revert "use vite for web builds (#58228)" (#59132) This reverts commit 86180de04a599ce847540badeb0e5dacf116c83b. --- client/build-config/BUILD.bazel | 1 + .../build-config/src/esbuild/monacoPlugin.ts | 76 ++++++ client/build-config/src/esbuild/plugins.ts | 1 + client/web/BUILD.bazel | 63 +++-- client/web/README.md | 10 + client/web/bundlesize.config.js | 6 +- client/web/dev/BUILD.bazel | 80 +++++-- .../esbuild/bazel/esbuild.bazel.production.js | 18 ++ client/web/dev/esbuild/build.ts | 52 ++++ client/web/dev/esbuild/config.ts | 106 +++++++++ client/web/dev/esbuild/manifest.test.ts | 66 +++++ client/web/dev/esbuild/manifest.ts | 90 +++++++ client/web/dev/esbuild/manifestPlugin.ts | 50 ++++ client/web/dev/esbuild/server.ts | 101 ++++++++ client/web/dev/server/devProxyServer.ts | 56 ----- client/web/dev/server/development.server.ts | 41 ++++ client/web/dev/server/production.server.ts | 64 +++++ client/web/dev/tsconfig.json | 2 +- client/web/dev/utils/constants.ts | 5 +- .../get-api-proxy-settings.ts} | 19 +- client/web/dev/utils/get-index-html.ts | 26 +- client/web/dev/utils/index.ts | 7 + client/web/dev/utils/webBuildManifest.ts | 19 -- client/web/dev/vite/manifestPlugin.ts | 159 ------------- client/web/dev/vite/vite.config.ts | 128 ---------- client/web/dist/assets.go | 17 +- client/web/package.json | 7 +- .../devsettings/DeveloperDialog.module.scss | 4 +- .../detail/BatchChangeBurndownChart.tsx | 4 - client/web/src/enterprise/main.tsx | 6 + client/web/src/integration/context.ts | 2 +- .../opentelemetry/initOpenTelemetry.ts | 19 +- client/web/src/nav/NavBar/NavBar.module.scss | 2 +- .../web/src/settings/MonacoSettingsEditor.tsx | 28 +-- client/web/tsconfig.json | 1 - client/web/vite.config.ts | 3 - cmd/frontend/internal/app/ui/app.html | 5 +- cmd/frontend/internal/app/ui/handlers.go | 7 +- cmd/frontend/internal/app/ui/tmpl.go | 4 - dev/Caddyfile | 11 +- dev/esbuild.bzl | 19 ++ dev/vite/vite.bzl | 225 ------------------ .../background-information/sg/reference.md | 2 + package.json | 4 +- pnpm-lock.yaml | 150 ++++++------ sg.config.yaml | 26 +- ui/assets/dev.go | 2 +- ui/assets/manifest.go | 23 +- 48 files changed, 972 insertions(+), 845 deletions(-) create mode 100644 client/build-config/src/esbuild/monacoPlugin.ts create mode 100644 client/web/dev/esbuild/bazel/esbuild.bazel.production.js create mode 100644 client/web/dev/esbuild/build.ts create mode 100644 client/web/dev/esbuild/config.ts create mode 100644 client/web/dev/esbuild/manifest.test.ts create mode 100644 client/web/dev/esbuild/manifest.ts create mode 100644 client/web/dev/esbuild/manifestPlugin.ts create mode 100644 client/web/dev/esbuild/server.ts delete mode 100644 client/web/dev/server/devProxyServer.ts create mode 100644 client/web/dev/server/development.server.ts create mode 100644 client/web/dev/server/production.server.ts rename client/web/dev/{server/apiProxySettings.ts => utils/get-api-proxy-settings.ts} (92%) create mode 100644 client/web/dev/utils/index.ts delete mode 100644 client/web/dev/utils/webBuildManifest.ts delete mode 100644 client/web/dev/vite/manifestPlugin.ts delete mode 100644 client/web/dev/vite/vite.config.ts delete mode 100644 client/web/vite.config.ts create mode 100644 dev/esbuild.bzl delete mode 100644 dev/vite/vite.bzl diff --git a/client/build-config/BUILD.bazel b/client/build-config/BUILD.bazel index 3219a709a42..19d605baa86 100644 --- a/client/build-config/BUILD.bazel +++ b/client/build-config/BUILD.bazel @@ -22,6 +22,7 @@ ts_config( ts_project( name = "build-config_lib", srcs = [ + "src/esbuild/monacoPlugin.ts", "src/esbuild/packageResolutionPlugin.ts", "src/esbuild/plugins.ts", "src/esbuild/stylePlugin.ts", diff --git a/client/build-config/src/esbuild/monacoPlugin.ts b/client/build-config/src/esbuild/monacoPlugin.ts new file mode 100644 index 00000000000..ae92b9f21f8 --- /dev/null +++ b/client/build-config/src/esbuild/monacoPlugin.ts @@ -0,0 +1,76 @@ +import path from 'path' + +import * as esbuild from 'esbuild' +// eslint-disable-next-line no-restricted-imports +import { type EditorFeature, featuresArr } from 'monaco-editor-webpack-plugin/out/features' +// eslint-disable-next-line no-restricted-imports +import { type EditorLanguage, languagesArr } from 'monaco-editor-webpack-plugin/out/languages' + +import type { MONACO_LANGUAGES_AND_FEATURES } from '../monaco-editor' +import { ROOT_PATH } from '../paths' + +const monacoModulePath = (modulePath: string): string => + require.resolve(path.join('monaco-editor/esm', modulePath), { + paths: [path.join(ROOT_PATH, 'node_modules')], + }) + +/** + * An esbuild plugin that omits some unneeded features and languages from monaco-editor when + * bundling, to reduce bundle size and speed up builds. Similar to + * https://github.com/microsoft/monaco-editor-webpack-plugin. + */ +export const monacoPlugin = ({ + languages, + features, +}: Required): esbuild.Plugin => ({ + name: 'monaco', + setup: build => { + for (const feature of features) { + if (feature.startsWith('!')) { + throw new Error('negated features (starting with "!") are not supported') + } + } + + // Some feature exclusions don't work because their module exports a symbol needed by + // another feature. + const ALWAYS_ENABLED_FEATURES = new Set(['snippets']) + + const skipLanguageModules = languagesArr + .filter(({ label }) => !languages.includes(label as EditorLanguage)) + .flatMap(({ entry }) => entry || []) + const skipFeatureModules = featuresArr + .filter( + ({ label }) => + !features.includes(label as EditorFeature) && !ALWAYS_ENABLED_FEATURES.has(label as EditorFeature) + ) + .flatMap(({ entry }) => entry || []) + + const skipModulePaths = [...skipLanguageModules, ...skipFeatureModules].map(monacoModulePath) + const filter = new RegExp(`^(${skipModulePaths.join('|')})$`) + + // For omitted features and languages, treat their modules as empty files. + // + // TODO(sqs): This is different from how + // https://github.com/microsoft/monaco-editor-webpack-plugin does it. The + // monaco-editor-webpack-plugin approach relies on injecting a different central module + // file, rather than zeroing out each feature/language module. Our approach necessitates the + // ALWAYS_ENABLED_FEATURES hack above. + build.onLoad({ filter }, () => ({ contents: '', loader: 'js' })) + }, +}) + +// TODO(sqs): These Monaco Web Workers could be built as part of the main build if we switch to +// using MonacoEnvironment#getWorker (from #getWorkerUrl), which would then let us use the worker +// plugin (and in Webpack the worker-loader) to load these instead of needing to hardcode them as +// build entrypoints. +export const buildMonaco = async (outdir: string): Promise => + esbuild.context({ + entryPoints: { + 'scripts/editor.worker.bundle': 'monaco-editor/esm/vs/editor/editor.worker.js', + 'scripts/json.worker.bundle': 'monaco-editor/esm/vs/language/json/json.worker.js', + }, + format: 'iife', + target: 'es2021', + bundle: true, + outdir, + }) diff --git a/client/build-config/src/esbuild/plugins.ts b/client/build-config/src/esbuild/plugins.ts index 6e411f10076..2fd16814639 100644 --- a/client/build-config/src/esbuild/plugins.ts +++ b/client/build-config/src/esbuild/plugins.ts @@ -1,5 +1,6 @@ import type * as esbuild from 'esbuild' +export * from './monacoPlugin' export * from './packageResolutionPlugin' export * from './stylePlugin' export * from './workerPlugin' diff --git a/client/web/BUILD.bazel b/client/web/BUILD.bazel index baf6dcdf2d0..9cf5989b83a 100644 --- a/client/web/BUILD.bazel +++ b/client/web/BUILD.bazel @@ -1,13 +1,14 @@ -load("@aspect_rules_js//js:defs.bzl", "js_library") -load("@aspect_rules_ts//ts:defs.bzl", "ts_config") load("@bazel_skylib//rules:build_test.bzl", "build_test") -load("@npm//:bundlesize2/package_json.bzl", bundlesize_bin = "bin") +load("@aspect_rules_js//js:defs.bzl", "js_library") load("@npm//:defs.bzl", "npm_link_all_packages") +load("@npm//:bundlesize2/package_json.bzl", bundlesize_bin = "bin") load("//client/shared/dev:generate_graphql_operations.bzl", "generate_graphql_operations") load("//client/shared/dev:tools.bzl", "module_style_typings") load("//dev:defs.bzl", "npm_package", "sass", "ts_project", "vitest_test") +load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild") +load("//dev:esbuild.bzl", "esbuild_web_app") +load("@aspect_rules_ts//ts:defs.bzl", "ts_config") load("//dev:eslint.bzl", "eslint_config_and_lint_root", "eslint_test_with_types") -load("//dev:vite/vite.bzl", "vite_project", "vite_web_app") # TODO(bazel): storybook build # gazelle:exclude **/*.story.{ts,tsx} @@ -1823,7 +1824,6 @@ ts_project( "//:node_modules/copy-to-clipboard", "//:node_modules/d3-time-format", "//:node_modules/date-fns", - "//:node_modules/events", "//:node_modules/fast-json-stable-stringify", "//:node_modules/focus-visible", "//:node_modules/fzf", @@ -2096,11 +2096,22 @@ vitest_test( ], ) -VITE_PROJECT_DEPS = [ +# esbuild dev environment ------------------- +ESBUILD_CONFIG_DEPS = [ ":node_modules/@sourcegraph/build-config", - "//:node_modules/open-color", + "//:node_modules/buffer", + "//:node_modules/lodash", + "//:node_modules/path-browserify", + "//:node_modules/punycode", + "//:node_modules/util", + "//:node_modules/events", + "//:node_modules/monaco-editor", + "//:node_modules/esbuild", + "//:node_modules/react-visibility-sensor", # required for https://github.com/joshwnj/react-visibility-sensor/issues/148 workaround "//:postcss_config_js", - "//client/web/dev", + + # HACKS: bundle-time css import + "//:node_modules/open-color", ] # Bundled data which was replaced with *.d.ts files at ts compiletime @@ -2111,40 +2122,50 @@ BUNDLE_DATA_DEPS = [ # TODO(bazel): this is already in the main ts_project(srcs). Why also here? "src/enterprise/codeintel/configuration/schema.json", ":enterprise-yaml", + "//client/web/dev:esbuild-config-production_bundle", ] -vite_project( +esbuild( name = "bundle", srcs = BUNDLE_DATA_DEPS + [ ":web_lib", "//:package_json", ], - config = "//client/web/dev:vite-config", - data = BUNDLE_DATA_DEPS, + config = "//client/web/dev:esbuild-config-production_bundle", + entry_points = [ + "src/enterprise/main.js", + "src/enterprise/embed/embedMain.js", + ], + minify = True, + sourcemap = "linked", + splitting = True, tags = ["exclusive"], - tsconfig = ":tsconfig", - visibility = ["//visibility:public"], - deps = VITE_PROJECT_DEPS, + visibility = ["//client/web/dist:__subpackages__"], + deps = ESBUILD_CONFIG_DEPS, ) # Used for integration tests. -vite_web_app( +esbuild_web_app( name = "app", srcs = BUNDLE_DATA_DEPS + [ ":web_lib", "//:package_json", ], - config = "//client/web/dev:vite-config", - env = { - "INTEGRATION_TESTS": "true", + config = "//client/web/dev:esbuild-config-production_bundle", + define = { + "process.env.INTEGRATION_TESTS": "true", }, - tsconfig = ":tsconfig", + entry_points = [ + "src/enterprise/main.js", + "src/enterprise/embed/embedMain.js", + ], + sourcemap = "linked", visibility = ["//visibility:public"], - deps = VITE_PROJECT_DEPS, + deps = ESBUILD_CONFIG_DEPS, ) build_test( - name = "vite_test", + name = "esbuild_test", size = "enormous", targets = [ ":bundle", diff --git a/client/web/README.md b/client/web/README.md index 6eb92b98a1f..dba2daefdc3 100644 --- a/client/web/README.md +++ b/client/web/README.md @@ -29,6 +29,16 @@ To use a public API that doesn't require authentication for most of the function SOURCEGRAPH_API_URL=https://sourcegraph.com sg start web-standalone ``` +### Production server + +```sh +sg start web-standalone-prod +``` + +Web app should be available at `https://${SOURCEGRAPH_HTTPS_DOMAIN}:${SOURCEGRAPH_HTTPS_PORT}`. Build artifacts will be served from `/client/web/dist`. + +Note: If you are unable to use the above commands (e.g. you can't install Caddy), you can use `sg run web-standalone-http` instead. This will start a development server using only Node, and will be available at `http://localhost:${SOURCEGRAPH_HTTP_PORT}`. + ### API proxy In both environments, server proxies API requests to `SOURCEGRAPH_API_URL` provided as the `.env` variable. diff --git a/client/web/bundlesize.config.js b/client/web/bundlesize.config.js index 1d1efd8a965..b82db41690c 100644 --- a/client/web/bundlesize.config.js +++ b/client/web/bundlesize.config.js @@ -1,6 +1,6 @@ const path = require('path') -const STATIC_ASSETS_PATH = path.join(process.env.WEB_BUNDLE_PATH || path.join(__dirname, 'dist'), 'assets') +const STATIC_ASSETS_PATH = process.env.WEB_BUNDLE_PATH || path.join(__dirname, 'dist') const config = { files: [ @@ -21,7 +21,7 @@ const config = { maxSize: '155kb', }, { - path: path.join(STATIC_ASSETS_PATH, '*.js'), + path: path.join(STATIC_ASSETS_PATH, 'chunks/chunk-*.js'), maxSize: '600kb', // 2 monaco chunks are very big }, /** @@ -39,7 +39,7 @@ const config = { maxSize: '350kb', }, { - path: path.join(STATIC_ASSETS_PATH, '*.css'), + path: path.join(STATIC_ASSETS_PATH, 'chunks/chunk-*.css'), maxSize: '45kb', }, ], diff --git a/client/web/dev/BUILD.bazel b/client/web/dev/BUILD.bazel index 3bb53c57ddb..aa11615cced 100644 --- a/client/web/dev/BUILD.bazel +++ b/client/web/dev/BUILD.bazel @@ -1,4 +1,5 @@ load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild") +load("@aspect_rules_js//js:defs.bzl", "js_library") load("@aspect_rules_ts//ts:defs.bzl", "ts_config") load("//dev:defs.bzl", "ts_project", "vitest_test") @@ -18,67 +19,104 @@ ts_config( ts_project( name = "dev", srcs = [ - "server/apiProxySettings.ts", - "server/devProxyServer.ts", + "esbuild/build.ts", + "esbuild/config.ts", + "esbuild/manifest.ts", + "esbuild/manifestPlugin.ts", + "esbuild/server.ts", + "server/development.server.ts", + "server/production.server.ts", "utils/constants.ts", "utils/create-js-context.ts", "utils/environment-config.ts", + "utils/get-api-proxy-settings.ts", "utils/get-index-html.ts", "utils/get-site-config.ts", + "utils/index.ts", "utils/should-compress-response.ts", "utils/success-banner.ts", - "utils/webBuildManifest.ts", - "vite/manifestPlugin.ts", - "vite/vite.config.ts", ], tsconfig = ":tsconfig", visibility = ["//client/web:__subpackages__"], deps = [ + "//:node_modules/@sentry/esbuild-plugin", "//:node_modules/@types/compression", + "//:node_modules/@types/connect-history-api-fallback", "//:node_modules/@types/express", "//:node_modules/@types/lodash", "//:node_modules/@types/node", "//:node_modules/@types/signale", - "//:node_modules/@vitejs/plugin-react", "//:node_modules/chalk", "//:node_modules/compression", + "//:node_modules/connect-history-api-fallback", + "//:node_modules/esbuild", "//:node_modules/express", + "//:node_modules/express-static-gzip", "//:node_modules/http-proxy-middleware", "//:node_modules/jsonc-parser", "//:node_modules/lodash", "//:node_modules/signale", - "//:node_modules/vite", "//client/web:node_modules/@sourcegraph/build-config", "//client/web:web_lib", ], ) -esbuild( - name = "vite-config", - srcs = [ - ":dev", +js_library( + name = "esbuild-config-production", + srcs = ["esbuild/bazel/esbuild.bazel.production.js"], + deps = [ + "//client/build-config:build-config_lib", + "//client/web:node_modules/@sourcegraph/build-config", + "//client/web/dev", ], - entry_point = "vite/vite.config.js", +) + +esbuild( + name = "esbuild-config-production_bundle", + srcs = [ + ":esbuild-config-production", + ], + entry_point = "esbuild/bazel/esbuild.bazel.production.js", external = [ - # suppress vite require-resolve-not-external warnings - "lightningcss", "fsevents", + "pnpapi", + "../../../../postcss.config", + + # suppress esbuild require-resolve-not-external warnings "esbuild", - "@vitejs/plugin-react", - "vite", "path-browserify", + "monaco-yaml/lib/esm/monaco.contribution", + "monaco-yaml/lib/esm/yaml.worker", + "rxjs/_esm5/internal/OuterSubscriber", + "rxjs/_esm5/internal/util/subscribeToResult", + "rxjs/_esm5/internal/util/subscribeToArray", + "rxjs/_esm5/internal/Observable", ], format = "cjs", platform = "node", sourcemap = False, splitting = False, target = "es2022", - visibility = ["//client/web:__subpackages__"], + visibility = ["//client:__subpackages__"], deps = [ - "//:node_modules/@vitejs/plugin-react", - "//:node_modules/esbuild", - "//:node_modules/path-browserify", - "//:node_modules/vite", "//:postcss_config_js", ], ) + +ts_project( + name = "dev_tests", + testonly = True, + srcs = ["esbuild/manifest.test.ts"], + tsconfig = ":tsconfig", + deps = [ + ":dev", + "//:node_modules/vitest", + ], +) + +vitest_test( + name = "test", + data = [ + ":dev_tests", + ], +) diff --git a/client/web/dev/esbuild/bazel/esbuild.bazel.production.js b/client/web/dev/esbuild/bazel/esbuild.bazel.production.js new file mode 100644 index 00000000000..0a0405d5ae3 --- /dev/null +++ b/client/web/dev/esbuild/bazel/esbuild.bazel.production.js @@ -0,0 +1,18 @@ +// This file is only used by Bazel production builds. + +process.env.NODE_ENV = 'production' + +const { esbuildBuildOptions } = require('../config.js') +const { ENVIRONMENT_CONFIG } = require('../../utils/environment-config.js') + +module.exports = { + ...esbuildBuildOptions(ENVIRONMENT_CONFIG), + + // Unset configuration properties that are provided by Bazel. + entryPoints: undefined, + bundle: undefined, + outdir: undefined, + sourcemap: undefined, + splitting: undefined, + external: undefined, +} diff --git a/client/web/dev/esbuild/build.ts b/client/web/dev/esbuild/build.ts new file mode 100644 index 00000000000..cc3ae51162a --- /dev/null +++ b/client/web/dev/esbuild/build.ts @@ -0,0 +1,52 @@ +import { writeFileSync } from 'fs' + +import * as esbuild from 'esbuild' + +import { buildMonaco } from '@sourcegraph/build-config/src/esbuild/plugins' + +import { ENVIRONMENT_CONFIG } from '../utils' + +import { esbuildBuildOptions } from './config' + +export async function build(): Promise { + const buildOptions = esbuildBuildOptions(ENVIRONMENT_CONFIG) + + if (!buildOptions.outdir) { + throw new Error('no outdir') + } + + const metafile = process.env.ESBUILD_METAFILE + const options: esbuild.BuildOptions = { + ...buildOptions, + metafile: Boolean(metafile), + } + const result = await esbuild.build(options) + if (metafile) { + writeFileSync(metafile, JSON.stringify(result.metafile), 'utf-8') + } + if (!ENVIRONMENT_CONFIG.DEV_WEB_BUILDER_OMIT_SLOW_DEPS) { + const ctx = await buildMonaco(buildOptions.outdir) + await ctx.rebuild() + await ctx.dispose() + } + + if (process.env.WATCH) { + const ctx = await esbuild.context(options) + await ctx.watch() + await new Promise(() => {}) // wait forever + } +} + +if (require.main === module) { + async function main(args: string[]): Promise { + if (args.length !== 0) { + throw new Error('Usage: (no options)') + } + await build() + } + // eslint-disable-next-line unicorn/prefer-top-level-await + main(process.argv.slice(2)).catch(error => { + console.error(error) + process.exit(1) + }) +} diff --git a/client/web/dev/esbuild/config.ts b/client/web/dev/esbuild/config.ts new file mode 100644 index 00000000000..2968eeb6dfe --- /dev/null +++ b/client/web/dev/esbuild/config.ts @@ -0,0 +1,106 @@ +import path from 'path' + +import { sentryEsbuildPlugin } from '@sentry/esbuild-plugin' +import type * as esbuild from 'esbuild' + +import { ROOT_PATH, STATIC_ASSETS_PATH } from '@sourcegraph/build-config' +import { + stylePlugin, + packageResolutionPlugin, + monacoPlugin, + RXJS_RESOLUTIONS, + buildTimerPlugin, +} from '@sourcegraph/build-config/src/esbuild/plugins' +import { MONACO_LANGUAGES_AND_FEATURES } from '@sourcegraph/build-config/src/monaco-editor' + +import type { EnvironmentConfig } from '../utils' + +import { manifestPlugin } from './manifestPlugin' + +/** + * Creates esbuild build options for the client/web app. + */ +export function esbuildBuildOptions(ENVIRONMENT_CONFIG: EnvironmentConfig): esbuild.BuildOptions { + return { + entryPoints: ENVIRONMENT_CONFIG.CODY_APP + ? [path.join(ROOT_PATH, 'client/web/src/enterprise/app/main.tsx')] + : [ + path.join(ROOT_PATH, 'client/web/src/enterprise/main.tsx'), + path.join(ROOT_PATH, 'client/web/src/enterprise/embed/embedMain.tsx'), + ], + bundle: true, + minify: ENVIRONMENT_CONFIG.NODE_ENV === 'production', + treeShaking: true, + + format: 'esm', + logLevel: 'error', + jsx: 'automatic', + jsxDev: ENVIRONMENT_CONFIG.NODE_ENV === 'development', + splitting: !ENVIRONMENT_CONFIG.DEV_WEB_BUILDER_NO_SPLITTING, + chunkNames: 'chunks/chunk-[name]-[hash]', + entryNames: '[name]-[hash]', + outdir: STATIC_ASSETS_PATH, + plugins: [ + stylePlugin, + manifestPlugin, + packageResolutionPlugin({ + path: require.resolve('path-browserify'), + ...RXJS_RESOLUTIONS, + ...(ENVIRONMENT_CONFIG.DEV_WEB_BUILDER_OMIT_SLOW_DEPS + ? { + // Monaco + '@sourcegraph/shared/src/components/MonacoEditor': + '@sourcegraph/shared/src/components/NoMonacoEditor', + 'monaco-editor': '/dev/null', + 'monaco-editor/esm/vs/editor/editor.api': '/dev/null', + 'monaco-yaml': '/dev/null', + + // GraphiQL + './api/ApiConsole': path.join(ROOT_PATH, 'client/web/src/api/NoApiConsole.tsx'), + '@graphiql/react': '/dev/null', + graphiql: '/dev/null', + + // Misc. + recharts: '/dev/null', + + // TODO(sqs): force use of same version when developing on opencodegraph because `pnpm link` breaks + '@codemirror/state': path.join(ROOT_PATH, 'node_modules/@codemirror/state'), + '@codemirror/view': path.join(ROOT_PATH, 'node_modules/@codemirror/view'), + react: path.join(ROOT_PATH, 'node_modules/react'), + 'react-dom': path.join(ROOT_PATH, 'node_modules/react-dom'), + 'react-dom/client': path.join(ROOT_PATH, 'node_modules/react-dom/client'), + } + : null), + }), + ENVIRONMENT_CONFIG.DEV_WEB_BUILDER_OMIT_SLOW_DEPS ? null : monacoPlugin(MONACO_LANGUAGES_AND_FEATURES), + buildTimerPlugin, + ENVIRONMENT_CONFIG.SENTRY_UPLOAD_SOURCE_MAPS + ? sentryEsbuildPlugin({ + org: ENVIRONMENT_CONFIG.SENTRY_ORGANIZATION, + project: ENVIRONMENT_CONFIG.SENTRY_PROJECT, + authToken: ENVIRONMENT_CONFIG.SENTRY_DOT_COM_AUTH_TOKEN, + silent: true, + release: { name: `frontend@${ENVIRONMENT_CONFIG.VERSION}` }, + sourcemaps: { assets: [path.join('dist', '*.map'), path.join('dist', 'chunks', '*.map')] }, + }) + : null, + ].filter((plugin): plugin is esbuild.Plugin => plugin !== null), + define: { + ...Object.fromEntries( + Object.entries({ ...ENVIRONMENT_CONFIG, SOURCEGRAPH_API_URL: undefined }).map(([key, value]) => [ + `process.env.${key}`, + JSON.stringify(value === undefined ? null : value), + ]) + ), + global: 'window', + }, + loader: { + '.yaml': 'text', + '.ttf': 'file', + '.woff2': 'file', + '.png': 'file', + }, + target: 'esnext', + sourcemap: true, + } +} diff --git a/client/web/dev/esbuild/manifest.test.ts b/client/web/dev/esbuild/manifest.test.ts new file mode 100644 index 00000000000..da594827c56 --- /dev/null +++ b/client/web/dev/esbuild/manifest.test.ts @@ -0,0 +1,66 @@ +import { expect, describe, test } from 'vitest' + +import { type WebBuildManifest, createManifestFromBuildResult } from './manifest' + +describe('createManifestFromBuildResult', () => { + test('non-bazel', () => + expect( + createManifestFromBuildResult( + { entryPoints: ['src/enterprise/main.tsx', 'src/enterprise/embed/embedMain.tsx'], outdir: 'dist' }, + { + 'dist/main-AAA.js': { + entryPoint: 'src/enterprise/main.tsx', + cssBundle: 'dist/main-BBB.css', + imports: [], + exports: [], + inputs: {}, + bytes: 123, + }, + 'dist/embedMain-CCC.js': { + entryPoint: 'src/enterprise/embed/embedMain.tsx', + cssBundle: 'dist/embedMain-DDD.css', + imports: [], + exports: [], + inputs: {}, + bytes: 123, + }, + } + ) + ).toEqual({ + 'main.js': 'main-AAA.js', + 'main.css': 'main-BBB.css', + 'embed.js': 'embedMain-CCC.js', + 'embed.css': 'embedMain-DDD.css', + } satisfies WebBuildManifest)) + + test('bazel', () => + expect( + createManifestFromBuildResult( + { entryPoints: ['src/enterprise/main.js', 'src/enterprise/embed/embedMain.js'], outdir: 'dist' }, + { + 'client/web/bundle/main-AAA.js': { + entryPoint: 'src/enterprise/main.js', + cssBundle: 'client/web/bundle/main-BBB.css', + imports: [], + exports: [], + inputs: {}, + bytes: 123, + }, + 'client/web/bundle/embedMain-CCC.js': { + entryPoint: 'src/enterprise/embed/embedMain.js', + cssBundle: 'client/web/bundle/embedMain-DDD.css', + imports: [], + exports: [], + inputs: {}, + bytes: 123, + }, + }, + true + ) + ).toEqual({ + 'main.js': 'main-AAA.js', + 'main.css': 'main-BBB.css', + 'embed.js': 'embedMain-CCC.js', + 'embed.css': 'embedMain-DDD.css', + } satisfies WebBuildManifest)) +}) diff --git a/client/web/dev/esbuild/manifest.ts b/client/web/dev/esbuild/manifest.ts new file mode 100644 index 00000000000..81849a9887a --- /dev/null +++ b/client/web/dev/esbuild/manifest.ts @@ -0,0 +1,90 @@ +import path from 'path' + +import type * as esbuild from 'esbuild' + +export interface WebBuildManifest { + /** Main JS bundle. */ + 'main.js': string + + /** Main CSS bundle. */ + 'main.css': string + + /** Embed JS bundle. */ + 'embed.js': string + + /** Embed CSS bundle. */ + 'embed.css': string +} + +export const assetPathPrefix = '/.assets' + +export const WEB_BUILD_MANIFEST_FILENAME = 'web.manifest.json' + +/** + * Create a web manifest from esbuild build results. + * + * + * @param buildOptions The esbuild options + * @param outputs The esbuild metafile outputs + * @param inBazel Whether the build is running in Bazel + */ +export function createManifestFromBuildResult( + buildOptions: { entryPoints: string[]; outdir: string }, + outputs: esbuild.Metafile['outputs'], + inBazel = Boolean(process.env.BAZEL_BINDIR) +): WebBuildManifest { + const outdir = path.relative(process.cwd(), buildOptions.outdir) + + const assetPath = (filePath: string): string => { + if (inBazel) { + const BAZEL_PATH_PREFIX = /^client\/web\/(app_)?bundle\// + if (!BAZEL_PATH_PREFIX.test(filePath)) { + throw new Error(`expected filePath to match ${BAZEL_PATH_PREFIX}, got ${filePath}`) + } + return filePath.replace(BAZEL_PATH_PREFIX, '') + } + return path.relative(outdir, filePath) + } + + if (buildOptions.entryPoints.length !== 2) { + throw new Error('expected 2 entryPoints (main and embed)') + } + const [mainEntrypoint, embedEntrypoint] = buildOptions.entryPoints.map(filePath => + path.relative(process.cwd(), filePath) + ) + + const manifest: Partial = {} + + // Find the entrypoint in the output files + for (const [asset, output] of Object.entries(outputs)) { + if (!output.entryPoint) { + continue + } + if (output.entryPoint.endsWith(mainEntrypoint)) { + manifest['main.js'] = assetPath(asset) + if (output.cssBundle) { + manifest['main.css'] = assetPath(output.cssBundle) + } + } else if (output.entryPoint.endsWith(embedEntrypoint)) { + manifest['embed.js'] = assetPath(asset) + if (output.cssBundle) { + manifest['embed.css'] = assetPath(output.cssBundle) + } + } + } + + if (!manifest['main.js']) { + throw new Error('no main.js found in outputs') + } + if (!manifest['main.css']) { + throw new Error('no main.css found in outputs') + } + if (!manifest['embed.js']) { + throw new Error('no embed.js found in outputs') + } + if (!manifest['embed.css']) { + throw new Error('no embed.css found in outputs') + } + + return manifest as WebBuildManifest +} diff --git a/client/web/dev/esbuild/manifestPlugin.ts b/client/web/dev/esbuild/manifestPlugin.ts new file mode 100644 index 00000000000..9811fc6d601 --- /dev/null +++ b/client/web/dev/esbuild/manifestPlugin.ts @@ -0,0 +1,50 @@ +import fs from 'fs' +import path from 'path' + +import type * as esbuild from 'esbuild' + +import { WEB_BUILD_MANIFEST_FILENAME, createManifestFromBuildResult } from './manifest' + +/** + * An esbuild plugin to write a web.manifest.json file. + */ +export const manifestPlugin: esbuild.Plugin = { + name: 'manifest', + setup: build => { + const origMetafile = build.initialOptions.metafile + build.initialOptions.metafile = true + + build.onEnd(async result => { + const { entryPoints } = build.initialOptions + const outputs = result?.metafile?.outputs + + if (!origMetafile) { + // If we were the only consumers of the metafile, then delete it from the result to + // avoid unexpected behavior from other downstream consumers relying on the metafile + // despite not actually enabling it in the config. + delete result.metafile + } + + if (!checkEntryPoints(entryPoints)) { + throw new Error('[manifestPlugin] Unexpected entryPoints format') + } + + const { outdir } = build.initialOptions + if (!outdir) { + throw new Error('[manifestPlugin] No outdir found') + } + + if (!outputs) { + throw new Error('[manifestPlugin] No outputs found') + } + + const manifest = createManifestFromBuildResult({ entryPoints, outdir }, outputs) + const manifestPath = path.join(outdir, WEB_BUILD_MANIFEST_FILENAME) + await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2)) + }) + }, +} + +function checkEntryPoints(entryPoints: esbuild.BuildOptions['entryPoints']): entryPoints is string[] { + return Array.isArray(entryPoints) && typeof entryPoints[0] === 'string' +} diff --git a/client/web/dev/esbuild/server.ts b/client/web/dev/esbuild/server.ts new file mode 100644 index 00000000000..644e23aace2 --- /dev/null +++ b/client/web/dev/esbuild/server.ts @@ -0,0 +1,101 @@ +import path from 'path' + +import { context as esbuildContext } from 'esbuild' +import express from 'express' +import { createProxyMiddleware } from 'http-proxy-middleware' +import signale from 'signale' + +import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config' +import { buildMonaco } from '@sourcegraph/build-config/src/esbuild/monacoPlugin' + +import { + DEV_SERVER_LISTEN_ADDR, + DEV_SERVER_PROXY_TARGET_ADDR, + ENVIRONMENT_CONFIG, + HTTPS_WEB_SERVER_URL, + printSuccessBanner, +} from '../utils' + +import { esbuildBuildOptions } from './config' +import { assetPathPrefix } from './manifest' + +export const esbuildDevelopmentServer = async ( + listenAddress: { host: string; port: number }, + configureProxy: (app: express.Application) => void +): Promise => { + const start = performance.now() + + // One-time build (these files only change when the monaco-editor npm package is changed, which + // is rare enough to ignore here). + if (!ENVIRONMENT_CONFIG.DEV_WEB_BUILDER_OMIT_SLOW_DEPS) { + const ctx = await buildMonaco(STATIC_ASSETS_PATH) + await ctx.rebuild() + await ctx.dispose() + } + + const ctx = await esbuildContext(esbuildBuildOptions(ENVIRONMENT_CONFIG)) + + await ctx.watch() + + // Start esbuild's server on a random local port. + const { host: esbuildHost, port: esbuildPort } = await ctx.serve({ + host: 'localhost', + servedir: STATIC_ASSETS_PATH, + }) + + // Start a proxy at :3080. Asset requests (underneath /.assets/) go to esbuild; all other + // requests go to the upstream. + const proxyApp = express() + proxyApp.use( + assetPathPrefix, + createProxyMiddleware({ + target: { protocol: 'http:', host: esbuildHost, port: esbuildPort }, + pathRewrite: { [`^${assetPathPrefix}`]: '' }, + onProxyRes: (_proxyResponse, request, response) => { + // Cache chunks because their filename includes a hash of the content. + const isCacheableChunk = path.basename(request.url).startsWith('chunk-') + if (isCacheableChunk) { + response.setHeader('Cache-Control', 'max-age=3600') + } + }, + logLevel: 'error', + }) + ) + configureProxy(proxyApp) + + const proxyServer = proxyApp.listen(listenAddress) + // eslint-disable-next-line @typescript-eslint/return-await + return await new Promise((_resolve, reject) => { + proxyServer.once('listening', () => { + signale.success(`esbuild server is ready after ${Math.round(performance.now() - start)}ms`) + printSuccessBanner(['✱ Sourcegraph is really ready now!', `Click here: ${HTTPS_WEB_SERVER_URL}/search`]) + }) + proxyServer.once('error', error => reject(error)) + }) +} + +if (require.main === module) { + async function main(args: string[]): Promise { + if (args.length !== 0) { + throw new Error('Usage: (no options)') + } + await esbuildDevelopmentServer(DEV_SERVER_LISTEN_ADDR, app => { + app.use( + '/', + createProxyMiddleware({ + target: { + protocol: 'http:', + host: DEV_SERVER_PROXY_TARGET_ADDR.host, + port: DEV_SERVER_PROXY_TARGET_ADDR.port, + }, + logLevel: 'error', + }) + ) + }) + } + // eslint-disable-next-line unicorn/prefer-top-level-await + main(process.argv.slice(2)).catch(error => { + console.error(error) + process.exit(1) + }) +} diff --git a/client/web/dev/server/devProxyServer.ts b/client/web/dev/server/devProxyServer.ts deleted file mode 100644 index 9be44a57144..00000000000 --- a/client/web/dev/server/devProxyServer.ts +++ /dev/null @@ -1,56 +0,0 @@ -import express from 'express' -import { createProxyMiddleware } from 'http-proxy-middleware' -import signale from 'signale' - -import { ENVIRONMENT_CONFIG, HTTPS_WEB_SERVER_URL } from '../utils/environment-config' -import { getIndexHTML, getWebBuildManifest } from '../utils/get-index-html' -import { printSuccessBanner } from '../utils/success-banner' - -import { getAPIProxySettings } from './apiProxySettings' - -const { SOURCEGRAPH_API_URL, SOURCEGRAPH_HTTP_PORT } = ENVIRONMENT_CONFIG - -interface DevelopmentServerInit { - apiURL: string - listenAddress?: { host: string; port: number } -} - -async function startDevProxyServer({ - apiURL, - listenAddress = { host: '127.0.0.1', port: SOURCEGRAPH_HTTP_PORT }, -}: DevelopmentServerInit): Promise { - const { proxyRoutes, ...proxyMiddlewareOptions } = getAPIProxySettings({ - apiURL, - getLocalIndexHTML(jsContextScript) { - return getIndexHTML({ manifest: getWebBuildManifest(), jsContextScript }) - }, - }) - - const proxyServer = express() - proxyServer.use(createProxyMiddleware(proxyRoutes, proxyMiddlewareOptions)) - return new Promise((_resolve, reject) => { - proxyServer - .listen(listenAddress) - .once('listening', () => { - printSuccessBanner(['✱ Sourcegraph is really ready now!', `Click here: ${HTTPS_WEB_SERVER_URL}`]) - }) - .once('error', error => reject(error)) - }) -} - -if (require.main === module) { - signale.start('Starting dev server.', ENVIRONMENT_CONFIG) - - if (!SOURCEGRAPH_API_URL) { - throw new Error( - 'development.server.ts only supports *web-standalone* usage (must set SOURCEGRAPH_API_URL env var)' - ) - } - - startDevProxyServer({ - apiURL: SOURCEGRAPH_API_URL, - }).catch(error => { - signale.error(error) - process.exit(1) - }) -} diff --git a/client/web/dev/server/development.server.ts b/client/web/dev/server/development.server.ts new file mode 100644 index 00000000000..8147430f3c6 --- /dev/null +++ b/client/web/dev/server/development.server.ts @@ -0,0 +1,41 @@ +import { createProxyMiddleware } from 'http-proxy-middleware' +import signale from 'signale' + +import { esbuildDevelopmentServer } from '../esbuild/server' +import { ENVIRONMENT_CONFIG, getAPIProxySettings, getIndexHTML, getWebBuildManifest } from '../utils' + +const { SOURCEGRAPH_API_URL, SOURCEGRAPH_HTTP_PORT } = ENVIRONMENT_CONFIG + +interface DevelopmentServerInit { + apiURL: string +} + +async function startDevelopmentServer(): Promise { + signale.start('Starting dev server.', ENVIRONMENT_CONFIG) + + if (!SOURCEGRAPH_API_URL) { + throw new Error('development.server.ts only supports *web-standalone* usage') + } + + await startEsbuildDevelopmentServer({ + apiURL: SOURCEGRAPH_API_URL, + }) +} + +async function startEsbuildDevelopmentServer({ apiURL }: DevelopmentServerInit): Promise { + const { proxyRoutes, ...proxyMiddlewareOptions } = getAPIProxySettings({ + apiURL, + getLocalIndexHTML(jsContextScript) { + return getIndexHTML({ manifestFile: getWebBuildManifest(), jsContextScript }) + }, + }) + + await esbuildDevelopmentServer({ host: '0.0.0.0', port: SOURCEGRAPH_HTTP_PORT }, app => { + app.use(createProxyMiddleware(proxyRoutes, proxyMiddlewareOptions)) + app.get(/.*/, (_request, response) => { + response.send(getIndexHTML({ manifestFile: getWebBuildManifest() })) + }) + }) +} + +startDevelopmentServer().catch(error => signale.error(error)) diff --git a/client/web/dev/server/production.server.ts b/client/web/dev/server/production.server.ts new file mode 100644 index 00000000000..a993c6c9c95 --- /dev/null +++ b/client/web/dev/server/production.server.ts @@ -0,0 +1,64 @@ +import chalk from 'chalk' +import historyApiFallback from 'connect-history-api-fallback' +import express, { type RequestHandler } from 'express' +import expressStaticGzip from 'express-static-gzip' +import { createProxyMiddleware } from 'http-proxy-middleware' +import signale from 'signale' + +import { + getAPIProxySettings, + ENVIRONMENT_CONFIG, + HTTP_WEB_SERVER_URL, + HTTPS_WEB_SERVER_URL, + getWebBuildManifest, + STATIC_INDEX_PATH, + getIndexHTML, +} from '../utils' + +const { SOURCEGRAPH_API_URL, SOURCEGRAPH_HTTP_PORT, STATIC_ASSETS_PATH } = ENVIRONMENT_CONFIG + +function startProductionServer(): void { + if (!SOURCEGRAPH_API_URL) { + throw new Error('production.server.ts only supports *web-standalone* usage') + } + + signale.await('Starting production server', ENVIRONMENT_CONFIG) + + const app = express() + + // Serve index.html in place of any 404 responses. + app.use(historyApiFallback() as RequestHandler) + + // Serve build artifacts. + app.use( + '/.assets', + expressStaticGzip(STATIC_ASSETS_PATH, { + enableBrotli: true, + orderPreference: ['br', 'gz'], + index: false, + }) + ) + + const { proxyRoutes, ...proxyConfig } = getAPIProxySettings({ + apiURL: SOURCEGRAPH_API_URL, + ...(ENVIRONMENT_CONFIG.WEB_BUILDER_SERVE_INDEX && { + getLocalIndexHTML(jsContextScript) { + const manifestFile = getWebBuildManifest() + return getIndexHTML({ manifestFile, jsContextScript }) + }, + }), + }) + + // Proxy API requests to the `process.env.SOURCEGRAPH_API_URL`. + app.use(proxyRoutes, createProxyMiddleware(proxyConfig)) + + // Redirect remaining routes to index.html + app.get('/*', (_request, response) => response.sendFile(STATIC_INDEX_PATH)) + + app.listen(SOURCEGRAPH_HTTP_PORT, () => { + signale.info(`Production HTTP server is ready at ${chalk.blue.bold(HTTP_WEB_SERVER_URL)}`) + signale.success(`Production HTTPS server is ready at ${chalk.blue.bold(HTTPS_WEB_SERVER_URL)}`) + }) +} + +startProductionServer() diff --git a/client/web/dev/tsconfig.json b/client/web/dev/tsconfig.json index f9b6f54d5ef..0379a30e954 100644 --- a/client/web/dev/tsconfig.json +++ b/client/web/dev/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "module": "commonjs", }, - "include": ["vite", "server", "utils", "../src/jscontext.ts", "../src/graphql-operations.ts"], + "include": ["esbuild", "server", "utils", "../src/jscontext.ts", "../src/graphql-operations.ts"], "exclude": ["vitest.config.ts"], } diff --git a/client/web/dev/utils/constants.ts b/client/web/dev/utils/constants.ts index 8fc2175698b..53060abfe70 100644 --- a/client/web/dev/utils/constants.ts +++ b/client/web/dev/utils/constants.ts @@ -2,7 +2,6 @@ import path from 'path' import { ROOT_PATH } from '@sourcegraph/build-config' +export const DEV_SERVER_LISTEN_ADDR = { host: '127.0.0.1', port: 3080 } as const +export const DEV_SERVER_PROXY_TARGET_ADDR = { host: '127.0.0.1', port: 3081 } as const export const DEFAULT_SITE_CONFIG_PATH = path.resolve(ROOT_PATH, '../dev-private/enterprise/dev/site-config.json') -export const assetPathPrefix = '/.assets' - -export const WEB_BUILD_MANIFEST_FILENAME = 'vite-manifest.json' diff --git a/client/web/dev/server/apiProxySettings.ts b/client/web/dev/utils/get-api-proxy-settings.ts similarity index 92% rename from client/web/dev/server/apiProxySettings.ts rename to client/web/dev/utils/get-api-proxy-settings.ts index 8871facaf95..1476050acf3 100644 --- a/client/web/dev/server/apiProxySettings.ts +++ b/client/web/dev/utils/get-api-proxy-settings.ts @@ -3,20 +3,19 @@ import * as zlib from 'zlib' import { type Options, responseInterceptor } from 'http-proxy-middleware' -import { ENVIRONMENT_CONFIG, HTTPS_WEB_SERVER_URL } from '../utils/environment-config' -import { STREAMING_ENDPOINTS } from '../utils/should-compress-response' +import { ENVIRONMENT_CONFIG, HTTPS_WEB_SERVER_URL } from './environment-config' +import { STREAMING_ENDPOINTS } from './should-compress-response' // One of the API routes: "/-/sign-in". const PROXY_ROUTES = ['/.api', '/search/stream', '/-', '/.auth'] interface GetAPIProxySettingsOptions { apiURL: string - /** - * Use the remote `window.context` as the basis for our local `window.context`, so that most - * parts of the remote site's config are applied here. + * If provided, the server will proxy requests to index.html + * and inject the `window.context` defined there into the local template. */ - getLocalIndexHTML: (jsContextScript?: string) => string + getLocalIndexHTML?: (jsContextScript?: string) => string } interface ProxySettings extends Options { @@ -27,8 +26,8 @@ export function getAPIProxySettings(options: GetAPIProxySettingsOptions): ProxyS const { apiURL, getLocalIndexHTML } = options return { - // Proxy '' (for index.html). - proxyRoutes: [...PROXY_ROUTES, ''], + // Enable index.html proxy if `getLocalIndexHTML` is provided. + proxyRoutes: [...PROXY_ROUTES, ...(getLocalIndexHTML ? [''] : [])], target: apiURL, // Do not SSL certificate. secure: false, @@ -53,6 +52,7 @@ export function getAPIProxySettings(options: GetAPIProxySettingsOptions): ProxyS // Extract remote `window.context` from the HTML response and inject it into // the index.html generated by `getLocalIndexHTML`. if ( + getLocalIndexHTML && // router.go is not up to date with client routes and still serves index.html with 404 (proxyRes.statusCode === 200 || proxyRes.statusCode === 404) && proxyRes.headers['content-type'] && @@ -76,7 +76,7 @@ export function getAPIProxySettings(options: GetAPIProxySettingsOptions): ProxyS // Don't log proxy errors, these usually just contain // ECONNRESET errors caused by the browser cancelling // requests. This should not be needed to actually debug something. - logLevel: 'debug', + logLevel: 'silent', onProxyReqWs: (_proxyRequest, _request, socket) => socket.on('error', error => console.error('WebSocket proxy error:', error)), } @@ -142,7 +142,6 @@ function conditionalResponseInterceptor( res.setHeader('content-type', 'text/event-stream') const _proxyRes = decompress(proxyRes, proxyRes.headers['content-encoding']) - // eslint-disable-next-line @typescript-eslint/no-explicit-any _proxyRes.on('data', (chunk: any) => res.write(chunk)) _proxyRes.on('end', () => { res.end() diff --git a/client/web/dev/utils/get-index-html.ts b/client/web/dev/utils/get-index-html.ts index d112256d939..875856c4ed6 100644 --- a/client/web/dev/utils/get-index-html.ts +++ b/client/web/dev/utils/get-index-html.ts @@ -2,11 +2,9 @@ import { readFileSync } from 'fs' import path from 'path' import type { SourcegraphContext } from '../../src/jscontext' +import { assetPathPrefix, WEB_BUILD_MANIFEST_FILENAME, type WebBuildManifest } from '../esbuild/manifest' -import { WEB_BUILD_MANIFEST_FILENAME, assetPathPrefix } from './constants' -import { createJsContext } from './create-js-context' -import { ENVIRONMENT_CONFIG, HTTPS_WEB_SERVER_URL } from './environment-config' -import type { WebBuildManifest } from './webBuildManifest' +import { createJsContext, ENVIRONMENT_CONFIG, HTTPS_WEB_SERVER_URL } from '.' const { STATIC_ASSETS_PATH } = ENVIRONMENT_CONFIG @@ -17,7 +15,7 @@ export const getWebBuildManifest = (): WebBuildManifest => JSON.parse(readFileSync(WEB_BUILD_MANIFEST_PATH, 'utf-8')) as WebBuildManifest interface GetHTMLPageOptions { - manifest: WebBuildManifest + manifestFile: WebBuildManifest /** * Used to inject dummy `window.context` in integration tests. */ @@ -35,10 +33,11 @@ interface GetHTMLPageOptions { * Note: This page should be kept as close as possible to `app.html` to avoid any inconsistencies * between our development server and the actual production server. */ -export function getIndexHTML({ manifest, jsContext, jsContextScript }: GetHTMLPageOptions): string { - if (!manifest.assets['src/enterprise/main']) { - throw new Error('entrypoint asset not found') - } +export function getIndexHTML(options: GetHTMLPageOptions): string { + const { manifestFile, jsContext, jsContextScript } = options + + const { 'main.js': mainJS, 'main.css': mainCSS } = manifestFile + return ` @@ -48,19 +47,14 @@ export function getIndexHTML({ manifest, jsContext, jsContextScript }: GetHTMLPa + ${ ENVIRONMENT_CONFIG.SOURCEGRAPHDOTCOM_MODE ? '' : '' } - ${ - manifest.assets['src/enterprise/main']?.css - ? `` - : '' - } - ${manifest.devInjectHTML ?? ''}
- + ` diff --git a/client/web/dev/utils/index.ts b/client/web/dev/utils/index.ts new file mode 100644 index 00000000000..805cc2cd1bc --- /dev/null +++ b/client/web/dev/utils/index.ts @@ -0,0 +1,7 @@ +export * from './constants' +export * from './create-js-context' +export * from './environment-config' +export * from './get-api-proxy-settings' +export * from './get-index-html' +export * from './should-compress-response' +export * from './success-banner' diff --git a/client/web/dev/utils/webBuildManifest.ts b/client/web/dev/utils/webBuildManifest.ts deleted file mode 100644 index 387f5d2b3b7..00000000000 --- a/client/web/dev/utils/webBuildManifest.ts +++ /dev/null @@ -1,19 +0,0 @@ -type Entry = 'src/enterprise/main' | 'src/enterprise/embed/embedMain' | 'src/enterprise/app/main' - -export function isEntry(value: string): value is Entry { - return ['src/enterprise/main', 'src/enterprise/embed/embedMain', 'src/enterprise/app/main'].includes(value) -} - -export interface WebBuildManifest { - /** Base URL for asset paths. */ - url?: string - - /** - * A map of entrypoint (such as "src/enterprise/main" with no extension) to its JavaScript and - * CSS assets. - */ - assets: Partial> - - /** Additional HTML - `, - } - for (const [entryAlias, entryPath] of Object.entries(inputs)) { - const relativeEntryAlias = noExt(normalizePath(path.relative(root, entryAlias))) - if (isEntry(relativeEntryAlias)) { - manifest.assets[relativeEntryAlias] = { - js: simplifyPath(entryPath), - } - } - } - - const outputDir = path.resolve(config.root, config.build.outDir) - writeFileSync(path.resolve(outputDir, pluginConfig.fileName), JSON.stringify(manifest, null, 2)) - }) - }, - - // Run when generating the production bundle. - generateBundle(_options, bundle): void { - if (root === undefined) { - throw new Error('no config') - } - - const manifest: WebBuildManifest = { assets: {} } - for (const chunk of Object.values(bundle)) { - if (chunk.type === 'chunk' && chunk.isEntry && chunk.facadeModuleId) { - let entryAlias = noExt(normalizePath(path.relative(root, chunk.facadeModuleId))) - const css = chunk.viteMetadata ? Array.from(chunk.viteMetadata?.importedCss.values()) : [] - if (css.length >= 2) { - throw new Error('multiple CSS asset files not supported') - } - - if (isEntry(entryAlias)) { - manifest.assets[entryAlias] = { - js: chunk.fileName, - css: css.length === 1 ? css[0] : undefined, - } - } - } - } - this.emitFile({ fileName: pluginConfig.fileName, type: 'asset', source: JSON.stringify(manifest, null, 2) }) - }, - } -} - -function noExt(path: string): string { - return path.replace(/\.(tsx|js)$/, '') -} - -/** - * Resolve host if is passed as `true` - * - * Copied from https://github.com/vitejs/vite/blob/d4dcdd1ffaea79ecf8a9fc78cdbe311f0d801fb5/packages/vite/src/node/logger.ts#L197 - */ -function resolveHost(host?: string | boolean): string { - if (!host) return 'localhost' - - if (host === true) { - const nInterface = Object.values(os.networkInterfaces()) - .flatMap(nInterface => nInterface ?? []) - .filter( - detail => - detail && - detail.address && - // Node < v18 - ((typeof detail.family === 'string' && detail.family === 'IPv4') || - // Node >= v18 - (typeof detail.family === 'number' && (detail as any).family === 4)) - ) - .filter(detail => { - return detail.address !== '127.0.0.1' - })[0] - - if (!nInterface) return 'localhost' - - return nInterface.address - } - - return host -} diff --git a/client/web/dev/vite/vite.config.ts b/client/web/dev/vite/vite.config.ts deleted file mode 100644 index a8a7b1577af..00000000000 --- a/client/web/dev/vite/vite.config.ts +++ /dev/null @@ -1,128 +0,0 @@ -import path from 'path' - -import react from '@vitejs/plugin-react' -import { UserConfig, defineConfig, mergeConfig } from 'vite' - -import { ENVIRONMENT_CONFIG } from '../utils/environment-config' - -import { manifestPlugin } from './manifestPlugin' - -/** Whether we're running in Bazel. */ -const BAZEL = !!process.env.BAZEL_BINDIR - -const repoRoot = BAZEL ? process.cwd() : path.join(__dirname, '../../../..') -const clientWebRoot = path.join(repoRoot, 'client/web') - -export default defineConfig(() => { - let config: UserConfig = { - plugins: [react(), manifestPlugin({ fileName: 'vite-manifest.json' })], - build: { - rollupOptions: { - input: ENVIRONMENT_CONFIG.CODY_APP - ? ['src/enterprise/app/main.tsx'] - : ['src/enterprise/main.tsx', 'src/enterprise/embed/embedMain.tsx'] - .map(BAZEL ? toJSExtension : String) - .map(p => path.join(clientWebRoot, p)), - }, - sourcemap: true, - - // modulepreload is supported widely enough now (https://caniuse.com/link-rel-modulepreload) - // and is only relevant for local dev. - modulePreload: { polyfill: false }, - - emptyOutDir: false, // client/web/dist has static assets checked in - }, - base: '/.assets', - root: clientWebRoot, - publicDir: 'dist', - assetsInclude: ['**/*.yaml'], - define: { - ...Object.fromEntries( - Object.entries({ ...ENVIRONMENT_CONFIG, SOURCEGRAPH_API_URL: undefined }).map(([key, value]) => [ - `process.env.${key}`, - JSON.stringify(value === undefined ? null : value), - ]) - ), - }, - optimizeDeps: { - exclude: [ - // Without addings this Vite throws an error - 'linguist-languages', - ], - }, - resolve: { - alias: { - path: require.resolve('path-browserify'), - }, - mainFields: ['browser', 'module', 'main'], - }, - css: { - devSourcemap: true, - preprocessorOptions: { - scss: { - includePaths: [ - // Our scss files and scss files in client/* often import global styles via @import 'wildcard/src/...' - // Adding '..' as load path causes scss to look for these imports in the client folder. - // (without it scss @import paths are always relative to the importing file) - path.join(clientWebRoot, '..'), - ], - }, - }, - modules: { - localsConvention: 'camelCaseOnly', - }, - }, - } - - if (BAZEL) { - // TODO(sqs): dedupe with client/web-sveltekit - - // Merge settings necessary to make the build work with bazel - config = mergeConfig(config, { - resolve: { - alias: [ - // When using Bazel, @sourcegraph/* dependencies will refer to the built packages. - // These do not contain the source *.module.scss files but still contain import statements - // that reference *.scss files. Processing them with vite throws an error unless we - // update the imports to reference the corresponding *.css files instead. - // Additionally our own source files might reference *.module.scss files, which we also want - // to rewrite. - { - find: /^(.+)\.module\.scss$/, - replacement: '$1.module.css', - customResolver(source, importer, options) { - // The this.resolve(...) part is taken from the @rollup/plugin-alias implementation. Without - // it it appears the bundler tries to resolve relative module IDs to the current working - // directory. - return source.includes('@sourcegraph') || importer?.includes('@sourcegraph/') - ? this.resolve(source, importer, { skipSelf: true, ...options }).then( - resolved => resolved || { id: source } - ) - : null - }, - }, - - // Assume all other *.scss files have been built. - { - find: /^(.+)\.scss(\?.*)?$/, - replacement: '$1.css$2', - }, - ], - }, - ssr: { - // By default vite treats dependencies that are links to other packages in the monorepo as source code - // and processes them as well. - // In a bazel sandbox however all @sourcegraph/* dependencies are built packages and thus not processed - // by vite without this additional setting. - // We have to process those files to apply certain "fixes", such as aliases defined in svelte.config.js. - noExternal: [/@sourcegraph\/.*/], - }, - } satisfies UserConfig) - } - - return config -}) - -function toJSExtension(path: string): string { - return path.replace(/\.ts$/, '.js').replace(/\.tsx$/, '.js') -} diff --git a/client/web/dist/assets.go b/client/web/dist/assets.go index 5e7d4927d62..b01fe33e498 100644 --- a/client/web/dist/assets.go +++ b/client/web/dist/assets.go @@ -17,17 +17,15 @@ import ( // //go:embed all:* var assetsFS embed.FS +var afs fs.FS = assetsFS -var ( - afs fs.FS = assetsFS - assetsHTTPFS http.FileSystem -) +var Assets http.FileSystem var ( webBuildManifestOnce sync.Once + assetsOnce sync.Once webBuildManifest *assets.WebBuildManifest webBuildManifestErr error - assetsOnce sync.Once ) func init() { @@ -39,7 +37,7 @@ type Provider struct{} func (p Provider) LoadWebBuildManifest() (*assets.WebBuildManifest, error) { webBuildManifestOnce.Do(func() { - f, err := afs.Open("vite-manifest.json") + f, err := afs.Open("web.manifest.json") if err != nil { webBuildManifestErr = errors.Wrap(err, "read manifest file") return @@ -66,7 +64,7 @@ func (p Provider) Assets() http.FileSystem { // it's already containing other files known to Bazel. So instead we put those into the dist folder. // If we do detect a dist folder when running this code, we immediately substitute the root to that dist folder. // - // Therefore, this code works with both the traditional build approach and when built with Bazel. + // Therefore, this code works with both the traditionnal build approach and when built with Bazel. if _, err := assetsFS.ReadDir("dist"); err == nil { var err error afs, err = fs.Sub(assetsFS, "dist") @@ -74,7 +72,8 @@ func (p Provider) Assets() http.FileSystem { panic("incorrect embed") } } - assetsHTTPFS = http.FS(afs) + Assets = http.FS(afs) }) - return assetsHTTPFS + + return Assets } diff --git a/client/web/package.json b/client/web/package.json index 5d9ada55104..93a3e8dbb73 100644 --- a/client/web/package.json +++ b/client/web/package.json @@ -16,10 +16,11 @@ "test:regression:onboarding": "pnpm task:mocha ./src/regression/onboarding.test.ts", "test:regression:search": "pnpm task:mocha ./src/regression/search.test.ts", "storybook": "STORIES_GLOB='client/web/src/**/*.story.tsx' pnpm --filter @sourcegraph/storybook run start", - "serve:dev": "ts-node-transpile-only --project ./dev/tsconfig.json ./dev/server/devProxyServer.ts", + "serve:dev": "ts-node-transpile-only --project ./dev/tsconfig.json ./dev/server/development.server.ts", + "serve:prod": "ts-node-transpile-only --project ./dev/tsconfig.json ./dev/server/production.server.ts", "generate": "pnpm -w run generate", - "dev": "ts-node -T ../../node_modules/vite/bin/vite.js dev --config vite.config.ts", - "build": "pnpm run generate && ts-node -T ../../node_modules/vite/bin/vite.js build --config vite.config.ts", + "dev": "ts-node -T dev/esbuild/server.ts", + "build": "pnpm run generate && ts-node -T dev/esbuild/build.ts", "watch": "WATCH=1 pnpm run --silent build", "lint": "pnpm lint:js && pnpm:lint:css", "lint:js": "NODE_OPTIONS=\"--max_old_space_size=16192\" eslint --cache '**/*.[tj]s?(x)'", diff --git a/client/web/src/devsettings/DeveloperDialog.module.scss b/client/web/src/devsettings/DeveloperDialog.module.scss index 566fbb9ea64..7146c8c0ced 100644 --- a/client/web/src/devsettings/DeveloperDialog.module.scss +++ b/client/web/src/devsettings/DeveloperDialog.module.scss @@ -1,9 +1,9 @@ .dialog { top: 3rem; + transform: translate(-50%, 0); max-height: calc(100vh - 6rem); - width: 48rem; - max-width: 95%; overflow: hidden; + width: auto; display: flex; flex-direction: column; } diff --git a/client/web/src/enterprise/batches/detail/BatchChangeBurndownChart.tsx b/client/web/src/enterprise/batches/detail/BatchChangeBurndownChart.tsx index 8f4140d5991..879081e9f4d 100644 --- a/client/web/src/enterprise/batches/detail/BatchChangeBurndownChart.tsx +++ b/client/web/src/enterprise/batches/detail/BatchChangeBurndownChart.tsx @@ -2,10 +2,6 @@ import React, { useCallback, useMemo, useState } from 'react' import classNames from 'classnames' import { getYear, parseISO } from 'date-fns' - -// for polyfill -import 'events' - import { Area, ComposedChart, diff --git a/client/web/src/enterprise/main.tsx b/client/web/src/enterprise/main.tsx index 31b090fa855..af444835f63 100644 --- a/client/web/src/enterprise/main.tsx +++ b/client/web/src/enterprise/main.tsx @@ -37,3 +37,9 @@ window.addEventListener('DOMContentLoaded', async () => { logger.error('Failed to initialize the app shell', error) } }) + +if (process.env.NODE_ENV === 'development') { + new EventSource('/.assets/esbuild').addEventListener('change', () => { + location.reload() + }) +} diff --git a/client/web/src/integration/context.ts b/client/web/src/integration/context.ts index f967f6b0821..1034da5794a 100644 --- a/client/web/src/integration/context.ts +++ b/client/web/src/integration/context.ts @@ -70,7 +70,7 @@ export const createWebIntegrationTestContext = async ({ .intercept((request, response) => { response.type('text/html').send( getIndexHTML({ - manifest: getWebBuildManifest(), + manifestFile: getWebBuildManifest(), jsContext: { ...jsContext, ...customContext }, }) ) diff --git a/client/web/src/monitoring/opentelemetry/initOpenTelemetry.ts b/client/web/src/monitoring/opentelemetry/initOpenTelemetry.ts index f32422c3fa4..fbccba8d4f4 100644 --- a/client/web/src/monitoring/opentelemetry/initOpenTelemetry.ts +++ b/client/web/src/monitoring/opentelemetry/initOpenTelemetry.ts @@ -2,13 +2,7 @@ // Don't remove the empty lines between these imports. import './initZones' -/** - * The @opentelemetry/context-zone import enables zone.js which patches global web API - * modules. You can find a list of the patched modules here: - * https://github.com/angular/angular/blob/main/packages/zone.js/MODULE.md - */ - -import { ZoneContextManager } from '@opentelemetry/context-zone' +import type { ZoneContextManager } from '@opentelemetry/context-zone' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' import { type InstrumentationOption, registerInstrumentations } from '@opentelemetry/instrumentation' import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch' @@ -60,6 +54,17 @@ export function initOpenTelemetry(): void { provider.addSpanProcessor(new BatchSpanProcessor(consoleExporter)) } + /** + * This import enables zone.js which patches global web API modules. + * You can find a list of the patched modules here: + * https://github.com/angular/angular/blob/main/packages/zone.js/MODULE.md + * + * It's added with the `require` statement to avoid polluting stack traces in + * the development environment when OpenTelemetry is disabled. + */ + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires + const ZoneContextManager = require('@opentelemetry/context-zone').ZoneContextManager + provider.register({ contextManager: new ZoneContextManager() as ZoneContextManager, }) diff --git a/client/web/src/nav/NavBar/NavBar.module.scss b/client/web/src/nav/NavBar/NavBar.module.scss index 03b9b75ef82..d9687d72ffa 100644 --- a/client/web/src/nav/NavBar/NavBar.module.scss +++ b/client/web/src/nav/NavBar/NavBar.module.scss @@ -21,7 +21,7 @@ .logo { display: flex; height: 100%; - margin: 0 !important; + margin: 0; } .divider { diff --git a/client/web/src/settings/MonacoSettingsEditor.tsx b/client/web/src/settings/MonacoSettingsEditor.tsx index 4c0d70d9982..b2613be58fd 100644 --- a/client/web/src/settings/MonacoSettingsEditor.tsx +++ b/client/web/src/settings/MonacoSettingsEditor.tsx @@ -361,36 +361,14 @@ declare global { } } -/* eslint-disable @typescript-eslint/ban-ts-comment */ - // Manually configure the MonacoEnvironment for the Monaco editor. if (!window.MonacoEnvironment) { window.MonacoEnvironment = { - // @ts-ignore - async getWorker(_, label) { - if (process.env.NODE_ENV === 'development') { - // In dev mode, we need to use a blob URL and a module worker because (1) the worker - // is loaded cross-origin and (2) the worker is not bundled and only module workers - // support `import` statements. - const workerModule = - label === 'json' - ? new URL('monaco-editor/esm/vs/language/json/json.worker.js', import.meta.url) - : new URL('monaco-editor/esm/vs/editor/editor.worker.js', import.meta.url) - const source = `import ${JSON.stringify(workerModule.toString())}` - const workerBlobUrl = URL.createObjectURL(new Blob([source], { type: 'text/javascript' })) - return new Worker(workerBlobUrl, { type: 'module' }) - } - - let worker: any + getWorkerUrl(_moduleId: string, label: string): string { if (label === 'json') { - // @ts-ignore - worker = await import('monaco-editor/esm/vs/language/json/json.worker?worker') - } else { - // @ts-ignore - worker = await import('monaco-editor/esm/vs/editor/editor.worker?worker') + return window.context.assetsRoot + '/scripts/json.worker.bundle.js' } - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - return new worker.default() + return window.context.assetsRoot + '/scripts/editor.worker.bundle.js' }, } } diff --git a/client/web/tsconfig.json b/client/web/tsconfig.json index ffb10045428..482389c903d 100644 --- a/client/web/tsconfig.json +++ b/client/web/tsconfig.json @@ -51,7 +51,6 @@ "include": ["src", "scripts", "__mocks__", "./src/**/*.json"], "exclude": [ "vitest.config.ts", - "vite.config.ts", "dev", "../../node_modules", "./node_modules", diff --git a/client/web/vite.config.ts b/client/web/vite.config.ts deleted file mode 100644 index eff16027f2c..00000000000 --- a/client/web/vite.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import viteConfig from './dev/vite/vite.config' - -export default viteConfig diff --git a/cmd/frontend/internal/app/ui/app.html b/cmd/frontend/internal/app/ui/app.html index 941b1c4a685..a7c8962fc98 100644 --- a/cmd/frontend/internal/app/ui/app.html +++ b/cmd/frontend/internal/app/ui/app.html @@ -28,7 +28,7 @@ {{end}} {{end}} {{.Title}} - {{if .Manifest.Assets.Main.CSS}}{{end}} + {{if .Manifest.MainCSSBundlePath}}{{end}} {{if .PreloadedAssets}} @@ -86,7 +86,6 @@ {{ end }} {{.Injected.BodyTop}} - {{.ManifestDevInjectHTML}}
{{ if .Context.RedirectUnsupportedBrowser }} + {{.Injected.BodyBottom}} diff --git a/cmd/frontend/internal/app/ui/handlers.go b/cmd/frontend/internal/app/ui/handlers.go index 28050a387c3..86071839d98 100644 --- a/cmd/frontend/internal/app/ui/handlers.go +++ b/cmd/frontend/internal/app/ui/handlers.go @@ -86,8 +86,7 @@ type Common struct { PreloadedAssets *[]PreloadedAsset - Manifest *assets.WebBuildManifest - ManifestDevInjectHTML template.HTML + Manifest *assets.WebBuildManifest WebBuilderDevServer bool // whether the web builder dev server is running (WEB_BUILDER_DEV_SERVER env var) @@ -183,10 +182,6 @@ func newCommon(w http.ResponseWriter, r *http.Request, db database.DB, title str WebBuilderDevServer: webBuilderDevServer, } - if env.InsecureDev && manifest.DevInjectHTML != "" { - common.ManifestDevInjectHTML = template.HTML(manifest.DevInjectHTML) - } - if enableHTMLInject != "true" { common.Injected = InjectedHTML{} } diff --git a/cmd/frontend/internal/app/ui/tmpl.go b/cmd/frontend/internal/app/ui/tmpl.go index dde72236383..c9215e0fb4f 100644 --- a/cmd/frontend/internal/app/ui/tmpl.go +++ b/cmd/frontend/internal/app/ui/tmpl.go @@ -9,7 +9,6 @@ import ( "io" "net/http" "os" - "strings" "sync" "github.com/sourcegraph/sourcegraph/cmd/frontend/internal/app/assetsutil" @@ -39,9 +38,6 @@ var ( // Functions that are exposed to templates. var funcMap = template.FuncMap{ "assetURL": func(filePath string) string { - if strings.HasPrefix(filePath, "http://") { - return filePath - } return assetsutil.URL(filePath).String() }, "version": func(fp string) (string, error) { diff --git a/dev/Caddyfile b/dev/Caddyfile index 60a1943eeda..7d97c917830 100644 --- a/dev/Caddyfile +++ b/dev/Caddyfile @@ -1,11 +1,20 @@ { + http_port 3081 auto_https disable_redirects } -# Caddy (tls :3443) -> sourcegraph-frontend (:3080) +# A bit of monstrosity, since we need to reverse proxy via the web builder dev server which then +# reverse proxies to us on HTTP. +# +# Caddy (tls :3443) -> web builder dev server (:3080) -> Caddy (:3081) -> sourcegraph-frontend (:3082) {$SOURCEGRAPH_HTTPS_DOMAIN}:{$SOURCEGRAPH_HTTPS_PORT} { tls internal reverse_proxy 127.0.0.1:3080 { lb_try_duration 60s } } + +# Caddy (:3081) -> sourcegraph-frontend (:3082) +:3081 { + reverse_proxy 127.0.0.1:3082 +} diff --git a/dev/esbuild.bzl b/dev/esbuild.bzl new file mode 100644 index 00000000000..3a9f0def9da --- /dev/null +++ b/dev/esbuild.bzl @@ -0,0 +1,19 @@ +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") +load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild") + +def esbuild_web_app(name, **kwargs): + bundle_name = "%s_bundle" % name + + esbuild( + name = bundle_name, + **kwargs + ) + + copy_to_directory( + name = name, + # flatten static assets + # https://docs.aspect.build/rules/aspect_bazel_lib/docs/copy_to_directory/#root_paths + root_paths = ["client/web/dist", "client/web/%s" % bundle_name], + srcs = ["//client/web/dist/img:img", ":%s" % bundle_name], + visibility = ["//visibility:public"], + ) diff --git a/dev/vite/vite.bzl b/dev/vite/vite.bzl deleted file mode 100644 index a62218ec783..00000000000 --- a/dev/vite/vite.bzl +++ /dev/null @@ -1,225 +0,0 @@ -"Vite rule" - -load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_file_to_bin_action", "copy_files_to_bin_actions") -load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") -load("@aspect_bazel_lib//lib:directory_path.bzl", "directory_path") -load("@aspect_rules_js//js:defs.bzl", "js_binary") -load("@aspect_rules_js//js:libs.bzl", "js_lib_helpers") -load("@aspect_rules_js//js:providers.bzl", "JsInfo", "js_info") -load("@bazel_skylib//lib:paths.bzl", "paths") - -_ATTRS = { - "config": attr.label( - mandatory = True, - allow_single_file = True, - doc = """Configuration file used for Vite""", - ), - "data": js_lib_helpers.JS_LIBRARY_DATA_ATTR, - "deps": attr.label_list( - default = [], - doc = "A list of direct dependencies that are required to build the bundle", - providers = [JsInfo], - ), - "entry_points": attr.label_list( - allow_files = True, - doc = """The bundle's entry points""", - ), - "srcs": attr.label_list( - allow_files = True, - default = [], - doc = """Source files to be made available to Vite""", - ), - "tsconfig": attr.label( - mandatory = True, - allow_single_file = True, - doc = """TypeScript configuration file used by Vite""", - ), - "vite_js_bin": attr.label( - executable = True, - doc = "Override the default vite executable", - cfg = "exec", - ), - "env": attr.string_dict(), -} - -def _bin_relative_path(ctx, file): - prefix = ctx.bin_dir.path + "/" - if file.path.startswith(prefix): - return file.path[len(prefix):] - - # Since file.path is relative to execroot, go up with ".." starting from - # ctx.bin_dir until we reach execroot, then join that with the file path. - up = "/".join([".." for _ in ctx.bin_dir.path.split("/")]) - return up + "/" + file.path - -def _output_relative_path(f): - "Give the path from bazel-out/[arch]/bin to the given File object" - if f.short_path.startswith("../"): - return "external/" + f.short_path[3:] - return f.short_path - -def _filter_js(files): - return [f for f in files if f.extension == "js" or f.extension == "mjs"] - -def _vite_project_impl(ctx): - input_sources = copy_files_to_bin_actions(ctx, ctx.files.srcs) - entry_points = copy_files_to_bin_actions(ctx, _filter_js(ctx.files.entry_points)) - inputs = entry_points + input_sources + ctx.files.deps - - args = ctx.actions.args() - - output_sources = [getattr(ctx.outputs, o) for o in dir(ctx.outputs)] - output_sources.append(ctx.actions.declare_directory(ctx.label.name)) - args.add_all(["--outDir", output_sources[0].basename]) - - config_file = copy_file_to_bin_action(ctx, ctx.file.config) - args.add_all(["--config", _output_relative_path(config_file)]) - inputs.append(config_file) - - env = { - "BAZEL_BINDIR": ctx.bin_dir.path, - } - for (key, value) in ctx.attr.env.items(): - env[key] = value - - args.add("build") - - ctx.actions.run( - executable = ctx.executable.vite_js_bin, - arguments = [args], - inputs = depset( - inputs, - transitive = [js_lib_helpers.gather_files_from_js_providers( - targets = ctx.attr.srcs + ctx.attr.deps, - include_transitive_sources = True, - include_declarations = False, - include_npm_linked_packages = True, - )], - ), - outputs = output_sources, - progress_message = "Building Vite project %s" % (" ".join([_bin_relative_path(ctx, entry_point) for entry_point in entry_points])), - mnemonic = "Vite", - env = env, - ) - - npm_linked_packages = js_lib_helpers.gather_npm_linked_packages( - srcs = ctx.attr.srcs, - deps = [], - ) - - npm_package_store_deps = js_lib_helpers.gather_npm_package_store_deps( - # Since we're bundling, only propagate `data` npm packages to the direct dependencies of - # downstream linked `npm_package` targets instead of the common `data` and `deps` pattern. - targets = ctx.attr.data, - ) - - output_sources_depset = depset(output_sources) - - runfiles = js_lib_helpers.gather_runfiles( - ctx = ctx, - sources = output_sources_depset, - data = ctx.attr.data, - # Since we're bundling, we don't propagate any transitive runfiles from dependencies - deps = [], - ) - - return [ - DefaultInfo( - files = output_sources_depset, - runfiles = runfiles, - ), - js_info( - npm_linked_package_files = npm_linked_packages.direct_files, - npm_linked_packages = npm_linked_packages.direct, - npm_package_store_deps = npm_package_store_deps, - sources = output_sources_depset, - # Since we're bundling, we don't propagate linked npm packages from dependencies since - # they are bundled and the dependencies are dropped. If a subset of linked npm - # dependencies are not bundled it is up the the user to re-specify these in `data` if - # they are runtime dependencies to progagate to binary rules or `srcs` if they are to be - # propagated to downstream build targets. - transitive_npm_linked_package_files = npm_linked_packages.direct_files, - transitive_npm_linked_packages = npm_linked_packages.direct, - # Since we're bundling, we don't propagate any transitive sources from dependencies - transitive_sources = output_sources_depset, - ), - ] - -lib = struct( - attrs = _ATTRS, - implementation = _vite_project_impl, - toolchains = [ - "@rules_nodejs//nodejs:toolchain_type", - ], -) - -_vite_project = rule( - implementation = _vite_project_impl, - attrs = _ATTRS, - toolchains = lib.toolchains, - doc = """\ -Runs Vite in Bazel -""", -) - -def vite_project(name, config, data = [], deps = [], entry_points = [], srcs = [], env = {}, **kwargs): - """Runs Vite in Bazel. - - Args: - - name: A unique name for this rule. - - config: The Vite config file. - - data: Runtime dependencies that are passed to the Vite build. - - deps: Other npm dependencies that are passed to the Vite build. - - entry_points: The entry points to build. - - srcs: Other sources that are passed to the Vite build. - - env: Environment variables to pass to the Vite build. - - **kwargs: Additional arguments - """ - vite_js_entry_point = "_{}_vite_js_entry_point".format(name) - node_modules = "//:node_modules" - directory_path( - name = vite_js_entry_point, - directory = "{}/vite/dir".format(node_modules), - path = "bin/vite.js", - ) - - vite_js_bin = "_{}_vite_js_bin".format(name) - js_binary( - name = vite_js_bin, - data = ["%s/vite" % node_modules], - entry_point = vite_js_entry_point, - ) - - _vite_project( - name = name, - config = config, - data = data, - deps = deps, - entry_points = entry_points, - srcs = srcs, - vite_js_bin = vite_js_bin, - env = env, - **kwargs - ) - -def vite_web_app(name, **kwargs): - bundle_name = "%s_bundle" % name - - vite_project(name = bundle_name, **kwargs) - - copy_to_directory( - name = name, - # flatten static assets - # https://docs.aspect.build/rules/aspect_bazel_lib/docs/copy_to_directory/#root_paths - root_paths = ["client/web/dist", "client/web/%s" % bundle_name], - srcs = ["//client/web/dist/img:img", ":%s" % bundle_name], - visibility = ["//visibility:public"], - ) diff --git a/doc/dev/background-information/sg/reference.md b/doc/dev/background-information/sg/reference.md index 7b70f9b796a..8cf4bd44abc 100644 --- a/doc/dev/background-information/sg/reference.md +++ b/doc/dev/background-information/sg/reference.md @@ -55,6 +55,7 @@ Available commandsets in `sg.config.yaml`: * qdrant * single-program * web-standalone +* web-standalone-prod * web-sveltekit-standalone ```sh @@ -141,6 +142,7 @@ Available commands in `sg.config.yaml`: * telemetry-gateway * web-integration-build-prod: Build production web application for integration tests * web-integration-build: Build development web application for integration tests +* web-standalone-http-prod: Standalone web frontend (production) with API proxy to a configurable URL * web-standalone-http: Standalone web frontend (dev) with API proxy to a configurable URL * web-sveltekit-prod-watch: Builds the prod version of the SvelteKit web app and rebuilds on changes * web-sveltekit-standalone: Standalone SvelteKit web frontend (dev) with API proxy to a configurable URL diff --git a/package.json b/package.json index 6577412be02..cf8b83a7c95 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "@types/uuid": "8.0.1", "@types/whatwg-url": "^11.0.3", "@types/yauzl": "^2.9.2", - "@vitejs/plugin-react": "^4.1.0", + "@vitejs/plugin-react": "^3.1.0", "abort-controller": "^3.0.0", "autoprefixer": "^10.2.1", "axe-core": "^4.4.1", @@ -260,7 +260,7 @@ "typed-scss-modules": "^4.1.1", "typescript": "^5.0.2", "utc-version": "^2.0.2", - "vite": "^4.5.0", + "vite": "^4.1.4", "vite-plugin-turbosnap": "^1.0.3", "vitest": "1.0.0-beta.4", "vitest-fetch-mock": "^0.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6cfd18d3167..faa6a24138f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -574,7 +574,7 @@ importers: version: 7.4.6(react-dom@18.1.0)(react@18.1.0) '@storybook/builder-vite': specifier: ^7.4.6 - version: 7.4.6(typescript@5.2.2)(vite@4.5.0) + version: 7.4.6(typescript@5.2.2)(vite@4.4.7) '@storybook/cli': specifier: ^7.4.6 version: 7.4.6 @@ -595,7 +595,7 @@ importers: version: 7.4.6(react-dom@18.1.0)(react@18.1.0)(typescript@5.2.2) '@storybook/react-vite': specifier: ^7.4.6 - version: 7.4.6(react-dom@18.1.0)(react@18.1.0)(typescript@5.2.2)(vite@4.5.0) + version: 7.4.6(react-dom@18.1.0)(react@18.1.0)(typescript@5.2.2)(vite@4.4.7) '@storybook/theming': specifier: ^7.4.6 version: 7.4.6(react-dom@18.1.0)(react@18.1.0) @@ -798,8 +798,8 @@ importers: specifier: ^2.9.2 version: 2.10.0 '@vitejs/plugin-react': - specifier: ^4.1.0 - version: 4.1.0(vite@4.5.0) + specifier: ^3.1.0 + version: 3.1.0(vite@4.4.7) abort-controller: specifier: ^3.0.0 version: 3.0.0 @@ -1047,8 +1047,8 @@ importers: specifier: ^2.0.2 version: 2.0.2 vite: - specifier: ^4.5.0 - version: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + specifier: ^4.1.4 + version: 4.4.7(@types/node@20.8.0)(sass@1.32.4) vite-plugin-turbosnap: specifier: ^1.0.3 version: 1.0.3 @@ -1486,7 +1486,7 @@ importers: version: 7.4.6(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-svelte-csf': specifier: ^3.0.7 - version: 3.0.7(@storybook/svelte@7.2.0)(@storybook/theming@7.4.6)(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.1.1)(vite@4.5.0) + version: 3.0.7(@storybook/svelte@7.2.0)(@storybook/theming@7.4.6)(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.1.1)(vite@4.4.7) '@storybook/blocks': specifier: ^7.2.0 version: 7.4.6(@types/react-dom@18.0.2)(@types/react@18.0.8)(react-dom@18.2.0)(react@18.2.0) @@ -1495,7 +1495,7 @@ importers: version: 7.2.0(svelte@4.1.1) '@storybook/sveltekit': specifier: ^7.2.0 - version: 7.2.0(svelte@4.1.1)(typescript@5.2.2)(vite@4.5.0) + version: 7.2.0(svelte@4.1.1)(typescript@5.2.2)(vite@4.4.7) '@storybook/testing-library': specifier: 0.2.0 version: 0.2.0 @@ -1507,7 +1507,7 @@ importers: version: 2.0.3(@sveltejs/kit@1.22.3) '@sveltejs/kit': specifier: ^1.22.3 - version: 1.22.3(svelte@4.1.1)(vite@4.5.0) + version: 1.22.3(svelte@4.1.1)(vite@4.4.7) '@testing-library/svelte': specifier: ^4.0.3 version: 4.0.3(svelte@4.1.1) @@ -1570,10 +1570,10 @@ importers: version: 2.1.0 vite: specifier: ^4.4.7 - version: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + version: 4.4.7(@types/node@20.8.0)(sass@1.32.4) vite-plugin-inspect: specifier: ^0.7.35 - version: 0.7.35(vite@4.5.0) + version: 0.7.35(vite@4.4.7) vitest: specifier: ^0.33.0 version: 0.33.0(happy-dom@12.10.1)(jsdom@22.1.0)(sass@1.32.4) @@ -5228,7 +5228,7 @@ packages: '@types/yargs': 17.0.23 chalk: 4.1.2 - /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.2.2)(vite@4.5.0): + /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.2.2)(vite@4.4.7): resolution: {integrity: sha512-ou4ZJSXMMWHqGS4g8uNRbC5TiTWxAgQZiVucoUrOCWuPrTbkpJbmVyIi9jU72SBry7gQtuMEDp4YR8EEXAg7VQ==} peerDependencies: typescript: '>= 4.3.x' @@ -5242,7 +5242,7 @@ packages: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.2.2) typescript: 5.2.2 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) dev: true /@jridgewell/gen-mapping@0.3.3: @@ -6253,6 +6253,7 @@ packages: /@playwright/test@1.25.0: resolution: {integrity: sha512-j4EZhTTQI3dBeWblE21EV//swwmBtOpIrLdOIJIRv4uqsLdHgBg1z+JtTg+AeC5o2bAXIE26kDNW5A0TimG8Bg==} engines: {node: '>=14'} + deprecated: Please update to the latest version of Playwright to test up-to-date browsers. hasBin: true dependencies: '@types/node': 20.8.0 @@ -9105,7 +9106,7 @@ packages: - '@types/react-dom' dev: true - /@storybook/addon-svelte-csf@3.0.7(@storybook/svelte@7.2.0)(@storybook/theming@7.4.6)(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.1.1)(vite@4.5.0): + /@storybook/addon-svelte-csf@3.0.7(@storybook/svelte@7.2.0)(@storybook/theming@7.4.6)(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.1.1)(vite@4.4.7): resolution: {integrity: sha512-T7KYWlhIs3G2N4r0UPawCCnHnYUWyg2rUMIfi/HLVYODsnqG7rJmK3ZAvtEgd1sFST0gRNBu13NIyH3YYUnA7A==} peerDependencies: '@storybook/svelte': ^7.0.0 @@ -9125,12 +9126,12 @@ packages: '@babel/runtime': 7.23.1 '@storybook/svelte': 7.2.0(svelte@4.1.1) '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0) - '@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.1.1)(vite@4.5.0) + '@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.1.1)(vite@4.4.7) dedent: 1.5.1 fs-extra: 11.1.1 magic-string: 0.30.5 svelte: 4.1.1 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - babel-plugin-macros dev: true @@ -9430,7 +9431,7 @@ packages: - supports-color dev: true - /@storybook/builder-vite@7.2.0(typescript@5.2.2)(vite@4.5.0): + /@storybook/builder-vite@7.2.0(typescript@5.2.2)(vite@4.4.7): resolution: {integrity: sha512-YmTtyIMxWKaFOsJaLU5rGHukvt37LjVvW6QEAEK1Clbl1f3QUNSEGJwVYLRYO7xQvhymBF4JMa8N5NSc1+xSEQ==} peerDependencies: '@preact/preset-vite': '*' @@ -9463,15 +9464,15 @@ packages: magic-string: 0.30.5 remark-external-links: 8.0.0 remark-slug: 6.1.0 - rollup: 3.29.4 + rollup: 3.26.3 typescript: 5.2.2 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - encoding - supports-color dev: true - /@storybook/builder-vite@7.4.6(typescript@5.2.2)(vite@4.5.0): + /@storybook/builder-vite@7.4.6(typescript@5.2.2)(vite@4.4.7): resolution: {integrity: sha512-xV9STYK+TkqWWTf2ydm6jx+7P70fjD2UPd1XTUw08uKszIjhuuxk+bG/OF5R1E25mPunAKXm6kBFh351AKejBg==} peerDependencies: '@preact/preset-vite': '*' @@ -9504,9 +9505,9 @@ packages: magic-string: 0.30.5 remark-external-links: 8.0.0 remark-slug: 6.1.0 - rollup: 3.29.4 + rollup: 3.26.3 typescript: 5.2.2 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - encoding - supports-color @@ -10232,7 +10233,7 @@ packages: react-dom: 18.1.0(react@18.1.0) dev: true - /@storybook/react-vite@7.4.6(react-dom@18.1.0)(react@18.1.0)(typescript@5.2.2)(vite@4.5.0): + /@storybook/react-vite@7.4.6(react-dom@18.1.0)(react@18.1.0)(typescript@5.2.2)(vite@4.4.7): resolution: {integrity: sha512-jkjnrf3FxzR5wcmebXRPflrsM4WIDjWyW/NVFJwxi5PeIOk7fE7/QAPrm4NFRUu2Q7DeuH3oLKsw8bigvUI9RA==} engines: {node: '>=16'} peerDependencies: @@ -10240,17 +10241,17 @@ packages: react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 vite: ^3.0.0 || ^4.0.0 dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.2.2)(vite@4.5.0) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.2.2)(vite@4.4.7) '@rollup/pluginutils': 5.0.2 - '@storybook/builder-vite': 7.4.6(typescript@5.2.2)(vite@4.5.0) + '@storybook/builder-vite': 7.4.6(typescript@5.2.2)(vite@4.4.7) '@storybook/react': 7.4.6(react-dom@18.1.0)(react@18.1.0)(typescript@5.2.2) - '@vitejs/plugin-react': 3.1.0(vite@4.5.0) + '@vitejs/plugin-react': 3.1.0(vite@4.4.7) ast-types: 0.14.2 magic-string: 0.30.5 react: 18.1.0 react-docgen: 6.0.0-alpha.3 react-dom: 18.1.0(react@18.1.0) - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -10354,22 +10355,22 @@ packages: react-dom: 18.1.0(react@18.1.0) dev: true - /@storybook/svelte-vite@7.2.0(svelte@4.1.1)(typescript@5.2.2)(vite@4.5.0): + /@storybook/svelte-vite@7.2.0(svelte@4.1.1)(typescript@5.2.2)(vite@4.4.7): resolution: {integrity: sha512-iGaaY64xWjMDzECpu+qw82Y0LtwZYD5FimCEmx1gjPLiv0TJDRVcpBvtGNzEf6ztevastRQs8HEDxv1H1xjCkQ==} engines: {node: ^14.18 || >=16} peerDependencies: svelte: ^3.0.0 || ^4.0.0 vite: ^3.0.0 || ^4.0.0 dependencies: - '@storybook/builder-vite': 7.2.0(typescript@5.2.2)(vite@4.5.0) + '@storybook/builder-vite': 7.2.0(typescript@5.2.2)(vite@4.4.7) '@storybook/node-logger': 7.2.0 '@storybook/svelte': 7.2.0(svelte@4.1.1) - '@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.1.1)(vite@4.5.0) + '@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.1.1)(vite@4.4.7) magic-string: 0.30.5 svelte: 4.1.1 sveltedoc-parser: 4.2.1 ts-dedent: 2.2.0 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -10399,18 +10400,18 @@ packages: - supports-color dev: true - /@storybook/sveltekit@7.2.0(svelte@4.1.1)(typescript@5.2.2)(vite@4.5.0): + /@storybook/sveltekit@7.2.0(svelte@4.1.1)(typescript@5.2.2)(vite@4.4.7): resolution: {integrity: sha512-JyhRofOywpIElqgpc6NR6xMyBgop5bVSnzeDl9aJk0VTVGFvQOEco85f9FHJmlhhBpFr7g+Nal7hzsqg5vkdPg==} engines: {node: ^14.18 || >=16} peerDependencies: svelte: ^3.0.0 || ^4.0.0 vite: ^4.0.0 dependencies: - '@storybook/builder-vite': 7.2.0(typescript@5.2.2)(vite@4.5.0) + '@storybook/builder-vite': 7.2.0(typescript@5.2.2)(vite@4.4.7) '@storybook/svelte': 7.2.0(svelte@4.1.1) - '@storybook/svelte-vite': 7.2.0(svelte@4.1.1)(typescript@5.2.2)(vite@4.5.0) + '@storybook/svelte-vite': 7.2.0(svelte@4.1.1)(typescript@5.2.2)(vite@4.4.7) svelte: 4.1.1 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -10505,7 +10506,7 @@ packages: resolution: {integrity: sha512-jwoA/TIp+U8Vz868aQT+XfoAw6qFrtn2HbZlTfwNWZsUhPFlMsGrwIVEpWqBWIoe6WITU/lNw3BuRmxul+wvAQ==} dependencies: '@storybook/channels': 7.2.0 - '@types/babel__core': 7.20.3 + '@types/babel__core': 7.1.20 '@types/express': 4.17.11 file-system-cache: 2.3.0 dev: true @@ -10514,7 +10515,7 @@ packages: resolution: {integrity: sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==} dependencies: '@storybook/channels': 7.4.6 - '@types/babel__core': 7.20.3 + '@types/babel__core': 7.1.20 '@types/express': 4.17.11 file-system-cache: 2.3.0 dev: true @@ -10524,7 +10525,7 @@ packages: peerDependencies: '@sveltejs/kit': ^1.0.0 dependencies: - '@sveltejs/kit': 1.22.3(svelte@4.1.1)(vite@4.5.0) + '@sveltejs/kit': 1.22.3(svelte@4.1.1)(vite@4.4.7) import-meta-resolve: 3.0.0 dev: true @@ -10533,10 +10534,10 @@ packages: peerDependencies: '@sveltejs/kit': ^1.5.0 dependencies: - '@sveltejs/kit': 1.22.3(svelte@4.1.1)(vite@4.5.0) + '@sveltejs/kit': 1.22.3(svelte@4.1.1)(vite@4.4.7) dev: true - /@sveltejs/kit@1.22.3(svelte@4.1.1)(vite@4.5.0): + /@sveltejs/kit@1.22.3(svelte@4.1.1)(vite@4.4.7): resolution: {integrity: sha512-IpHD5wvuoOIHYaHQUBJ1zERD2Iz+fB/rBXhXjl8InKw6X4VKE9BSus+ttHhE7Ke+Ie9ecfilzX8BnWE3FeQyng==} engines: {node: ^16.14 || >=18} hasBin: true @@ -10545,7 +10546,7 @@ packages: svelte: ^3.54.0 || ^4.0.0-next.0 vite: ^4.0.0 dependencies: - '@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.1.1)(vite@4.5.0) + '@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.1.1)(vite@4.4.7) '@types/cookie': 0.5.1 cookie: 0.5.0 devalue: 4.3.2 @@ -10558,12 +10559,12 @@ packages: sirv: 2.0.3 svelte: 4.1.1 undici: 5.22.1 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - supports-color dev: true - /@sveltejs/vite-plugin-svelte-inspector@1.0.3(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.1.1)(vite@4.5.0): + /@sveltejs/vite-plugin-svelte-inspector@1.0.3(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.1.1)(vite@4.4.7): resolution: {integrity: sha512-Khdl5jmmPN6SUsVuqSXatKpQTMIifoQPDanaxC84m9JxIibWvSABJyHpyys0Z+1yYrxY5TTEQm+6elh0XCMaOA==} engines: {node: ^14.18.0 || >= 16} peerDependencies: @@ -10571,30 +10572,30 @@ packages: svelte: ^3.54.0 || ^4.0.0 vite: ^4.0.0 dependencies: - '@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.1.1)(vite@4.5.0) + '@sveltejs/vite-plugin-svelte': 2.4.2(svelte@4.1.1)(vite@4.4.7) debug: 4.3.4 svelte: 4.1.1 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - supports-color dev: true - /@sveltejs/vite-plugin-svelte@2.4.2(svelte@4.1.1)(vite@4.5.0): + /@sveltejs/vite-plugin-svelte@2.4.2(svelte@4.1.1)(vite@4.4.7): resolution: {integrity: sha512-ePfcC48ftMKhkT0OFGdOyycYKnnkT6i/buzey+vHRTR/JpQvuPzzhf1PtKqCDQfJRgoPSN2vscXs6gLigx/zGw==} engines: {node: ^14.18.0 || >= 16} peerDependencies: svelte: ^3.54.0 || ^4.0.0 vite: ^4.0.0 dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 1.0.3(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.1.1)(vite@4.5.0) + '@sveltejs/vite-plugin-svelte-inspector': 1.0.3(@sveltejs/vite-plugin-svelte@2.4.2)(svelte@4.1.1)(vite@4.4.7) debug: 4.3.4 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.5 svelte: 4.1.1 svelte-hmr: 0.15.2(svelte@4.1.1) - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) - vitefu: 0.2.4(vite@4.5.0) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) + vitefu: 0.2.4(vite@4.4.7) transitivePeerDependencies: - supports-color dev: true @@ -10883,8 +10884,8 @@ packages: resolution: {integrity: sha512-yzgMaql7aW1by1XuhKhovuhLyK/1A60lapFXDXXBeHmoyRGQFO2T8lkL3g8hAhHoW5PEvqPJFWPd8jvXiRnxeQ==} dev: true - /@types/babel__core@7.20.3: - resolution: {integrity: sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==} + /@types/babel__core@7.1.20: + resolution: {integrity: sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==} dependencies: '@babel/parser': 7.23.0 '@babel/types': 7.23.0 @@ -12158,7 +12159,7 @@ packages: react: 18.1.0 dev: false - /@vitejs/plugin-react@3.1.0(vite@4.5.0): + /@vitejs/plugin-react@3.1.0(vite@4.4.7): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -12169,23 +12170,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.0) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) - transitivePeerDependencies: - - supports-color - dev: true - - /@vitejs/plugin-react@4.1.0(vite@4.5.0): - resolution: {integrity: sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 - dependencies: - '@babel/core': 7.23.0 - '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.23.0) - '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.0) - '@types/babel__core': 7.20.3 - react-refresh: 0.14.0 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - supports-color dev: true @@ -23022,8 +23007,8 @@ packages: dependencies: glob: 7.2.3 - /rollup@3.29.4: - resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} + /rollup@3.26.3: + resolution: {integrity: sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -24724,6 +24709,7 @@ packages: /trim@0.0.1: resolution: {integrity: sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==} + deprecated: Use String.prototype.trim() instead dev: true /ts-api-utils@1.0.3(typescript@5.2.2): @@ -25488,7 +25474,7 @@ packages: mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - '@types/node' - less @@ -25510,7 +25496,7 @@ packages: mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - '@types/node' - less @@ -25522,7 +25508,7 @@ packages: - terser dev: true - /vite-plugin-inspect@0.7.35(vite@4.5.0): + /vite-plugin-inspect@0.7.35(vite@4.4.7): resolution: {integrity: sha512-e5w5dJAj3vDcHTxn8hHbiH+mVqYs17gaW00f3aGuMTXiqUog+T1Lsxr9Jb4WRiip84cpuhR0KFFBT1egtXboiA==} engines: {node: '>=14'} peerDependencies: @@ -25539,7 +25525,7 @@ packages: open: 9.1.0 picocolors: 1.0.0 sirv: 2.0.3 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) transitivePeerDependencies: - rollup - supports-color @@ -25549,8 +25535,8 @@ packages: resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==} dev: true - /vite@4.5.0(@types/node@20.8.0)(sass@1.32.4): - resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} + /vite@4.4.7(@types/node@20.8.0)(sass@1.32.4): + resolution: {integrity: sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -25580,13 +25566,13 @@ packages: '@types/node': 20.8.0 esbuild: 0.18.17 postcss: 8.4.31 - rollup: 3.29.4 + rollup: 3.26.3 sass: 1.32.4 optionalDependencies: fsevents: 2.3.3 dev: true - /vitefu@0.2.4(vite@4.5.0): + /vitefu@0.2.4(vite@4.4.7): resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} peerDependencies: vite: ^3.0.0 || ^4.0.0 @@ -25594,7 +25580,7 @@ packages: vite: optional: true dependencies: - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) dev: true /vitest-fetch-mock@0.2.2(vitest@1.0.0-beta.4): @@ -25663,7 +25649,7 @@ packages: strip-literal: 1.0.1 tinybench: 2.5.0 tinypool: 0.6.0 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) vite-node: 0.33.0(@types/node@20.8.0)(sass@1.32.4) why-is-node-running: 2.2.2 transitivePeerDependencies: @@ -25722,7 +25708,7 @@ packages: strip-literal: 1.0.1 tinybench: 2.5.0 tinypool: 0.8.1 - vite: 4.5.0(@types/node@20.8.0)(sass@1.32.4) + vite: 4.4.7(@types/node@20.8.0)(sass@1.32.4) vite-node: 1.0.0-beta.4(@types/node@20.8.0)(sass@1.32.4) why-is-node-running: 2.2.2 transitivePeerDependencies: diff --git a/sg.config.yaml b/sg.config.yaml index 2716bbd144d..a8e78897822 100644 --- a/sg.config.yaml +++ b/sg.config.yaml @@ -29,6 +29,8 @@ env: DEPLOY_TYPE: dev + SRC_HTTP_ADDR: ':3082' + # I don't think we even need to set these? SEARCHER_URL: http://127.0.0.1:3181 REPO_UPDATER_URL: http://127.0.0.1:3182 @@ -404,7 +406,7 @@ commands: web: description: Enterprise version of the web app - cmd: pnpm --filter @sourcegraph/web run dev --clearScreen=false + cmd: pnpm --filter @sourcegraph/web dev install: | pnpm install pnpm run generate @@ -421,6 +423,15 @@ commands: WEB_BUILDER_SERVE_INDEX: true SOURCEGRAPH_API_URL: https://sourcegraph.sourcegraph.com + web-standalone-http-prod: + description: Standalone web frontend (production) with API proxy to a configurable URL + cmd: pnpm --filter @sourcegraph/web serve:prod + install: pnpm --filter @sourcegraph/web run build + env: + NODE_ENV: production + WEB_BUILDER_SERVE_INDEX: true + SOURCEGRAPH_API_URL: https://k8s.sgdev.org + web-integration-build: description: Build development web application for integration tests cmd: pnpm --filter @sourcegraph/web run build @@ -1505,6 +1516,11 @@ commandsets: env: SK_PORT: 3080 + web-standalone-prod: + commands: + - web-standalone-http-prod + - caddy + # For testing our OpenTelemetry stack otel: checks: @@ -1632,14 +1648,6 @@ tests: export PERCY_TOKEN=$(gcloud secrets versions access latest --secret=PERCY_TOKEN --quiet --project=sourcegraph-ci) bazel test //client/web/src/integration:integration-tests --define=E2E_HEADLESS=false --define=E2E_SOURCEGRAPH_BASE_URL="http://localhost:7080" --define=GH_TOKEN=$GH_TOKEN --define=DISPLAY=$DISPLAY --define=PERCY_TOKEN=$PERCY_TOKEN - bazel-backend-integration: - cmd: | - export GHE_GITHUB_TOKEN=$(gcloud secrets versions access latest --secret=GHE_GITHUB_TOKEN --quiet --project=sourcegraph-ci) - export GH_TOKEN=$(gcloud secrets versions access latest --secret=GITHUB_TOKEN --quiet --project=sourcegraph-ci) - export SOURCEGRAPH_LICENSE_KEY=$(gcloud secrets versions access latest --secret=SOURCEGRAPH_LICENSE_KEY --quiet --project=sourcegraph-ci) - export SOURCEGRAPH_LICENSE_GENERATION_KEY=$(gcloud secrets versions access latest --secret=SOURCEGRAPH_LICENSE_GENERATION_KEY --quiet --project=sourcegraph-ci) - bazel test //testing:backend_integration_test --define=GHE_GITHUB_TOKEN=$GHE_GITHUB_TOKEN - backend-integration: cmd: cd dev/gqltest && go test -long -base-url $BASE_URL -email $EMAIL -username $USERNAME -password $PASSWORD ./gqltest env: diff --git a/ui/assets/dev.go b/ui/assets/dev.go index 046a975e424..acb1caf77e9 100644 --- a/ui/assets/dev.go +++ b/ui/assets/dev.go @@ -42,7 +42,7 @@ func loadWebBuildManifest() (m *WebBuildManifest, err error) { return MockLoadWebBuildManifest() } - manifestContent, err := os.ReadFile(filepath.Join(assetsDir, "vite-manifest.json")) + manifestContent, err := os.ReadFile(filepath.Join(assetsDir, "web.manifest.json")) if err != nil { return nil, errors.Wrap(err, "loading web build manifest file from disk") } diff --git a/ui/assets/manifest.go b/ui/assets/manifest.go index 650d817b661..9541eccb5a8 100644 --- a/ui/assets/manifest.go +++ b/ui/assets/manifest.go @@ -1,23 +1,8 @@ package assets -// WebBuildManifest describes the web build and is produced by Vite. Keep it in sync with `interface -// WebBuildManifest`. type WebBuildManifest struct { - // URL is the base URL for asset paths. - URL string `json:"url,omitempty"` - - // Assets is a map of entrypoint (such as "src/enterprise/main.tsx") to its JavaScript and CSS assets. - Assets struct { - Main *entryAssets `json:"src/enterprise/main"` - EmbedMain *entryAssets `json:"src/enterprise/embed/embedMain"` - AppMain *entryAssets `json:"src/enterprise/app/main"` - } `json:"assets"` - - // DevInjectHTML contains additional HTML