use esbuild for client/web builds (#57365)
Use [esbuild](https://esbuild.github.io/) instead of Webpack for builds of `client/web`, for faster builds (dev and prod) and greater dev-prod parity. This PR completely removes all use of Webpack in this repository. `client/web` is the last build target that still uses Webpack; all others have been recently migrated to esbuild. Most devs here have been using esbuild for local dev of `client/web` for the last 6-12 months anyway. The change here is that now our production builds will be built by esbuild. All sg commands, integration/e2e tests, etc., continue to work as-is. The bundlesize report will take a while to stabilize because the new build products use different filenames. ## Benchmarks Running `pnpm run generate && time pnpm -C client/web run task:gulp webBuild` and taking the `time` output from the last command: - Webpack: 62.5s - esbuild: 6.7s Note: This understates esbuild's victory for 2 reasons: (1) because esbuild is building both the main and embed entrypoints, whereas Webpack only builds the main entrypoint in this benchmark) and (2) because a lot of it is in the fixed startup time of `gulp`; esbuild incremental rebuilds during local dev only take ~1s. ## Notes We no longer use Babel to produce web builds (we use esbuild), so we don't need any Babel plugins that optimize the output or improve browser compatibility. Right now, Babel is only used by Jest (for tests) and by Bazel as an intermediate step.
@ -1,19 +0,0 @@
|
||||
# This file is used in our production build to adjust CSS and JS output to support specified browsers. For additional information regarding the format and rule options, please see: https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# Run `pnpm browserslist` to see which browsers are targeted by these queries
|
||||
|
||||
### Primary browsers ###
|
||||
# Target browsers with the highest usage.
|
||||
> 1%
|
||||
|
||||
### Fallback browsers ###
|
||||
# Target the latest versions of browsers with moderate usage.
|
||||
# Note: This is primarily to catch any new browser versions that haven't yet reached 1% usage.
|
||||
last 1 versions AND not dead AND not <0.25%
|
||||
|
||||
### Additional configuration ###
|
||||
# Any browser-specific configuration that we need.
|
||||
# Note: We include additional Safari versions due to the slower upgrade cycle on MacOS.
|
||||
last 1 Chrome versions
|
||||
last 2 Safari major versions
|
||||
not IE > 0
|
||||
@ -46,21 +46,6 @@ pnpm-debug.log
|
||||
|
||||
/cmd/frontend/internal/graphqlbackend/node_modules
|
||||
|
||||
/ui/artifacts
|
||||
/ui/node_modules
|
||||
/ui/assets/_goTemplates.js
|
||||
/ui/assets/analytics.js
|
||||
/ui/assets/bundle.js
|
||||
/ui/assets/bundle.css
|
||||
/ui/assets/sourcebox.js
|
||||
/ui/assets/sourcebox.css
|
||||
/ui/assets/test.js
|
||||
/ui/assets/test.css
|
||||
/ui/assets/vs
|
||||
/ui/assets/*.html
|
||||
/ui/assets/*.map
|
||||
/ui/assets/extension
|
||||
/ui/.tmp
|
||||
*.json.actual
|
||||
|
||||
eb-bundle.zip
|
||||
|
||||
@ -99,6 +99,8 @@ See https://handbook.sourcegraph.com/community/faq#is-all-of-sourcegraph-open-so
|
||||
'!@sourcegraph/branded/src/search-ui/experimental',
|
||||
'!@sourcegraph/*/src/testing',
|
||||
'!@sourcegraph/*/src/stories',
|
||||
'!@sourcegraph/build-config/src/esbuild/*',
|
||||
'!@sourcegraph/build-config/src/*',
|
||||
],
|
||||
message:
|
||||
'Imports from package internals are banned. Add relevant export to the entry point of the package to import it from the outside world.',
|
||||
|
||||
25
.gitignore
vendored
@ -55,29 +55,6 @@ pnpm-debug.log
|
||||
|
||||
/cmd/frontend/internal/graphqlbackend/node_modules
|
||||
|
||||
/ui/artifacts
|
||||
/ui/node_modules
|
||||
/ui/assets/_goTemplates.js
|
||||
/ui/assets/analytics.js
|
||||
/ui/assets/bundle.js
|
||||
/ui/assets/bundle.css
|
||||
/ui/assets/sourcebox.js
|
||||
/ui/assets/sourcebox.css
|
||||
/ui/assets/test.js
|
||||
/ui/assets/test.css
|
||||
/ui/assets/web.manifest.json
|
||||
/ui/assets/vs
|
||||
/ui/assets/*.html
|
||||
/ui/assets/*.map
|
||||
/ui/assets/*.txt
|
||||
/ui/assets/extension
|
||||
/ui/.tmp
|
||||
/ui/assets/scripts/
|
||||
/ui/assets/styles/
|
||||
/ui/assets/*.br
|
||||
/ui/assets/*.gz
|
||||
/ui/assets/stats-*
|
||||
|
||||
*.json.actual
|
||||
|
||||
eb-bundle.zip
|
||||
@ -116,7 +93,7 @@ out/
|
||||
client/shared/src/schema/*.d.ts
|
||||
puppeteer/
|
||||
package-lock.json
|
||||
/dist
|
||||
dist/
|
||||
sourcegraph-webapp-*.tgz
|
||||
*.tsbuildinfo
|
||||
graphql-operations.ts
|
||||
|
||||
@ -11,7 +11,6 @@ internal/database/migration/shared/data/**/*.json
|
||||
cmd/xlang-python/python-langserver/
|
||||
package-lock.json
|
||||
package.json
|
||||
ui/assets/
|
||||
src-tauri/assets/
|
||||
src-tauri/target/
|
||||
vendor/
|
||||
|
||||
13
.vscode/launch.json
vendored
@ -96,19 +96,6 @@
|
||||
],
|
||||
"smartStep": false
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Gulp",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js",
|
||||
"args": ["webpack"],
|
||||
"env": {
|
||||
// prettier-ignore
|
||||
"TS_NODE_COMPILER_OPTIONS": "{\"module\":\"commonjs\"}"
|
||||
},
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": "Webapp Chrome",
|
||||
"type": "chrome",
|
||||
|
||||
10
BUILD.bazel
@ -75,12 +75,6 @@ js_library(
|
||||
],
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "browserslist",
|
||||
srcs = [".browserslistrc"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "package_json",
|
||||
srcs = ["package.json"],
|
||||
@ -134,14 +128,10 @@ js_library(
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//:node_modules/@babel/plugin-transform-runtime",
|
||||
"//:node_modules/@babel/plugin-transform-typescript",
|
||||
"//:node_modules/@babel/preset-env",
|
||||
"//:node_modules/@babel/preset-react",
|
||||
"//:node_modules/@babel/preset-typescript",
|
||||
"//:node_modules/@babel/runtime",
|
||||
"//:node_modules/babel-plugin-lodash",
|
||||
"//:node_modules/babel-plugin-webpack-chunkname",
|
||||
"//:node_modules/semver",
|
||||
"//:node_modules/signale",
|
||||
],
|
||||
|
||||
21
WORKSPACE
@ -226,27 +226,6 @@ esbuild_register_toolchains(
|
||||
esbuild_version = LATEST_VERSION,
|
||||
)
|
||||
|
||||
# rules_webpack setup ===========================
|
||||
# Commit to include unreleased https://github.com/aspect-build/rules_webpack/commit/4a5f04a4bc504f71d32825124c7872ff721aa1b0
|
||||
http_archive(
|
||||
name = "aspect_rules_webpack",
|
||||
sha256 = "8d81f8d018127c72270ea4b7287be5c4ff63d9656a34334c305d52f14e0c922f",
|
||||
strip_prefix = "rules_webpack-4a5f04a4bc504f71d32825124c7872ff721aa1b0",
|
||||
url = "https://github.com/aspect-build/rules_webpack/archive/4a5f04a4bc504f71d32825124c7872ff721aa1b0.tar.gz",
|
||||
)
|
||||
|
||||
load("@aspect_rules_webpack//webpack:dependencies.bzl", "rules_webpack_dependencies")
|
||||
|
||||
rules_webpack_dependencies()
|
||||
|
||||
load("@aspect_rules_webpack//webpack:repositories.bzl", "webpack_repositories")
|
||||
|
||||
webpack_repositories(name = "webpack")
|
||||
|
||||
load("@webpack//:npm_repositories.bzl", webpack_npm_repositories = "npm_repositories")
|
||||
|
||||
webpack_npm_repositories()
|
||||
|
||||
# Go toolchain setup
|
||||
load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains")
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
// @ts-check
|
||||
const path = require('path')
|
||||
|
||||
const semver = require('semver')
|
||||
const logger = require('signale')
|
||||
|
||||
/** @type {import('@babel/core').ConfigFunction} */
|
||||
@ -36,25 +35,10 @@ module.exports = api => {
|
||||
{
|
||||
// Node (used for testing) doesn't support modules, so compile to CommonJS for testing.
|
||||
modules: process.env.BABEL_MODULE ?? (isTest ? 'commonjs' : false),
|
||||
bugfixes: true,
|
||||
useBuiltIns: 'entry',
|
||||
include: [
|
||||
// Polyfill URL because Chrome and Firefox are not spec-compliant
|
||||
// Hostnames of URIs with custom schemes (e.g. git) are not parsed out
|
||||
'web.url',
|
||||
// URLSearchParams.prototype.keys() is not iterable in Firefox
|
||||
'web.url-search-params',
|
||||
// Commonly needed by extensions (used by vscode-jsonrpc)
|
||||
'web.immediate',
|
||||
// Always define Symbol.observable before libraries are loaded, ensuring interopability between different libraries.
|
||||
'esnext.symbol.observable',
|
||||
],
|
||||
// See https://github.com/zloirock/core-js#babelpreset-env
|
||||
corejs: semver.minVersion(require('./package.json').dependencies['core-js']),
|
||||
},
|
||||
],
|
||||
]),
|
||||
'@babel/preset-typescript',
|
||||
['@babel/preset-typescript', { isTSX: true, allExtensions: true }],
|
||||
[
|
||||
'@babel/preset-react',
|
||||
{
|
||||
@ -62,27 +46,5 @@ module.exports = api => {
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-transform-runtime',
|
||||
['@babel/plugin-transform-typescript', { isTSX: true }],
|
||||
'babel-plugin-lodash',
|
||||
[
|
||||
'webpack-chunkname',
|
||||
{
|
||||
/**
|
||||
* Autogenerate `webpackChunkName` for dynamic imports.
|
||||
*
|
||||
* import('./pages/Home') -> import(/* webpackChunkName: 'sg_pages_Home' *\/'./pages/Home')
|
||||
*/
|
||||
getChunkName: (/** @type string */ importPath) => {
|
||||
const chunkName = importPath
|
||||
.replace(/[./]+/g, '_') // replace "." and "/" with "_".
|
||||
.replace(/(^_+)/g, '') // remove all leading "_".
|
||||
|
||||
return `sg_${chunkName}`
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,5 +27,3 @@
|
||||
5. **shared** contains utility functions, types, polyfills, etc which is not a part of the Wildcard component library. These modules should be moved into **utils** package and other new packages: e.g. **api** for GraphQL client and type generators, etc.
|
||||
|
||||
6. Packages should use package name (e.g. `@sourcegraph/wildcard`) for imports instead of the relative paths (e.g. `../../../../wildcard/src/components/Markdown`) to avoid long relative-paths and make dependency graph between packages clear. (Typescript will warn if packages have circular dependencies). It's easy to refactor such isolated packages, extract functionality into new ones, or even into new repositories.
|
||||
|
||||
7. **build** or **config** package should be added later to encapsulate all the configurations reused between packages which will allow removing `jest.config`, `babel.config` from the root of the repo.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { listen, Event } from '@tauri-apps/api/event'
|
||||
import { listen, type Event } from '@tauri-apps/api/event'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
import { logger } from '@sourcegraph/common'
|
||||
@ -8,7 +8,7 @@ import { logger } from '@sourcegraph/common'
|
||||
// * app-shell.tsx: before the Go backend has started, this is served. If the Go backend crashes,
|
||||
// then the Tauri Rust application can bring the user back here to present debugging/error handling
|
||||
// options.
|
||||
// * app-main.tsx: served by the Go backend, renders the Sourcegraph web UI that you see everywhere else.
|
||||
// * app/main.tsx: served by the Go backend, renders the Sourcegraph web UI that you see everywhere else.
|
||||
|
||||
function addRedirectParamToSignInUrl(url: string, returnTo: string): string {
|
||||
const urlObject = new URL(url)
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@babel/core').TransformOptions} */
|
||||
const config = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
@ -1,6 +0,0 @@
|
||||
# This file is used in our production build to adjust CSS and JS output to support specified browsers. For additional information regarding the format and rule options, please see: https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# Run `pnpm --filter @sourcegraph/browser browserslist` to see which browsers are targeted by these queries
|
||||
|
||||
last 3 Chrome versions
|
||||
last 3 Firefox versions
|
||||
@ -359,8 +359,6 @@ esbuild(
|
||||
":package_styles",
|
||||
|
||||
# BUNDLE CONFIG
|
||||
"//:babel_config",
|
||||
"//:browserslist",
|
||||
"//:package_json",
|
||||
|
||||
# STATIC ASSETS
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@babel/core').TransformOptions} */
|
||||
const config = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
@ -2,7 +2,8 @@ import path from 'path'
|
||||
|
||||
import type * as esbuild from 'esbuild'
|
||||
|
||||
import { ROOT_PATH, stylePlugin } from '@sourcegraph/build-config'
|
||||
import { ROOT_PATH } from '@sourcegraph/build-config'
|
||||
import { stylePlugin } from '@sourcegraph/build-config/src/esbuild/plugins'
|
||||
|
||||
import { generateBundleUID } from './utils'
|
||||
|
||||
|
||||
@ -127,10 +127,10 @@ export function copyIntegrationAssets(): void {
|
||||
shelljs.cp('build/dist/css/contentPage.main.bundle.css', 'build/integration/css')
|
||||
shelljs.cp('src/native-integration/extensionHostFrame.html', 'build/integration')
|
||||
copyInlineExtensions('build/integration')
|
||||
// Copy to the ui/assets directory so that these files can be served by
|
||||
// Copy to the dist directory so that these files can be served by
|
||||
// the webapp.
|
||||
shelljs.mkdir('-p', '../../ui/assets/extension')
|
||||
shelljs.cp('-r', 'build/integration/*', '../../ui/assets/extension')
|
||||
shelljs.mkdir('-p', '../../client/web/dist/extension')
|
||||
shelljs.cp('-r', 'build/integration/*', '../../client/web/dist/extension')
|
||||
}
|
||||
|
||||
const BROWSER_TITLES = {
|
||||
|
||||
@ -28,21 +28,15 @@ ts_project(
|
||||
"src/esbuild/stylePlugin.ts",
|
||||
"src/esbuild/workerPlugin.ts",
|
||||
"src/index.ts",
|
||||
"src/monaco-editor.ts",
|
||||
"src/paths.ts",
|
||||
"src/utils/environment-config.ts",
|
||||
"src/webpack/babel-loader.ts",
|
||||
"src/webpack/css-loader.ts",
|
||||
"src/webpack/getCacheConfig.ts",
|
||||
"src/webpack/monaco-editor.ts",
|
||||
"src/webpack/plugins.ts",
|
||||
],
|
||||
module = "commonjs",
|
||||
tsconfig = ":tsconfig",
|
||||
deps = [
|
||||
":node_modules/@statoscope/webpack-plugin",
|
||||
":node_modules/@types/sass",
|
||||
":node_modules/buffer", #keep
|
||||
":node_modules/css-loader", #keep
|
||||
":node_modules/enhanced-resolve",
|
||||
":node_modules/esbuild",
|
||||
":node_modules/monaco-editor-webpack-plugin",
|
||||
@ -50,9 +44,6 @@ ts_project(
|
||||
":node_modules/postcss-modules",
|
||||
":node_modules/process", #keep
|
||||
":node_modules/sass",
|
||||
":node_modules/style-loader", #keep
|
||||
":node_modules/terser-webpack-plugin",
|
||||
":node_modules/webpack",
|
||||
|
||||
# TODO: figure out why is this needed for integration tests even though it's defined in babel_config
|
||||
"//:node_modules/@babel/runtime", #keep
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -13,14 +13,7 @@
|
||||
"devDependencies": {
|
||||
"@types/sass": "1.16.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"css-loader": "*",
|
||||
"postcss-loader": "*",
|
||||
"sass-loader": "*",
|
||||
"style-loader": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@statoscope/webpack-plugin": "^5.24.0",
|
||||
"buffer": "^6.0.3",
|
||||
"enhanced-resolve": "^5.9.3",
|
||||
"esbuild": "^0.17.7",
|
||||
@ -31,8 +24,11 @@
|
||||
"process": "^0.11.10",
|
||||
"sass": "^1.32.4",
|
||||
"signale": "^1.4.0",
|
||||
"terser-webpack-plugin": "^5.3.6",
|
||||
"typed-scss-modules": "^4.1.1",
|
||||
"webpack": "^5.75.0"
|
||||
"typed-scss-modules": "^4.1.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": ["webpack"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,8 @@ import { type EditorFeature, featuresArr } from 'monaco-editor-webpack-plugin/ou
|
||||
// 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'
|
||||
import type { MONACO_LANGUAGES_AND_FEATURES } from '../webpack/monaco-editor'
|
||||
|
||||
const monacoModulePath = (modulePath: string): string =>
|
||||
require.resolve(path.join('monaco-editor/esm', modulePath), {
|
||||
@ -54,9 +54,7 @@ export const monacoPlugin = ({
|
||||
// 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. Our approach is fine for when esbuild is still an
|
||||
// optional prototype build method for local dev, but this implementation should be fixed if
|
||||
// we switch to esbuild by default.
|
||||
// ALWAYS_ENABLED_FEATURES hack above.
|
||||
build.onLoad({ filter }, () => ({ contents: '', loader: 'js' }))
|
||||
},
|
||||
})
|
||||
|
||||
@ -32,7 +32,7 @@ async function buildWorker(
|
||||
|
||||
/**
|
||||
* An esbuild plugin that bundles a Web Worker (classic, not a module worker) given an import of a
|
||||
* `.worker.ts` file. Similar to https://github.com/webpack-contrib/worker-loader.
|
||||
* `.worker.ts` file.
|
||||
*
|
||||
* TODO(sqs): This could be improved to use the new `new Worker(new URL(..., import.meta.url))`
|
||||
* worker syntax that many bundlers are starting to prefer.
|
||||
|
||||
@ -1,8 +1,2 @@
|
||||
export * from './esbuild/plugins'
|
||||
export * from './paths'
|
||||
export * from './webpack/babel-loader'
|
||||
export * from './webpack/css-loader'
|
||||
export * from './webpack/getCacheConfig'
|
||||
export * from './webpack/monaco-editor'
|
||||
export * from './webpack/plugins'
|
||||
export * from './utils/environment-config'
|
||||
|
||||
@ -1,21 +1,4 @@
|
||||
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'
|
||||
import type { WebpackPluginInstance, RuleSetRule } from 'webpack'
|
||||
|
||||
import { MONACO_EDITOR_PATH } from '../paths'
|
||||
|
||||
// CSS rule for monaco-editor and other external plain CSS (skip SASS and PostCSS for build perf)
|
||||
export const getMonacoCSSRule = (): RuleSetRule => ({
|
||||
test: /\.css$/,
|
||||
include: [MONACO_EDITOR_PATH],
|
||||
use: ['style-loader', { loader: 'css-loader' }],
|
||||
})
|
||||
|
||||
// TTF rule for monaco-editor
|
||||
export const getMonacoTTFRule = (): RuleSetRule => ({
|
||||
test: /\.ttf$/,
|
||||
include: [MONACO_EDITOR_PATH],
|
||||
type: 'asset/resource',
|
||||
})
|
||||
import type MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'
|
||||
|
||||
/**
|
||||
* Configuration for https://github.com/microsoft/monaco-editor-webpack-plugin.
|
||||
@ -48,6 +31,3 @@ export const MONACO_LANGUAGES_AND_FEATURES: Required<
|
||||
'suggest',
|
||||
],
|
||||
}
|
||||
|
||||
export const getMonacoWebpackPlugin = (): WebpackPluginInstance =>
|
||||
new MonacoWebpackPlugin(MONACO_LANGUAGES_AND_FEATURES)
|
||||
@ -5,11 +5,9 @@ import path from 'path'
|
||||
// TODO(bazel): drop when non-bazel removed.
|
||||
const IS_BAZEL = !!(process.env.JS_BINARY__TARGET || process.env.BAZEL_BINDIR || process.env.BAZEL_TEST)
|
||||
|
||||
// NOTE: use fs.realpathSync() in addition to path.resolve() to resolve
|
||||
// symlinks to the real path. This is required for webpack plugins using
|
||||
// `include: [...file path...]` when the file path contains symlinks such
|
||||
// as when using pnpm.
|
||||
export function resolveWithSymlink(...args: string[]): string {
|
||||
// NOTE: use fs.realpathSync() in addition to path.resolve() to resolve symlinks to the real path.
|
||||
// This canonicalizes the path, which avoids potential bugs in the frontend bundler step.
|
||||
function resolveWithSymlink(...args: string[]): string {
|
||||
const resolvedPath = path.resolve(...args)
|
||||
|
||||
try {
|
||||
@ -19,20 +17,12 @@ export function resolveWithSymlink(...args: string[]): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveAssetsPath(root: string): string {
|
||||
function resolveAssetsPath(root: string): string {
|
||||
if (IS_BAZEL && process.env.WEB_BUNDLE_PATH) {
|
||||
return resolveWithSymlink(root, process.env.WEB_BUNDLE_PATH)
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV && process.env.NODE_ENV === 'development') {
|
||||
return resolveWithSymlink(root, 'ui/assets')
|
||||
}
|
||||
|
||||
// With Bazel we changed how assets gets bundled. Previsouly, we would just put the assets at /ui/assets/.assets
|
||||
// and be done with it. With Bazel, we have different loaders on the backend where the assets gets embedded. So
|
||||
// what we do here is "simulate" what happens in bazel, by putting the assets in the correct relative directory
|
||||
// so that when the backend is compiled the assets gets embedded properly
|
||||
return resolveWithSymlink(root, 'ui/assets/enterprise')
|
||||
return resolveWithSymlink(root, 'client/web/dist')
|
||||
}
|
||||
|
||||
export const ROOT_PATH = IS_BAZEL ? process.cwd() : resolveWithSymlink(__dirname, '../../../')
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
import path from 'path'
|
||||
|
||||
import type { RuleSetRule } from 'webpack'
|
||||
|
||||
import { ROOT_PATH } from '../paths'
|
||||
|
||||
export const getBabelLoader = (): RuleSetRule => ({
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
configFile: path.join(ROOT_PATH, 'babel.config.js'),
|
||||
},
|
||||
})
|
||||
@ -1,50 +0,0 @@
|
||||
import path from 'path'
|
||||
|
||||
import type webpack from 'webpack'
|
||||
|
||||
import { ROOT_PATH, NODE_MODULES_PATH } from '../paths'
|
||||
|
||||
export const getBasicCSSLoader = (): webpack.RuleSetUseItem => ({
|
||||
loader: 'css-loader',
|
||||
options: { url: false },
|
||||
})
|
||||
|
||||
interface CSSModulesLoaderOptions {
|
||||
/**
|
||||
* Allow to enable/disable handling the CSS functions `url` and `image-set`.
|
||||
* Documentation: https://webpack.js.org/loaders/css-loader/#url.
|
||||
*/
|
||||
url?: boolean
|
||||
sourceMap?: boolean
|
||||
}
|
||||
|
||||
export const getCSSModulesLoader = ({ sourceMap, url }: CSSModulesLoaderOptions): webpack.RuleSetUseItem => ({
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap,
|
||||
modules: {
|
||||
exportLocalsConvention: 'camelCase',
|
||||
localIdentName: '[name]__[local]_[hash:base64:5]',
|
||||
},
|
||||
url,
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Generates array of CSS loaders both for regular CSS and CSS modules.
|
||||
* Useful to ensure that we use the same configuration for shared loaders: postcss-loader, sass-loader, etc.
|
||||
* */
|
||||
export const getCSSLoaders = (...loaders: webpack.RuleSetUseItem[]): webpack.RuleSetUse => [
|
||||
...loaders,
|
||||
'postcss-loader',
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sassOptions: {
|
||||
includePaths: [NODE_MODULES_PATH, path.resolve(ROOT_PATH, 'client')],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const getBazelCSSLoaders = (...loaders: webpack.RuleSetUseItem[]): webpack.RuleSetUse => loaders
|
||||
@ -1,26 +0,0 @@
|
||||
import path from 'path'
|
||||
|
||||
import type webpack from 'webpack'
|
||||
|
||||
import { ROOT_PATH } from '../paths'
|
||||
|
||||
// TODO(bazel): drop when non-bazel removed.
|
||||
const IS_BAZEL = !!(process.env.JS_BINARY__TARGET || process.env.BAZEL_BINDIR)
|
||||
|
||||
interface CacheConfigOptions {
|
||||
invalidateCacheFiles?: string[]
|
||||
}
|
||||
|
||||
export const getCacheConfig = ({ invalidateCacheFiles = [] }: CacheConfigOptions): webpack.Configuration['cache'] => ({
|
||||
type: 'filesystem',
|
||||
buildDependencies: {
|
||||
// Invalidate cache on config change.
|
||||
config: IS_BAZEL
|
||||
? invalidateCacheFiles
|
||||
: [
|
||||
...invalidateCacheFiles,
|
||||
path.resolve(ROOT_PATH, 'babel.config.js'),
|
||||
path.resolve(ROOT_PATH, 'postcss.config.js'),
|
||||
],
|
||||
},
|
||||
})
|
||||
@ -1,58 +0,0 @@
|
||||
import path from 'path'
|
||||
|
||||
import StatoscopeWebpackPlugin from '@statoscope/webpack-plugin'
|
||||
import TerserPlugin from 'terser-webpack-plugin'
|
||||
import webpack, { type StatsOptions } from 'webpack'
|
||||
|
||||
import { STATIC_ASSETS_PATH } from '../paths'
|
||||
|
||||
export const getTerserPlugin = (): TerserPlugin =>
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
compress: {
|
||||
// Don't inline functions, which causes name collisions with uglify-es:
|
||||
// https://github.com/mishoo/UglifyJS2/issues/2842
|
||||
inline: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const getProvidePlugin = (): webpack.ProvidePlugin =>
|
||||
new webpack.ProvidePlugin({
|
||||
// Adding the file extension is necessary to make importing this file
|
||||
// work inside JavaScript modules. The alternative is to set
|
||||
// `fullySpecified: false` (https://webpack.js.org/configuration/module/#resolvefullyspecified).
|
||||
process: require.resolve('process/browser.js'),
|
||||
// Based on the issue: https://github.com/webpack/changelog-v5/issues/10
|
||||
Buffer: [require.resolve('buffer/index.js'), 'Buffer'],
|
||||
})
|
||||
|
||||
const STATOSCOPE_STATS: StatsOptions = {
|
||||
all: false, // disable all the stats
|
||||
hash: true, // compilation hash
|
||||
entrypoints: true,
|
||||
chunks: true,
|
||||
chunkModules: true, // modules
|
||||
reasons: true, // modules reasons
|
||||
ids: true, // IDs of modules and chunks (webpack 5)
|
||||
dependentModules: true, // dependent modules of chunks (webpack 5)
|
||||
chunkRelations: true, // chunk parents, children and siblings (webpack 5)
|
||||
cachedAssets: true, // information about the cached assets (webpack 5)
|
||||
|
||||
nestedModules: true, // concatenated modules
|
||||
usedExports: true,
|
||||
providedExports: true, // provided imports
|
||||
assets: true,
|
||||
chunkOrigins: true, // chunks origins stats (to find out which modules require a chunk)
|
||||
version: true, // webpack version
|
||||
builtAt: true, // build at time
|
||||
timings: true, // modules timing information
|
||||
performance: true, // info about oversized assets
|
||||
}
|
||||
|
||||
export const getStatoscopePlugin = (name = '[name]'): StatoscopeWebpackPlugin =>
|
||||
new StatoscopeWebpackPlugin({
|
||||
statsOptions: STATOSCOPE_STATS as Record<string, unknown>,
|
||||
saveStatsTo: path.join(STATIC_ASSETS_PATH, `stats-${name}-[hash].json`),
|
||||
saveReportTo: path.join(STATIC_ASSETS_PATH, `report-${name}-[hash].html`),
|
||||
})
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -4,7 +4,12 @@ import path from 'path'
|
||||
import * as esbuild from 'esbuild'
|
||||
import { rm } from 'shelljs'
|
||||
|
||||
import { packageResolutionPlugin, stylePlugin, workerPlugin, buildTimerPlugin } from '@sourcegraph/build-config'
|
||||
import {
|
||||
packageResolutionPlugin,
|
||||
stylePlugin,
|
||||
workerPlugin,
|
||||
buildTimerPlugin,
|
||||
} from '@sourcegraph/build-config/src/esbuild/plugins'
|
||||
|
||||
const rootPath = path.resolve(__dirname, '../../../')
|
||||
const jetbrainsWorkspacePath = path.resolve(rootPath, 'client', 'jetbrains')
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -59,9 +59,9 @@ ts_project(
|
||||
jest_test(
|
||||
name = "test",
|
||||
data = [
|
||||
"src/webBundleSize/__mocks__/assets/scripts/app.bundle.js",
|
||||
"src/webBundleSize/__mocks__/assets/scripts/app.bundle.js.br",
|
||||
"src/webBundleSize/__mocks__/assets/scripts/app.bundle.js.gz",
|
||||
"src/webBundleSize/__mocks__/assets/scripts/main.js",
|
||||
"src/webBundleSize/__mocks__/assets/scripts/main.js.br",
|
||||
"src/webBundleSize/__mocks__/assets/scripts/main.js.gz",
|
||||
"src/webBundleSize/__mocks__/assets/scripts/sg_home.js",
|
||||
"src/webBundleSize/__mocks__/assets/scripts/sg_home.js.br",
|
||||
"src/webBundleSize/__mocks__/assets/scripts/sg_home.js.gz",
|
||||
@ -70,6 +70,6 @@ jest_test(
|
||||
"src/webBundleSize/__mocks__/assets/styles/app.123.bundle.css.gz",
|
||||
":observability-server_tests",
|
||||
],
|
||||
# TODO(bazel): requires webpack setup for testing
|
||||
# TODO(bazel): requires dev server setup for testing
|
||||
tags = ["manual"],
|
||||
)
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
111
|
||||
@ -1 +0,0 @@
|
||||
11111
|
||||
@ -1 +0,0 @@
|
||||
console.log(1)
|
||||
@ -1 +0,0 @@
|
||||
111
|
||||
@ -1 +0,0 @@
|
||||
11111
|
||||
@ -1 +0,0 @@
|
||||
111
|
||||
@ -1 +0,0 @@
|
||||
11111
|
||||
@ -9,11 +9,11 @@ jest.mock(
|
||||
() => ({
|
||||
files: [
|
||||
{
|
||||
path: MOCK_ASSETS_PATH + '/scripts/*.br',
|
||||
path: MOCK_ASSETS_PATH + '/scripts/*.js',
|
||||
maxSize: '10kb',
|
||||
},
|
||||
{
|
||||
path: MOCK_ASSETS_PATH + '/styles/*.br',
|
||||
path: MOCK_ASSETS_PATH + '/styles/*.css',
|
||||
maxSize: '10kb',
|
||||
},
|
||||
],
|
||||
@ -24,8 +24,8 @@ jest.mock(
|
||||
jest.mock(
|
||||
'web.manifest.json',
|
||||
() => ({
|
||||
'app.js': '/.assets/scripts/app.bundle.js',
|
||||
'app.css': '/.assets/styles/app.123.bundle.css',
|
||||
'main.js': '/.assets/scripts/main.js',
|
||||
'main.css': '/.assets/styles/main.css',
|
||||
}),
|
||||
{ virtual: true }
|
||||
)
|
||||
@ -39,33 +39,17 @@ describe('getBundleSizeStats', () => {
|
||||
})
|
||||
|
||||
expect(stats).toEqual({
|
||||
'scripts/app.bundle.js': {
|
||||
'scripts/main.js': {
|
||||
raw: 15,
|
||||
gzip: 6,
|
||||
brotli: 4,
|
||||
isInitial: true,
|
||||
isDynamicImport: false,
|
||||
isDefaultVendors: false,
|
||||
isCss: false,
|
||||
isJs: true,
|
||||
},
|
||||
'scripts/sg_home.js': {
|
||||
brotli: 4,
|
||||
gzip: 6,
|
||||
isCss: false,
|
||||
isDefaultVendors: false,
|
||||
isDynamicImport: true,
|
||||
isInitial: false,
|
||||
isJs: true,
|
||||
raw: 15,
|
||||
},
|
||||
'styles/app.123.bundle.css': {
|
||||
'styles/main.css': {
|
||||
raw: 25,
|
||||
gzip: 6,
|
||||
brotli: 4,
|
||||
isInitial: true,
|
||||
isDynamicImport: false,
|
||||
isDefaultVendors: false,
|
||||
isCss: true,
|
||||
isJs: false,
|
||||
},
|
||||
|
||||
@ -14,11 +14,8 @@ interface BundleSizeConfig {
|
||||
interface BundleSizeStats {
|
||||
[baseFilePath: string]: {
|
||||
raw: number
|
||||
gzip: number
|
||||
brotli: number
|
||||
isInitial: boolean
|
||||
isDynamicImport: boolean
|
||||
isDefaultVendors: boolean
|
||||
isCss: boolean
|
||||
isJs: boolean
|
||||
}
|
||||
@ -39,28 +36,21 @@ export function getBundleSizeStats(options: GetBundleSizeStatsOptions): BundleSi
|
||||
const webBuildManifest = require(webBuildManifestPath) as Record<string, string>
|
||||
|
||||
const initialResources = new Set(
|
||||
Object.values(webBuildManifest).map(resourcePath =>
|
||||
path.join(staticAssetsPath, resourcePath.replace('/.assets/', ''))
|
||||
)
|
||||
Object.values(webBuildManifest)
|
||||
.filter(value => typeof value === 'string')
|
||||
.map(resourcePath => path.join(staticAssetsPath, resourcePath.replace('/.assets/', '')))
|
||||
)
|
||||
|
||||
return bundleSizeConfig.files.reduce<BundleSizeStats>((result, file) => {
|
||||
const filePaths = glob.sync(file.path)
|
||||
|
||||
const fileStats = filePaths.reduce((fileStats, brotliFilePath) => {
|
||||
const { dir, name } = path.parse(brotliFilePath)
|
||||
const noCompressionFilePath = path.join(dir, name)
|
||||
const gzipFilePath = `${noCompressionFilePath}.gz`
|
||||
|
||||
const fileStats = filePaths.reduce((fileStats, noCompressionFilePath) => {
|
||||
const name = path.basename(noCompressionFilePath)
|
||||
return {
|
||||
...fileStats,
|
||||
[noCompressionFilePath.replace(`${staticAssetsPath}/`, '')]: {
|
||||
raw: statSync(noCompressionFilePath).size,
|
||||
gzip: statSync(gzipFilePath).size,
|
||||
brotli: statSync(brotliFilePath).size,
|
||||
isInitial: initialResources.has(noCompressionFilePath),
|
||||
isDynamicImport: name.startsWith('sg_'),
|
||||
isDefaultVendors: /\d+.chunk.js/.test(name),
|
||||
isDynamicImport: name.startsWith('chunk-'),
|
||||
isCss: path.parse(name).ext === '.css',
|
||||
isJs: path.parse(name).ext === '.js',
|
||||
},
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
* The script collects web application bundlesize information from the disk and uploads it to Honeycomb.
|
||||
*
|
||||
* 1. Build web application using:
|
||||
* NODE_ENV=production DISABLE_TYPECHECKING=true WEBPACK_USE_NAMED_CHUNKS=true pnpm build-web
|
||||
* NODE_ENV=production DISABLE_TYPECHECKING=true pnpm build-web
|
||||
*
|
||||
* 2. Upload bundlesize information to Honeycomb:
|
||||
* HONEYCOMB_API_KEY=XXX pnpm --filter @sourcegraph/observability-server run bundlesize:web:upload
|
||||
@ -48,11 +48,8 @@ for (const [baseFilePath, fileInfo] of Object.entries(bundleSizeStats)) {
|
||||
|
||||
'bundle.file.name': baseFilePath,
|
||||
'bundle.file.size.raw': fileInfo.raw,
|
||||
'bundle.file.size.gzip': fileInfo.gzip,
|
||||
'bundle.file.size.brotli': fileInfo.brotli,
|
||||
'bundle.file.isInitial': fileInfo.isInitial,
|
||||
'bundle.file.isDynamicImport': fileInfo.isDynamicImport,
|
||||
'bundle.file.isDefaultVendors': fileInfo.isDefaultVendors,
|
||||
'bundle.file.isCss': fileInfo.isCss,
|
||||
'bundle.file.isJs': fileInfo.isJs,
|
||||
'bundle.env': environment.NODE_ENV,
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@babel/core').TransformOptions} */
|
||||
const config = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
8
client/shared/src/globals.d.ts
vendored
@ -8,15 +8,13 @@ declare module '*.css' {
|
||||
}
|
||||
|
||||
/**
|
||||
* For Web Worker entrypoints using Webpack's worker-loader.
|
||||
*
|
||||
* See https://github.com/webpack-contrib/worker-loader#integrating-with-typescript.
|
||||
* For Web Worker entrypoints using workerPlugin.
|
||||
*/
|
||||
declare module '*.worker.ts' {
|
||||
class WebpackWorker extends Worker {
|
||||
class _Worker extends Worker {
|
||||
constructor()
|
||||
}
|
||||
export default WebpackWorker
|
||||
export default _Worker
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -179,10 +179,7 @@ function getDebugExpressionFromRegexp(tag: string, regexp: string): string {
|
||||
|
||||
// Console logs with these keywords will be removed from the console output.
|
||||
const MUTE_CONSOLE_KEYWORDS = [
|
||||
'[webpack-dev-server]',
|
||||
'Download the React DevTools',
|
||||
'[HMR]',
|
||||
'[WDS]',
|
||||
'Warning: componentWillReceiveProps has been renamed',
|
||||
'Download the Apollo DevTools',
|
||||
'Compiled in DEBUG mode',
|
||||
|
||||
@ -3,7 +3,7 @@ import React, { type Attributes, type PropsWithChildren, type PropsWithRef } fro
|
||||
/**
|
||||
* Returns a lazy-loaded reference to a React component in another module.
|
||||
*
|
||||
* This should be used in URL routes and anywhere else that Webpack code splitting can occur, to
|
||||
* This should be used in URL routes and anywhere else that code splitting can occur, to
|
||||
* avoid all referenced components being in the initial bundle.
|
||||
*
|
||||
* @param componentFactory Asynchronously imports the component's module; e.g., `() =>
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
module.exports = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
@ -10,7 +10,7 @@ import {
|
||||
workerPlugin,
|
||||
RXJS_RESOLUTIONS,
|
||||
buildTimerPlugin,
|
||||
} from '@sourcegraph/build-config'
|
||||
} from '@sourcegraph/build-config/src/esbuild/plugins'
|
||||
|
||||
const minify = process.env.NODE_ENV === 'production'
|
||||
const outdir = path.join(__dirname, '../dist')
|
||||
|
||||
@ -45,7 +45,7 @@ SRCS = [
|
||||
".env.oss",
|
||||
"//client/wildcard:sass-breakpoints",
|
||||
"//client/wildcard:global-style-sources",
|
||||
"//ui/assets/img:copy",
|
||||
"//client/web/dist/img:copy",
|
||||
] + glob(
|
||||
["src/" + d for d in [
|
||||
"**/*.scss",
|
||||
@ -133,7 +133,7 @@ vite_bin.vite(
|
||||
env = {
|
||||
"BAZEL": "1",
|
||||
},
|
||||
visibility = ["//ui/assets/enterprise:__pkg__"],
|
||||
visibility = ["//client/web/dist:__pkg__"],
|
||||
# silent_on_success = False,
|
||||
)
|
||||
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export { default as logoLight } from '$root/ui/assets/img/sourcegraph-logo-light.svg'
|
||||
export { default as logoDark } from '$root/ui/assets/img/sourcegraph-logo-dark.svg'
|
||||
export { default as mark } from '$root/ui/assets/img/sourcegraph-mark.svg'
|
||||
export { default as logoLight } from '$root/client/web/dist/img/sourcegraph-logo-light.svg'
|
||||
export { default as logoDark } from '$root/client/web/dist/img/sourcegraph-logo-dark.svg'
|
||||
export { default as mark } from '$root/client/web/dist/img/sourcegraph-mark.svg'
|
||||
|
||||
@ -6,16 +6,16 @@ import { vitePreprocess } from '@sveltejs/kit/vite'
|
||||
let adapter
|
||||
|
||||
if (process.env.BAZEL || process.env.DEPLOY_TYPE === 'dev') {
|
||||
// The folder inside ui/assets to write the production files to.
|
||||
// The folder to write the production files to.
|
||||
// We store the files in a separate folder to avoid any conflicts
|
||||
// with files generated by webpack.
|
||||
// with files generated by the web builder.
|
||||
const OUTPUT_DIR = '_sk'
|
||||
|
||||
let out = 'build/'
|
||||
if (process.env.DEPLOY_TYPE === 'dev' && !process.env.BAZEL) {
|
||||
// When DEPLOY_TYPE is set to 'dev' we copy output files to the
|
||||
// 'assets' folder where the web server reads them from
|
||||
out = '../../ui/assets/'
|
||||
out = '../../client/web/dist/'
|
||||
}
|
||||
|
||||
out += OUTPUT_DIR
|
||||
@ -97,7 +97,7 @@ export default config
|
||||
* web root directory.
|
||||
*
|
||||
* The SvelteKit artifacts however are deployed into a subdirectory,
|
||||
* to not conflict with any webpack artifacts, which means they are
|
||||
* to not conflict with any artifacts, which means they are
|
||||
* served from a different location than SvelteKit assumes.
|
||||
* In theory we could use the 'paths.assets' option to configure
|
||||
* this path, but at the moment it only accepts a fully-qualified
|
||||
|
||||
@ -5,7 +5,8 @@ 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", "jest_test", "npm_package", "sass", "ts_project")
|
||||
load("//dev:webpack.bzl", "webpack_bundle", "webpack_web_app")
|
||||
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")
|
||||
|
||||
@ -399,10 +400,10 @@ ts_project(
|
||||
"src/devsettings/settings/eventLoggingDebug.tsx",
|
||||
"src/devsettings/utils.ts",
|
||||
"src/enterprise/EnterpriseWebApp.tsx",
|
||||
"src/enterprise/app-main.tsx",
|
||||
"src/enterprise/app/AppAuthCallbackPage.tsx",
|
||||
"src/enterprise/app/components/index.ts",
|
||||
"src/enterprise/app/components/zero-states/AppZeroStates.tsx",
|
||||
"src/enterprise/app/main.tsx",
|
||||
"src/enterprise/app/routes.tsx",
|
||||
"src/enterprise/app/settings/AppSettingsArea.tsx",
|
||||
"src/enterprise/app/settings/about/AboutPage.tsx",
|
||||
@ -722,7 +723,7 @@ ts_project(
|
||||
"src/enterprise/dotcom/productSubscriptions/features.ts",
|
||||
"src/enterprise/embed/EmbeddedWebApp.tsx",
|
||||
"src/enterprise/embed/OpenNewTabAnchorLink.tsx",
|
||||
"src/enterprise/embed/main.tsx",
|
||||
"src/enterprise/embed/embedMain.tsx",
|
||||
"src/enterprise/executors/ExecutorsSiteAdminArea.tsx",
|
||||
"src/enterprise/executors/ExecutorsUserArea.tsx",
|
||||
"src/enterprise/executors/instances/ExecutorCompatibilityAlert.tsx",
|
||||
@ -2078,29 +2079,17 @@ jest_test(
|
||||
],
|
||||
)
|
||||
|
||||
# webpack dev environment -------------------
|
||||
WEBPACK_CONFIG_DEPS = [
|
||||
# esbuild dev environment -------------------
|
||||
ESBUILD_CONFIG_DEPS = [
|
||||
":node_modules/@sourcegraph/build-config",
|
||||
"//:node_modules/@babel/runtime",
|
||||
"//:node_modules/@pmmmwh/react-refresh-webpack-plugin",
|
||||
"//:node_modules/@sentry/webpack-plugin",
|
||||
"//:node_modules/buffer",
|
||||
"//:node_modules/css-loader",
|
||||
"//:node_modules/style-loader",
|
||||
"//:node_modules/css-minimizer-webpack-plugin",
|
||||
"//:node_modules/lodash",
|
||||
"//:node_modules/mini-css-extract-plugin",
|
||||
"//:node_modules/path-browserify",
|
||||
"//:node_modules/punycode",
|
||||
"//:node_modules/util",
|
||||
"//:node_modules/events",
|
||||
"//:node_modules/webpack-manifest-plugin",
|
||||
"//:node_modules/webpack-stats-plugin",
|
||||
"//:node_modules/monaco-editor",
|
||||
"//:node_modules/babel-loader",
|
||||
"//:node_modules/compression-webpack-plugin",
|
||||
"//:node_modules/webpack",
|
||||
"//:node_modules/react-refresh",
|
||||
"//:node_modules/esbuild",
|
||||
"//client/web/dev",
|
||||
"//:node_modules/react-visibility-sensor", # required for https://github.com/joshwnj/react-visibility-sensor/issues/148 workaround
|
||||
"//:postcss_config_js",
|
||||
@ -2117,62 +2106,51 @@ 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",
|
||||
]
|
||||
|
||||
[
|
||||
webpack_bundle(
|
||||
name = "bundle" if prod else "bundle-dev",
|
||||
srcs = BUNDLE_DATA_DEPS + [
|
||||
":web_lib",
|
||||
"//:babel_config",
|
||||
"//:browserslist",
|
||||
"//:package_json",
|
||||
],
|
||||
entry_points = {
|
||||
"src/enterprise/main.js": "app",
|
||||
"src/enterprise/embed/main.js": "embed",
|
||||
},
|
||||
env = {
|
||||
"NODE_ENV": "production" if prod else "development",
|
||||
"INTEGRATION_TESTS": "false",
|
||||
"WEBPACK_BUNDLE_ANALYZER": "false",
|
||||
"WEBPACK_USE_NAMED_CHUNKS": "false",
|
||||
},
|
||||
output_dir = True,
|
||||
visibility = ["//ui/assets:__subpackages__"],
|
||||
webpack_config = "webpack.bazel.config.js",
|
||||
deps = WEBPACK_CONFIG_DEPS,
|
||||
)
|
||||
for prod in [
|
||||
True,
|
||||
False,
|
||||
]
|
||||
]
|
||||
|
||||
# Used for integration tests and has bigger bundle size
|
||||
# because of the inlined source-maps.
|
||||
webpack_web_app(
|
||||
name = "app",
|
||||
esbuild(
|
||||
name = "bundle",
|
||||
srcs = BUNDLE_DATA_DEPS + [
|
||||
"//:babel_config",
|
||||
"//:browserslist",
|
||||
":web_lib",
|
||||
"//:package_json",
|
||||
],
|
||||
entry_points = {
|
||||
"src/enterprise/main.js": "app",
|
||||
config = "//client/web/dev:esbuild-config",
|
||||
define = {
|
||||
"process.env.NODE_ENV": "\"production\"",
|
||||
},
|
||||
env = {
|
||||
"NODE_ENV": "production",
|
||||
"INTEGRATION_TESTS": "true",
|
||||
entry_points = [
|
||||
"src/enterprise/main.js",
|
||||
"src/enterprise/embed/embedMain.js",
|
||||
],
|
||||
minify = True,
|
||||
splitting = True,
|
||||
visibility = ["//client/web/dist:__subpackages__"],
|
||||
deps = ESBUILD_CONFIG_DEPS,
|
||||
)
|
||||
|
||||
# Used for integration tests.
|
||||
esbuild_web_app(
|
||||
name = "app",
|
||||
srcs = BUNDLE_DATA_DEPS + [
|
||||
":web_lib",
|
||||
"//:package_json",
|
||||
],
|
||||
config = "//client/web/dev:esbuild-config",
|
||||
define = {
|
||||
"process.env.NODE_ENV": "\"production\"",
|
||||
"process.env.INTEGRATION_TESTS": "true",
|
||||
},
|
||||
output_dir = True,
|
||||
entry_points = [
|
||||
"src/enterprise/main.js",
|
||||
"src/enterprise/embed/embedMain.js",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
webpack_config = "webpack.bazel.config.js",
|
||||
deps = WEBPACK_CONFIG_DEPS,
|
||||
deps = ESBUILD_CONFIG_DEPS,
|
||||
)
|
||||
|
||||
build_test(
|
||||
name = "webpack_test",
|
||||
name = "esbuild_test",
|
||||
size = "enormous",
|
||||
targets = [
|
||||
":bundle",
|
||||
|
||||
@ -10,7 +10,7 @@ Our local development server runs by starting both a [Caddy](https://caddyserver
|
||||
|
||||
Environment variables important for the web server:
|
||||
|
||||
1. `WEB_BUILDER_SERVE_INDEX` should be set to `true` to enable `HTMLWebpackPlugin`.
|
||||
1. `WEB_BUILDER_SERVE_INDEX` should be set to `true` to enable serving of an index page.
|
||||
2. `SOURCEGRAPH_API_URL` is used as a proxied API url. By default it points to the [https://k8s.sgdev.org](https://k8s.sgdev.org).
|
||||
|
||||
It's possible to overwrite these variables by creating `sg.config.overwrite.yaml` in the root folder and adjusting the `env` section of the relevant command.
|
||||
@ -21,12 +21,6 @@ It's possible to overwrite these variables by creating `sg.config.overwrite.yaml
|
||||
sg start web-standalone
|
||||
```
|
||||
|
||||
For open-source version:
|
||||
|
||||
```sh
|
||||
sg start oss-web-standalone
|
||||
```
|
||||
|
||||
#### Public API
|
||||
|
||||
To use a public API that doesn't require authentication for most of the functionality:
|
||||
@ -35,32 +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
|
||||
```
|
||||
|
||||
For open-source version:
|
||||
|
||||
```sh
|
||||
SOURCEGRAPH_API_URL=https://sourcegraph.com sg start oss-web-standalone
|
||||
```
|
||||
|
||||
### Production server
|
||||
|
||||
```sh
|
||||
sg start web-standalone-prod
|
||||
```
|
||||
|
||||
For open-source version:
|
||||
|
||||
```sh
|
||||
sg start oss-web-standalone-prod
|
||||
```
|
||||
|
||||
Web app should be available at `https://${SOURCEGRAPH_HTTPS_DOMAIN}:${SOURCEGRAPH_HTTPS_PORT}`. Build artifacts will be served from `<rootRepoPath>/ui/assets`.
|
||||
Web app should be available at `https://${SOURCEGRAPH_HTTPS_DOMAIN}:${SOURCEGRAPH_HTTPS_PORT}`. Build artifacts will be served from `<rootRepoPath>/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.
|
||||
|
||||
### esbuild (experimental)
|
||||
|
||||
See https://docs.sourcegraph.com/dev/background-information/web/build#esbuild.
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@babel/core').TransformOptions} */
|
||||
const config = {
|
||||
extends: '../../babel.config.js',
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
@ -1,13 +1,6 @@
|
||||
const path = require('path')
|
||||
|
||||
function relativeAssets(base) {
|
||||
if (process.env.NODE_ENV !== undefined && process.env.NODE_ENV === 'development') {
|
||||
return path.join(base, '../../ui/assets')
|
||||
}
|
||||
return path.join(base, '../../ui/assets/enterprise')
|
||||
}
|
||||
|
||||
const STATIC_ASSETS_PATH = process.env.WEB_BUNDLE_PATH || relativeAssets(__dirname)
|
||||
const STATIC_ASSETS_PATH = process.env.WEB_BUNDLE_PATH || path.join(__dirname, 'dist')
|
||||
|
||||
const config = {
|
||||
files: [
|
||||
@ -15,75 +8,39 @@ const config = {
|
||||
* Our main entry JavaScript bundles, contains core logic that is loaded on every page.
|
||||
*/
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, 'scripts/app.*.bundle.js.br'),
|
||||
path: path.join(STATIC_ASSETS_PATH, 'main*.js'),
|
||||
/**
|
||||
* Note: Temporary increase from 400kb.
|
||||
* Primary cause is due to multiple ongoing migrations that mean we are duplicating similar dependencies.
|
||||
* Issue to track: https://github.com/sourcegraph/sourcegraph/issues/37845
|
||||
*/
|
||||
maxSize: '500kb',
|
||||
compression: 'none',
|
||||
},
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, 'scripts/embed.*.bundle.js.br'),
|
||||
path: path.join(STATIC_ASSETS_PATH, 'embedMain*.js'),
|
||||
maxSize: '155kb',
|
||||
compression: 'none',
|
||||
},
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, 'scripts/react.*.bundle.js.br'),
|
||||
maxSize: '45kb',
|
||||
compression: 'none',
|
||||
},
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, 'scripts/opentelemetry.*.bundle.js.br'),
|
||||
maxSize: '40kb',
|
||||
compression: 'none',
|
||||
},
|
||||
/**
|
||||
* Our generated application chunks. Matches the deterministic id generated by Webpack.
|
||||
*
|
||||
* Note: The vast majority of our chunks are under 200kb, this threshold is bloated as we treat the Monaco editor as a normal chunk.
|
||||
* We should consider not doing this, as it is much larger than other chunks and we would likely benefit from caching this differently.
|
||||
* Issue to improve this: https://github.com/sourcegraph/sourcegraph/issues/26573
|
||||
*/
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, 'scripts/!(sg_)*.chunk.js.br'),
|
||||
maxSize: '500kb',
|
||||
compression: 'none',
|
||||
},
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, 'scripts/sg_*.chunk.js.br'),
|
||||
maxSize: '200kb',
|
||||
compression: 'none',
|
||||
},
|
||||
/**
|
||||
* Our generated worker files.
|
||||
*/
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, '*.worker.js.br'),
|
||||
maxSize: '250kb',
|
||||
compression: 'none',
|
||||
path: path.join(STATIC_ASSETS_PATH, 'chunks/chunk-*.js'),
|
||||
maxSize: '600kb', // 2 monaco chunks are very big
|
||||
},
|
||||
/**
|
||||
* Our main entry CSS bundle, contains core styles that are loaded on every page.
|
||||
*/
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, 'styles/app.*.css.br'),
|
||||
maxSize: '50kb',
|
||||
compression: 'none',
|
||||
path: path.join(STATIC_ASSETS_PATH, 'main*.css'),
|
||||
maxSize: '350kb',
|
||||
},
|
||||
/**
|
||||
* Notebook embed main entry CSS bundle.
|
||||
*/
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, 'styles/embed.*.css.br'),
|
||||
maxSize: '35kb',
|
||||
compression: 'none',
|
||||
path: path.join(STATIC_ASSETS_PATH, 'embedMain*.css'),
|
||||
maxSize: '350kb',
|
||||
},
|
||||
{
|
||||
path: path.join(STATIC_ASSETS_PATH, 'styles/!(app|embed).*.css.br'),
|
||||
maxSize: '25kb',
|
||||
compression: 'none',
|
||||
path: path.join(STATIC_ASSETS_PATH, 'chunks/chunk-*.css'),
|
||||
maxSize: '45kb',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
load("@aspect_rules_js//js:defs.bzl", "js_library")
|
||||
load("@aspect_rules_ts//ts:defs.bzl", "ts_config")
|
||||
load("//dev:defs.bzl", "ts_project")
|
||||
|
||||
@ -5,9 +6,6 @@ load("//dev:defs.bzl", "ts_project")
|
||||
# gazelle:js_files **/*.{ts,tsx}
|
||||
# gazelle:js_test_files there-are-no-tests
|
||||
|
||||
# TODO(bazel): wip - currently broken
|
||||
# gazelle:exclude **/server/development.server.ts
|
||||
|
||||
ts_config(
|
||||
name = "tsconfig",
|
||||
src = "tsconfig.json",
|
||||
@ -20,8 +18,13 @@ ts_config(
|
||||
ts_project(
|
||||
name = "dev",
|
||||
srcs = [
|
||||
"esbuild/build.ts",
|
||||
"esbuild/manifest.test.ts",
|
||||
"esbuild/manifest.ts",
|
||||
"esbuild/manifestPlugin.ts",
|
||||
"esbuild/server.ts",
|
||||
"mocks/mockEventLogger.ts",
|
||||
"server/bazel.server.ts",
|
||||
"server/development.server.ts",
|
||||
"server/production.server.ts",
|
||||
"utils/constants.ts",
|
||||
"utils/create-js-context.ts",
|
||||
@ -37,6 +40,7 @@ ts_project(
|
||||
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",
|
||||
@ -47,15 +51,25 @@ ts_project(
|
||||
"//: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/webpack",
|
||||
"//:node_modules/webpack-dev-server",
|
||||
"//client/web:node_modules/@sourcegraph/build-config",
|
||||
"//client/web:web_lib",
|
||||
],
|
||||
)
|
||||
|
||||
js_library(
|
||||
name = "esbuild-config",
|
||||
srcs = ["esbuild/esbuild.bazel.js"],
|
||||
visibility = ["//client:__subpackages__"],
|
||||
deps = [
|
||||
"//client/build-config:build-config_lib",
|
||||
"//client/web:node_modules/@sourcegraph/build-config",
|
||||
"//client/web/dev",
|
||||
],
|
||||
)
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
# TODO(bazel): esbuild dev tools currently disabled in bazel
|
||||
# See client/build-config/src/esbuild/BUILD.bazel
|
||||
|
||||
# gazelle:js disabled
|
||||
@ -4,18 +4,16 @@ import path from 'path'
|
||||
import { sentryEsbuildPlugin } from '@sentry/esbuild-plugin'
|
||||
import * as esbuild from 'esbuild'
|
||||
|
||||
import { ROOT_PATH, STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
import {
|
||||
MONACO_LANGUAGES_AND_FEATURES,
|
||||
ROOT_PATH,
|
||||
STATIC_ASSETS_PATH,
|
||||
stylePlugin,
|
||||
packageResolutionPlugin,
|
||||
monacoPlugin,
|
||||
RXJS_RESOLUTIONS,
|
||||
buildMonaco,
|
||||
buildTimerPlugin,
|
||||
} from '@sourcegraph/build-config'
|
||||
import { isDefined } from '@sourcegraph/common'
|
||||
} from '@sourcegraph/build-config/src/esbuild/plugins'
|
||||
import { MONACO_LANGUAGES_AND_FEATURES } from '@sourcegraph/build-config/src/monaco-editor'
|
||||
|
||||
import { ENVIRONMENT_CONFIG, IS_DEVELOPMENT, IS_PRODUCTION } from '../utils'
|
||||
|
||||
@ -25,20 +23,22 @@ const isCodyApp = ENVIRONMENT_CONFIG.CODY_APP
|
||||
const omitSlowDeps = ENVIRONMENT_CONFIG.DEV_WEB_BUILDER_OMIT_SLOW_DEPS
|
||||
|
||||
export const BUILD_OPTIONS: esbuild.BuildOptions = {
|
||||
entryPoints: {
|
||||
'scripts/app': isCodyApp
|
||||
? path.join(ROOT_PATH, 'client/web/src/enterprise/app-main.tsx')
|
||||
: path.join(ROOT_PATH, 'client/web/src/enterprise/main.tsx'),
|
||||
},
|
||||
entryPoints: isCodyApp
|
||||
? [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: IS_PRODUCTION,
|
||||
|
||||
format: 'esm',
|
||||
logLevel: 'error',
|
||||
jsx: 'automatic',
|
||||
jsxDev: IS_DEVELOPMENT,
|
||||
splitting: true,
|
||||
chunkNames: 'chunks/chunk-[name]-[hash]',
|
||||
entryNames: IS_PRODUCTION ? 'scripts/[name]-[hash]' : undefined,
|
||||
entryNames: '[name]-[hash]',
|
||||
outdir: STATIC_ASSETS_PATH,
|
||||
plugins: [
|
||||
stylePlugin,
|
||||
@ -74,10 +74,10 @@ export const BUILD_OPTIONS: esbuild.BuildOptions = {
|
||||
authToken: ENVIRONMENT_CONFIG.SENTRY_DOT_COM_AUTH_TOKEN,
|
||||
silent: true,
|
||||
release: { name: `frontend@${ENVIRONMENT_CONFIG.VERSION}` },
|
||||
sourcemaps: { assets: path.join(ENVIRONMENT_CONFIG.STATIC_ASSETS_PATH, 'scripts', '*.map') },
|
||||
sourcemaps: { assets: [path.join('dist', '*.map'), path.join('dist', 'chunks', '*.map')] },
|
||||
})
|
||||
: null,
|
||||
].filter(isDefined),
|
||||
].filter((plugin): plugin is esbuild.Plugin => plugin !== null),
|
||||
define: {
|
||||
...Object.fromEntries(
|
||||
Object.entries({ ...ENVIRONMENT_CONFIG, SOURCEGRAPH_API_URL: undefined }).map(([key, value]) => [
|
||||
@ -98,20 +98,30 @@ export const BUILD_OPTIONS: esbuild.BuildOptions = {
|
||||
}
|
||||
|
||||
export const build = async (): Promise<void> => {
|
||||
if (!BUILD_OPTIONS.outdir) {
|
||||
throw new Error('no outdir')
|
||||
}
|
||||
|
||||
const metafile = process.env.ESBUILD_METAFILE
|
||||
const result = await esbuild.build({
|
||||
const options: esbuild.BuildOptions = {
|
||||
...BUILD_OPTIONS,
|
||||
outdir: STATIC_ASSETS_PATH,
|
||||
metafile: Boolean(metafile),
|
||||
})
|
||||
}
|
||||
const result = await esbuild.build(options)
|
||||
if (metafile) {
|
||||
writeFileSync(metafile, JSON.stringify(result.metafile), 'utf-8')
|
||||
}
|
||||
if (!omitSlowDeps) {
|
||||
const ctx = await buildMonaco(STATIC_ASSETS_PATH)
|
||||
const ctx = await buildMonaco(BUILD_OPTIONS.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) {
|
||||
|
||||
18
client/web/dev/esbuild/esbuild.bazel.js
Normal file
@ -0,0 +1,18 @@
|
||||
// This file is only used by Bazel builds.
|
||||
|
||||
const { BUILD_OPTIONS } = require('./build.js')
|
||||
|
||||
module.exports = {
|
||||
// TODO(sqs): does not support Cody app build
|
||||
...BUILD_OPTIONS,
|
||||
|
||||
// Unset configuration properties that are provided by Bazel.
|
||||
entryPoints: undefined,
|
||||
bundle: undefined,
|
||||
outdir: undefined,
|
||||
sourcemap: undefined,
|
||||
splitting: undefined,
|
||||
|
||||
// Bazel's esbuild rule can't set process.env.NODE_ENV, so we need to override this here.
|
||||
jsxDev: false,
|
||||
}
|
||||
64
client/web/dev/esbuild/manifest.test.ts
Normal file
@ -0,0 +1,64 @@
|
||||
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<WebBuildManifest>({
|
||||
'main.js': 'main-AAA.js',
|
||||
'main.css': 'main-BBB.css',
|
||||
'embed.js': 'embedMain-CCC.js',
|
||||
'embed.css': 'embedMain-DDD.css',
|
||||
}))
|
||||
|
||||
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<WebBuildManifest>({
|
||||
'main.js': 'main-AAA.js',
|
||||
'main.css': 'main-BBB.css',
|
||||
'embed.js': 'embedMain-CCC.js',
|
||||
'embed.css': 'embedMain-DDD.css',
|
||||
}))
|
||||
})
|
||||
90
client/web/dev/esbuild/manifest.ts
Normal file
@ -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<WebBuildManifest> = {}
|
||||
|
||||
// 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
|
||||
}
|
||||
@ -3,66 +3,48 @@ import path from 'path'
|
||||
|
||||
import type * as esbuild from 'esbuild'
|
||||
|
||||
import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
import { type WebBuildManifest, WEB_BUILD_MANIFEST_PATH } from '../utils'
|
||||
|
||||
export const assetPathPrefix = '/.assets'
|
||||
|
||||
export const getManifest = (jsEntrypoint?: string, cssEntrypoint?: string): WebBuildManifest => ({
|
||||
'app.js': path.join(assetPathPrefix, jsEntrypoint ?? 'scripts/app.js'),
|
||||
'app.css': path.join(assetPathPrefix, cssEntrypoint ?? 'scripts/app.css'),
|
||||
isModule: true,
|
||||
})
|
||||
|
||||
const writeManifest = async (manifest: WebBuildManifest): Promise<void> => {
|
||||
await fs.promises.writeFile(WEB_BUILD_MANIFEST_PATH, JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
const ENTRYPOINT_NAME = 'scripts/app'
|
||||
import { WEB_BUILD_MANIFEST_FILENAME, createManifestFromBuildResult } from './manifest'
|
||||
|
||||
/**
|
||||
* An esbuild plugin to write a web.manifest.json file (just as Webpack does), for compatibility
|
||||
* with our current Webpack build.
|
||||
* 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 => {
|
||||
console.log(process.cwd())
|
||||
const { entryPoints } = build.initialOptions
|
||||
const outputs = result?.metafile?.outputs
|
||||
|
||||
if (!entryPoints) {
|
||||
console.error('[manifestPlugin] No entrypoints found')
|
||||
return
|
||||
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
|
||||
}
|
||||
const absoluteEntrypoint: string | undefined = (entryPoints as any)[ENTRYPOINT_NAME]
|
||||
if (!absoluteEntrypoint) {
|
||||
console.error('[manifestPlugin] No entrypoint found with the name scripts/app')
|
||||
return
|
||||
|
||||
if (!checkEntryPoints(entryPoints)) {
|
||||
throw new Error('[manifestPlugin] Unexpected entryPoints format')
|
||||
}
|
||||
|
||||
const { outdir } = build.initialOptions
|
||||
if (!outdir) {
|
||||
throw new Error('[manifestPlugin] No outdir found')
|
||||
}
|
||||
const relativeEntrypoint = path.relative(process.cwd(), absoluteEntrypoint)
|
||||
|
||||
if (!outputs) {
|
||||
return
|
||||
}
|
||||
let jsEntrypoint: string | undefined
|
||||
let cssEntrypoint: string | undefined
|
||||
|
||||
// Find the entrypoint in the output files
|
||||
for (const [asset, output] of Object.entries(outputs)) {
|
||||
if (output.entryPoint === relativeEntrypoint) {
|
||||
jsEntrypoint = path.relative(STATIC_ASSETS_PATH, asset)
|
||||
if (output.cssBundle) {
|
||||
cssEntrypoint = path.relative(STATIC_ASSETS_PATH, output.cssBundle)
|
||||
}
|
||||
}
|
||||
throw new Error('[manifestPlugin] No outputs found')
|
||||
}
|
||||
|
||||
await writeManifest(getManifest(jsEntrypoint, cssEntrypoint))
|
||||
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'
|
||||
}
|
||||
|
||||
@ -5,12 +5,13 @@ import express from 'express'
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware'
|
||||
import signale from 'signale'
|
||||
|
||||
import { STATIC_ASSETS_PATH, buildMonaco } from '@sourcegraph/build-config'
|
||||
import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
import { buildMonaco } from '@sourcegraph/build-config/src/esbuild/monacoPlugin'
|
||||
|
||||
import { ENVIRONMENT_CONFIG, HTTPS_WEB_SERVER_URL, printSuccessBanner } from '../utils'
|
||||
|
||||
import { BUILD_OPTIONS } from './build'
|
||||
import { assetPathPrefix } from './manifestPlugin'
|
||||
import { assetPathPrefix } from './manifest'
|
||||
|
||||
export const esbuildDevelopmentServer = async (
|
||||
listenAddress: { host: string; port: number },
|
||||
@ -44,7 +45,7 @@ export const esbuildDevelopmentServer = async (
|
||||
createProxyMiddleware({
|
||||
target: { protocol: 'http:', host: esbuildHost, port: esbuildPort },
|
||||
pathRewrite: { [`^${assetPathPrefix}`]: '' },
|
||||
onProxyRes: (proxyResponse, request, response) => {
|
||||
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) {
|
||||
@ -58,7 +59,7 @@ export const esbuildDevelopmentServer = async (
|
||||
|
||||
const proxyServer = proxyApp.listen(listenAddress)
|
||||
// eslint-disable-next-line @typescript-eslint/return-await
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
return await new Promise<void>((_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}`])
|
||||
|
||||
12
client/web/dev/jest.config.js
Normal file
@ -0,0 +1,12 @@
|
||||
// @ts-check
|
||||
const config = require('../../../jest.config.base')
|
||||
|
||||
/** @type {import('@jest/types').Config.InitialOptions} */
|
||||
const exportedConfig = {
|
||||
...config,
|
||||
displayName: 'web-dev',
|
||||
rootDir: __dirname,
|
||||
roots: ['<rootDir>'],
|
||||
}
|
||||
|
||||
module.exports = exportedConfig
|
||||
@ -1,69 +0,0 @@
|
||||
import compression from 'compression'
|
||||
import type WebpackDevServer from 'webpack-dev-server'
|
||||
|
||||
import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
import {
|
||||
ENVIRONMENT_CONFIG,
|
||||
getAPIProxySettings,
|
||||
getIndexHTML,
|
||||
getWebBuildManifest,
|
||||
shouldCompressResponse,
|
||||
STATIC_ASSETS_URL,
|
||||
} from '../utils'
|
||||
|
||||
const { SOURCEGRAPH_API_URL, SOURCEGRAPH_HTTPS_PORT, SOURCEGRAPH_HTTP_PORT } = ENVIRONMENT_CONFIG
|
||||
|
||||
export function createDevelopmentServerConfig(): WebpackDevServer.Configuration {
|
||||
console.log(ENVIRONMENT_CONFIG)
|
||||
// if (!SOURCEGRAPH_API_URL) {
|
||||
// throw new Error('bazel.server.ts only supports *web-standalone* usage')
|
||||
// }
|
||||
|
||||
const apiURL = SOURCEGRAPH_API_URL!
|
||||
|
||||
const { proxyRoutes, ...proxyConfig } = getAPIProxySettings({
|
||||
apiURL,
|
||||
getLocalIndexHTML(jsContextScript) {
|
||||
const manifestFile = getWebBuildManifest()
|
||||
return getIndexHTML({ manifestFile, jsContextScript })
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
// react-refresh plugin triggers page reload if needed.
|
||||
liveReload: false,
|
||||
allowedHosts: 'all',
|
||||
hot: true,
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
},
|
||||
port: SOURCEGRAPH_HTTP_PORT,
|
||||
client: {
|
||||
overlay: false,
|
||||
webSocketTransport: 'ws',
|
||||
logging: 'verbose',
|
||||
webSocketURL: {
|
||||
port: SOURCEGRAPH_HTTPS_PORT,
|
||||
protocol: 'wss',
|
||||
},
|
||||
},
|
||||
static: {
|
||||
directory: STATIC_ASSETS_PATH,
|
||||
publicPath: [STATIC_ASSETS_URL, '/'],
|
||||
},
|
||||
proxy: [
|
||||
{
|
||||
context: proxyRoutes,
|
||||
...proxyConfig,
|
||||
},
|
||||
],
|
||||
// Disable default DevServer compression. We need more fine grained compression to support streaming search.
|
||||
compress: false,
|
||||
setupMiddlewares: (middlewares, developmentServer) => {
|
||||
// Re-enable gzip compression using our own `compression` filter.
|
||||
developmentServer.app!.use(compression({ filter: shouldCompressResponse }))
|
||||
return middlewares
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,140 +1,39 @@
|
||||
import compression from 'compression'
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware'
|
||||
import { once } from 'lodash'
|
||||
import signale from 'signale'
|
||||
import createWebpackCompiler, { type Configuration } from 'webpack'
|
||||
import WebpackDevServer from 'webpack-dev-server'
|
||||
|
||||
import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
import { getManifest } from '../esbuild/manifestPlugin'
|
||||
import { esbuildDevelopmentServer } from '../esbuild/server'
|
||||
import {
|
||||
ENVIRONMENT_CONFIG,
|
||||
getAPIProxySettings,
|
||||
getIndexHTML,
|
||||
getWebBuildManifest,
|
||||
HTTP_WEB_SERVER_URL,
|
||||
HTTPS_WEB_SERVER_URL,
|
||||
printSuccessBanner,
|
||||
shouldCompressResponse,
|
||||
STATIC_ASSETS_URL,
|
||||
} from '../utils'
|
||||
import { ENVIRONMENT_CONFIG, getAPIProxySettings, getIndexHTML, getWebBuildManifest } from '../utils'
|
||||
|
||||
// TODO: migrate webpack.config.js to TS to use `import` in this file.
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const webpackConfig = require('../../webpack.config') as Configuration
|
||||
|
||||
const { SOURCEGRAPH_API_URL, SOURCEGRAPH_HTTPS_PORT, SOURCEGRAPH_HTTP_PORT } = ENVIRONMENT_CONFIG
|
||||
const { SOURCEGRAPH_API_URL, SOURCEGRAPH_HTTP_PORT } = ENVIRONMENT_CONFIG
|
||||
|
||||
interface DevelopmentServerInit {
|
||||
apiURL: string
|
||||
}
|
||||
|
||||
async function startDevelopmentServer(): Promise<void> {
|
||||
signale.start(`Starting ${ENVIRONMENT_CONFIG.DEV_WEB_BUILDER} dev server.`, ENVIRONMENT_CONFIG)
|
||||
signale.start('Starting dev server.', ENVIRONMENT_CONFIG)
|
||||
|
||||
if (!SOURCEGRAPH_API_URL) {
|
||||
throw new Error('development.server.ts only supports *web-standalone* usage')
|
||||
}
|
||||
|
||||
const init: DevelopmentServerInit = {
|
||||
await startEsbuildDevelopmentServer({
|
||||
apiURL: SOURCEGRAPH_API_URL,
|
||||
}
|
||||
|
||||
switch (ENVIRONMENT_CONFIG.DEV_WEB_BUILDER) {
|
||||
case 'webpack':
|
||||
await startWebpackDevelopmentServer(init)
|
||||
break
|
||||
|
||||
case 'esbuild':
|
||||
await startEsbuildDevelopmentServer(init)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async function startWebpackDevelopmentServer({ apiURL }: DevelopmentServerInit): Promise<void> {
|
||||
const compiler = createWebpackCompiler(webpackConfig)
|
||||
|
||||
const { proxyRoutes, ...proxyConfig } = getAPIProxySettings({
|
||||
apiURL,
|
||||
getLocalIndexHTML(jsContextScript) {
|
||||
const manifestFile = getWebBuildManifest()
|
||||
return getIndexHTML({ manifestFile, jsContextScript })
|
||||
},
|
||||
})
|
||||
|
||||
const developmentServerConfig: WebpackDevServer.Configuration = {
|
||||
// react-refresh plugin triggers page reload if needed.
|
||||
liveReload: false,
|
||||
allowedHosts: 'all',
|
||||
hot: true,
|
||||
historyApiFallback: {
|
||||
disableDotRule: true,
|
||||
},
|
||||
port: SOURCEGRAPH_HTTP_PORT,
|
||||
client: {
|
||||
overlay: false,
|
||||
webSocketTransport: 'ws',
|
||||
logging: 'verbose',
|
||||
webSocketURL: {
|
||||
port: SOURCEGRAPH_HTTPS_PORT,
|
||||
protocol: 'wss',
|
||||
},
|
||||
},
|
||||
static: {
|
||||
directory: STATIC_ASSETS_PATH,
|
||||
publicPath: [STATIC_ASSETS_URL, '/'],
|
||||
},
|
||||
proxy: [
|
||||
{
|
||||
context: proxyRoutes,
|
||||
...proxyConfig,
|
||||
},
|
||||
],
|
||||
// Disable default DevServer compression. We need more fine grained compression to support streaming search.
|
||||
compress: false,
|
||||
setupMiddlewares: (middlewares, developmentServer) => {
|
||||
// Re-enable gzip compression using our own `compression` filter.
|
||||
developmentServer.app!.use(compression({ filter: shouldCompressResponse }))
|
||||
return middlewares
|
||||
},
|
||||
}
|
||||
|
||||
const server = new WebpackDevServer(developmentServerConfig, compiler)
|
||||
|
||||
compiler.hooks.done.tap(
|
||||
'development-server-logger',
|
||||
once(() => {
|
||||
printSuccessBanner(
|
||||
[
|
||||
'Webpack build is ready!',
|
||||
`Development HTTP server is ready at ${HTTP_WEB_SERVER_URL}`,
|
||||
`Development HTTPS server is ready at ${HTTPS_WEB_SERVER_URL}`,
|
||||
],
|
||||
signale.log.bind(signale)
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
await server.start()
|
||||
}
|
||||
|
||||
async function startEsbuildDevelopmentServer({ apiURL }: DevelopmentServerInit): Promise<void> {
|
||||
const manifestFile = getManifest()
|
||||
const htmlPage = getIndexHTML({ manifestFile })
|
||||
|
||||
const { proxyRoutes, ...proxyMiddlewareOptions } = getAPIProxySettings({
|
||||
apiURL,
|
||||
getLocalIndexHTML(jsContextScript) {
|
||||
return getIndexHTML({ manifestFile, 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(htmlPage)
|
||||
response.send(getIndexHTML({ manifestFile: getWebBuildManifest() }))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,15 +2,6 @@ import path from 'path'
|
||||
|
||||
import { ROOT_PATH } from '@sourcegraph/build-config'
|
||||
|
||||
export const STATIC_ASSETS_URL = '/.assets/'
|
||||
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 WEBPACK_STATS_OPTIONS = {
|
||||
all: false,
|
||||
timings: true,
|
||||
errors: true,
|
||||
warnings: true,
|
||||
colors: true,
|
||||
} as const
|
||||
|
||||
@ -8,9 +8,7 @@ import { getEnvironmentBoolean, STATIC_ASSETS_PATH } from '@sourcegraph/build-co
|
||||
|
||||
import { DEFAULT_SITE_CONFIG_PATH } from './constants'
|
||||
|
||||
type WEB_BUILDER = 'esbuild' | 'webpack'
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development'
|
||||
const NODE_ENV = process.env.NODE_ENV || (process.env.BAZEL_BIN ? 'production' : 'development')
|
||||
|
||||
const NODE_DEBUG = process.env.NODE_DEBUG
|
||||
|
||||
@ -31,21 +29,8 @@ export const ENVIRONMENT_CONFIG = {
|
||||
// Can be used to expose global variables to integration tests (e.g., CodeMirror API).
|
||||
// Enabled in the dev environment to allow debugging integration tests with the dev server.
|
||||
INTEGRATION_TESTS: getEnvironmentBoolean('INTEGRATION_TESTS') || IS_DEVELOPMENT,
|
||||
// Enables `embed` entrypoint.
|
||||
EMBED_DEVELOPMENT: getEnvironmentBoolean('EMBED_DEVELOPMENT'),
|
||||
|
||||
// Should the web builder serve `index.html` with `HTMLWebpackPlugin`.
|
||||
WEB_BUILDER_SERVE_INDEX: getEnvironmentBoolean('WEB_BUILDER_SERVE_INDEX'),
|
||||
// Enables `StatoscopeWebpackPlugin` that allows to analyze application bundle.
|
||||
WEBPACK_BUNDLE_ANALYZER: getEnvironmentBoolean('WEBPACK_BUNDLE_ANALYZER'),
|
||||
// The name used to generate Statoscope JSON stats and HTML report in the `/ui/assets` folder.
|
||||
WEBPACK_STATS_NAME: process.env.WEBPACK_STATS_NAME,
|
||||
// Allow overriding default Webpack naming behavior for debugging.
|
||||
WEBPACK_USE_NAMED_CHUNKS: getEnvironmentBoolean('WEBPACK_USE_NAMED_CHUNKS'),
|
||||
// Enables the plugin that write Webpack stats to disk.
|
||||
WEBPACK_EXPORT_STATS: process.env.WEBPACK_EXPORT_STATS,
|
||||
// Allow to adjust https://webpack.js.org/configuration/devtool/ in the dev environment.
|
||||
WEBPACK_DEVELOPMENT_DEVTOOL: process.env.WEBPACK_DEVELOPMENT_DEVTOOL || 'eval-cheap-module-source-map',
|
||||
STATIC_ASSETS_PATH: process.env.STATIC_ASSETS_PATH || STATIC_ASSETS_PATH,
|
||||
|
||||
// The commit SHA the client bundle was built with.
|
||||
@ -61,10 +46,6 @@ export const ENVIRONMENT_CONFIG = {
|
||||
// Sentry project
|
||||
SENTRY_PROJECT: process.env.SENTRY_PROJECT,
|
||||
|
||||
// Webpack is the default web build tool, and esbuild is an experimental option (see
|
||||
// https://docs.sourcegraph.com/dev/background-information/web/build#esbuild).
|
||||
DEV_WEB_BUILDER: (process.env.DEV_WEB_BUILDER === 'esbuild' ? 'esbuild' : 'webpack') as WEB_BUILDER,
|
||||
|
||||
/**
|
||||
* Omit slow deps (such as Monaco and GraphiQL) in the build to get a ~40% reduction in esbuild
|
||||
* rebuild time. The web app will show placeholders if features needing these deps are used.
|
||||
|
||||
@ -1,37 +1,19 @@
|
||||
import { readFileSync, writeFileSync } from 'fs'
|
||||
import { readFileSync } from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import type { WebpackPluginFunction } from 'webpack'
|
||||
|
||||
import type { SourcegraphContext } from '../../src/jscontext'
|
||||
import { assetPathPrefix, WEB_BUILD_MANIFEST_FILENAME, type WebBuildManifest } from '../esbuild/manifest'
|
||||
|
||||
import { createJsContext, ENVIRONMENT_CONFIG, HTTPS_WEB_SERVER_URL, STATIC_INDEX_PATH } from '.'
|
||||
import { createJsContext, ENVIRONMENT_CONFIG, HTTPS_WEB_SERVER_URL } from '.'
|
||||
|
||||
const { NODE_ENV, STATIC_ASSETS_PATH } = ENVIRONMENT_CONFIG
|
||||
const { STATIC_ASSETS_PATH } = ENVIRONMENT_CONFIG
|
||||
|
||||
export const WEB_BUILD_MANIFEST_PATH = path.resolve(STATIC_ASSETS_PATH, 'web.manifest.json')
|
||||
const WEB_BUILD_MANIFEST_PATH = path.resolve(STATIC_ASSETS_PATH, WEB_BUILD_MANIFEST_FILENAME)
|
||||
export const HTML_INDEX_PATH = path.resolve(STATIC_ASSETS_PATH, 'index.html')
|
||||
|
||||
export const getWebBuildManifest = (): WebBuildManifest =>
|
||||
JSON.parse(readFileSync(WEB_BUILD_MANIFEST_PATH, 'utf-8')) as WebBuildManifest
|
||||
|
||||
export interface WebBuildManifest {
|
||||
/** Main app entry JS bundle */
|
||||
'app.js': string
|
||||
/** Main app entry CSS bundle, only used in production mode */
|
||||
'app.css'?: string
|
||||
/** Runtime bundle, only used in development mode */
|
||||
'runtime.js'?: string
|
||||
/** React entry bundle, only used in production mode */
|
||||
'react.js'?: string
|
||||
/** Opentelemetry entry bundle, only used in production mode */
|
||||
'opentelemetry.js'?: string
|
||||
/** If script files should be treated as JS modules. Required for esbuild bundle. */
|
||||
isModule?: boolean
|
||||
/** The node env value `production | development` */
|
||||
environment?: 'development' | 'production'
|
||||
}
|
||||
|
||||
interface GetHTMLPageOptions {
|
||||
manifestFile: WebBuildManifest
|
||||
/**
|
||||
@ -54,14 +36,7 @@ interface GetHTMLPageOptions {
|
||||
export function getIndexHTML(options: GetHTMLPageOptions): string {
|
||||
const { manifestFile, jsContext, jsContextScript } = options
|
||||
|
||||
const {
|
||||
'app.js': appBundle,
|
||||
'app.css': cssBundle,
|
||||
'runtime.js': runtimeBundle,
|
||||
'react.js': reactBundle,
|
||||
'opentelemetry.js': oTelBundle,
|
||||
isModule,
|
||||
} = manifestFile
|
||||
const { 'main.js': mainJS, 'main.css': mainCSS } = manifestFile
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
@ -72,7 +47,7 @@ export function getIndexHTML(options: GetHTMLPageOptions): string {
|
||||
<meta name="viewport" content="width=device-width, viewport-fit=cover" />
|
||||
<meta name="referrer" content="origin-when-cross-origin"/>
|
||||
<meta name="color-scheme" content="light dark"/>
|
||||
${cssBundle ? `<link rel="stylesheet" href="${cssBundle}">` : ''}
|
||||
<link rel="stylesheet" href="${assetPathPrefix}/${mainCSS}">
|
||||
${
|
||||
ENVIRONMENT_CONFIG.SOURCEGRAPHDOTCOM_MODE
|
||||
? '<script src="https://js.sentry-cdn.com/ae2f74442b154faf90b5ff0f7cd1c618.min.js" crossorigin="anonymous"></script>'
|
||||
@ -82,9 +57,6 @@ export function getIndexHTML(options: GetHTMLPageOptions): string {
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
// Optional value useful for checking if index.html is created by HtmlWebpackPlugin with the right NODE_ENV.
|
||||
window.webpackBuildEnvironment = '${NODE_ENV}'
|
||||
|
||||
${
|
||||
jsContextScript ||
|
||||
`
|
||||
@ -96,17 +68,8 @@ export function getIndexHTML(options: GetHTMLPageOptions): string {
|
||||
}
|
||||
</script>
|
||||
|
||||
${runtimeBundle ? `<script src="${runtimeBundle}"></script>` : ''}
|
||||
${reactBundle ? `<script src="${reactBundle}" ${isModule ? 'type="module"' : ''}></script>` : ''}
|
||||
${oTelBundle ? `<script src="${oTelBundle}" ${isModule ? 'type="module"' : ''}></script>` : ''}
|
||||
<script src="${appBundle}" ${isModule ? 'type="module"' : ''}></script>
|
||||
<script src="${assetPathPrefix}/${mainJS}" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
}
|
||||
|
||||
export const writeIndexHTMLPlugin: WebpackPluginFunction = compiler => {
|
||||
compiler.hooks.done.tap('WriteIndexHTMLPlugin', () => {
|
||||
writeFileSync(STATIC_INDEX_PATH, getIndexHTML({ manifestFile: getWebBuildManifest() }), 'utf-8')
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,10 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
|
||||
|
||||
go_library(
|
||||
name = "enterprise",
|
||||
name = "dist",
|
||||
srcs = ["assets.go"],
|
||||
embedsrcs = ["copy_bundle"], # keep
|
||||
importpath = "github.com/sourcegraph/sourcegraph/ui/assets/enterprise",
|
||||
importpath = "github.com/sourcegraph/sourcegraph/client/web/dist",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//lib/errors",
|
||||
@ -20,13 +20,13 @@ copy_to_directory(
|
||||
"//client/browser:integration-assets",
|
||||
"//client/web:bundle",
|
||||
"//client/web-sveltekit",
|
||||
"//ui/assets/img",
|
||||
"//client/web/dist/img",
|
||||
],
|
||||
out = "dist",
|
||||
replace_prefixes = {
|
||||
"client/web/bundle": "",
|
||||
"client/web-sveltekit/build": "",
|
||||
"ui/assets/img": "img",
|
||||
"client/web/dist/img": "img",
|
||||
"client/browser/integration-assets": "",
|
||||
},
|
||||
)
|
||||
@ -1,4 +1,4 @@
|
||||
package enterprise
|
||||
package dist
|
||||
|
||||
import (
|
||||
"embed"
|
||||
@ -8,6 +8,6 @@ filegroup(
|
||||
|
||||
copy_to_bin(
|
||||
name = "copy",
|
||||
srcs = ["//ui/assets/img"],
|
||||
srcs = ["//client/web/dist/img"],
|
||||
visibility = ["//client/web-sveltekit:__pkg__"],
|
||||
)
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 539 KiB After Width: | Height: | Size: 539 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1010 B After Width: | Height: | Size: 1010 B |
|
Before Width: | Height: | Size: 1007 B After Width: | Height: | Size: 1007 B |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 840 B After Width: | Height: | Size: 840 B |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |