From bf6fadfed9ddb66dac4bfd598a28b6bf522d5f70 Mon Sep 17 00:00:00 2001 From: TJ Kandala Date: Thu, 9 Jun 2022 22:42:45 -0400 Subject: [PATCH] vscode: add experimental esbuild support (#34010) Co-authored-by: Beatrix <68532117+abeatrix@users.noreply.github.com> --- .vscode/launch.json | 1 + .../src}/esbuild/monacoPlugin.ts | 21 ++- .../src/esbuild/packageResolutionPlugin.ts | 55 +++++++ client/build-config/src/esbuild/plugins.ts | 27 ++++ .../src}/esbuild/stylePlugin.ts | 23 ++- .../src}/esbuild/workerPlugin.ts | 9 +- client/build-config/src/index.ts | 1 + client/vscode/gulpfile.js | 7 +- client/vscode/package.json | 5 +- client/vscode/scripts/buffer-shim.js | 5 + client/vscode/scripts/build.ts | 149 ++++++++++++++++++ client/vscode/scripts/process-shim.js | 5 + client/vscode/src/webview/index.scss | 2 +- client/vscode/src/webview/initialize.ts | 25 ++- client/web/dev/esbuild/build.ts | 68 ++------ .../dev/esbuild/packageResolutionPlugin.ts | 17 -- client/web/dev/esbuild/server.ts | 6 +- package.json | 4 + yarn.lock | 32 +++- 19 files changed, 360 insertions(+), 102 deletions(-) rename client/{web/dev => build-config/src}/esbuild/monacoPlugin.ts (75%) create mode 100644 client/build-config/src/esbuild/packageResolutionPlugin.ts create mode 100644 client/build-config/src/esbuild/plugins.ts rename client/{web/dev => build-config/src}/esbuild/stylePlugin.ts (88%) rename client/{web/dev => build-config/src}/esbuild/workerPlugin.ts (92%) create mode 100644 client/vscode/scripts/buffer-shim.js create mode 100644 client/vscode/scripts/build.ts create mode 100644 client/vscode/scripts/process-shim.js delete mode 100644 client/web/dev/esbuild/packageResolutionPlugin.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 5ee886ddcff..5b32af5c3c8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -33,6 +33,7 @@ "--extensionDevelopmentKind=web", "--disable-web-security", ], + "sourceMaps": true, "outFiles": ["${workspaceRoot}/client/vscode/dist/webworker/*.js"], }, { diff --git a/client/web/dev/esbuild/monacoPlugin.ts b/client/build-config/src/esbuild/monacoPlugin.ts similarity index 75% rename from client/web/dev/esbuild/monacoPlugin.ts rename to client/build-config/src/esbuild/monacoPlugin.ts index 9d9f6254685..686f3e503b2 100644 --- a/client/web/dev/esbuild/monacoPlugin.ts +++ b/client/build-config/src/esbuild/monacoPlugin.ts @@ -4,7 +4,9 @@ import * as esbuild from 'esbuild' import { EditorFeature, featuresArr } from 'monaco-editor-webpack-plugin/out/features' import { EditorLanguage, languagesArr } from 'monaco-editor-webpack-plugin/out/languages' -import { MONACO_LANGUAGES_AND_FEATURES, ROOT_PATH } from '@sourcegraph/build-config' +import { MONACO_LANGUAGES_AND_FEATURES } from '@sourcegraph/build-config' + +import { ROOT_PATH } from '../paths' const monacoModulePath = (modulePath: string): string => require.resolve(path.join('monaco-editor/esm', modulePath), { @@ -57,3 +59,20 @@ export const monacoPlugin = ({ 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 => { + await esbuild.build({ + 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/packageResolutionPlugin.ts b/client/build-config/src/esbuild/packageResolutionPlugin.ts new file mode 100644 index 00000000000..193c2a2d722 --- /dev/null +++ b/client/build-config/src/esbuild/packageResolutionPlugin.ts @@ -0,0 +1,55 @@ +import fs from 'fs' + +import { CachedInputFileSystem, ResolverFactory } from 'enhanced-resolve' +import * as esbuild from 'esbuild' + +import { NODE_MODULES_PATH } from '../paths' + +interface Resolutions { + [fromModule: string]: string +} + +/** + * An esbuild plugin to redirect imports from one package to another (for example, from 'path' to + * 'path-browserify' to run in the browser). + */ +export const packageResolutionPlugin = (resolutions: Resolutions): esbuild.Plugin => ({ + name: 'packageResolution', + setup: build => { + const filter = new RegExp(`^(${Object.keys(resolutions).join('|')})$`) + + const resolver = ResolverFactory.createResolver({ + fileSystem: new CachedInputFileSystem(fs, 4000), + extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], + symlinks: true, // Resolve workspace symlinks + modules: [NODE_MODULES_PATH], + }) + + build.onResolve({ filter, namespace: 'file' }, async args => { + if ((args.kind === 'import-statement' || args.kind === 'require-call') && resolutions[args.path]) { + const resolvedPath = await new Promise((resolve, reject) => { + resolver.resolve({}, args.resolveDir, resolutions[args.path], {}, (error, filepath) => { + if (filepath) { + resolve(filepath) + } else { + reject(error ?? new Error(`Could not resolve file path for ${resolutions[args.path]}`)) + } + }) + }) + return { path: resolvedPath } + } + return undefined + }) + }, +}) + +export const RXJS_RESOLUTIONS: Resolutions = { + // Needed because imports of rxjs/internal/... actually import a different variant of + // rxjs in the same package, which leads to observables from combineLatestOrDefault (and + // other places that use rxjs/internal/...) not being cross-compatible. See + // https://stackoverflow.com/questions/53758889/rxjs-subscribeto-js-observable-check-works-in-chrome-but-fails-in-chrome-incogn. + 'rxjs/internal/OuterSubscriber': require.resolve('rxjs/_esm5/internal/OuterSubscriber'), + 'rxjs/internal/util/subscribeToResult': require.resolve('rxjs/_esm5/internal/util/subscribeToResult'), + 'rxjs/internal/util/subscribeToArray': require.resolve('rxjs/_esm5/internal/util/subscribeToArray'), + 'rxjs/internal/Observable': require.resolve('rxjs/_esm5/internal/Observable'), +} diff --git a/client/build-config/src/esbuild/plugins.ts b/client/build-config/src/esbuild/plugins.ts new file mode 100644 index 00000000000..6ca58eee246 --- /dev/null +++ b/client/build-config/src/esbuild/plugins.ts @@ -0,0 +1,27 @@ +import * as esbuild from 'esbuild' +import signale from 'signale' + +export * from './monacoPlugin' +export * from './packageResolutionPlugin' +export * from './stylePlugin' +export * from './workerPlugin' + +export const buildTimerPlugin: esbuild.Plugin = { + name: 'buildTimer', + setup: (build: esbuild.PluginBuild): void => { + let buildStarted: number + build.onStart(() => { + buildStarted = Date.now() + }) + build.onEnd(() => console.log(`# esbuild: build took ${Date.now() - buildStarted}ms`)) + }, +} + +export const experimentalNoticePlugin: esbuild.Plugin = { + name: 'experimentalNotice', + setup: (): void => { + signale.info( + 'esbuild usage is experimental. See https://docs.sourcegraph.com/dev/background-information/web/build#esbuild.' + ) + }, +} diff --git a/client/web/dev/esbuild/stylePlugin.ts b/client/build-config/src/esbuild/stylePlugin.ts similarity index 88% rename from client/web/dev/esbuild/stylePlugin.ts rename to client/build-config/src/esbuild/stylePlugin.ts index 5057ca40b86..f931ab16a89 100644 --- a/client/web/dev/esbuild/stylePlugin.ts +++ b/client/build-config/src/esbuild/stylePlugin.ts @@ -1,16 +1,16 @@ import fs from 'fs' import path from 'path' +import { ResolverFactory, CachedInputFileSystem } from 'enhanced-resolve' import esbuild from 'esbuild' import postcss from 'postcss' import postcssModules from 'postcss-modules' import sass from 'sass' -import { ROOT_PATH } from '@sourcegraph/build-config' - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import postcssConfig from '../../../../postcss.config' +import { NODE_MODULES_PATH, ROOT_PATH } from '../paths' /** * An esbuild plugin that builds .css and .scss stylesheets (including support for CSS modules). @@ -98,14 +98,29 @@ export const stylePlugin: esbuild.Plugin = { return output } + const resolver = ResolverFactory.createResolver({ + fileSystem: new CachedInputFileSystem(fs, 4000), + extensions: ['.css', '.scss'], + symlinks: true, // Resolve workspace symlinks + modules: [NODE_MODULES_PATH], + }) + build.onResolve({ filter: /\.s?css$/, namespace: 'file' }, async args => { - const inputPath = path.join(args.resolveDir, args.path) + const inputPath = await new Promise((resolve, reject) => { + resolver.resolve({}, args.resolveDir, args.path, {}, (error, filepath) => { + if (filepath) { + resolve(filepath) + } else { + reject(error ?? new Error(`Could not resolve file path for ${args.path}`)) + } + }) + }) + const { outputPath, outputContents, includedFiles } = await cachedTransform({ inputPath, inputContents: await fs.promises.readFile(inputPath, 'utf8'), }) const isCSSModule = outputPath.endsWith('.module.css') - return { path: outputPath, namespace: isCSSModule ? 'css-module' : 'css', diff --git a/client/web/dev/esbuild/workerPlugin.ts b/client/build-config/src/esbuild/workerPlugin.ts similarity index 92% rename from client/web/dev/esbuild/workerPlugin.ts rename to client/build-config/src/esbuild/workerPlugin.ts index 858e89e9bd5..3d06505ae83 100644 --- a/client/web/dev/esbuild/workerPlugin.ts +++ b/client/build-config/src/esbuild/workerPlugin.ts @@ -2,7 +2,7 @@ import path from 'path' import * as esbuild from 'esbuild' -import { BUILD_OPTIONS } from './build' +import { packageResolutionPlugin, RXJS_RESOLUTIONS } from '@sourcegraph/build-config' async function buildWorker( workerPath: string, @@ -12,7 +12,12 @@ async function buildWorker( entryPoints: [workerPath], bundle: true, write: false, - plugins: BUILD_OPTIONS.plugins?.filter(plugin => plugin.name === 'packageResolution'), + plugins: [ + packageResolutionPlugin({ + path: require.resolve('path-browserify'), + ...RXJS_RESOLUTIONS, + }), + ], sourcemap: true, metafile: true, ...extraConfig, diff --git a/client/build-config/src/index.ts b/client/build-config/src/index.ts index 9f034b677d9..55b8303d7aa 100644 --- a/client/build-config/src/index.ts +++ b/client/build-config/src/index.ts @@ -1,3 +1,4 @@ +export * from './esbuild/plugins' export * from './paths' export * from './webpack/babel-loader' export * from './webpack/css-loader' diff --git a/client/vscode/gulpfile.js b/client/vscode/gulpfile.js index fa47dffaf6e..bcf7d1a3e36 100644 --- a/client/vscode/gulpfile.js +++ b/client/vscode/gulpfile.js @@ -21,6 +21,7 @@ const { watchCSSModulesTypings, } = require('../shared/gulpfile') +const buildScripts = require('./scripts/build') const createWebpackConfig = require('./webpack.config') const WEBPACK_STATS_OPTIONS = { @@ -67,6 +68,10 @@ async function watchWebpack() { }) } +async function esbuild() { + await buildScripts.build() +} + // Ensure the typings that TypeScript depends on are build to avoid first-time-run errors const generate = gulp.parallel(schema, graphQlSchema, graphQlOperations, cssModulesTypings) @@ -88,4 +93,4 @@ const watch = gulp.series( gulp.parallel(watchGenerators, watchWebpack) ) -module.exports = { build, watch, webpack, watchWebpack } +module.exports = { build, watch, webpack, watchWebpack, esbuild } diff --git a/client/vscode/package.json b/client/vscode/package.json index d0b1dcbd9ec..a072e89e5f7 100644 --- a/client/vscode/package.json +++ b/client/vscode/package.json @@ -5,7 +5,7 @@ "version": "2.2.3", "description": "Sourcegraph for VS Code", "publisher": "sourcegraph", - "sideEffects": false, + "sideEffects": true, "license": "Apache-2.0", "icon": "images/logo.png", "repository": { @@ -221,6 +221,9 @@ "test": "ts-node ./tests/runTests.ts", "package": "ts-node ./scripts/package.ts", "task:gulp": "cross-env NODE_OPTIONS=\"--max_old_space_size=8192\" gulp", + "build:esbuild": "NODE_ENV=development yarn task:gulp esbuild", + "build:esbuild:web": "NODE_ENV=development TARGET_TYPE=webworker yarn task:gulp esbuild", + "watch:esbuild": "NODE_ENV=development WATCH=true yarn task:gulp esbuild", "build": "NODE_ENV=production yarn task:gulp webpack", "build:node": "NODE_ENV=production TARGET_TYPE=node yarn task:gulp webpack", "build:web": "NODE_ENV=production TARGET_TYPE=webworker yarn task:gulp webpack", diff --git a/client/vscode/scripts/buffer-shim.js b/client/vscode/scripts/buffer-shim.js new file mode 100644 index 00000000000..9f0abdf2d81 --- /dev/null +++ b/client/vscode/scripts/buffer-shim.js @@ -0,0 +1,5 @@ +// This file is used for esbuild's `inject` option +// in order to load node polyfills in the webworker +// extension host. +// See: https://esbuild.github.io/api/#inject. +export const Buffer = require('buffer/').Buffer diff --git a/client/vscode/scripts/build.ts b/client/vscode/scripts/build.ts new file mode 100644 index 00000000000..d41f116890d --- /dev/null +++ b/client/vscode/scripts/build.ts @@ -0,0 +1,149 @@ +import { existsSync } from 'fs' +import path from 'path' + +import * as esbuild from 'esbuild' +import { rm } from 'shelljs' + +import { + buildMonaco, + monacoPlugin, + MONACO_LANGUAGES_AND_FEATURES, + packageResolutionPlugin, + stylePlugin, + workerPlugin, + RXJS_RESOLUTIONS, + buildTimerPlugin, +} from '@sourcegraph/build-config' + +const watch = !!process.env.WATCH +const minify = process.env.NODE_ENV === 'production' +const outdir = path.join(__dirname, '../dist') +const isTest = !!process.env.IS_TEST + +const TARGET_TYPE = process.env.TARGET_TYPE + +const SHARED_CONFIG = { + outdir, + watch, + minify, + sourcemap: true, +} + +export async function build(): Promise { + if (existsSync(outdir)) { + rm('-rf', outdir) + } + + const buildPromises = [] + + if (TARGET_TYPE === 'node' || !TARGET_TYPE) { + buildPromises.push( + esbuild.build({ + entryPoints: { extension: path.join(__dirname, '/../src/extension.ts') }, + bundle: true, + format: 'cjs', + platform: 'node', + external: ['vscode'], + banner: { js: 'global.Buffer = require("buffer").Buffer' }, + define: { + 'process.env.IS_TEST': isTest ? 'true' : 'false', + }, + ...SHARED_CONFIG, + outdir: path.join(SHARED_CONFIG.outdir, 'node'), + }) + ) + } + if (TARGET_TYPE === 'webworker' || !TARGET_TYPE) { + buildPromises.push( + esbuild.build({ + entryPoints: { extension: path.join(__dirname, '/../src/extension.ts') }, + bundle: true, + format: 'cjs', + platform: 'browser', + external: ['vscode'], + define: { + 'process.env.IS_TEST': isTest ? 'true' : 'false', + global: 'globalThis', + }, + inject: ['./scripts/process-shim.js', './scripts/buffer-shim.js'], + plugins: [ + packageResolutionPlugin({ + process: require.resolve('process/browser'), + path: require.resolve('path-browserify'), + http: require.resolve('stream-http'), + https: require.resolve('https-browserify'), + stream: require.resolve('stream-browserify'), + util: require.resolve('util'), + events: require.resolve('events'), + buffer: require.resolve('buffer/'), + './browserActionsNode': path.resolve(__dirname, '../src', 'link-commands', 'browserActionsWeb'), + }), + ], + ...SHARED_CONFIG, + outdir: path.join(SHARED_CONFIG.outdir, 'webworker'), + }) + ) + } + + buildPromises.push( + esbuild.build({ + entryPoints: { + helpSidebar: path.join(__dirname, '../src/webview/sidebars/help'), + searchSidebar: path.join(__dirname, '../src/webview/sidebars/search'), + searchPanel: path.join(__dirname, '../src/webview/search-panel'), + style: path.join(__dirname, '../src/webview/index.scss'), + }, + bundle: true, + format: 'esm', + platform: 'browser', + splitting: true, + plugins: [ + stylePlugin, + workerPlugin, + packageResolutionPlugin({ + path: require.resolve('path-browserify'), + ...RXJS_RESOLUTIONS, + './Link': require.resolve('../src/webview/search-panel/alias/Link'), + '../Link': require.resolve('../src/webview/search-panel/alias/Link'), + './RepoSearchResult': require.resolve('../src/webview/search-panel/alias/RepoSearchResult'), + './CommitSearchResult': require.resolve('../src/webview/search-panel/alias/CommitSearchResult'), + './FileMatchChildren': require.resolve('../src/webview/search-panel/alias/FileMatchChildren'), + './RepoFileLink': require.resolve('../src/webview/search-panel/alias/RepoFileLink'), + '../documentation/ModalVideo': require.resolve('../src/webview/search-panel/alias/ModalVideo'), + }), + // Note: leads to "file has no exports" warnings + monacoPlugin(MONACO_LANGUAGES_AND_FEATURES), + buildTimerPlugin, + { + name: 'codiconsDeduplicator', + setup(build): void { + build.onLoad({ filter: /\.ttf$/ }, args => { + // Both `@vscode/codicons` and `monaco-editor` + // node modules include a `codicons.ttf` file, + // so null one out. + if (!args.path.includes('@vscode/codicons')) { + return { + contents: '', + loader: 'text', + } + } + return null + }) + }, + }, + ], + loader: { + '.ttf': 'file', + }, + assetNames: '[name]', + ignoreAnnotations: true, + treeShaking: false, + ...SHARED_CONFIG, + outdir: path.join(SHARED_CONFIG.outdir, 'webview'), + }) + ) + + buildPromises.push(buildMonaco(outdir)) + + await Promise.all(buildPromises) +} diff --git a/client/vscode/scripts/process-shim.js b/client/vscode/scripts/process-shim.js new file mode 100644 index 00000000000..33a4a9d1465 --- /dev/null +++ b/client/vscode/scripts/process-shim.js @@ -0,0 +1,5 @@ +// This file is used for esbuild's `inject` option +// in order to load node polyfills in the webworker +// extension host. +// See: https://esbuild.github.io/api/#inject. +export const process = require('process/browser') diff --git a/client/vscode/src/webview/index.scss b/client/vscode/src/webview/index.scss index 0827f722773..99ca3ed6f21 100644 --- a/client/vscode/src/webview/index.scss +++ b/client/vscode/src/webview/index.scss @@ -1,7 +1,7 @@ @import './forkedBranded.scss'; @import './theming/highlight.scss'; @import './theming/monaco.scss'; -@import '~@vscode/codicons/dist/codicon.css'; +@import '../../../../node_modules/@vscode/codicons/dist/codicon.css'; :root { // v2/debt: redefine our CSS variables using VS Code's CSS variables diff --git a/client/vscode/src/webview/initialize.ts b/client/vscode/src/webview/initialize.ts index 3adcf182b87..b5aab9ce076 100644 --- a/client/vscode/src/webview/initialize.ts +++ b/client/vscode/src/webview/initialize.ts @@ -23,15 +23,15 @@ export async function initializeSearchPanelWebview({ searchPanelAPI: Comlink.Remote webviewPanel: vscode.WebviewPanel }> { + const webviewPath = vscode.Uri.joinPath(extensionUri, 'dist', 'webview') + const panel = vscode.window.createWebviewPanel('sourcegraphSearch', 'Sourcegraph', vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true, enableFindWidget: true, - localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist', 'webview')], + localResourceRoots: [webviewPath], }) - const webviewPath = vscode.Uri.joinPath(extensionUri, 'dist', 'webview') - const scriptSource = panel.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'searchPanel.js')) const cssModuleSource = panel.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'searchPanel.css')) const styleSource = panel.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'style.css')) @@ -53,7 +53,6 @@ export async function initializeSearchPanelWebview({ // Expose the "Core" extension API to the Webview. Comlink.expose(extensionCoreAPI, expose) - // Use a nonce to only allow specific scripts to be run const nonce = getNonce() panel.iconPath = vscode.Uri.joinPath(extensionUri, 'images', 'logo.svg') @@ -85,7 +84,7 @@ export async function initializeSearchPanelWebview({
- + ` @@ -107,13 +106,13 @@ export function initializeSearchSidebarWebview({ }): { searchSidebarAPI: Comlink.Remote } { + const webviewPath = vscode.Uri.joinPath(extensionUri, 'dist', 'webview') + webviewView.webview.options = { enableScripts: true, - localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist', 'webview')], + localResourceRoots: [webviewPath], } - const webviewPath = vscode.Uri.joinPath(extensionUri, 'dist', 'webview') - const scriptSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'searchSidebar.js')) const cssModuleSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'searchSidebar.css')) const styleSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'style.css')) @@ -151,7 +150,7 @@ export function initializeSearchSidebarWebview({
- + ` @@ -169,13 +168,13 @@ export function initializeHelpSidebarWebview({ }): { helpSidebarAPI: Comlink.Remote } { + const webviewPath = vscode.Uri.joinPath(extensionUri, 'dist', 'webview') + webviewView.webview.options = { enableScripts: true, - localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist', 'webview')], + localResourceRoots: [webviewPath], } - const webviewPath = vscode.Uri.joinPath(extensionUri, 'dist', 'webview') - const scriptSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'helpSidebar.js')) const cssModuleSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'helpSidebar.css')) const styleSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'style.css')) @@ -202,7 +201,7 @@ export function initializeHelpSidebarWebview({
- + ` diff --git a/client/web/dev/esbuild/build.ts b/client/web/dev/esbuild/build.ts index ec03e33e413..f006f13fac9 100644 --- a/client/web/dev/esbuild/build.ts +++ b/client/web/dev/esbuild/build.ts @@ -1,17 +1,24 @@ import path from 'path' import * as esbuild from 'esbuild' -import signale from 'signale' -import { MONACO_LANGUAGES_AND_FEATURES, ROOT_PATH, STATIC_ASSETS_PATH } from '@sourcegraph/build-config' +import { + MONACO_LANGUAGES_AND_FEATURES, + ROOT_PATH, + STATIC_ASSETS_PATH, + stylePlugin, + packageResolutionPlugin, + workerPlugin, + monacoPlugin, + RXJS_RESOLUTIONS, + buildMonaco, + experimentalNoticePlugin, + buildTimerPlugin, +} from '@sourcegraph/build-config' import { ENVIRONMENT_CONFIG } from '../utils' import { manifestPlugin } from './manifestPlugin' -import { monacoPlugin } from './monacoPlugin' -import { packageResolutionPlugin } from './packageResolutionPlugin' -import { stylePlugin } from './stylePlugin' -import { workerPlugin } from './workerPlugin' const isEnterpriseBuild = ENVIRONMENT_CONFIG.ENTERPRISE @@ -35,35 +42,11 @@ export const BUILD_OPTIONS: esbuild.BuildOptions = { manifestPlugin, packageResolutionPlugin({ path: require.resolve('path-browserify'), - - // Needed because imports of rxjs/internal/... actually import a different variant of - // rxjs in the same package, which leads to observables from combineLatestOrDefault (and - // other places that use rxjs/internal/...) not being cross-compatible. See - // https://stackoverflow.com/questions/53758889/rxjs-subscribeto-js-observable-check-works-in-chrome-but-fails-in-chrome-incogn. - 'rxjs/internal/OuterSubscriber': require.resolve('rxjs/_esm5/internal/OuterSubscriber'), - 'rxjs/internal/util/subscribeToResult': require.resolve('rxjs/_esm5/internal/util/subscribeToResult'), - 'rxjs/internal/util/subscribeToArray': require.resolve('rxjs/_esm5/internal/util/subscribeToArray'), - 'rxjs/internal/Observable': require.resolve('rxjs/_esm5/internal/Observable'), + ...RXJS_RESOLUTIONS, }), monacoPlugin(MONACO_LANGUAGES_AND_FEATURES), - { - name: 'buildTimer', - setup: (build: esbuild.PluginBuild): void => { - let buildStarted: number - build.onStart(() => { - buildStarted = Date.now() - }) - build.onEnd(() => console.log(`# esbuild: build took ${Date.now() - buildStarted}ms`)) - }, - }, - { - name: 'experimentalNotice', - setup: (): void => { - signale.info( - 'esbuild usage is experimental. See https://docs.sourcegraph.com/dev/background-information/web/build#esbuild.' - ) - }, - }, + buildTimerPlugin, + experimentalNoticePlugin, ], define: { ...Object.fromEntries( @@ -90,29 +73,12 @@ export const BUILD_OPTIONS: esbuild.BuildOptions = { treeShaking: false, } -// 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 (): Promise => { - await esbuild.build({ - 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: STATIC_ASSETS_PATH, - }) -} - export const build = async (): Promise => { await esbuild.build({ ...BUILD_OPTIONS, outdir: STATIC_ASSETS_PATH, }) - await buildMonaco() + await buildMonaco(STATIC_ASSETS_PATH) } if (require.main === module) { diff --git a/client/web/dev/esbuild/packageResolutionPlugin.ts b/client/web/dev/esbuild/packageResolutionPlugin.ts deleted file mode 100644 index c77e47ad089..00000000000 --- a/client/web/dev/esbuild/packageResolutionPlugin.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as esbuild from 'esbuild' - -/** - * An esbuild plugin to redirect imports from one package to another (for example, from 'path' to - * 'path-browserify' to run in the browser). - */ -export const packageResolutionPlugin = (resolutions: { [fromModule: string]: string }): esbuild.Plugin => ({ - name: 'packageResolution', - setup: build => { - const filter = new RegExp(`^(${Object.keys(resolutions).join('|')})$`) - build.onResolve({ filter, namespace: 'file' }, args => - (args.kind === 'import-statement' || args.kind === 'require-call') && resolutions[args.path] - ? { path: resolutions[args.path] } - : undefined - ) - }, -}) diff --git a/client/web/dev/esbuild/server.ts b/client/web/dev/esbuild/server.ts index 7e700b97a17..e4c8f8b44a1 100644 --- a/client/web/dev/esbuild/server.ts +++ b/client/web/dev/esbuild/server.ts @@ -5,9 +5,9 @@ import express from 'express' import { createProxyMiddleware } from 'http-proxy-middleware' import signale from 'signale' -import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config' +import { STATIC_ASSETS_PATH, buildMonaco } from '@sourcegraph/build-config' -import { buildMonaco, BUILD_OPTIONS } from './build' +import { BUILD_OPTIONS } from './build' import { assetPathPrefix } from './manifestPlugin' export const esbuildDevelopmentServer = async ( @@ -16,7 +16,7 @@ export const esbuildDevelopmentServer = async ( ): Promise => { // One-time build (these files only change when the monaco-editor npm package is changed, which // is rare enough to ignore here). - await buildMonaco() + await buildMonaco(STATIC_ASSETS_PATH) // Start esbuild's server on a random local port. const { host: esbuildHost, port: esbuildPort, wait: esbuildStopped } = await serve( diff --git a/package.json b/package.json index d3c5c14bbf7..8de066990e2 100644 --- a/package.json +++ b/package.json @@ -243,6 +243,7 @@ "babel-plugin-lodash": "^3.3.4", "babel-preset-react-app": "^10.0.0", "browserslist": "^4.17.4", + "buffer": "^6.0.3", "bundlesize2": "^0.0.31", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", @@ -259,10 +260,12 @@ "css-minimizer-webpack-plugin": "^3.0.2", "elm": "^0.19.1-3", "elm-webpack-loader": "^8.0.0", + "enhanced-resolve": "^5.9.3", "esbuild": "^0.14.2", "eslint": "^8.13.0", "eslint-formatter-lsif": "^1.0.3", "eslint-plugin-monorepo": "^0.3.2", + "events": "^3.3.0", "execa": "^5.0.0", "express": "^4.17.1", "express-static-gzip": "^2.1.1", @@ -325,6 +328,7 @@ "speed-measure-webpack-plugin": "^1.5.0", "storybook-addon-designs": "^6.2.1", "storybook-dark-mode": "^1.1.0", + "stream-browserify": "^3.0.0", "string-width": "^4.2.0", "style-loader": "^3.1.0", "stylelint": "^14.3.0", diff --git a/yarn.lock b/yarn.lock index ddbc4db9cbd..7e186d29a12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8567,6 +8567,14 @@ buffer@^5.2.1, buffer@^5.5.0, buffer@^5.7.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffers@~0.1.1: version "0.1.1" resolved "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" @@ -11752,10 +11760,10 @@ engine.io@~3.4.0: engine.io-parser "~2.2.0" ws "^7.1.2" -enhanced-resolve@^5.0.0, enhanced-resolve@^5.9.2: - version "5.9.2" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" - integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== +enhanced-resolve@^5.0.0, enhanced-resolve@^5.9.2, enhanced-resolve@^5.9.3: + version "5.9.3" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88" + integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -12528,7 +12536,7 @@ eventemitter3@^4.0.0: resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.2.0: +events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -14907,7 +14915,7 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -15030,7 +15038,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -21216,7 +21224,7 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -22879,6 +22887,14 @@ storybook-dark-mode@^1.1.0: fast-deep-equal "^3.0.0" memoizerific "^1.11.3" +stream-browserify@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" + integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== + dependencies: + inherits "~2.0.4" + readable-stream "^3.5.0" + stream-exhaust@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d"