diff --git a/client/browser/BUILD.bazel b/client/browser/BUILD.bazel index f30b4852078..c174886497d 100644 --- a/client/browser/BUILD.bazel +++ b/client/browser/BUILD.bazel @@ -2,7 +2,7 @@ load("@aspect_rules_ts//ts:defs.bzl", "ts_config") load("@aspect_rules_js//js:defs.bzl", "js_library") load("@npm//:defs.bzl", "npm_link_all_packages") load("//dev:defs.bzl", "jest_test", "sass", "ts_project") -load("//dev:webpack.bzl", "webpack_bundle") +load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild") load("//client/shared/dev:generate_graphql_operations.bzl", "generate_graphql_operations") load("//client/shared/dev:build_code_intel_extensions.bzl", "build_code_intel_extensions") load("//client/shared/dev:tools.bzl", "module_style_typings") @@ -216,6 +216,7 @@ ts_project( "src/shared/components/TrackAnchorClick.tsx", "src/shared/components/WildcardThemeProvider.tsx", "src/shared/context.ts", + "src/shared/extensionHostWorker.ts", "src/shared/platform/context.ts", "src/shared/platform/extensionHost.ts", "src/shared/platform/inlineExtensionsService.ts", @@ -341,20 +342,19 @@ jest_test( ) filegroup( - name = "entry-conigs", + name = "entry-configs", srcs = glob(["src/config/*.js"]), ) -webpack_bundle( +esbuild( name = "bundle", srcs = [ # JSON imports - "src/browser-extension/manifest.spec.json", "code-intel-extensions.json", # SRCS ":browser", - ":entry-conigs", + ":entry-configs", ":module_styles", ":package_styles", @@ -363,24 +363,21 @@ webpack_bundle( "//:browserslist", "//:package_json", - # STATIC ASSSETS + # STATIC ASSETS "//client/browser/assets", ], - entry_points = { - "src/browser-extension/scripts/backgroundPage.main.js": "backgroundPage.main", - "src/browser-extension/scripts/contentPage.main.js": "contentPage.main", - "src/browser-extension/scripts/optionsPage.main.js": "optionsPage.main", - "src/browser-extension/scripts/afterInstallPage.main.js": "afterInstallPage.main", - "src/native-integration/nativeIntegration.main.js": "nativeIntegration.main", - "src/native-integration/phabricator/phabricatorNativeIntegration.main.js": "phabricatorNativeIntegration.main", - "src/app.css": "app", - "src/branded.css": "branded", - }, - env = { - "NODE_ENV": "production", - }, - output_dir = True, - webpack_config = "//client/browser/config:webpack-config", + config = "//client/browser/config:esbuild-config", + entry_points = [ + "src/browser-extension/scripts/backgroundPage.main.js", + "src/browser-extension/scripts/contentPage.main.js", + "src/browser-extension/scripts/optionsPage.main.js", + "src/browser-extension/scripts/afterInstallPage.main.js", + "src/native-integration/nativeIntegration.main.js", + "src/native-integration/phabricator/phabricatorNativeIntegration.main.js", + "src/app.css", + "src/branded.css", + "src/shared/extensionHostWorker.js", + ], deps = ["//client/browser/config"], ) diff --git a/client/browser/README.md b/client/browser/README.md index 27c8ccba77a..e99cfac4a24 100644 --- a/client/browser/README.md +++ b/client/browser/README.md @@ -51,7 +51,7 @@ It works as follows: - `shared/` Code shared between multiple code hosts. - `config/` - Configuration code that is bundled via webpack. The configuration code adds properties to `window` that make it easier to tell what environment the script is running in. This is useful because the code can be run in the content script, background, options page, or in the actual page when injected by Phabricator and each environment will have different ways to do different things. + Configuration code that adds properties to `window` that make it easier to tell what environment the script is running in. This is useful because the code can be run in the content script, background, options page, or in the actual page when injected by Phabricator and each environment will have different ways to do different things. - `end-to-end/` E2E test suite. - `scripts/` @@ -59,7 +59,7 @@ It works as follows: - `config/` Build configs. - `build/` - Generated directory containing the output from webpack and the generated bundles for each browser. + Generated directory containing the build output and the generated bundles for each browser. ## Requirements diff --git a/client/browser/config/BUILD.bazel b/client/browser/config/BUILD.bazel index 56889a59a59..715ef4930d1 100644 --- a/client/browser/config/BUILD.bazel +++ b/client/browser/config/BUILD.bazel @@ -9,12 +9,8 @@ load("@aspect_rules_js//js:defs.bzl", "js_library") ts_project( name = "config", srcs = [ - "webpack/base.config.bazel.ts", - "webpack/base.config.ts", - "webpack/development.config.ts", - "webpack/production.config.bazel.ts", - "webpack/production.config.ts", - "webpack/utils.ts", + "esbuild.ts", + "utils.ts", ], module = "commonjs", tsconfig = "//client/browser:tsconfig", @@ -22,22 +18,21 @@ ts_project( deps = [ "//:node_modules/@babel/runtime", #keep "//:node_modules/@types/node", - "//:node_modules/css-loader", #keep - "//:node_modules/css-minimizer-webpack-plugin", - "//:node_modules/mini-css-extract-plugin", + "//:node_modules/esbuild", # HACKS: bundle-time css import "//:node_modules/open-color", #keep "//:node_modules/path-browserify", #keep - "//:node_modules/style-loader", #keep - "//:node_modules/terser-webpack-plugin", - "//:node_modules/webpack", - "//:node_modules/worker-loader", #keep "//client/browser:node_modules/@sourcegraph/build-config", ], ) js_library( - name = "webpack-config", - srcs = ["webpack/production.config.bazel.js"], + name = "esbuild-config", + srcs = ["esbuild.bazel.js"], visibility = ["//client:__subpackages__"], + deps = [ + "//client/browser:node_modules/@sourcegraph/build-config", + "//client/browser/config", + "//client/build-config:build-config_lib", + ], ) diff --git a/client/browser/config/esbuild.bazel.js b/client/browser/config/esbuild.bazel.js new file mode 100644 index 00000000000..aaa3c8becb7 --- /dev/null +++ b/client/browser/config/esbuild.bazel.js @@ -0,0 +1,13 @@ +// This file is only used by Bazel builds. + +const { esbuildBuildOptions } = require('./esbuild.js') + +module.exports = { + ...esbuildBuildOptions(process.env.NODE_ENV === 'development' ? 'dev' : 'prod'), + + // Unset configuration properties that are provided by Bazel. + entryPoints: undefined, + bundle: undefined, + outdir: undefined, + sourcemap: undefined, +} diff --git a/client/browser/config/esbuild.ts b/client/browser/config/esbuild.ts new file mode 100644 index 00000000000..f63fa9de527 --- /dev/null +++ b/client/browser/config/esbuild.ts @@ -0,0 +1,57 @@ +import path from 'path' + +import type * as esbuild from 'esbuild' + +import { ROOT_PATH, stylePlugin } from '@sourcegraph/build-config' + +import { generateBundleUID } from './utils' + +const browserWorkspacePath = path.resolve(ROOT_PATH, 'client/browser') +const browserSourcePath = path.resolve(browserWorkspacePath, 'src') + +/** + * Returns the esbuild build options for the browser extension build. + */ +export function esbuildBuildOptions(mode: 'dev' | 'prod', extraPlugins: esbuild.Plugin[] = []): esbuild.BuildOptions { + return { + entryPoints: [ + // Browser extension + path.resolve(browserSourcePath, 'browser-extension/scripts/backgroundPage.main.ts'), + path.resolve(browserSourcePath, 'browser-extension/scripts/contentPage.main.ts'), + path.resolve(browserSourcePath, 'browser-extension/scripts/optionsPage.main.tsx'), + path.resolve(browserSourcePath, 'browser-extension/scripts/afterInstallPage.main.tsx'), + + // Common native integration entry point (Gitlab, Bitbucket) + path.resolve(browserSourcePath, 'native-integration/nativeIntegration.main.ts'), + // Phabricator-only native integration entry point + path.resolve(browserSourcePath, 'native-integration/phabricator/phabricatorNativeIntegration.main.ts'), + + // Styles + path.join(browserSourcePath, 'app.scss'), + path.join(browserSourcePath, 'branded.scss'), + + // Worker + path.resolve(browserSourcePath, 'shared/extensionHostWorker.ts'), + ], + format: 'cjs', + platform: 'browser', + plugins: [stylePlugin, ...extraPlugins], + define: { + 'process.env.NODE_ENV': JSON.stringify(mode === 'dev' ? 'development' : 'production'), + 'process.env.BUNDLE_UID': JSON.stringify(generateBundleUID()), + }, + bundle: true, + minify: false, + logLevel: 'error', + jsx: 'automatic', + outdir: path.join(browserWorkspacePath, 'build/dist'), + chunkNames: '[ext]/[hash].chunk', + entryNames: '[ext]/[name].bundle', + target: 'esnext', + sourcemap: true, + alias: { path: 'path-browserify' }, + loader: { + '.svg': 'text', + }, + } +} diff --git a/client/browser/config/webpack/utils.ts b/client/browser/config/utils.ts similarity index 100% rename from client/browser/config/webpack/utils.ts rename to client/browser/config/utils.ts diff --git a/client/browser/config/webpack/base.config.bazel.ts b/client/browser/config/webpack/base.config.bazel.ts deleted file mode 100644 index 2f98ec38573..00000000000 --- a/client/browser/config/webpack/base.config.bazel.ts +++ /dev/null @@ -1,97 +0,0 @@ -import path from 'path' - -import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin' -import MiniCssExtractPlugin from 'mini-css-extract-plugin' -import webpack, { optimize } from 'webpack' - -import { - getBazelCSSLoaders as getCSSLoaders, - getProvidePlugin, - getTerserPlugin, - getBasicCSSLoader, - getCSSModulesLoader, -} from '@sourcegraph/build-config' - -const browserWorkspacePath = path.resolve(process.cwd(), 'client/browser') - -const JS_OUTPUT_FOLDER = 'scripts' -const CSS_OUTPUT_FOLDER = 'css' - -const extensionHostWorker = /main\.worker\.js$/ - -export const config = { - stats: { - children: true, - }, - target: 'browserslist', - output: { - path: path.join(browserWorkspacePath, 'build/dist/js'), - filename: `${JS_OUTPUT_FOLDER}/[name].bundle.js`, - chunkFilename: '[id].chunk.js', - }, - devtool: 'inline-cheap-module-source-map', - optimization: { - minimizer: [getTerserPlugin() as webpack.WebpackPluginInstance, new CssMinimizerWebpackPlugin()], - }, - - plugins: [ - // Change scss imports to the pre-compiled css files - new webpack.NormalModuleReplacementPlugin(/.*\.scss$/, resource => { - resource.request = resource.request.replace(/\.scss$/, '.css') - }), - new MiniCssExtractPlugin({ filename: `${CSS_OUTPUT_FOLDER}/[name].bundle.css` }), - // Code splitting doesn't make sense/work in the browser extension, but we still want to use dynamic import() - new optimize.LimitChunkCountPlugin({ maxChunks: 1 }), - getProvidePlugin(), - ], - resolve: { - extensions: ['.ts', '.tsx', '.js'], - alias: { - path: require.resolve('path-browserify'), - }, - }, - module: { - rules: [ - { - test: /\.m?js$/, - exclude: extensionHostWorker, - type: 'javascript/auto', - resolve: { - // Allow importing without file extensions - // https://webpack.js.org/configuration/module/#resolvefullyspecified - fullySpecified: false, - }, - }, - { - // SCSS rule for our own and third-party styles - test: /\.css$/, - exclude: /\.module\.css$/, - use: getCSSLoaders(MiniCssExtractPlugin.loader, getBasicCSSLoader()), - }, - { - test: /\.css$/, - // CSS Modules loaders are only applied when the file is explicitly named as CSS module stylesheet using the extension `.module.scss`. - include: /\.module\.css$/, - use: getCSSLoaders(MiniCssExtractPlugin.loader, getCSSModulesLoader({ sourceMap: false, url: false })), - }, - { - test: /\.svg$/i, - type: 'asset/inline', - }, - { - test: extensionHostWorker, - use: [ - { - loader: 'worker-loader', - options: { filename: `${JS_OUTPUT_FOLDER}/extensionHostWorker.bundle.js` }, - }, - ], - resolve: { - // Allow importing without file extensions - // https://webpack.js.org/configuration/module/#resolvefullyspecified - fullySpecified: false, - }, - }, - ], - }, -} satisfies webpack.Configuration diff --git a/client/browser/config/webpack/base.config.ts b/client/browser/config/webpack/base.config.ts deleted file mode 100644 index c82dfb9a6d3..00000000000 --- a/client/browser/config/webpack/base.config.ts +++ /dev/null @@ -1,97 +0,0 @@ -import path from 'path' - -import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin' -import MiniCssExtractPlugin from 'mini-css-extract-plugin' -import type webpack from 'webpack' -import { optimize } from 'webpack' - -import { - ROOT_PATH, - getBabelLoader, - getCSSLoaders, - getProvidePlugin, - getTerserPlugin, - getCSSModulesLoader, - getBasicCSSLoader, -} from '@sourcegraph/build-config' - -export const browserWorkspacePath = path.resolve(ROOT_PATH, 'client/browser') -const browserSourcePath = path.resolve(browserWorkspacePath, 'src') - -const extensionHostWorker = /main\.worker\.ts$/ - -export const config = { - target: 'browserslist', - entry: [ - // Browser extension - path.resolve(browserSourcePath, 'browser-extension/scripts/backgroundPage.main.ts'), - path.resolve(browserSourcePath, 'browser-extension/scripts/contentPage.main.ts'), - path.resolve(browserSourcePath, 'browser-extension/scripts/optionsPage.main.tsx'), - path.resolve(browserSourcePath, 'browser-extension/scripts/afterInstallPage.main.tsx'), - - // Common native integration entry point (Gitlab, Bitbucket) - path.resolve(browserSourcePath, 'native-integration/nativeIntegration.main.ts'), - // Phabricator-only native integration entry point - path.resolve(browserSourcePath, 'native-integration/phabricator/phabricatorNativeIntegration.main.ts'), - - // Styles - path.join(browserSourcePath, 'app.scss'), - path.join(browserSourcePath, 'branded.scss'), - ], - output: { - path: path.join(browserWorkspacePath, 'build/dist/js'), - filename: '[name].bundle.js', - chunkFilename: '[id].chunk.js', - }, - devtool: 'inline-cheap-module-source-map', - optimization: { - minimizer: [getTerserPlugin() as webpack.WebpackPluginInstance, new CssMinimizerWebpackPlugin()], - }, - - plugins: [ - new MiniCssExtractPlugin({ filename: '../css/[name].bundle.css' }), - // Code splitting doesn't make sense/work in the browser extension, but we still want to use dynamic import() - new optimize.LimitChunkCountPlugin({ maxChunks: 1 }), - getProvidePlugin(), - ], - resolve: { - extensions: ['.ts', '.tsx', '.js'], - alias: { - path: require.resolve('path-browserify'), - }, - }, - module: { - rules: [ - { - test: /\.[jt]sx?$/, - exclude: extensionHostWorker, - use: [getBabelLoader()], - }, - { - // SCSS rule for our own and third-party styles - test: /\.(css|sass|scss)$/, - exclude: /\.module\.(sass|scss)$/, - use: getCSSLoaders(MiniCssExtractPlugin.loader, getBasicCSSLoader()), - }, - { - test: /\.(css|sass|scss)$/, - include: /\.module\.(sass|scss)$/, - use: getCSSLoaders(MiniCssExtractPlugin.loader, getCSSModulesLoader({ sourceMap: false, url: false })), - }, - { - test: /\.svg$/i, - type: 'asset/inline', - }, - { - test: extensionHostWorker, - use: [ - { - loader: 'worker-loader', - options: { filename: 'extensionHostWorker.bundle.js' }, - }, - getBabelLoader(), - ], - }, - ], - }, -} satisfies webpack.Configuration diff --git a/client/browser/config/webpack/development.config.ts b/client/browser/config/webpack/development.config.ts deleted file mode 100644 index 868321d8b9d..00000000000 --- a/client/browser/config/webpack/development.config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as path from 'path' - -import * as webpack from 'webpack' - -import { getCacheConfig } from '@sourcegraph/build-config' - -import { config as baseConfig, browserWorkspacePath } from './base.config' -import { generateBundleUID } from './utils' - -const { plugins, ...base } = baseConfig - -export const config: webpack.Configuration = { - ...base, - mode: 'development', - // Use cache only in `development` mode to speed up production build. - cache: getCacheConfig({ invalidateCacheFiles: [path.resolve(browserWorkspacePath, 'babel.config.js')] }), - plugins: (plugins || []).concat( - ...[ - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('development'), - BUNDLE_UID: JSON.stringify(generateBundleUID()), - }, - }), - ] - ), -} diff --git a/client/browser/config/webpack/production.config.bazel.ts b/client/browser/config/webpack/production.config.bazel.ts deleted file mode 100644 index b75f241ae9d..00000000000 --- a/client/browser/config/webpack/production.config.bazel.ts +++ /dev/null @@ -1,47 +0,0 @@ -import TerserPlugin from 'terser-webpack-plugin' -import * as webpack from 'webpack' - -import { config as baseConfig } from './base.config.bazel' -import { generateBundleUID } from './utils' - -const { plugins, ...base } = baseConfig - -export const config: webpack.Configuration = { - ...base, - mode: 'production', - optimization: { - minimize: true, - minimizer: [ - new TerserPlugin({ - extractComments: false, - terserOptions: { - output: { - // Without this, Uglify will change \u0000 to \0 (NULL byte), - // which causes Chrome to complain that the bundle is not UTF8 - ascii_only: true, - beautify: false, - comments: false, - }, - }, - }) as webpack.WebpackPluginInstance, - ], - }, - plugins: (plugins || []).concat( - ...[ - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production'), - BUNDLE_UID: JSON.stringify(generateBundleUID()), - }, - }), - new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - '$.fn.pjax': 'jquery-pjax', - }), - ] - ), -} - -// eslint-disable-next-line import/no-default-export -export default config diff --git a/client/browser/config/webpack/production.config.ts b/client/browser/config/webpack/production.config.ts deleted file mode 100644 index 613bf4b0235..00000000000 --- a/client/browser/config/webpack/production.config.ts +++ /dev/null @@ -1,44 +0,0 @@ -import TerserPlugin from 'terser-webpack-plugin' -import * as webpack from 'webpack' - -import { config as baseConfig } from './base.config' -import { generateBundleUID } from './utils' - -const { plugins, ...base } = baseConfig - -export const config: webpack.Configuration = { - ...base, - mode: 'production', - optimization: { - minimize: true, - minimizer: [ - new TerserPlugin({ - extractComments: false, - terserOptions: { - output: { - // Without this, Uglify will change \u0000 to \0 (NULL byte), - // which causes Chrome to complain that the bundle is not UTF8 - ascii_only: true, - beautify: false, - comments: false, - }, - }, - }) as webpack.WebpackPluginInstance, - ], - }, - plugins: (plugins || []).concat( - ...[ - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production'), - BUNDLE_UID: JSON.stringify(generateBundleUID()), - }, - }), - new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - '$.fn.pjax': 'jquery-pjax', - }), - ] - ), -} diff --git a/client/browser/scripts/build.ts b/client/browser/scripts/build.ts index f39e3e96761..6c99b1d7f1d 100644 --- a/client/browser/scripts/build.ts +++ b/client/browser/scripts/build.ts @@ -1,45 +1,16 @@ -import shelljs from 'shelljs' -import signale from 'signale' -import webpack from 'webpack' +import * as esbuild from 'esbuild' -import { config } from '../config/webpack/production.config' +import { esbuildBuildOptions } from '../config/esbuild' -import { buildNpm } from './build-npm' -import * as tasks from './tasks' +import { browserBuildStepsPlugin, copyAssetsPlugin } from './esbuildPlugins' -const buildChrome = tasks.buildChrome('prod') -const buildFirefox = tasks.buildFirefox('prod') -const buildSafari = tasks.buildSafari('prod') -const buildEdge = tasks.buildEdge('prod') - -tasks.copyAssets() - -const compiler = webpack(config) - -signale.await('Webpack compilation') - -compiler.run(async (error, stats) => { - console.log(stats?.toString(tasks.WEBPACK_STATS_OPTIONS)) - - if (stats?.hasErrors()) { - signale.error('Webpack compilation error') - process.exit(1) - } - signale.success('Webpack compilation done') - - buildChrome() - buildEdge() - buildFirefox() - if (isXcodeAvailable()) { - buildSafari() - } else { - signale.debug('Skipping Safari build because Xcode tools were not found (xcrun, xcodebuild)') - } - tasks.copyIntegrationAssets() - await buildNpm() - signale.success('Build done') -}) - -function isXcodeAvailable(): boolean { - return !!shelljs.which('xcrun') && !!shelljs.which('xcodebuild') +async function build(): Promise { + await esbuild.build(esbuildBuildOptions('prod', [copyAssetsPlugin, browserBuildStepsPlugin('prod')])) +} + +if (require.main === module) { + build().catch(error => { + console.error('Error:', error) + process.exit(1) + }) } diff --git a/client/browser/scripts/development.ts b/client/browser/scripts/development.ts index b6a982e5281..c53c044c758 100644 --- a/client/browser/scripts/development.ts +++ b/client/browser/scripts/development.ts @@ -1,40 +1,20 @@ +import * as esbuild from 'esbuild' import signale from 'signale' -import webpack from 'webpack' -import { config } from '../config/webpack/development.config' +import { esbuildBuildOptions } from '../config/esbuild' -import * as tasks from './tasks' +import { browserBuildStepsPlugin, copyAssetsPlugin } from './esbuildPlugins' -signale.config({ displayTimestamp: true }) +async function watch(): Promise { + const ctx = await esbuild.context(esbuildBuildOptions('dev', [copyAssetsPlugin, browserBuildStepsPlugin('dev')])) + await ctx.watch() + signale.info('Watching...') + await new Promise(() => {}) // wait forever +} -const buildChrome = tasks.buildChrome('dev') -const buildFirefox = tasks.buildFirefox('dev') -const buildEdge = tasks.buildEdge('dev') - -tasks.copyAssets() - -const compiler = webpack(config) - -signale.info('Running webpack') - -compiler.hooks.watchRun.tap('Notify', () => signale.await('Compiling...')) - -compiler.watch( - { - aggregateTimeout: 300, - }, - (error, stats) => { - signale.complete(stats?.toString(tasks.WEBPACK_STATS_OPTIONS)) - - if (error || stats?.hasErrors()) { - signale.error('Webpack compilation error') - return - } - signale.success('Webpack compilation done') - - buildChrome() - buildEdge() - buildFirefox() - tasks.copyIntegrationAssets() - } -) +if (require.main === module) { + watch().catch(error => { + console.error('Error:', error) + process.exit(1) + }) +} diff --git a/client/browser/scripts/esbuildPlugins.ts b/client/browser/scripts/esbuildPlugins.ts new file mode 100644 index 00000000000..cb627f419f0 --- /dev/null +++ b/client/browser/scripts/esbuildPlugins.ts @@ -0,0 +1,46 @@ +import type * as esbuild from 'esbuild' +import shelljs from 'shelljs' +import signale from 'signale' + +import { buildNpm } from './build-npm' +import * as tasks from './tasks' + +export const copyAssetsPlugin: esbuild.Plugin = { + name: 'copyAssets', + setup: (): void => { + tasks.copyAssets() + }, +} + +export function browserBuildStepsPlugin(mode: 'dev' | 'prod'): esbuild.Plugin { + return { + name: 'browserBuildSteps', + setup: (build: esbuild.PluginBuild): void => { + build.onEnd(async result => { + if (result.errors.length === 0) { + signale.info('Running browser build steps...') + tasks.buildChrome(mode)() + tasks.buildFirefox(mode)() + tasks.buildEdge(mode)() + if (mode === 'prod') { + if (isXcodeAvailable()) { + tasks.buildSafari(mode)() + } else { + signale.debug( + 'Skipping Safari build because Xcode tools were not found (xcrun, xcodebuild)' + ) + } + } + tasks.copyIntegrationAssets() + if (mode === 'prod') { + await buildNpm() + } + } + }) + }, + } +} + +function isXcodeAvailable(): boolean { + return !!shelljs.which('xcrun') && !!shelljs.which('xcodebuild') +} diff --git a/client/browser/scripts/tasks.ts b/client/browser/scripts/tasks.ts index 5e83f1c6441..9ebf1113657 100644 --- a/client/browser/scripts/tasks.ts +++ b/client/browser/scripts/tasks.ts @@ -6,7 +6,6 @@ import { omit, cloneDeep, curry } from 'lodash' import shelljs from 'shelljs' import signale from 'signale' import utcVersion from 'utc-version' -import type { Configuration } from 'webpack' import manifestSpec from '../src/browser-extension/manifest.spec.json' import schema from '../src/browser-extension/schema.json' @@ -20,7 +19,7 @@ const EXTENSION_PERMISSIONS_ALL_URLS = Boolean( process.env.EXTENSION_PERMISSIONS_ALL_URLS && JSON.parse(process.env.EXTENSION_PERMISSIONS_ALL_URLS) ) -export type BuildEnvironment = 'dev' | 'prod' +type BuildEnvironment = 'dev' | 'prod' type Browser = 'firefox' | 'chrome' | 'safari' | 'edge' @@ -37,14 +36,6 @@ const BUILDS_DIR = 'build' */ const useUtcVersion = true -export const WEBPACK_STATS_OPTIONS: Configuration['stats'] = { - all: false, - timings: true, - errors: true, - warnings: true, - colors: true, -} - function ensurePaths(browser?: Browser): void { shelljs.mkdir('-p', 'build/dist') shelljs.mkdir('-p', 'build/bundles') diff --git a/client/shared/src/api/extension/main.worker.ts b/client/browser/src/shared/extensionHostWorker.ts similarity index 86% rename from client/shared/src/api/extension/main.worker.ts rename to client/browser/src/shared/extensionHostWorker.ts index 33aa832e0c0..1c6862f9bad 100644 --- a/client/shared/src/api/extension/main.worker.ts +++ b/client/browser/src/shared/extensionHostWorker.ts @@ -1,13 +1,11 @@ -import '../../polyfills' +import '@sourcegraph/shared/src/polyfills' import { fromEvent } from 'rxjs' import { take } from 'rxjs/operators' import { hasProperty, logger } from '@sourcegraph/common' - -import { isEndpointPair } from '../../platform/context' - -import { startExtensionHost } from './extensionHost' +import { startExtensionHost } from '@sourcegraph/shared/src/api/extension/extensionHost' +import { isEndpointPair } from '@sourcegraph/shared/src/platform/context' interface InitMessage { endpoints: { diff --git a/client/build-config/BUILD.bazel b/client/build-config/BUILD.bazel index ef3dc601df2..b6212386590 100644 --- a/client/build-config/BUILD.bazel +++ b/client/build-config/BUILD.bazel @@ -3,8 +3,8 @@ load("@npm//:defs.bzl", "npm_link_all_packages") load("//dev:defs.bzl", "npm_package", "ts_project") load("//dev:eslint.bzl", "eslint_config_and_lint_root") -# TODO(bazel): currently handled with #keep -# gazelle:js_resolve **/esbuild/* //client/build-config/src/esbuild +# gazelle:js_ignore_imports ../../../../postcss.config +# gazelle:js_ignore_imports ../../../../../../../../postcss.config npm_link_all_packages(name = "node_modules") @@ -22,6 +22,11 @@ ts_config( ts_project( name = "build-config_lib", srcs = [ + "src/esbuild/monacoPlugin.ts", + "src/esbuild/packageResolutionPlugin.ts", + "src/esbuild/plugins.ts", + "src/esbuild/stylePlugin.ts", + "src/esbuild/workerPlugin.ts", "src/index.ts", "src/paths.ts", "src/utils/environment-config.ts", @@ -35,10 +40,16 @@ ts_project( 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", + ":node_modules/postcss", + ":node_modules/postcss-modules", ":node_modules/process", #keep + ":node_modules/sass", ":node_modules/style-loader", #keep ":node_modules/terser-webpack-plugin", ":node_modules/webpack", @@ -48,7 +59,6 @@ ts_project( "//:node_modules/@types/node", "//:node_modules/monaco-editor", #keep "//:node_modules/monaco-yaml", #keep - "//client/build-config/src/esbuild", ], ) diff --git a/client/build-config/src/esbuild/BUILD.bazel b/client/build-config/src/esbuild/BUILD.bazel deleted file mode 100644 index e56b75e55f4..00000000000 --- a/client/build-config/src/esbuild/BUILD.bazel +++ /dev/null @@ -1,24 +0,0 @@ -# TODO(bazel): esbuild build disabled, mocked for now - -load("@aspect_rules_js//js:defs.bzl", "js_library") - -genrule( - name = "esbuild-mocked", - outs = ["plugins.js"], - cmd = "echo 'exports.stylePlugin = {}' > $@", -) - -genrule( - name = "esbuild-mocked-types", - outs = ["plugins.d.ts"], - cmd = "echo 'export const stylePlugin: any' > $@", -) - -js_library( - name = "esbuild", - srcs = [ - ":esbuild-mocked", - ":esbuild-mocked-types", - ], - visibility = ["//client/build-config:__pkg__"], -) diff --git a/client/build-config/src/esbuild/stylePlugin.ts b/client/build-config/src/esbuild/stylePlugin.ts index f64fb108b32..29fee01196c 100644 --- a/client/build-config/src/esbuild/stylePlugin.ts +++ b/client/build-config/src/esbuild/stylePlugin.ts @@ -7,17 +7,24 @@ import postcss from 'postcss' import postcssModules from 'postcss-modules' import sass from 'sass' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import postcssConfig from '../../../../postcss.config' import { NODE_MODULES_PATH, ROOT_PATH, WORKSPACE_NODE_MODULES_PATHS } from '../paths' +/* eslint-disable import/extensions */ + +const postcssConfig = process.env.BAZEL_BINDIR + ? // eslint-disable-next-line @typescript-eslint/no-require-imports + require('../../../../../../../../postcss.config') + : // eslint-disable-next-line @typescript-eslint/no-require-imports + require('../../../../postcss.config') + /** * An esbuild plugin that builds .css and .scss stylesheets (including support for CSS modules). */ export const stylePlugin: esbuild.Plugin = { name: 'style', setup: build => { + const isBazel = process.env.BAZEL_BINDIR + const modulesMap = new Map() const modulesPlugin = postcssModules({ generateScopedName: '[name]__[local]', // omit hash for local dev @@ -105,13 +112,19 @@ export const stylePlugin: esbuild.Plugin = { unsafeCache: true, }) - build.onResolve({ filter: /\.s?css$/, namespace: 'file' }, async args => { + build.onResolve({ filter: /\.s?css$/, namespace: 'file' }, async ({ path: argsPath, ...args }) => { + // If running in Bazel, assume that the SASS compiler has already been run and just + // import the `.css` file. + if (isBazel) { + argsPath = argsPath.replace(/\.scss$/, '.css') + } + const inputPath = await new Promise((resolve, reject) => { - resolver.resolve({}, args.resolveDir, args.path, {}, (error, filepath) => { + resolver.resolve({}, args.resolveDir, argsPath, {}, (error, filepath) => { if (filepath) { resolve(filepath) } else { - reject(error ?? new Error(`Could not resolve file path for ${args.path}`)) + reject(error ?? new Error(`Could not resolve file path for ${argsPath}`)) } }) }) diff --git a/client/shared/BUILD.bazel b/client/shared/BUILD.bazel index bc708b21dde..484d91e1795 100644 --- a/client/shared/BUILD.bazel +++ b/client/shared/BUILD.bazel @@ -154,7 +154,6 @@ ts_project( "src/api/extension/extensionHost.ts", "src/api/extension/extensionHostApi.ts", "src/api/extension/extensionHostState.ts", - "src/api/extension/main.worker.ts", "src/api/extension/test/test-helpers.ts", "src/api/extension/util.ts", "src/api/extension/utils/ReferenceCounter.ts", diff --git a/client/shared/src/api/extension/worker.ts b/client/shared/src/api/extension/worker.ts index 4ffd4098011..0ced19f250a 100644 --- a/client/shared/src/api/extension/worker.ts +++ b/client/shared/src/api/extension/worker.ts @@ -1,24 +1,12 @@ -// eslint-disable-next-line import/extensions -import { Subscription } from 'rxjs' - -import type { EndpointPair, ClosableEndpointPair } from '../../platform/context' - -/* eslint-disable import/extensions, @typescript-eslint/ban-ts-comment */ -// @ts-ignore -import ExtensionHostWorker from './main.worker' - -/* eslint-enable import/extensions, @typescript-eslint/ban-ts-comment */ +import type { EndpointPair } from '../../platform/context' /** * Creates a web worker with the extension host and sets up a bidirectional MessageChannel-based communication channel. - * - * If a `workerBundleURL` is provided, it is used to create a new Worker(), instead of using the ExtensionHostWorker - * returned by worker-loader. This is useful to load the worker bundle from a different path. */ -export function createExtensionHostWorker(workerBundleURL?: string): { worker: Worker; clientEndpoints: EndpointPair } { +export function createExtensionHostWorker(workerBundleURL: string): { worker: Worker; clientEndpoints: EndpointPair } { const clientAPIChannel = new MessageChannel() const extensionHostAPIChannel = new MessageChannel() - const worker = workerBundleURL ? new Worker(workerBundleURL) : new ExtensionHostWorker() + const worker = new Worker(workerBundleURL) const workerEndpoints: EndpointPair = { proxy: clientAPIChannel.port2, expose: extensionHostAPIChannel.port2, @@ -30,8 +18,3 @@ export function createExtensionHostWorker(workerBundleURL?: string): { worker: W } return { worker, clientEndpoints } } - -export function createExtensionHost(workerBundleURL?: string): ClosableEndpointPair { - const { clientEndpoints, worker } = createExtensionHostWorker(workerBundleURL) - return { endpoints: clientEndpoints, subscription: new Subscription(() => worker.terminate()) } -} diff --git a/client/web/BUILD.bazel b/client/web/BUILD.bazel index 2d5f28a3aa2..26071e90408 100644 --- a/client/web/BUILD.bazel +++ b/client/web/BUILD.bazel @@ -2100,6 +2100,7 @@ WEBPACK_CONFIG_DEPS = [ "//:node_modules/react-refresh", "//client/web/dev", "//:node_modules/react-visibility-sensor", # required for https://github.com/joshwnj/react-visibility-sensor/issues/148 workaround + "//:postcss_config_js", # HACKS: bundle-time css import "//:node_modules/open-color",