mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:51:43 +00:00
- web: upgrade `Webpack` to v5 for the web app - web: upgrade `Webpack` to v5 for Storybook - web: upgrade `Webpack` to v5 for the browser extension - web: enable filesystem cache only in the development environment - web: disable node.js polyfills - web: disable `webpack.IgnorePlugin` for the web app - web: switch to `react-refresh-webpack-plugin` from `react-hot-loader` - web: use `cache` only in development mode - web: remove redundant polyfill dependencies - web: do not use `Webpack` cache on CI - web: use runtime bundle in development mode by `frontend` server
This commit is contained in:
parent
edab7134e7
commit
e31288f052
@ -1,17 +1,21 @@
|
||||
import * as path from 'path'
|
||||
import path from 'path'
|
||||
|
||||
import CssMinimizerWebpackPlugin from 'css-minimizer-webpack-plugin'
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
|
||||
import TerserPlugin from 'terser-webpack-plugin'
|
||||
import * as webpack from 'webpack'
|
||||
import webpack, { optimize } from 'webpack'
|
||||
|
||||
const buildEntry = (...files: string[]): string[] => files.map(file => path.join(__dirname, file))
|
||||
import { subtypeOf } from '../../../shared/src/util/types'
|
||||
|
||||
const contentEntry = '../../src/config/content.entry.js'
|
||||
const backgroundEntry = '../../src/config/background.entry.js'
|
||||
const optionsEntry = '../../src/config/options.entry.js'
|
||||
const pageEntry = '../../src/config/page.entry.js'
|
||||
const extensionEntry = '../../src/config/extension.entry.js'
|
||||
export const rootPath = path.resolve(__dirname, '../../../../')
|
||||
export const browserWorkspacePath = path.resolve(rootPath, 'client/browser')
|
||||
const browserSourcePath = path.resolve(browserWorkspacePath, 'src')
|
||||
|
||||
const contentEntry = path.resolve(browserSourcePath, 'config/content.entry.js')
|
||||
const backgroundEntry = path.resolve(browserSourcePath, 'config/background.entry.js')
|
||||
const optionsEntry = path.resolve(browserSourcePath, 'config/options.entry.js')
|
||||
const pageEntry = path.resolve(browserSourcePath, 'config/page.entry.js')
|
||||
const extensionEntry = path.resolve(browserSourcePath, 'config/extension.entry.js')
|
||||
|
||||
const babelLoader = {
|
||||
loader: 'babel-loader',
|
||||
@ -22,7 +26,6 @@ const babelLoader = {
|
||||
}
|
||||
|
||||
const extensionHostWorker = /main\.worker\.ts$/
|
||||
const rootPath = path.resolve(__dirname, '../../../../')
|
||||
|
||||
const getCSSLoaders = (...loaders: webpack.RuleSetUseItem[]): webpack.RuleSetUse => [
|
||||
MiniCssExtractPlugin.loader,
|
||||
@ -40,29 +43,38 @@ const getCSSLoaders = (...loaders: webpack.RuleSetUseItem[]): webpack.RuleSetUse
|
||||
},
|
||||
]
|
||||
|
||||
export const config: webpack.Configuration = {
|
||||
export const config = subtypeOf<webpack.Configuration>()({
|
||||
target: 'browserslist',
|
||||
entry: {
|
||||
// Browser extension
|
||||
background: buildEntry(
|
||||
background: [
|
||||
extensionEntry,
|
||||
backgroundEntry,
|
||||
'../../src/browser-extension/scripts/backgroundPage.main.ts'
|
||||
),
|
||||
inject: buildEntry(extensionEntry, contentEntry, '../../src/browser-extension/scripts/contentPage.main.ts'),
|
||||
options: buildEntry(extensionEntry, optionsEntry, '../../src/browser-extension/scripts/optionsPage.main.tsx'),
|
||||
'after-install': path.resolve(__dirname, '../../src/browser-extension/scripts/afterInstallPage.main.tsx'),
|
||||
path.resolve(browserSourcePath, 'browser-extension/scripts/backgroundPage.main.ts'),
|
||||
],
|
||||
inject: [
|
||||
extensionEntry,
|
||||
contentEntry,
|
||||
path.resolve(browserSourcePath, 'browser-extension/scripts/contentPage.main.ts'),
|
||||
],
|
||||
options: [
|
||||
extensionEntry,
|
||||
optionsEntry,
|
||||
path.resolve(browserSourcePath, 'browser-extension/scripts/optionsPage.main.tsx'),
|
||||
],
|
||||
'after-install': path.resolve(browserSourcePath, 'browser-extension/scripts/afterInstallPage.main.tsx'),
|
||||
|
||||
// Common native integration entry point (Gitlab, Bitbucket)
|
||||
integration: buildEntry(pageEntry, '../../src/native-integration/integration.main.ts'),
|
||||
integration: [pageEntry, path.resolve(browserSourcePath, 'native-integration/integration.main.ts')],
|
||||
// Phabricator-only native integration entry point
|
||||
phabricator: buildEntry(pageEntry, '../../src/native-integration/phabricator/integration.main.ts'),
|
||||
phabricator: [pageEntry, path.resolve(browserSourcePath, 'native-integration/phabricator/integration.main.ts')],
|
||||
|
||||
// Styles
|
||||
style: path.join(__dirname, '../../src/app.scss'),
|
||||
'branded-style': path.join(__dirname, '../../src/branded.scss'),
|
||||
style: path.join(browserSourcePath, 'app.scss'),
|
||||
'branded-style': path.join(browserSourcePath, 'branded.scss'),
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, '../../build/dist/js'),
|
||||
path: path.join(browserWorkspacePath, 'build/dist/js'),
|
||||
filename: '[name].bundle.js',
|
||||
chunkFilename: '[id].chunk.js',
|
||||
},
|
||||
@ -70,15 +82,14 @@ export const config: webpack.Configuration = {
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
sourceMap: true,
|
||||
terserOptions: {
|
||||
compress: {
|
||||
// // Don't inline functions, which causes name collisions with uglify-es:
|
||||
// Don't inline functions, which causes name collisions with uglify-es:
|
||||
// https://github.com/mishoo/UglifyJS2/issues/2842
|
||||
inline: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}) as webpack.WebpackPluginInstance,
|
||||
new CssMinimizerWebpackPlugin(),
|
||||
],
|
||||
},
|
||||
@ -86,7 +97,7 @@ export const config: webpack.Configuration = {
|
||||
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 webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
||||
new optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js'],
|
||||
@ -131,4 +142,4 @@ export const config: webpack.Configuration = {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -2,12 +2,10 @@ import * as path from 'path'
|
||||
|
||||
import * as webpack from 'webpack'
|
||||
|
||||
import { config as baseConfig } from './base.config'
|
||||
import { config as baseConfig, browserWorkspacePath, rootPath } from './base.config'
|
||||
import { generateBundleUID } from './utils'
|
||||
|
||||
const { plugins, entry, ...base } = baseConfig
|
||||
|
||||
const entries = entry as webpack.Entry
|
||||
const { plugins, entry: entries, ...base } = baseConfig
|
||||
|
||||
const entriesWithAutoReload = {
|
||||
...entries,
|
||||
@ -18,6 +16,19 @@ export const config: webpack.Configuration = {
|
||||
...base,
|
||||
entry: process.env.AUTO_RELOAD === 'false' ? entries : entriesWithAutoReload,
|
||||
mode: 'development',
|
||||
// Use cache only in `development` mode to speed up production build.
|
||||
cache: {
|
||||
type: 'filesystem',
|
||||
buildDependencies: {
|
||||
// Invalidate cache on config change.
|
||||
config: [
|
||||
__filename,
|
||||
path.resolve(browserWorkspacePath, 'babel.config.js'),
|
||||
path.resolve(rootPath, 'babel.config.js'),
|
||||
path.resolve(rootPath, 'postcss.config.js'),
|
||||
],
|
||||
},
|
||||
},
|
||||
plugins: (plugins || []).concat(
|
||||
...[
|
||||
new webpack.DefinePlugin({
|
||||
|
||||
@ -13,7 +13,6 @@ export const config: webpack.Configuration = {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
sourceMap: true,
|
||||
terserOptions: {
|
||||
output: {
|
||||
// Without this, Uglify will change \u0000 to \0 (NULL byte),
|
||||
@ -22,7 +21,7 @@ export const config: webpack.Configuration = {
|
||||
beautify: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}) as webpack.WebpackPluginInstance,
|
||||
],
|
||||
},
|
||||
plugins: (plugins || []).concat(
|
||||
|
||||
@ -17,9 +17,9 @@ const compiler = webpack(config)
|
||||
signale.await('Webpack compilation')
|
||||
|
||||
compiler.run((error, stats) => {
|
||||
console.log(stats.toString(tasks.WEBPACK_STATS_OPTIONS))
|
||||
console.log(stats?.toString(tasks.WEBPACK_STATS_OPTIONS))
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
if (stats?.hasErrors()) {
|
||||
signale.error('Webpack compilation error')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@ -27,9 +27,9 @@ compiler.watch(
|
||||
aggregateTimeout: 300,
|
||||
},
|
||||
(error, stats) => {
|
||||
signale.complete(stats.toString(tasks.WEBPACK_STATS_OPTIONS))
|
||||
signale.complete(stats?.toString(tasks.WEBPACK_STATS_OPTIONS))
|
||||
|
||||
if (error || stats.hasErrors()) {
|
||||
if (error || stats?.hasErrors()) {
|
||||
signale.error('Webpack compilation error')
|
||||
return
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { omit } from 'lodash'
|
||||
import shelljs from 'shelljs'
|
||||
import signale from 'signale'
|
||||
import utcVersion from 'utc-version'
|
||||
import { Stats } from 'webpack'
|
||||
import { Configuration } from 'webpack'
|
||||
|
||||
import extensionInfo from '../src/browser-extension/manifest.spec.json'
|
||||
import schema from '../src/browser-extension/schema.json'
|
||||
@ -37,7 +37,7 @@ const BUILDS_DIR = 'build'
|
||||
*/
|
||||
const useUtcVersion = true
|
||||
|
||||
export const WEBPACK_STATS_OPTIONS: Stats.ToStringOptions = {
|
||||
export const WEBPACK_STATS_OPTIONS: Configuration['stats'] = {
|
||||
all: false,
|
||||
timings: true,
|
||||
errors: true,
|
||||
|
||||
@ -11,7 +11,7 @@ import { isErrorLike } from '../util/errors'
|
||||
import { sanitizeClass } from '../util/strings'
|
||||
|
||||
import { toNativeEvent } from './helpers'
|
||||
import { HoverContext, HoverOverlayBaseProps, GetAlertClassName } from './HoverOverlay.types'
|
||||
import type { HoverContext, HoverOverlayBaseProps, GetAlertClassName } from './HoverOverlay.types'
|
||||
import { HoverOverlayAlerts, HoverOverlayAlertsProps } from './HoverOverlayAlerts'
|
||||
import { HoverOverlayContents } from './HoverOverlayContents'
|
||||
import { useLogTelemetryEvent } from './useLogTelemetryEvent'
|
||||
@ -21,7 +21,7 @@ const LOADING = 'loading' as const
|
||||
const transformMouseEvent = (handler: (event: MouseEvent) => void) => (event: React.MouseEvent<HTMLElement>) =>
|
||||
handler(toNativeEvent(event))
|
||||
|
||||
export { HoverContext }
|
||||
export type { HoverContext }
|
||||
|
||||
export interface HoverOverlayClassProps {
|
||||
/** An optional class name to apply to the outermost element of the HoverOverlay */
|
||||
|
||||
@ -176,7 +176,8 @@ export class Driver {
|
||||
!message
|
||||
.text()
|
||||
.includes('Warning: componentWillReceiveProps has been renamed') &&
|
||||
!message.text().includes('React-Hot-Loader') &&
|
||||
!message.text().includes('Download the Apollo DevTools') &&
|
||||
!message.text().includes('debug') &&
|
||||
// These requests are expected to fail, we use them to check if the browser extension is installed.
|
||||
message.location().url !== 'chrome-extension://invalid/'
|
||||
),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Stats } from 'webpack'
|
||||
import { StatsCompilation } from 'webpack'
|
||||
|
||||
import { nodeModulesPath } from '../webpack.config.common'
|
||||
|
||||
@ -6,7 +6,7 @@ import { nodeModulesPath } from '../webpack.config.common'
|
||||
const SKIP_VENDOR_MODULES = ['webpack']
|
||||
|
||||
// Get a list of files to include into a DLL bundle based on Webpack stats provided.
|
||||
export function getVendorModules(webpackStats: Stats.ToJsonOutput): Set<string> {
|
||||
export function getVendorModules(webpackStats: StatsCompilation): Set<string> {
|
||||
const vendorsChunk = webpackStats.chunks?.find(
|
||||
chunk => typeof chunk.id === 'string' && chunk.id.includes('vendors')
|
||||
)
|
||||
@ -16,9 +16,13 @@ export function getVendorModules(webpackStats: Stats.ToJsonOutput): Set<string>
|
||||
}
|
||||
|
||||
const vendorModules = vendorsChunk.modules
|
||||
.map(({ identifier }) => {
|
||||
.map(module => {
|
||||
if (!module.identifier) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// `identifier` contains loaders prefix, so `path.relative()` doesn't work for all cases.
|
||||
const [relativePathToModule] = identifier.split(`${nodeModulesPath}/`).slice(-1)
|
||||
const [relativePathToModule] = module.identifier.split(`${nodeModulesPath}/`).slice(-1)
|
||||
|
||||
// Remove suffix generated for some Storybook modules.
|
||||
return relativePathToModule.replace('-generated-other-entry.js', '')
|
||||
|
||||
@ -3,7 +3,7 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import signale from 'signale'
|
||||
import { Stats } from 'webpack'
|
||||
import { StatsCompilation } from 'webpack'
|
||||
|
||||
import { readJsonFile, storybookWorkspacePath, rootPath } from '../webpack.config.common'
|
||||
|
||||
@ -27,8 +27,8 @@ export const ensureWebpackStatsAreReady = (): void => {
|
||||
}
|
||||
|
||||
// Read Webpack stats JSON file. If it's not available use `yarn build:webpack-stats` command to create it.
|
||||
export function getWebpackStats(): Stats.ToJsonOutput {
|
||||
export function getWebpackStats(): StatsCompilation {
|
||||
ensureWebpackStatsAreReady()
|
||||
|
||||
return readJsonFile(webpackStatsPath) as Stats.ToJsonOutput
|
||||
return readJsonFile(webpackStatsPath) as StatsCompilation
|
||||
}
|
||||
|
||||
@ -6,7 +6,15 @@ import { remove } from 'lodash'
|
||||
import signale from 'signale'
|
||||
import SpeedMeasurePlugin from 'speed-measure-webpack-plugin'
|
||||
import TerserPlugin from 'terser-webpack-plugin'
|
||||
import { DllReferencePlugin, Configuration, DefinePlugin, ProgressPlugin, RuleSetUseItem, RuleSetUse } from 'webpack'
|
||||
import webpack, {
|
||||
DllReferencePlugin,
|
||||
Configuration,
|
||||
DefinePlugin,
|
||||
ProgressPlugin,
|
||||
RuleSetUseItem,
|
||||
RuleSetUse,
|
||||
RuleSetRule,
|
||||
} from 'webpack'
|
||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
|
||||
|
||||
import { ensureDllBundleIsReady } from './dllPlugin'
|
||||
@ -22,6 +30,7 @@ import {
|
||||
nodeModulesPath,
|
||||
getBasicCSSLoader,
|
||||
readJsonFile,
|
||||
storybookWorkspacePath,
|
||||
} from './webpack.config.common'
|
||||
|
||||
const getStoriesGlob = (): string[] => {
|
||||
@ -79,6 +88,10 @@ const config = {
|
||||
'@storybook/addon-toolbars',
|
||||
],
|
||||
|
||||
core: {
|
||||
builder: 'webpack5',
|
||||
},
|
||||
|
||||
features: {
|
||||
// Explicitly disable the deprecated, not used postCSS support,
|
||||
// so no warning is rendered on each start of storybook.
|
||||
@ -101,7 +114,7 @@ const config = {
|
||||
config.mode = environment.shouldMinify ? 'production' : 'development'
|
||||
|
||||
// Check the default config is in an expected shape.
|
||||
if (!config.module || !config.plugins) {
|
||||
if (!config.module?.rules || !config.plugins) {
|
||||
throw new Error(
|
||||
'The format of the default storybook webpack config changed, please check if the config in ./src/main.ts is still valid'
|
||||
)
|
||||
@ -111,6 +124,11 @@ const config = {
|
||||
new DefinePlugin({
|
||||
NODE_ENV: JSON.stringify(config.mode),
|
||||
'process.env.NODE_ENV': JSON.stringify(config.mode),
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
// Based on the issue: https://github.com/webpack/changelog-v5/issues/10
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
})
|
||||
)
|
||||
|
||||
@ -118,12 +136,10 @@ const config = {
|
||||
if (!config.optimization) {
|
||||
throw new Error('The structure of the config changed, expected config.optimization to be not-null')
|
||||
}
|
||||
config.optimization.namedModules = false
|
||||
config.optimization.minimize = true
|
||||
config.optimization.minimizer = [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
sourceMap: true,
|
||||
compress: {
|
||||
// Don't inline functions, which causes name collisions with uglify-es:
|
||||
// https://github.com/mishoo/UglifyJS2/issues/2842
|
||||
@ -132,6 +148,21 @@ const config = {
|
||||
},
|
||||
}),
|
||||
]
|
||||
} else {
|
||||
// Use cache only in `development` mode to speed up production build.
|
||||
config.cache = {
|
||||
type: 'filesystem',
|
||||
buildDependencies: {
|
||||
// Invalidate cache on config change.
|
||||
config: [
|
||||
__filename,
|
||||
path.resolve(storybookWorkspacePath, 'babel.config.js'),
|
||||
path.resolve(rootPath, 'babel.config.js'),
|
||||
path.resolve(rootPath, 'postcss.config.js'),
|
||||
path.resolve(__dirname, './webpack.config.dll.ts'),
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// We don't use Storybook's default Babel config for our repo, it doesn't include everything we need.
|
||||
@ -174,7 +205,9 @@ const config = {
|
||||
})
|
||||
|
||||
// Make sure Storybook style loaders are only evaluated for Storybook styles.
|
||||
const cssRule = config.module.rules.find(rule => rule.test?.toString() === /\.css$/.toString())
|
||||
const cssRule = config.module.rules.find(
|
||||
(rule): rule is RuleSetRule => typeof rule !== 'string' && rule.test?.toString() === /\.css$/.toString()
|
||||
)
|
||||
if (!cssRule) {
|
||||
throw new Error('Cannot find original CSS rule')
|
||||
}
|
||||
@ -190,7 +223,7 @@ const config = {
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.ya?ml$/,
|
||||
use: ['raw-loader'],
|
||||
type: 'asset/source',
|
||||
})
|
||||
|
||||
// Disable `CaseSensitivePathsPlugin` by default to speed up development build.
|
||||
|
||||
@ -26,7 +26,7 @@ export const getMonacoCSSRule = (): RuleSetRule => ({
|
||||
export const getMonacoTTFRule = (): RuleSetRule => ({
|
||||
test: /\.ttf$/,
|
||||
include: [monacoEditorPath],
|
||||
use: ['file-loader'],
|
||||
type: 'asset/resource',
|
||||
})
|
||||
|
||||
export const getBasicCSSLoader = (): RuleSetUseItem => ({
|
||||
|
||||
@ -26,6 +26,8 @@ const config: Configuration = {
|
||||
filename: '[name].bundle.[contenthash].js',
|
||||
path: dllPluginConfig.context,
|
||||
library: dllPluginConfig.name,
|
||||
// Required to fix the `WebpackManifestPlugin` output: https://github.com/shellscape/webpack-manifest-plugin/issues/229
|
||||
publicPath: '',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import chalk from 'chalk'
|
||||
import { Application } from 'express'
|
||||
import { once } from 'lodash'
|
||||
import signale from 'signale'
|
||||
import createWebpackCompiler, { Configuration } from 'webpack'
|
||||
import WebpackDevServer, { ProxyConfigArrayItem } from 'webpack-dev-server'
|
||||
@ -11,7 +13,6 @@ import {
|
||||
getCSRFTokenAndCookie,
|
||||
STATIC_ASSETS_PATH,
|
||||
STATIC_ASSETS_URL,
|
||||
WEBPACK_STATS_OPTIONS,
|
||||
WEB_SERVER_URL,
|
||||
} from '../utils'
|
||||
|
||||
@ -23,9 +24,9 @@ const { SOURCEGRAPH_API_URL, SOURCEGRAPH_HTTPS_PORT, IS_HOT_RELOAD_ENABLED } = e
|
||||
export async function startDevelopmentServer(): Promise<void> {
|
||||
// Get CSRF token value from the `SOURCEGRAPH_API_URL`.
|
||||
const { csrfContextValue, csrfCookieValue } = await getCSRFTokenAndCookie(SOURCEGRAPH_API_URL)
|
||||
signale.await('Development server', { ...environmentConfig, csrfContextValue, csrfCookieValue })
|
||||
signale.start('Starting webpack-dev-server with environment config:\n', environmentConfig)
|
||||
|
||||
const proxyConfig = {
|
||||
const proxyConfig: ProxyConfigArrayItem = {
|
||||
context: PROXY_ROUTES,
|
||||
...getAPIProxySettings({
|
||||
csrfContextValue,
|
||||
@ -33,7 +34,11 @@ export async function startDevelopmentServer(): Promise<void> {
|
||||
}),
|
||||
}
|
||||
|
||||
const options: WebpackDevServer.Configuration = {
|
||||
// It's not possible to use `WebpackDevServer.Configuration` here yet, because
|
||||
// type definitions for the `webpack-dev-server` are not updated to match v4.
|
||||
const developmentServerConfig = {
|
||||
// react-refresh plugin triggers page reload if needed.
|
||||
liveReload: false,
|
||||
hot: IS_HOT_RELOAD_ENABLED,
|
||||
// TODO: resolve https://github.com/webpack/webpack-dev-server/issues/2313 and enable HTTPS.
|
||||
https: false,
|
||||
@ -41,24 +46,33 @@ export async function startDevelopmentServer(): Promise<void> {
|
||||
disableDotRule: true,
|
||||
},
|
||||
port: SOURCEGRAPH_HTTPS_PORT,
|
||||
publicPath: STATIC_ASSETS_URL,
|
||||
contentBase: STATIC_ASSETS_PATH,
|
||||
contentBasePublicPath: [STATIC_ASSETS_URL, '/'],
|
||||
stats: WEBPACK_STATS_OPTIONS,
|
||||
noInfo: false,
|
||||
disableHostCheck: true,
|
||||
proxy: [proxyConfig as ProxyConfigArrayItem],
|
||||
before(app) {
|
||||
client: {
|
||||
overlay: false,
|
||||
},
|
||||
static: {
|
||||
directory: STATIC_ASSETS_PATH,
|
||||
publicPath: [STATIC_ASSETS_URL, '/'],
|
||||
},
|
||||
firewall: false,
|
||||
proxy: [proxyConfig],
|
||||
onBeforeSetupMiddleware(app: Application) {
|
||||
app.use(getCSRFTokenCookieMiddleware(csrfCookieValue))
|
||||
},
|
||||
}
|
||||
|
||||
WebpackDevServer.addDevServerEntrypoints(webpackConfig, options)
|
||||
const compiler = createWebpackCompiler(webpackConfig)
|
||||
const server = new WebpackDevServer(compiler, developmentServerConfig as WebpackDevServer.Configuration)
|
||||
|
||||
const server = new WebpackDevServer(createWebpackCompiler(webpackConfig), options)
|
||||
compiler.hooks.afterEmit.tap(
|
||||
'development-server-logger',
|
||||
once(() => {
|
||||
signale.success('Webpack build is ready!')
|
||||
})
|
||||
)
|
||||
|
||||
server.listen(SOURCEGRAPH_HTTPS_PORT, '0.0.0.0', () => {
|
||||
signale.success(`Development server is ready at ${chalk.blue.bold(WEB_SERVER_URL)}`)
|
||||
signale.await('Waiting for Webpack to compile assets')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -2,21 +2,25 @@ import path from 'path'
|
||||
|
||||
import HtmlWebpackHarddiskPlugin from 'html-webpack-harddisk-plugin'
|
||||
import HtmlWebpackPlugin, { TemplateParameter, Options } from 'html-webpack-plugin'
|
||||
import { Plugin } from 'webpack'
|
||||
import { WebpackPluginInstance } from 'webpack'
|
||||
|
||||
import { createJsContext, environmentConfig, STATIC_ASSETS_PATH } from '../utils'
|
||||
|
||||
const { SOURCEGRAPH_HTTPS_PORT, NODE_ENV } = environmentConfig
|
||||
|
||||
export const getHTMLWebpackPlugins = (): Plugin[] => {
|
||||
export const getHTMLWebpackPlugins = (): WebpackPluginInstance[] => {
|
||||
const jsContext = createJsContext({ sourcegraphBaseUrl: `http://localhost:${SOURCEGRAPH_HTTPS_PORT}` })
|
||||
|
||||
// TODO: use `cmd/frontend/internal/app/ui/app.html` template to be consistent with the default production setup.
|
||||
/**
|
||||
* To match `cmd/frontend/internal/app/ui/app.html` template used by our `frontend` server
|
||||
* script tags are injected after the <body> tag.
|
||||
*/
|
||||
const templateContent = ({ htmlWebpackPlugin }: TemplateParameter): string => `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>${htmlWebpackPlugin.options.title || 'Sourcegraph'}</title>
|
||||
<title>Sourcegraph</title>
|
||||
${htmlWebpackPlugin.tags.headTags.filter(tag => tag.tagName !== 'script').toString()}
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
@ -27,6 +31,7 @@ export const getHTMLWebpackPlugins = (): Plugin[] => {
|
||||
// Required mock of the JS context object.
|
||||
window.context = ${JSON.stringify(jsContext)}
|
||||
</script>
|
||||
${htmlWebpackPlugin.tags.headTags.filter(tag => tag.tagName === 'script').toString()}
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
@ -42,6 +47,7 @@ export const getHTMLWebpackPlugins = (): Plugin[] => {
|
||||
},
|
||||
filename: path.resolve(STATIC_ASSETS_PATH, 'index.html'),
|
||||
alwaysWriteToDisk: true,
|
||||
inject: false,
|
||||
})
|
||||
|
||||
// Write index.html to the disk so it can be served by dev/prod servers.
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// @ts-check
|
||||
const path = require('path')
|
||||
|
||||
require('ts-node').register({
|
||||
@ -9,8 +8,12 @@ require('ts-node').register({
|
||||
|
||||
const log = require('fancy-log')
|
||||
const gulp = require('gulp')
|
||||
const signale = require('signale')
|
||||
const createWebpackCompiler = require('webpack')
|
||||
const WebpackDevServer = require('webpack-dev-server')
|
||||
// The `DevServerPlugin` should be exposed after the `webpack-dev-server@4` goes out of the beta stage.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const DevServerPlugin = require('webpack-dev-server/lib/utils/DevServerPlugin')
|
||||
|
||||
const {
|
||||
graphQlSchema,
|
||||
@ -72,37 +75,52 @@ async function webpackDevelopmentServer() {
|
||||
const sockHost = process.env.SOURCEGRAPH_HTTPS_DOMAIN || 'sourcegraph.test'
|
||||
const sockPort = Number(process.env.SOURCEGRAPH_HTTPS_PORT || 3443)
|
||||
|
||||
/** @type {import('webpack-dev-server').Configuration & { liveReload?: boolean }} */
|
||||
/** @type {import('webpack-dev-server').ProxyConfigMap } */
|
||||
const proxyConfig = {
|
||||
'/': {
|
||||
target: 'http://localhost:3081',
|
||||
// Avoid crashing on "read ECONNRESET".
|
||||
onError: () => undefined,
|
||||
// Don't log proxy errors, these usually just contain
|
||||
// ECONNRESET errors caused by the browser cancelling
|
||||
// requests. This should not be needed to actually debug something.
|
||||
logLevel: 'silent',
|
||||
onProxyReqWs: (_proxyRequest, _request, socket) =>
|
||||
socket.on('error', error => console.error('WebSocket proxy error:', error)),
|
||||
},
|
||||
}
|
||||
|
||||
const options = {
|
||||
// react-refresh plugin triggers page reload if needed.
|
||||
liveReload: false,
|
||||
hot: !process.env.NO_HOT,
|
||||
inline: !process.env.NO_HOT,
|
||||
allowedHosts: ['.host.docker.internal'],
|
||||
firewall: false,
|
||||
host: 'localhost',
|
||||
port: 3080,
|
||||
publicPath: '/.assets/',
|
||||
contentBase: './ui/assets',
|
||||
stats: WEBPACK_STATS_OPTIONS,
|
||||
noInfo: false,
|
||||
disableHostCheck: true,
|
||||
proxy: {
|
||||
'/': {
|
||||
target: 'http://localhost:3081',
|
||||
// Avoid crashing on "read ECONNRESET".
|
||||
onError: () => undefined,
|
||||
// Don't log proxy errors, these usually just contain
|
||||
// ECONNRESET errors caused by the browser cancelling
|
||||
// requests. This should not be needed to actually debug something.
|
||||
logLevel: 'silent',
|
||||
onProxyReqWs: (_proxyRequest, _request, socket) =>
|
||||
socket.on('error', error => console.error('WebSocket proxy error:', error)),
|
||||
},
|
||||
client: {
|
||||
host: sockHost,
|
||||
port: sockPort,
|
||||
overlay: false,
|
||||
},
|
||||
static: {
|
||||
directory: './ui/assets',
|
||||
publicPath: '/.assets/',
|
||||
},
|
||||
proxy: proxyConfig,
|
||||
transportMode: {
|
||||
client: 'ws',
|
||||
},
|
||||
sockHost,
|
||||
sockPort,
|
||||
}
|
||||
WebpackDevServer.addDevServerEntrypoints(webpackConfig, options)
|
||||
|
||||
// Based on the update: https://github.com/webpack/webpack-dev-server/pull/2844
|
||||
if (!webpackConfig.plugins.find(plugin => plugin.constructor === DevServerPlugin)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
webpackConfig.plugins.push(new DevServerPlugin(options))
|
||||
}
|
||||
|
||||
const server = new WebpackDevServer(createWebpackCompiler(webpackConfig), options)
|
||||
await new Promise((resolve, reject) => {
|
||||
signale.await('Waiting for Webpack to compile assets')
|
||||
server.listen(3080, '0.0.0.0', error => (error ? reject(error) : resolve()))
|
||||
})
|
||||
}
|
||||
|
||||
51
client/web/src/OpenSourceWebApp.tsx
Normal file
51
client/web/src/OpenSourceWebApp.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React from 'react'
|
||||
|
||||
import { extensionAreaHeaderNavItems } from './extensions/extension/extensionAreaHeaderNavItems'
|
||||
import { extensionAreaRoutes } from './extensions/extension/routes'
|
||||
import { extensionsAreaHeaderActionButtons } from './extensions/extensionsAreaHeaderActionButtons'
|
||||
import { extensionsAreaRoutes } from './extensions/routes'
|
||||
import './SourcegraphWebApp.scss'
|
||||
import { KEYBOARD_SHORTCUTS } from './keyboardShortcuts/keyboardShortcuts'
|
||||
import { orgAreaHeaderNavItems } from './org/area/navitems'
|
||||
import { orgAreaRoutes } from './org/area/routes'
|
||||
import { repoHeaderActionButtons } from './repo/repoHeaderActionButtons'
|
||||
import { repoContainerRoutes, repoRevisionContainerRoutes } from './repo/routes'
|
||||
import { repoSettingsAreaRoutes } from './repo/settings/routes'
|
||||
import { repoSettingsSideBarGroups } from './repo/settings/sidebaritems'
|
||||
import { routes } from './routes'
|
||||
import { siteAdminOverviewComponents } from './site-admin/overview/overviewComponents'
|
||||
import { siteAdminAreaRoutes } from './site-admin/routes'
|
||||
import { siteAdminSidebarGroups } from './site-admin/sidebaritems'
|
||||
import { SourcegraphWebApp } from './SourcegraphWebApp'
|
||||
import { userAreaHeaderNavItems } from './user/area/navitems'
|
||||
import { userAreaRoutes } from './user/area/routes'
|
||||
import { userSettingsAreaRoutes } from './user/settings/routes'
|
||||
import { userSettingsSideBarItems } from './user/settings/sidebaritems'
|
||||
|
||||
// Entry point for the app without enterprise functionality.
|
||||
// For more info see: https://docs.sourcegraph.com/admin/subscriptions#paid-subscriptions-for-sourcegraph-enterprise
|
||||
export const OpenSourceWebApp: React.FunctionComponent = () => (
|
||||
<SourcegraphWebApp
|
||||
extensionAreaRoutes={extensionAreaRoutes}
|
||||
extensionAreaHeaderNavItems={extensionAreaHeaderNavItems}
|
||||
extensionsAreaRoutes={extensionsAreaRoutes}
|
||||
extensionsAreaHeaderActionButtons={extensionsAreaHeaderActionButtons}
|
||||
siteAdminAreaRoutes={siteAdminAreaRoutes}
|
||||
siteAdminSideBarGroups={siteAdminSidebarGroups}
|
||||
siteAdminOverviewComponents={siteAdminOverviewComponents}
|
||||
userAreaRoutes={userAreaRoutes}
|
||||
userAreaHeaderNavItems={userAreaHeaderNavItems}
|
||||
userSettingsSideBarItems={userSettingsSideBarItems}
|
||||
userSettingsAreaRoutes={userSettingsAreaRoutes}
|
||||
orgAreaRoutes={orgAreaRoutes}
|
||||
orgAreaHeaderNavItems={orgAreaHeaderNavItems}
|
||||
repoContainerRoutes={repoContainerRoutes}
|
||||
repoRevisionContainerRoutes={repoRevisionContainerRoutes}
|
||||
repoHeaderActionButtons={repoHeaderActionButtons}
|
||||
repoSettingsAreaRoutes={repoSettingsAreaRoutes}
|
||||
repoSettingsSidebarGroups={repoSettingsSideBarGroups}
|
||||
routes={routes}
|
||||
keyboardShortcuts={KEYBOARD_SHORTCUTS}
|
||||
showBatchChanges={false}
|
||||
/>
|
||||
)
|
||||
@ -4,7 +4,6 @@ import { ApolloProvider } from '@apollo/client'
|
||||
import { ShortcutProvider } from '@slimsag/react-shortcuts'
|
||||
import ServerIcon from 'mdi-react/ServerIcon'
|
||||
import * as React from 'react'
|
||||
import { hot } from 'react-hot-loader/root'
|
||||
import { Route } from 'react-router'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { combineLatest, from, Subscription, fromEvent, of, Subject } from 'rxjs'
|
||||
@ -259,11 +258,8 @@ const LayoutWithActivation = window.context.sourcegraphDotComMode ? Layout : wit
|
||||
|
||||
/**
|
||||
* The root component.
|
||||
*
|
||||
* This is the non-hot-reload component. It is wrapped in `hot(...)` below to make it
|
||||
* hot-reloadable in development.
|
||||
*/
|
||||
class ColdSourcegraphWebApp extends React.Component<SourcegraphWebAppProps, SourcegraphWebAppState> {
|
||||
export class SourcegraphWebApp extends React.Component<SourcegraphWebAppProps, SourcegraphWebAppState> {
|
||||
private readonly subscriptions = new Subscription()
|
||||
private readonly userRepositoriesUpdates = new Subject<void>()
|
||||
private readonly darkThemeMediaList = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
@ -671,5 +667,3 @@ class ColdSourcegraphWebApp extends React.Component<SourcegraphWebAppProps, Sour
|
||||
await extensionHostAPI.setSearchContext(spec)
|
||||
}
|
||||
}
|
||||
|
||||
export const SourcegraphWebApp = hot(ColdSourcegraphWebApp)
|
||||
|
||||
@ -11,14 +11,17 @@ export const withAuthenticatedUser = <P extends object & { authenticatedUser: Au
|
||||
Component: React.ComponentType<P>
|
||||
): React.ComponentType<
|
||||
Pick<P, Exclude<keyof P, 'authenticatedUser'>> & { authenticatedUser: AuthenticatedUser | null }
|
||||
> => ({ authenticatedUser, ...props }) => {
|
||||
// If not logged in, redirect to sign in.
|
||||
if (!authenticatedUser) {
|
||||
const newUrl = new URL(window.location.href)
|
||||
newUrl.pathname = '/sign-in'
|
||||
// Return to the current page after sign up/in.
|
||||
newUrl.searchParams.set('returnTo', window.location.href)
|
||||
return <Redirect to={newUrl.pathname + newUrl.search} />
|
||||
> =>
|
||||
// It's important to add names to all components to avoid full reload on hot-update.
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#edits-always-lead-to-full-reload
|
||||
function WithAuthenticatedUser({ authenticatedUser, ...props }) {
|
||||
// If not logged in, redirect to sign in.
|
||||
if (!authenticatedUser) {
|
||||
const newUrl = new URL(window.location.href)
|
||||
newUrl.pathname = '/sign-in'
|
||||
// Return to the current page after sign up/in.
|
||||
newUrl.searchParams.set('returnTo', window.location.href)
|
||||
return <Redirect to={newUrl.pathname + newUrl.search} />
|
||||
}
|
||||
return <Component {...({ ...props, authenticatedUser } as P)} />
|
||||
}
|
||||
return <Component {...({ ...props, authenticatedUser } as P)} />
|
||||
}
|
||||
|
||||
51
client/web/src/enterprise/EnterpriseWebApp.tsx
Normal file
51
client/web/src/enterprise/EnterpriseWebApp.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React from 'react'
|
||||
|
||||
import '../SourcegraphWebApp.scss'
|
||||
import '../enterprise.scss'
|
||||
import { KEYBOARD_SHORTCUTS } from '../keyboardShortcuts/keyboardShortcuts'
|
||||
import { SourcegraphWebApp } from '../SourcegraphWebApp'
|
||||
|
||||
import { enterpriseExtensionAreaHeaderNavItems } from './extensions/extension/extensionAreaHeaderNavItems'
|
||||
import { enterpriseExtensionAreaRoutes } from './extensions/extension/routes'
|
||||
import { enterpriseExtensionsAreaHeaderActionButtons } from './extensions/extensionsAreaHeaderActionButtons'
|
||||
import { enterpriseExtensionsAreaRoutes } from './extensions/routes'
|
||||
import { enterpriseOrgAreaHeaderNavItems } from './organizations/navitems'
|
||||
import { enterpriseOrganizationAreaRoutes } from './organizations/routes'
|
||||
import { enterpriseRepoHeaderActionButtons } from './repo/repoHeaderActionButtons'
|
||||
import { enterpriseRepoContainerRoutes, enterpriseRepoRevisionContainerRoutes } from './repo/routes'
|
||||
import { enterpriseRepoSettingsAreaRoutes } from './repo/settings/routes'
|
||||
import { enterpriseRepoSettingsSidebarGroups } from './repo/settings/sidebaritems'
|
||||
import { enterpriseRoutes } from './routes'
|
||||
import { enterpriseSiteAdminOverviewComponents } from './site-admin/overview/overviewComponents'
|
||||
import { enterpriseSiteAdminAreaRoutes } from './site-admin/routes'
|
||||
import { enterpriseSiteAdminSidebarGroups } from './site-admin/sidebaritems'
|
||||
import { enterpriseUserAreaHeaderNavItems } from './user/navitems'
|
||||
import { enterpriseUserAreaRoutes } from './user/routes'
|
||||
import { enterpriseUserSettingsAreaRoutes } from './user/settings/routes'
|
||||
import { enterpriseUserSettingsSideBarItems } from './user/settings/sidebaritems'
|
||||
|
||||
export const EnterpriseWebApp: React.FunctionComponent = () => (
|
||||
<SourcegraphWebApp
|
||||
extensionAreaRoutes={enterpriseExtensionAreaRoutes}
|
||||
extensionAreaHeaderNavItems={enterpriseExtensionAreaHeaderNavItems}
|
||||
extensionsAreaRoutes={enterpriseExtensionsAreaRoutes}
|
||||
extensionsAreaHeaderActionButtons={enterpriseExtensionsAreaHeaderActionButtons}
|
||||
siteAdminAreaRoutes={enterpriseSiteAdminAreaRoutes}
|
||||
siteAdminSideBarGroups={enterpriseSiteAdminSidebarGroups}
|
||||
siteAdminOverviewComponents={enterpriseSiteAdminOverviewComponents}
|
||||
userAreaHeaderNavItems={enterpriseUserAreaHeaderNavItems}
|
||||
userAreaRoutes={enterpriseUserAreaRoutes}
|
||||
userSettingsSideBarItems={enterpriseUserSettingsSideBarItems}
|
||||
userSettingsAreaRoutes={enterpriseUserSettingsAreaRoutes}
|
||||
orgAreaRoutes={enterpriseOrganizationAreaRoutes}
|
||||
orgAreaHeaderNavItems={enterpriseOrgAreaHeaderNavItems}
|
||||
repoContainerRoutes={enterpriseRepoContainerRoutes}
|
||||
repoRevisionContainerRoutes={enterpriseRepoRevisionContainerRoutes}
|
||||
repoHeaderActionButtons={enterpriseRepoHeaderActionButtons}
|
||||
repoSettingsAreaRoutes={enterpriseRepoSettingsAreaRoutes}
|
||||
repoSettingsSidebarGroups={enterpriseRepoSettingsSidebarGroups}
|
||||
routes={enterpriseRoutes}
|
||||
keyboardShortcuts={KEYBOARD_SHORTCUTS}
|
||||
showBatchChanges={window.context.batchChangesEnabled}
|
||||
/>
|
||||
)
|
||||
@ -10,55 +10,10 @@ import '../sentry'
|
||||
import React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
|
||||
import '../SourcegraphWebApp.scss'
|
||||
import '../enterprise.scss'
|
||||
import { KEYBOARD_SHORTCUTS } from '../keyboardShortcuts/keyboardShortcuts'
|
||||
import { SourcegraphWebApp } from '../SourcegraphWebApp'
|
||||
|
||||
import { enterpriseExtensionAreaHeaderNavItems } from './extensions/extension/extensionAreaHeaderNavItems'
|
||||
import { enterpriseExtensionAreaRoutes } from './extensions/extension/routes'
|
||||
import { enterpriseExtensionsAreaHeaderActionButtons } from './extensions/extensionsAreaHeaderActionButtons'
|
||||
import { enterpriseExtensionsAreaRoutes } from './extensions/routes'
|
||||
import { enterpriseOrgAreaHeaderNavItems } from './organizations/navitems'
|
||||
import { enterpriseOrganizationAreaRoutes } from './organizations/routes'
|
||||
import { enterpriseRepoHeaderActionButtons } from './repo/repoHeaderActionButtons'
|
||||
import { enterpriseRepoContainerRoutes, enterpriseRepoRevisionContainerRoutes } from './repo/routes'
|
||||
import { enterpriseRepoSettingsAreaRoutes } from './repo/settings/routes'
|
||||
import { enterpriseRepoSettingsSidebarGroups } from './repo/settings/sidebaritems'
|
||||
import { enterpriseRoutes } from './routes'
|
||||
import { enterpriseSiteAdminOverviewComponents } from './site-admin/overview/overviewComponents'
|
||||
import { enterpriseSiteAdminAreaRoutes } from './site-admin/routes'
|
||||
import { enterpriseSiteAdminSidebarGroups } from './site-admin/sidebaritems'
|
||||
import { enterpriseUserAreaHeaderNavItems } from './user/navitems'
|
||||
import { enterpriseUserAreaRoutes } from './user/routes'
|
||||
import { enterpriseUserSettingsAreaRoutes } from './user/settings/routes'
|
||||
import { enterpriseUserSettingsSideBarItems } from './user/settings/sidebaritems'
|
||||
import { EnterpriseWebApp } from './EnterpriseWebApp'
|
||||
|
||||
// It's important to have a root component in a separate file to create a react-refresh boundary and avoid page reload.
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#edits-always-lead-to-full-reload
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
render(
|
||||
<SourcegraphWebApp
|
||||
extensionAreaRoutes={enterpriseExtensionAreaRoutes}
|
||||
extensionAreaHeaderNavItems={enterpriseExtensionAreaHeaderNavItems}
|
||||
extensionsAreaRoutes={enterpriseExtensionsAreaRoutes}
|
||||
extensionsAreaHeaderActionButtons={enterpriseExtensionsAreaHeaderActionButtons}
|
||||
siteAdminAreaRoutes={enterpriseSiteAdminAreaRoutes}
|
||||
siteAdminSideBarGroups={enterpriseSiteAdminSidebarGroups}
|
||||
siteAdminOverviewComponents={enterpriseSiteAdminOverviewComponents}
|
||||
userAreaHeaderNavItems={enterpriseUserAreaHeaderNavItems}
|
||||
userAreaRoutes={enterpriseUserAreaRoutes}
|
||||
userSettingsSideBarItems={enterpriseUserSettingsSideBarItems}
|
||||
userSettingsAreaRoutes={enterpriseUserSettingsAreaRoutes}
|
||||
orgAreaRoutes={enterpriseOrganizationAreaRoutes}
|
||||
orgAreaHeaderNavItems={enterpriseOrgAreaHeaderNavItems}
|
||||
repoContainerRoutes={enterpriseRepoContainerRoutes}
|
||||
repoRevisionContainerRoutes={enterpriseRepoRevisionContainerRoutes}
|
||||
repoHeaderActionButtons={enterpriseRepoHeaderActionButtons}
|
||||
repoSettingsAreaRoutes={enterpriseRepoSettingsAreaRoutes}
|
||||
repoSettingsSidebarGroups={enterpriseRepoSettingsSidebarGroups}
|
||||
routes={enterpriseRoutes}
|
||||
keyboardShortcuts={KEYBOARD_SHORTCUTS}
|
||||
showBatchChanges={window.context.batchChangesEnabled}
|
||||
/>,
|
||||
document.querySelector('#root')
|
||||
)
|
||||
render(<EnterpriseWebApp />, document.querySelector('#root'))
|
||||
})
|
||||
|
||||
@ -10,53 +10,10 @@ import './sentry'
|
||||
import React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
|
||||
import { extensionAreaHeaderNavItems } from './extensions/extension/extensionAreaHeaderNavItems'
|
||||
import { extensionAreaRoutes } from './extensions/extension/routes'
|
||||
import { extensionsAreaHeaderActionButtons } from './extensions/extensionsAreaHeaderActionButtons'
|
||||
import { extensionsAreaRoutes } from './extensions/routes'
|
||||
import './SourcegraphWebApp.scss'
|
||||
import { KEYBOARD_SHORTCUTS } from './keyboardShortcuts/keyboardShortcuts'
|
||||
import { orgAreaHeaderNavItems } from './org/area/navitems'
|
||||
import { orgAreaRoutes } from './org/area/routes'
|
||||
import { repoHeaderActionButtons } from './repo/repoHeaderActionButtons'
|
||||
import { repoContainerRoutes, repoRevisionContainerRoutes } from './repo/routes'
|
||||
import { repoSettingsAreaRoutes } from './repo/settings/routes'
|
||||
import { repoSettingsSideBarGroups } from './repo/settings/sidebaritems'
|
||||
import { routes } from './routes'
|
||||
import { siteAdminOverviewComponents } from './site-admin/overview/overviewComponents'
|
||||
import { siteAdminAreaRoutes } from './site-admin/routes'
|
||||
import { siteAdminSidebarGroups } from './site-admin/sidebaritems'
|
||||
import { SourcegraphWebApp } from './SourcegraphWebApp'
|
||||
import { userAreaHeaderNavItems } from './user/area/navitems'
|
||||
import { userAreaRoutes } from './user/area/routes'
|
||||
import { userSettingsAreaRoutes } from './user/settings/routes'
|
||||
import { userSettingsSideBarItems } from './user/settings/sidebaritems'
|
||||
import { OpenSourceWebApp } from './OpenSourceWebApp'
|
||||
|
||||
// It's important to have a root component in a separate file to create a react-refresh boundary and avoid page reload.
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#edits-always-lead-to-full-reload
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
render(
|
||||
<SourcegraphWebApp
|
||||
extensionAreaRoutes={extensionAreaRoutes}
|
||||
extensionAreaHeaderNavItems={extensionAreaHeaderNavItems}
|
||||
extensionsAreaRoutes={extensionsAreaRoutes}
|
||||
extensionsAreaHeaderActionButtons={extensionsAreaHeaderActionButtons}
|
||||
siteAdminAreaRoutes={siteAdminAreaRoutes}
|
||||
siteAdminSideBarGroups={siteAdminSidebarGroups}
|
||||
siteAdminOverviewComponents={siteAdminOverviewComponents}
|
||||
userAreaRoutes={userAreaRoutes}
|
||||
userAreaHeaderNavItems={userAreaHeaderNavItems}
|
||||
userSettingsSideBarItems={userSettingsSideBarItems}
|
||||
userSettingsAreaRoutes={userSettingsAreaRoutes}
|
||||
orgAreaRoutes={orgAreaRoutes}
|
||||
orgAreaHeaderNavItems={orgAreaHeaderNavItems}
|
||||
repoContainerRoutes={repoContainerRoutes}
|
||||
repoRevisionContainerRoutes={repoRevisionContainerRoutes}
|
||||
repoHeaderActionButtons={repoHeaderActionButtons}
|
||||
repoSettingsAreaRoutes={repoSettingsAreaRoutes}
|
||||
repoSettingsSidebarGroups={repoSettingsSideBarGroups}
|
||||
routes={routes}
|
||||
keyboardShortcuts={KEYBOARD_SHORTCUTS}
|
||||
showBatchChanges={false}
|
||||
/>,
|
||||
document.querySelector('#root')
|
||||
)
|
||||
render(<OpenSourceWebApp />, document.querySelector('#root'))
|
||||
})
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
const path = require('path')
|
||||
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
|
||||
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')
|
||||
const logger = require('gulplog')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
@ -19,7 +20,11 @@ logger.info('Using mode', mode)
|
||||
|
||||
const isDevelopment = mode === 'development'
|
||||
const isProduction = mode === 'production'
|
||||
const devtool = isProduction ? 'source-map' : 'cheap-module-eval-source-map'
|
||||
const isCI = process.env.CI === 'true'
|
||||
const isCacheEnabled = isDevelopment && !isCI
|
||||
const isHotReloadEnabled = isDevelopment && !isCI
|
||||
|
||||
const devtool = isProduction ? 'source-map' : 'eval-cheap-module-source-map'
|
||||
|
||||
const shouldServeIndexHTML = process.env.WEBPACK_SERVE_INDEX === 'true'
|
||||
if (shouldServeIndexHTML) {
|
||||
@ -36,6 +41,9 @@ if (shouldAnalyze) {
|
||||
}
|
||||
|
||||
const rootPath = path.resolve(__dirname, '..', '..')
|
||||
const hotLoadablePaths = ['branded', 'shared', 'web', 'wildcard'].map(workspace =>
|
||||
path.resolve(rootPath, 'client', workspace, 'src')
|
||||
)
|
||||
const nodeModulesPath = path.resolve(rootPath, 'node_modules')
|
||||
const monacoEditorPaths = [path.resolve(nodeModulesPath, 'monaco-editor')]
|
||||
|
||||
@ -56,11 +64,35 @@ const extensionHostWorker = /main\.worker\.ts$/
|
||||
const config = {
|
||||
context: __dirname, // needed when running `gulp webpackDevServer` from the root dir
|
||||
mode,
|
||||
stats: {
|
||||
// Minimize logging in case if Webpack is used along with multiple other services.
|
||||
// Use `normal` output preset in case of running standalone web server.
|
||||
preset: shouldServeIndexHTML || isProduction ? 'normal' : 'errors-warnings',
|
||||
errorDetails: true,
|
||||
timings: true,
|
||||
},
|
||||
infrastructureLogging: {
|
||||
// Controls webpack-dev-server logging level.
|
||||
level: 'warn',
|
||||
},
|
||||
target: 'browserslist',
|
||||
// Use cache only in `development` mode to speed up production build.
|
||||
cache: isCacheEnabled && {
|
||||
type: 'filesystem',
|
||||
buildDependencies: {
|
||||
// Invalidate cache on config change.
|
||||
config: [
|
||||
__filename,
|
||||
path.resolve(__dirname, 'babel.config.js'),
|
||||
path.resolve(rootPath, 'babel.config.js'),
|
||||
path.resolve(rootPath, 'postcss.config.js'),
|
||||
],
|
||||
},
|
||||
},
|
||||
optimization: {
|
||||
minimize: isProduction,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
sourceMap: true,
|
||||
terserOptions: {
|
||||
compress: {
|
||||
// Don't inline functions, which causes name collisions with uglify-es:
|
||||
@ -71,24 +103,19 @@ const config = {
|
||||
}),
|
||||
new CssMinimizerWebpackPlugin(),
|
||||
],
|
||||
namedModules: false,
|
||||
|
||||
...(isDevelopment
|
||||
? {
|
||||
removeAvailableModules: false,
|
||||
removeEmptyChunks: false,
|
||||
splitChunks: false,
|
||||
}
|
||||
: {}),
|
||||
...(isDevelopment && {
|
||||
// Running multiple entries on a single page that do not share a runtime chunk from the same compilation is not supported.
|
||||
// https://github.com/webpack/webpack-dev-server/issues/2792#issuecomment-808328432
|
||||
runtimeChunk: isHotReloadEnabled ? 'single' : false,
|
||||
removeAvailableModules: false,
|
||||
removeEmptyChunks: false,
|
||||
splitChunks: false,
|
||||
}),
|
||||
},
|
||||
entry: {
|
||||
// Enterprise vs. OSS builds use different entrypoints. The enterprise entrypoint imports a
|
||||
// strict superset of the OSS entrypoint.
|
||||
app: [
|
||||
'react-hot-loader/patch',
|
||||
isEnterpriseBuild ? path.join(enterpriseDirectory, 'main.tsx') : path.join(__dirname, 'src', 'main.tsx'),
|
||||
],
|
||||
|
||||
app: isEnterpriseBuild ? path.join(enterpriseDirectory, 'main.tsx') : path.join(__dirname, 'src', 'main.tsx'),
|
||||
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
|
||||
'json.worker': 'monaco-editor/esm/vs/language/json/json.worker',
|
||||
},
|
||||
@ -110,6 +137,11 @@ const config = {
|
||||
...(shouldServeIndexHTML && webServerEnvironmentVariables),
|
||||
},
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
// Based on the issue: https://github.com/webpack/changelog-v5/issues/10
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
// Do not [hash] for development -- see https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405
|
||||
filename: mode === 'production' ? 'styles/[name].[contenthash].bundle.css' : 'styles/[name].bundle.css',
|
||||
@ -130,16 +162,18 @@ const config = {
|
||||
'suggest',
|
||||
],
|
||||
}),
|
||||
new webpack.IgnorePlugin(/\.flow$/, /.*/),
|
||||
new WebpackManifestPlugin({
|
||||
writeToFileEmit: true,
|
||||
fileName: 'webpack.manifest.json',
|
||||
// Only output files that are required to run the application
|
||||
filter: ({ isInitial }) => isInitial,
|
||||
}),
|
||||
!shouldServeIndexHTML &&
|
||||
new WebpackManifestPlugin({
|
||||
writeToFileEmit: true,
|
||||
fileName: 'webpack.manifest.json',
|
||||
// Only output files that are required to run the application
|
||||
filter: ({ isInitial }) => isInitial,
|
||||
}),
|
||||
...(shouldServeIndexHTML ? getHTMLWebpackPlugins() : []),
|
||||
...(shouldAnalyze ? [new BundleAnalyzerPlugin()] : []),
|
||||
],
|
||||
shouldAnalyze && new BundleAnalyzerPlugin(),
|
||||
isHotReloadEnabled && new webpack.HotModuleReplacementPlugin(),
|
||||
isHotReloadEnabled && new ReactRefreshWebpackPlugin({ overlay: false }),
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
extensions: ['.mjs', '.ts', '.tsx', '.js', '.json'],
|
||||
mainFields: ['es2015', 'module', 'browser', 'main'],
|
||||
@ -147,7 +181,6 @@ const config = {
|
||||
// react-visibility-sensor's main field points to a UMD bundle instead of ESM
|
||||
// https://github.com/joshwnj/react-visibility-sensor/issues/148
|
||||
'react-visibility-sensor': path.resolve(rootPath, 'node_modules/react-visibility-sensor/visibility-sensor.js'),
|
||||
'react-dom': '@hot-loader/react-dom',
|
||||
},
|
||||
},
|
||||
module: {
|
||||
@ -156,7 +189,7 @@ const config = {
|
||||
// slow to run on all JavaScript code).
|
||||
{
|
||||
test: /\.[jt]sx?$/,
|
||||
include: path.join(__dirname, 'src'),
|
||||
include: hotLoadablePaths,
|
||||
exclude: extensionHostWorker,
|
||||
use: [
|
||||
...(isProduction ? ['thread-loader'] : []),
|
||||
@ -164,23 +197,14 @@ const config = {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
plugins: [
|
||||
'react-hot-loader/babel',
|
||||
[
|
||||
'@sourcegraph/babel-plugin-transform-react-hot-loader-wrapper',
|
||||
{
|
||||
modulePattern: 'web/src/.*\\.tsx$',
|
||||
componentNamePattern: '(Page|Area)$',
|
||||
},
|
||||
],
|
||||
],
|
||||
...(isHotReloadEnabled && { plugins: ['react-refresh/babel'] }),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.[jt]sx?$/,
|
||||
exclude: [path.join(__dirname, 'src'), extensionHostWorker],
|
||||
exclude: [...hotLoadablePaths, extensionHostWorker],
|
||||
use: [...(isProduction ? ['thread-loader'] : []), babelLoader],
|
||||
},
|
||||
{
|
||||
@ -214,13 +238,13 @@ const config = {
|
||||
// TTF rule for monaco-editor
|
||||
test: /\.ttf$/,
|
||||
include: monacoEditorPaths,
|
||||
use: ['file-loader'],
|
||||
type: 'asset/resource',
|
||||
},
|
||||
{
|
||||
test: extensionHostWorker,
|
||||
use: [{ loader: 'worker-loader', options: { inline: 'no-fallback' } }, babelLoader],
|
||||
},
|
||||
{ test: /\.ya?ml$/, use: ['raw-loader'] },
|
||||
{ test: /\.ya?ml$/, type: 'asset/source' },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@ -63,6 +63,7 @@
|
||||
<br>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
{{if .WebpackDevServer}}<script src="{{.Manifest.AppJSRuntimeBundlePath}}"></script>{{end}}
|
||||
<script src="{{.Manifest.AppJSBundlePath}}"></script>
|
||||
{{.Injected.BodyBottom}}
|
||||
</body>
|
||||
|
||||
56
package.json
56
package.json
@ -105,12 +105,11 @@
|
||||
"@octokit/rest": "^16.36.0",
|
||||
"@peculiar/webcrypto": "^1.1.7",
|
||||
"@percy/puppeteer": "^1.1.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.0-rc.1",
|
||||
"@pollyjs/adapter": "^5.0.0",
|
||||
"@pollyjs/core": "^5.1.0",
|
||||
"@pollyjs/persister-fs": "^5.0.0",
|
||||
"@slack/web-api": "^5.10.0",
|
||||
"@sourcegraph/babel-plugin-transform-react-hot-loader-wrapper": "^1.1.0",
|
||||
"@sourcegraph/eslint-config": "^0.25.1",
|
||||
"@sourcegraph/eslint-plugin-sourcegraph": "1.0.0",
|
||||
"@sourcegraph/prettierrc": "^3.0.3",
|
||||
@ -126,10 +125,12 @@
|
||||
"@storybook/addon-toolbars": "^6.3.0",
|
||||
"@storybook/addons": "^6.3.0",
|
||||
"@storybook/api": "^6.3.0",
|
||||
"@storybook/builder-webpack5": "^6.3.0",
|
||||
"@storybook/client-api": "^6.3.0",
|
||||
"@storybook/components": "^6.3.0",
|
||||
"@storybook/core": "^6.3.0",
|
||||
"@storybook/core-events": "^6.3.0",
|
||||
"@storybook/manager-webpack5": "^6.3.2",
|
||||
"@storybook/react": "^6.3.0",
|
||||
"@storybook/theming": "^6.3.0",
|
||||
"@terminus-term/to-string-loader": "^1.1.7-beta.1",
|
||||
@ -145,7 +146,7 @@
|
||||
"@types/classnames": "2.2.10",
|
||||
"@types/command-exists": "1.2.0",
|
||||
"@types/connect-history-api-fallback": "^1.3.4",
|
||||
"@types/css-minimizer-webpack-plugin": "^1.1.3",
|
||||
"@types/css-minimizer-webpack-plugin": "^3.0.1",
|
||||
"@types/d3-axis": "1.0.12",
|
||||
"@types/d3-format": "^2.0.0",
|
||||
"@types/d3-scale": "2.2.0",
|
||||
@ -167,7 +168,7 @@
|
||||
"@types/lodash": "4.14.167",
|
||||
"@types/marked": "2.0.2",
|
||||
"@types/mime-types": "2.1.0",
|
||||
"@types/mini-css-extract-plugin": "1.2.2",
|
||||
"@types/mini-css-extract-plugin": "^2.0.1",
|
||||
"@types/mocha": "8.2.0",
|
||||
"@types/mockdate": "2.0.0",
|
||||
"@types/mz": "2.7.3",
|
||||
@ -180,7 +181,6 @@
|
||||
"@types/react-circular-progressbar": "1.0.2",
|
||||
"@types/react-dom": "16.9.8",
|
||||
"@types/react-grid-layout": "0.17.2",
|
||||
"@types/react-hot-loader": "4.1.0",
|
||||
"@types/react-router": "5.1.10",
|
||||
"@types/react-router-dom": "5.1.7",
|
||||
"@types/react-stripe-elements": "6.0.4",
|
||||
@ -196,15 +196,14 @@
|
||||
"@types/sinon": "9.0.4",
|
||||
"@types/socket.io": "2.1.10",
|
||||
"@types/socket.io-client": "1.4.33",
|
||||
"@types/speed-measure-webpack-plugin": "^1.3.3",
|
||||
"@types/terser-webpack-plugin": "^4.2.0",
|
||||
"@types/speed-measure-webpack-plugin": "^1.3.4",
|
||||
"@types/terser-webpack-plugin": "^5.0.4",
|
||||
"@types/testing-library__jest-dom": "^5.9.5",
|
||||
"@types/textarea-caret": "3.0.0",
|
||||
"@types/uuid": "8.0.1",
|
||||
"@types/webpack": "^4.41.25",
|
||||
"@types/webpack-bundle-analyzer": "^2.9.0",
|
||||
"@types/webpack-bundle-analyzer": "^4.4.1",
|
||||
"@types/webpack-dev-server": "^3.11.5",
|
||||
"@types/webpack-manifest-plugin": "^3.0.4",
|
||||
"@types/webpack-manifest-plugin": "^3.0.5",
|
||||
"abort-controller": "^3.0.0",
|
||||
"autoprefixer": "^10.2.1",
|
||||
"babel-jest": "^25.5.1",
|
||||
@ -221,8 +220,8 @@
|
||||
"command-exists": "^1.2.9",
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^5.2.4",
|
||||
"css-minimizer-webpack-plugin": "^1.3.0",
|
||||
"css-loader": "^5.2.6",
|
||||
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.4",
|
||||
"enzyme-to-json": "^3.5.0",
|
||||
@ -240,7 +239,7 @@
|
||||
"graphql-schema-linter": "^2.0.1",
|
||||
"gulp": "^4.0.2",
|
||||
"html-webpack-harddisk-plugin": "^2.0.0",
|
||||
"html-webpack-plugin": "^4.5.2",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"http-proxy-middleware": "^1.1.2",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^25.5.4",
|
||||
@ -252,7 +251,7 @@
|
||||
"license-checker": "^25.0.1",
|
||||
"message-port-polyfill": "^0.2.0",
|
||||
"mime-types": "^2.1.28",
|
||||
"mini-css-extract-plugin": "^1.3.3",
|
||||
"mini-css-extract-plugin": "^2.1.0",
|
||||
"mocha": "^8.3.2",
|
||||
"mockdate": "^3.0.2",
|
||||
"monaco-editor-webpack-plugin": "^3.1.0",
|
||||
@ -262,21 +261,20 @@
|
||||
"open": "^7.0.4",
|
||||
"p-retry": "^4.2.0",
|
||||
"p-timeout": "^4.1.0",
|
||||
"postcss": "^8.2.4",
|
||||
"postcss": "^8.3.5",
|
||||
"postcss-custom-media": "^8.0.0",
|
||||
"postcss-focus-visible": "^5.0.0",
|
||||
"postcss-loader": "^4.1.0",
|
||||
"postcss-loader": "^6.1.1",
|
||||
"prettier": "^2.2.1",
|
||||
"process": "^0.11.10",
|
||||
"puppeteer": "5.5.0",
|
||||
"puppeteer-firefox": "^0.5.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react-docgen-typescript-webpack-plugin": "^1.1.0",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"react-refresh": "^0.10.0",
|
||||
"react-spring": "^9.0.0",
|
||||
"react-test-renderer": "^16.14.0",
|
||||
"sass": "^1.32.4",
|
||||
"sass-loader": "^10.1.0",
|
||||
"sass-loader": "^12.1.0",
|
||||
"shelljs": "^0.8.4",
|
||||
"signale": "^1.4.0",
|
||||
"simmerjs": "^0.5.6",
|
||||
@ -284,31 +282,30 @@
|
||||
"socket.io": "^2.3.0",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"speed-measure-webpack-plugin": "^1.5.0",
|
||||
"storybook-addon-designs": "^5.4.5",
|
||||
"storybook-addon-designs": "^6.0.0",
|
||||
"storybook-dark-mode": "^1.0.4",
|
||||
"string-width": "^4.2.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"style-loader": "^3.1.0",
|
||||
"stylelint": "^13.8.0",
|
||||
"term-size": "^2.2.0",
|
||||
"terser-webpack-plugin": "^4.2.3",
|
||||
"thread-loader": "^3.0.1",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"thread-loader": "^3.0.4",
|
||||
"ts-morph": "^8.1.0",
|
||||
"ts-node": "^9.1.1",
|
||||
"typed-scss-modules": "^4.1.1",
|
||||
"typescript": "^4.1.3",
|
||||
"utc-version": "^2.0.2",
|
||||
"web-ext": "^4.2.0",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-bundle-analyzer": "^4.0.0",
|
||||
"webpack-cli": "^4.6.0",
|
||||
"webpack-dev-server": "^3.11.1",
|
||||
"webpack": "^5.45.1",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "4.0.0-beta.3",
|
||||
"webpack-manifest-plugin": "^3.1.0",
|
||||
"worker-loader": "^3.0.8",
|
||||
"yarn-deduplicate": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.20",
|
||||
"@hot-loader/react-dom": "^16.14.0",
|
||||
"@reach/accordion": "^0.10.2",
|
||||
"@reach/combobox": "^0.15.2",
|
||||
"@reach/dialog": "^0.11.2",
|
||||
@ -401,6 +398,7 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"history": "4.5.1",
|
||||
"cssnano": "4.1.10"
|
||||
"cssnano": "4.1.10",
|
||||
"webpack": "5"
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,10 @@ type WebpackManifest struct {
|
||||
// Webpack bundle that serves as the entrypoint
|
||||
// for the webapp code.
|
||||
AppJSBundlePath string `json:"app.js"`
|
||||
// AppJSRuntimeBundlePath contains the file name of the
|
||||
// JS runtime bundle which is served only in development environment
|
||||
// to kick off the main application code.
|
||||
AppJSRuntimeBundlePath *string `json:"runtime.js"`
|
||||
// Main CSS bundle, only present in production.
|
||||
AppCSSBundlePath *string `json:"app.css"`
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user