Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for non root (home) deployment of bit-server #4923 #4958

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions scopes/compositions/compositions/compositions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Tab, TabContainer, TabList, TabPanel } from '@teambit/panels';
import { useDocs } from '@teambit/docs.ui.queries.get-docs';
import { Collapser } from '@teambit/ui-foundation.ui.buttons.collapser';
import { EmptyBox } from '@teambit/design.ui.empty-box';
import { toPreviewUrl } from '@teambit/preview.ui.component-preview';
import { usePreviewUrl } from '@teambit/preview.ui.component-preview';
import { useIsMobile } from '@teambit/ui-foundation.ui.hooks.use-is-mobile';
import { CompositionsMenuBar } from '@teambit/compositions.ui.compositions-menu-bar';
import { CompositionContextProvider } from '@teambit/compositions.ui.hooks.use-composition';
Expand Down Expand Up @@ -56,8 +56,8 @@ export function Compositions({ menuBarWidgets, emptyState }: CompositionsProp) {

const sidebarOpenness = isSidebarOpen ? Layout.row : Layout.left;

const compositionUrl = toPreviewUrl(component, 'compositions');
const currentCompositionUrl = toPreviewUrl(component, 'compositions', selected?.identifier);
const compositionUrl = usePreviewUrl(component, 'compositions');
const currentCompositionUrl = usePreviewUrl(component, 'compositions', selected?.identifier);

const [compositionParams, setCompositionParams] = useState<Record<string, any>>({});
const queryParams = useMemo(() => queryString.stringify(compositionParams), [compositionParams]);
Expand Down
14 changes: 14 additions & 0 deletions scopes/harmony/graphql/gql-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type GqlConfig = {
/** gql pathname
* @default '/graphql'
*/
endpoint: string;
/** graphql pathname during SSR
* @default '/graphql
*/
ssrEndpoint?: string;
/** gql pathname for subscriptions
* @default '/subscriptions'
*/
subscriptionEndpoint?: string;
};
20 changes: 17 additions & 3 deletions scopes/harmony/graphql/graphql.preview.runtime.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { PreviewRuntime } from '@teambit/preview';
import PreviewAspect, { PreviewPreview, PreviewRuntime } from '@teambit/preview';

import { GraphqlAspect } from './graphql.aspect';
import { GraphqlUI } from './graphql.ui.runtime';

GraphqlUI.runtime = PreviewRuntime;
GraphqlAspect.addRuntime(GraphqlUI);
export class GraphqlPreview extends GraphqlUI {
static runtime = PreviewRuntime;
static slots = GraphqlUI.slots;
static dependencies = [PreviewAspect];
}

// @ts-ignore
GraphqlPreview.provider = ([previewPreview]: [PreviewPreview]) => {
const graphqlPreview = new GraphqlPreview();

previewPreview.registerRenderContext(() => graphqlPreview.getConfig());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forward gql config to preview


return graphqlPreview;
};

GraphqlAspect.addRuntime(GraphqlPreview);
18 changes: 17 additions & 1 deletion scopes/harmony/graphql/graphql.ui.runtime.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { ReactNode } from 'react';
import urlJoin from 'url-join';
import { UIRuntime } from '@teambit/ui';
import type { Aspect } from '@teambit/harmony';

import { InMemoryCache, ApolloClient, ApolloLink, HttpLink, createHttpLink } from '@apollo/client';
import type { NormalizedCacheObject } from '@apollo/client';
Expand All @@ -13,6 +15,7 @@ import { GraphQLProvider } from './graphql-provider';
import { GraphqlAspect } from './graphql.aspect';
import { GraphqlRenderLifecycle } from './render-lifecycle';
import { logError } from './logging';
import { GqlConfig } from './gql-config';

/**
* Type of gql client.
Expand Down Expand Up @@ -57,6 +60,19 @@ export class GraphqlUI {
return client;
}

getConfig = (): GqlConfig => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dynamic gql endpoints, based on configuration

return {
endpoint: urlJoin(this.baseName, 'graphql'),
ssrEndpoint: urlJoin(this.baseName, 'graphql'),
subscriptionEndpoint: urlJoin(this.baseName, 'subscriptions'),
};
};

private baseName = '/';
setBasename(value: string) {
this.baseName = value;
}

private createCache({ state }: { state?: NormalizedCacheObject } = {}) {
const cache = new InMemoryCache();

Expand Down Expand Up @@ -90,7 +106,7 @@ export class GraphqlUI {
renderHooks = new GraphqlRenderLifecycle(this);

static runtime = UIRuntime;
static dependencies = [];
static dependencies: Aspect[] = [];
static slots = [];

static async provider() {
Expand Down
1 change: 1 addition & 0 deletions scopes/harmony/graphql/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { GraphqlAspect, GraphqlAspect as default } from './graphql.aspect';
export type { GqlConfig } from './gql-config';

export type { Schema } from './schema';
export type { GraphqlMain, SchemaSlot } from './graphql.main.runtime';
Expand Down
10 changes: 7 additions & 3 deletions scopes/harmony/graphql/render-lifecycle.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { ReactNode } from 'react';
import urlJoin from 'url-join';
import { getDataFromTree } from '@apollo/client/react/ssr';
import type { NormalizedCacheObject } from '@apollo/client';
import pick from 'lodash.pick';
Expand All @@ -22,8 +23,10 @@ export class GraphqlRenderLifecycle implements RenderLifecycle<RenderContext, {
if (!browser) return undefined;

const port = server?.port || 3000;
const serverUrl = `http://localhost:${port}/graphql`;
const { ssrEndpoint } = this.graphqlUI.getConfig();
if (!ssrEndpoint) return undefined;

const serverUrl = urlJoin(`http://localhost:${port}`, ssrEndpoint);
const client = this.graphqlUI.createSsrClient({
serverUrl,
headers: pick(browser.connection.headers, ALLOWED_HEADERS),
Expand Down Expand Up @@ -66,10 +69,11 @@ export class GraphqlRenderLifecycle implements RenderLifecycle<RenderContext, {
browserInit = ({ state }: { state?: NormalizedCacheObject } = {}) => {
const { location } = window;
const isInsecure = location.protocol === 'http:';
const wsUrl = `${isInsecure ? 'ws:' : 'wss:'}//${location.host}/subscriptions`;
const { endpoint, subscriptionEndpoint } = this.graphqlUI.getConfig();

const client = this.graphqlUI.createClient('/graphql', { state, subscriptionUri: wsUrl });
const wsUrl = subscriptionEndpoint && urlJoin(isInsecure ? 'ws://' : 'wss://', location.host, subscriptionEndpoint);

const client = this.graphqlUI.createClient(endpoint, { state, subscriptionUri: wsUrl });
return { client };
};

Expand Down
2 changes: 1 addition & 1 deletion scopes/preview/ui/component-preview/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { ComponentPreview } from './preview';
export { toPreviewUrl, toPreviewServer, toPreviewHash } from './urls';
export { usePreviewUrl, toPreviewUrl, toPreviewServer, toPreviewHash } from './urls';
4 changes: 2 additions & 2 deletions scopes/preview/ui/component-preview/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { createRef, IframeHTMLAttributes } from 'react';
import { ComponentModel } from '@teambit/component';
import { usePubSubIframe } from '@teambit/pubsub';

import { toPreviewUrl } from './urls';
import { usePreviewUrl } from './urls';

// omitting 'referrerPolicy' because of an TS error during build. Re-include when needed
export interface ComponentPreviewProps extends Omit<IframeHTMLAttributes<HTMLIFrameElement>, 'src' | 'referrerPolicy'> {
Expand Down Expand Up @@ -38,7 +38,7 @@ export function ComponentPreview({ component, previewName, queryParams, hotReloa
const ref = createRef<HTMLIFrameElement>();
usePubSubIframe(ref);

const url = toPreviewUrl(component, previewName, queryParams);
const url = usePreviewUrl(component, previewName, queryParams);

return <iframe {...rest} ref={ref} src={url} />;
}
Expand Down
13 changes: 13 additions & 0 deletions scopes/preview/ui/component-preview/urls.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import type { ComponentModel } from '@teambit/component';
import { affix } from '@teambit/base-ui.utils.string.affix';
import { useHistory } from 'react-router-dom';

/**
* a routing-aware generator for the full url to a preview (overview / docs etc).
* This will include basename and other routing configratuions
*/
export function usePreviewUrl(component: ComponentModel, previewName?: string, additionalParams?: string | string[]) {
const history = useHistory();
const pathname = toPreviewServer(component);
const hash = toPreviewHash(component, previewName, additionalParams);

return history.createHref({ pathname, hash });
}

/**
* generates a full url to a preview (overview / docs etc)
Expand Down
5 changes: 3 additions & 2 deletions scopes/react/ui/docs-app/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MDXLayout } from '@teambit/mdx.ui.mdx-layout';
import { ErrorFallback } from '@teambit/react.ui.error-fallback';
import { RenderingContext } from '@teambit/preview';
import { ReactAspect } from '@teambit/react';
import { GqlConfig, GraphqlAspect } from '@teambit/graphql';
import styles from './base.module.scss';
import { ComponentOverview } from './component-overview';
import { CompositionsSummary } from './compositions-summary/compositions-summary';
Expand All @@ -33,9 +34,9 @@ const defaultDocs = {
* base template for react component documentation
*/
export function Base({ docs = defaultDocs, componentId, compositions, renderingContext, ...rest }: DocsSectionProps) {
const { loading, error, data } = useFetchDocs(componentId);

const rawProviders = renderingContext.get(ReactAspect.id);
const graphqlContext = renderingContext.get(GraphqlAspect.id) as GqlConfig | undefined;
const { loading, error, data } = useFetchDocs(componentId, graphqlContext?.endpoint);
const reactContext = useMemo(() => flatten(Object.values(rawProviders || {})), [rawProviders]);

if (!data || loading) return null;
Expand Down
5 changes: 2 additions & 3 deletions scopes/react/ui/docs-app/use-fetch-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react';
import { request, gql } from 'graphql-request';
import { ComponentModel } from '@teambit/component';

const GQL_SERVER = '/graphql';
const DOCS_QUERY = gql`
query getComponentDocs($id: String!) {
getHost {
Expand Down Expand Up @@ -78,7 +77,7 @@ type FetchDocsObj =
}
| undefined;

export function useFetchDocs(componentId: string) {
export function useFetchDocs(componentId: string, gqlServer = '/graphql') {
const [data, setData] = useState<FetchDocsObj>(undefined);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(undefined);
Expand All @@ -87,7 +86,7 @@ export function useFetchDocs(componentId: string) {
setLoading(true);

const variables = { id: componentId };
request(GQL_SERVER, DOCS_QUERY, variables)
request(gqlServer, DOCS_QUERY, variables)
.then((result: QueryResults) => {
setData({
component: ComponentModel.from(result.getHost.get),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type RenderRoutesOptions = {
};

export class ReactRouterUI {
private basename?: string;
private routerHistory?: History;
private routingMode = isBrowser ? Routing.url : Routing.static;

Expand All @@ -35,7 +36,12 @@ export class ReactRouterUI {
*/
renderRoutes(routes: RouteProps[], options: RenderRoutesOptions = {}): JSX.Element {
return (
<RouteContext reactRouterUi={this} routing={this.routingMode} location={options.initialLocation}>
<RouteContext
reactRouterUi={this}
routing={this.routingMode}
location={options.initialLocation}
basename={this.basename}
>
<RootRoute routeSlot={this.routeSlot} rootRoutes={routes}></RootRoute>
</RouteContext>
);
Expand All @@ -52,6 +58,10 @@ export class ReactRouterUI {
});
};

setBasename = (value: string) => {
this.basename = value;
};

/** decides how navigation is stored and applied.
* Url - updates the `window.location.pathname`.
* Hash - updates `window.location.hash`.
Expand Down
33 changes: 27 additions & 6 deletions scopes/ui-foundation/react-router/react-router/route-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type RouterContextProps = {
routing?: Routing;
children: ReactNode;
location?: string;
basename?: string;
};

type RootRouteProps = {
Expand All @@ -23,10 +24,16 @@ type RootRouteProps = {
/**
* Setup context needed for routing.
*/
export function RouteContext({ reactRouterUi, routing = Routing.url, children, location }: RouterContextProps) {
export function RouteContext({
reactRouterUi,
routing = Routing.url,
children,
location,
basename,
}: RouterContextProps) {
return (
// {/* set up the virtual router (browser, inMemory, etc) */}
<Router type={routing} location={location}>
<Router type={routing} location={location} basename={basename}>
{/* injects History object back to reactRouterUi */}
<HistoryGetter onRouterChange={reactRouterUi.setRouter} />
{/* injects react-router Link into context */}
Expand All @@ -43,21 +50,35 @@ export function RootRoute({ rootRoutes, routeSlot }: RootRouteProps) {
}

/** provides the router engine (browser, inMemory, etc) */
function Router({ type, children, location }: { type: Routing; children: ReactNode; location?: string }) {
function Router({
type,
children,
location,
basename,
}: {
type: Routing;
children: ReactNode;
location?: string;
basename?: string;
}) {
switch (type) {
case Routing.static:
return <StaticRouter location={location}>{children}</StaticRouter>;
return (
<StaticRouter basename={basename} location={location}>
{children}
</StaticRouter>
);
case Routing.inMemory:
return (
<MemoryRouter initialEntries={[location || '/']} initialIndex={1}>
{children}
</MemoryRouter>
);
case Routing.hash:
return <HashRouter>{children}</HashRouter>;
return <HashRouter basename={basename}>{children}</HashRouter>;
case Routing.url:
default:
return <BrowserRouter>{children}</BrowserRouter>;
return <BrowserRouter basename={basename}>{children}</BrowserRouter>;
}
}

Expand Down
23 changes: 19 additions & 4 deletions scopes/ui-foundation/ui/ui.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,13 @@ export type UIConfig = {
*/
publicDir: string;

/** the url to display when server is listening. Note that bit does not provide proxying to this url */
/**
* set `publicPath` value for webpack.config to override
* in case server is not accessed using root route.
*/
publicPath: string;

/** the url to *display* when server is listening. Note that bit does not provide proxying to this url */
publicUrl?: string;
};

Expand Down Expand Up @@ -216,7 +222,13 @@ export class UiMain {
const ssr = uiRoot.buildOptions?.ssr || false;
const mainEntry = await this.generateRoot(await uiRoot.resolveAspects(UIRuntime.name), name);

const browserConfig = createWebpackConfig(uiRoot.path, [mainEntry], uiRoot.name, await this.publicDir(uiRoot));
const browserConfig = createWebpackConfig(
uiRoot.path,
[mainEntry],
uiRoot.name,
await this.publicDir(uiRoot),
this.config.publicPath
);
const ssrConfig = ssr && createSsrWebpackConfig(uiRoot.path, [mainEntry], await this.publicDir(uiRoot));

const config = [browserConfig, ssrConfig].filter((x) => !!x) as webpack.Configuration[];
Expand Down Expand Up @@ -490,7 +502,8 @@ export class UiMain {
uiRoot.path,
[await this.generateRoot(await uiRoot.resolveAspects(UIRuntime.name), name)],
uiRoot.name,
await this.publicDir(uiRoot)
await this.publicDir(uiRoot),
this.config.publicPath
);
if (config.output?.path && fs.pathExistsSync(config.output.path)) return;
const hash = await this.buildUiHash(uiRoot);
Expand All @@ -509,6 +522,8 @@ export class UiMain {

static defaultConfig: UIConfig = {
publicDir: 'public/bit',
// Changing to relative path which should work in all cases
publicPath: process.env.ASSET_PATH || './',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh this looks interesting, how does it work

portRange: [3000, 3100],
host: 'localhost',
};
Expand All @@ -535,7 +550,7 @@ export class UiMain {

static async provider(
[pubsub, cli, graphql, express, componentExtension, cache, loggerMain]: UIDeps,
config,
config: UIConfig,
[uiRootSlot, preStartSlot, onStartSlot, publicDirOverwriteSlot, buildMethodOverwriteSlot, proxyGetterSlot]: [
UIRootRegistry,
PreStartSlot,
Expand Down
Loading