Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
Added dev server support
Browse files Browse the repository at this point in the history
added exbuild stuff

refactor

Update EsbuildConfig.ts

Update utils.ts

Added logging middleware

Added symbolicator

remove legacy

added license

Clean up extras

clean up more

split out logging plugin

Unify platform extensions

Refactor

refactor
  • Loading branch information
EvanBacon committed Jul 14, 2021
1 parent f5c3339 commit 741e231
Show file tree
Hide file tree
Showing 26 changed files with 1,561 additions and 186 deletions.
5 changes: 5 additions & 0 deletions packages/dev-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@
"@expo/metro-config": "0.1.78",
"@react-native-community/cli-server-api": "^5.0.1",
"body-parser": "1.19.0",
"deepmerge": "^4.2.2",
"deno-importmap": "^0.1.6",
"esbuild": "^0.12.15",
"flow-remove-types": "^2.155.0",
"fs-extra": "9.0.0",
"image-size": "^1.0.0",
"open": "^8.2.0",
"resolve-from": "^5.0.0",
"semver": "7.3.2",
Expand Down
75 changes: 3 additions & 72 deletions packages/dev-server/src/MetroDevServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ import {
import bodyParser from 'body-parser';
import type { Server as ConnectServer, HandleFunction } from 'connect';
import http from 'http';
import type { IncomingMessage, ServerResponse } from 'http';
import type Metro from 'metro';
import path from 'path';
import resolveFrom from 'resolve-from';
import semver from 'semver';
import { parse as parseUrl } from 'url';

import {
buildHermesBundleAsync,
Expand All @@ -23,6 +21,9 @@ import {
import LogReporter from './LogReporter';
import clientLogsMiddleware from './middleware/clientLogsMiddleware';
import createJsInspectorMiddleware from './middleware/createJsInspectorMiddleware';
import { remoteDevtoolsCorsMiddleware } from './middleware/remoteDevtoolsCorsMiddleware';
import { remoteDevtoolsSecurityHeadersMiddleware } from './middleware/remoteDevtoolsSecurityHeadersMiddleware';
import { replaceMiddlewareWith } from './middleware/replaceMiddlewareWith';

export type MetroDevServerOptions = ExpoMetroConfig.LoadOptions & {
logger: Log;
Expand Down Expand Up @@ -257,76 +258,6 @@ function importMetroServerFromProject(projectRoot: string): typeof Metro.Server
return require(resolvedPath);
}

function replaceMiddlewareWith(
app: ConnectServer,
sourceMiddleware: HandleFunction,
targetMiddleware: HandleFunction
) {
const item = app.stack.find(middleware => middleware.handle === sourceMiddleware);
if (item) {
item.handle = targetMiddleware;
}
}

// Like securityHeadersMiddleware but further allow cross-origin requests
// from https://chrome-devtools-frontend.appspot.com/
function remoteDevtoolsSecurityHeadersMiddleware(
req: IncomingMessage,
res: ServerResponse,
next: (err?: Error) => void
) {
// Block any cross origin request.
if (
typeof req.headers.origin === 'string' &&
!req.headers.origin.match(/^https?:\/\/localhost:/) &&
!req.headers.origin.match(/^https:\/\/chrome-devtools-frontend\.appspot\.com/)
) {
next(
new Error(
`Unauthorized request from ${req.headers.origin}. ` +
'This may happen because of a conflicting browser extension to intercept HTTP requests. ' +
'Please try again without browser extensions or using incognito mode.'
)
);
return;
}

// Block MIME-type sniffing.
res.setHeader('X-Content-Type-Options', 'nosniff');

next();
}

// Middleware that accepts multiple Access-Control-Allow-Origin for processing *.map.
// This is a hook middleware before metro processing *.map,
// which originally allow only devtools://devtools
function remoteDevtoolsCorsMiddleware(
req: IncomingMessage,
res: ServerResponse,
next: (err?: Error) => void
) {
if (req.url) {
const url = parseUrl(req.url);
const origin = req.headers.origin;
const isValidOrigin =
origin &&
['devtools://devtools', 'https://chrome-devtools-frontend.appspot.com'].includes(origin);
if (url.pathname?.endsWith('.map') && origin && isValidOrigin) {
res.setHeader('Access-Control-Allow-Origin', origin);

// Prevent metro overwrite Access-Control-Allow-Origin header
const setHeader = res.setHeader.bind(res);
res.setHeader = (key, ...args) => {
if (key === 'Access-Control-Allow-Origin') {
return;
}
setHeader(key, ...args);
};
}
}
next();
}

// Cloned from xdl/src/Versions.ts, we cannot use that because of circular dependency
function gteSdkVersion(expJson: Pick<ExpoConfig, 'sdkVersion'>, sdkVersion: string): boolean {
if (!expJson.sdkVersion) {
Expand Down
161 changes: 161 additions & 0 deletions packages/dev-server/src/esbuild/EsbuildConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import Log from '@expo/bunyan';
import { getBareExtensions } from '@expo/config/paths';
import merge from 'deepmerge';
import { BuildOptions } from 'esbuild';
import fs from 'fs-extra';
import path from 'path';
import resolveFrom from 'resolve-from';
import { resolveEntryPoint } from 'xdl/build/tools/resolveEntryPoint';

import { mergePlugins, setAssetLoaders, setPlugins } from './utils';

const assetExts = ['bmp', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'svg', 'webp', 'm4v', 'mov', 'mp4', 'mpeg', 'mpg', 'webm', 'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', 'html', 'pdf', 'yaml', 'yml', 'otf', 'ttf', 'zip', 'db'] //prettier-ignore

const native = {
target: 'esnext',
format: 'iife',
plugins: [
{ name: 'expoLogging' },
{
name: 'stripFlowTypes',
params: [
'react-native',
'@react-native-community/masked-view',
'expo-asset-utils',
'@react-native-picker/picker',
'@react-native-segmented-control/segmented-control',
'@react-native-community/datetimepicker',
'@react-native-async-storage/async-storage',
'react-native-view-shot',
'react-native-gesture-handler',
'@react-native-community/toolbar-android',
'@react-native/normalize-color',
'@react-native/assets',
'@react-native/polyfills',
],
},
{
name: 'reactNativeAssets',
params: assetExts,
},
{ name: 'patches' },
],
};

const config: { ios: any; android: any; web: any; native: any } = {
web: {
target: 'es2020',
format: 'esm',
plugins: [
{ name: 'expoLogging' },
{
name: 'alias',
params: {
// TODO: Dynamic
'react-native': './node_modules/react-native-web/dist/index.js',
},
},
],
},
native,
ios: native,
android: native,
};

async function getBuildOptions(
projectRoot: string,
logger: Log,
{
platform,
minify,
cleanCache,
}: { platform: keyof typeof config; minify?: boolean; cleanCache?: boolean },
customConfig?: any
) {
const filename = resolveEntryPoint(projectRoot, platform);

const outputPath = path.resolve(`dist/index.${platform}.js`);
await fs.ensureDir(path.dirname(outputPath));

const base: BuildOptions = {
entryPoints: [filename || 'App.js'],
outfile: outputPath,
assetNames: 'assets/[name]',
publicPath: '/',
minify,
write: true,
bundle: true,
legalComments: 'none',
sourcemap: true,
incremental: true,
logLevel: 'debug',
mainFields: ['react-native', 'browser', 'module', 'main'],
define: {
'process.env.JEST_WORKER_ID': 'false',
'process.env.NODE_DEV': minify ? '"production"' : '"development"',
__DEV__: minify ? 'false' : 'true',
global: 'window',
},
loader: { '.js': 'jsx', ...setAssetLoaders(assetExts) },
resolveExtensions: getBareExtensions([platform, 'native'], {
isModern: false,
isTS: true,
isReact: true,
}).map(value => '.' + value),
};

if (platform !== 'web') {
if (!base.plugins) {
base.plugins = [];
}

base.plugins.push({
name: 'alias',
params: {
'react-native-vector-icons/': resolveFrom.silent(projectRoot, '@expo/vector-icons'),
},
});

base.inject = [
resolveRelative(projectRoot, 'react-native/Libraries/polyfills/console.js'),
resolveRelative(projectRoot, 'react-native/Libraries/polyfills/error-guard.js'),
resolveRelative(projectRoot, 'react-native/Libraries/polyfills/Object.es7.js'),
// Sets up React DevTools
resolveRelative(projectRoot, 'react-native/Libraries/Core/InitializeCore.js'),
];
} else {
base.inject = [resolveFrom(projectRoot, 'setimmediate/setImmediate.js')];
}

const buildOptions: BuildOptions = merge.all([base, config[platform], customConfig]);
const mergedPlugins = mergePlugins(config[platform].plugins, customConfig?.plugins);
buildOptions.plugins = setPlugins(projectRoot, logger, mergedPlugins, platform, cleanCache);

// Append to the top of the bundle
if (!buildOptions.banner) {
buildOptions.banner = { js: '' };
}

if (platform === 'web' && !minify) {
buildOptions.banner.js =
`(() => new EventSource("/esbuild").onmessage = () => location.reload())();\n` +
buildOptions.banner.js;
}
if (platform !== 'web') {
if (!buildOptions.banner.js) {
buildOptions.banner.js = ``;
}
buildOptions.banner.js += `\nvar __BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now();
var window = typeof globalThis !== 'undefined' ? globalThis : typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this;`;
}

return { buildOptions, mergedPlugins };
}

function resolveRelative(projectRoot: string, moduleId: string): string {
const _path = path.relative(projectRoot, resolveFrom(projectRoot, moduleId));
if (_path.startsWith('.')) return _path;
return './' + _path;
}

export default getBuildOptions;
Loading

0 comments on commit 741e231

Please sign in to comment.