larry babby and threejs for glsl

This commit is contained in:
Sam
2024-06-24 21:24:00 +12:00
parent 87d5dc634d
commit 907ebae4c0
6474 changed files with 1279596 additions and 8 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-present Devon Govett
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,49 @@
{
"name": "@parcel/reporter-dev-server",
"version": "2.12.0",
"description": "Blazing fast, zero configuration web application bundler",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"repository": {
"type": "git",
"url": "https://github.com/parcel-bundler/parcel.git"
},
"main": "lib/ServerReporter.js",
"source": "src/ServerReporter.js",
"engines": {
"node": ">= 12.0.0",
"parcel": "^2.12.0"
},
"targets": {
"main": {
"includeNodeModules": {
"@parcel/plugin": false,
"@parcel/utils": false
}
}
},
"dependencies": {
"@parcel/plugin": "2.12.0",
"@parcel/utils": "2.12.0"
},
"devDependencies": {
"@parcel/babel-preset": "2.12.0",
"@parcel/types": "2.12.0",
"connect": "^3.7.0",
"ejs": "^3.1.6",
"fresh": "^0.5.2",
"http-proxy-middleware": "^2.0.1",
"launch-editor": "^2.3.0",
"mime-types": "2.1.18",
"nullthrows": "^1.1.1",
"serve-handler": "^6.0.0",
"ws": "^7.0.0"
},
"gitHead": "2059029ee91e5f03a273b0954d3e629d7375f986"
}

View File

@@ -0,0 +1,288 @@
// @flow
import type {
Asset,
BundleGraph,
Dependency,
NamedBundle,
PackagedBundle,
PluginOptions,
} from '@parcel/types';
import type {Diagnostic} from '@parcel/diagnostic';
import type {AnsiDiagnosticResult} from '@parcel/utils';
import type {
ServerError,
HMRServerOptions,
Request,
Response,
} from './types.js.flow';
import {setHeaders, SOURCES_ENDPOINT} from './Server';
import nullthrows from 'nullthrows';
import url from 'url';
import mime from 'mime-types';
import WebSocket from 'ws';
import invariant from 'assert';
import {
ansiHtml,
createHTTPServer,
prettyDiagnostic,
PromiseQueue,
} from '@parcel/utils';
export type HMRAsset = {|
id: string,
url: string,
type: string,
output: string,
envHash: string,
outputFormat: string,
depsByBundle: {[string]: {[string]: string, ...}, ...},
|};
export type HMRMessage =
| {|
type: 'update',
assets: Array<HMRAsset>,
|}
| {|
type: 'error',
diagnostics: {|
ansi: Array<AnsiDiagnosticResult>,
html: Array<$Rest<AnsiDiagnosticResult, {|codeframe: string|}>>,
|},
|};
const FS_CONCURRENCY = 64;
const HMR_ENDPOINT = '/__parcel_hmr';
export default class HMRServer {
wss: WebSocket.Server;
unresolvedError: HMRMessage | null = null;
options: HMRServerOptions;
bundleGraph: BundleGraph<PackagedBundle> | BundleGraph<NamedBundle> | null =
null;
stopServer: ?() => Promise<void>;
constructor(options: HMRServerOptions) {
this.options = options;
}
async start() {
let server = this.options.devServer;
if (!server) {
let result = await createHTTPServer({
listener: (req, res) => {
setHeaders(res);
if (!this.handle(req, res)) {
res.statusCode = 404;
res.end();
}
},
});
server = result.server;
server.listen(this.options.port, this.options.host);
this.stopServer = result.stop;
} else {
this.options.addMiddleware?.((req, res) => this.handle(req, res));
}
this.wss = new WebSocket.Server({server});
this.wss.on('connection', ws => {
if (this.unresolvedError) {
ws.send(JSON.stringify(this.unresolvedError));
}
});
// $FlowFixMe[incompatible-exact]
this.wss.on('error', err => this.handleSocketError(err));
}
handle(req: Request, res: Response): boolean {
let {pathname} = url.parse(req.originalUrl || req.url);
if (pathname != null && pathname.startsWith(HMR_ENDPOINT)) {
let id = pathname.slice(HMR_ENDPOINT.length + 1);
let bundleGraph = nullthrows(this.bundleGraph);
let asset = bundleGraph.getAssetById(id);
this.getHotAssetContents(asset).then(output => {
res.setHeader('Content-Type', mime.contentType(asset.type));
res.end(output);
});
return true;
}
return false;
}
async stop() {
if (this.stopServer != null) {
await this.stopServer();
this.stopServer = null;
}
this.wss.close();
}
async emitError(options: PluginOptions, diagnostics: Array<Diagnostic>) {
let renderedDiagnostics = await Promise.all(
diagnostics.map(d => prettyDiagnostic(d, options)),
);
// store the most recent error so we can notify new connections
// and so we can broadcast when the error is resolved
this.unresolvedError = {
type: 'error',
diagnostics: {
ansi: renderedDiagnostics,
html: renderedDiagnostics.map((d, i) => {
return {
message: ansiHtml(d.message),
stack: ansiHtml(d.stack),
frames: d.frames.map(f => ({
location: f.location,
code: ansiHtml(f.code),
})),
hints: d.hints.map(hint => ansiHtml(hint)),
documentation: diagnostics[i].documentationURL ?? '',
};
}),
},
};
this.broadcast(this.unresolvedError);
}
async emitUpdate(event: {
+bundleGraph: BundleGraph<PackagedBundle> | BundleGraph<NamedBundle>,
+changedAssets: Map<string, Asset>,
...
}) {
this.unresolvedError = null;
this.bundleGraph = event.bundleGraph;
let changedAssets = new Set(event.changedAssets.values());
if (changedAssets.size === 0) return;
let queue = new PromiseQueue({maxConcurrent: FS_CONCURRENCY});
for (let asset of changedAssets) {
if (asset.type !== 'js' && asset.type !== 'css') {
// If all of the incoming dependencies of the asset actually resolve to a JS asset
// rather than the original, we can mark the runtimes as changed instead. URL runtimes
// have a cache busting query param added with HMR enabled which will trigger a reload.
let runtimes = new Set();
let incomingDeps = event.bundleGraph.getIncomingDependencies(asset);
let isOnlyReferencedByRuntimes = incomingDeps.every(dep => {
let resolved = event.bundleGraph.getResolvedAsset(dep);
let isRuntime = resolved?.type === 'js' && resolved !== asset;
if (resolved && isRuntime) {
runtimes.add(resolved);
}
return isRuntime;
});
if (isOnlyReferencedByRuntimes) {
for (let runtime of runtimes) {
changedAssets.add(runtime);
}
continue;
}
}
queue.add(async () => {
let dependencies = event.bundleGraph.getDependencies(asset);
let depsByBundle = {};
for (let bundle of event.bundleGraph.getBundlesWithAsset(asset)) {
let deps = {};
for (let dep of dependencies) {
let resolved = event.bundleGraph.getResolvedAsset(dep, bundle);
if (resolved) {
deps[getSpecifier(dep)] =
event.bundleGraph.getAssetPublicId(resolved);
}
}
depsByBundle[bundle.id] = deps;
}
return {
id: event.bundleGraph.getAssetPublicId(asset),
url: this.getSourceURL(asset),
type: asset.type,
// No need to send the contents of non-JS assets to the client.
output:
asset.type === 'js' ? await this.getHotAssetContents(asset) : '',
envHash: asset.env.id,
outputFormat: asset.env.outputFormat,
depsByBundle,
};
});
}
let assets = await queue.run();
this.broadcast({
type: 'update',
assets: assets,
});
}
async getHotAssetContents(asset: Asset): Promise<string> {
let output = await asset.getCode();
let bundleGraph = nullthrows(this.bundleGraph);
if (asset.type === 'js') {
let publicId = bundleGraph.getAssetPublicId(asset);
output = `parcelHotUpdate['${publicId}'] = function (require, module, exports) {${output}}`;
}
let sourcemap = await asset.getMap();
if (sourcemap) {
let sourcemapStringified = await sourcemap.stringify({
format: 'inline',
sourceRoot: SOURCES_ENDPOINT + '/',
// $FlowFixMe
fs: asset.fs,
});
invariant(typeof sourcemapStringified === 'string');
output += `\n//# sourceMappingURL=${sourcemapStringified}`;
output += `\n//# sourceURL=${encodeURI(this.getSourceURL(asset))}\n`;
}
return output;
}
getSourceURL(asset: Asset): string {
let origin = '';
if (!this.options.devServer) {
origin = `http://${this.options.host || 'localhost'}:${
this.options.port
}`;
}
return origin + HMR_ENDPOINT + '/' + asset.id;
}
handleSocketError(err: ServerError) {
if (err.code === 'ECONNRESET') {
// This gets triggered on page refresh, ignore this
return;
}
this.options.logger.warn({
origin: '@parcel/reporter-dev-server',
message: `[${err.code}]: ${err.message}`,
stack: err.stack,
});
}
broadcast(msg: HMRMessage) {
const json = JSON.stringify(msg);
for (let ws of this.wss.clients) {
ws.send(json);
}
}
}
function getSpecifier(dep: Dependency): string {
if (typeof dep.meta.placeholder === 'string') {
return dep.meta.placeholder;
}
return dep.specifier;
}

View File

@@ -0,0 +1,536 @@
// @flow
import type {DevServerOptions, Request, Response} from './types.js.flow';
import type {
BuildSuccessEvent,
BundleGraph,
FilePath,
PluginOptions,
PackagedBundle,
} from '@parcel/types';
import type {Diagnostic} from '@parcel/diagnostic';
import type {FileSystem} from '@parcel/fs';
import type {HTTPServer, FormattedCodeFrame} from '@parcel/utils';
import invariant from 'assert';
import path from 'path';
import url from 'url';
import {
ansiHtml,
createHTTPServer,
resolveConfig,
readConfig,
prettyDiagnostic,
relativePath,
} from '@parcel/utils';
import serverErrors from './serverErrors';
import fs from 'fs';
import ejs from 'ejs';
import connect from 'connect';
import serveHandler from 'serve-handler';
import {createProxyMiddleware} from 'http-proxy-middleware';
import {URL, URLSearchParams} from 'url';
import launchEditor from 'launch-editor';
import fresh from 'fresh';
export function setHeaders(res: Response) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader(
'Access-Control-Allow-Methods',
'GET, HEAD, PUT, PATCH, POST, DELETE',
);
res.setHeader(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Content-Type',
);
res.setHeader('Cache-Control', 'max-age=0, must-revalidate');
}
const SLASH_REGEX = /\//g;
export const SOURCES_ENDPOINT = '/__parcel_source_root';
const EDITOR_ENDPOINT = '/__parcel_launch_editor';
const TEMPLATE_404 = fs.readFileSync(
path.join(__dirname, 'templates/404.html'),
'utf8',
);
const TEMPLATE_500 = fs.readFileSync(
path.join(__dirname, 'templates/500.html'),
'utf8',
);
type NextFunction = (req: Request, res: Response, next?: (any) => any) => any;
export default class Server {
pending: boolean;
pendingRequests: Array<[Request, Response]>;
middleware: Array<(req: Request, res: Response) => boolean>;
options: DevServerOptions;
rootPath: string;
bundleGraph: BundleGraph<PackagedBundle> | null;
requestBundle: ?(bundle: PackagedBundle) => Promise<BuildSuccessEvent>;
errors: Array<{|
message: string,
stack: ?string,
frames: Array<FormattedCodeFrame>,
hints: Array<string>,
documentation: string,
|}> | null;
stopServer: ?() => Promise<void>;
constructor(options: DevServerOptions) {
this.options = options;
try {
this.rootPath = new URL(options.publicUrl).pathname;
} catch (e) {
this.rootPath = options.publicUrl;
}
this.pending = true;
this.pendingRequests = [];
this.middleware = [];
this.bundleGraph = null;
this.requestBundle = null;
this.errors = null;
}
buildStart() {
this.pending = true;
}
buildSuccess(
bundleGraph: BundleGraph<PackagedBundle>,
requestBundle: (bundle: PackagedBundle) => Promise<BuildSuccessEvent>,
) {
this.bundleGraph = bundleGraph;
this.requestBundle = requestBundle;
this.errors = null;
this.pending = false;
if (this.pendingRequests.length > 0) {
let pendingRequests = this.pendingRequests;
this.pendingRequests = [];
for (let [req, res] of pendingRequests) {
this.respond(req, res);
}
}
}
async buildError(options: PluginOptions, diagnostics: Array<Diagnostic>) {
this.pending = false;
this.errors = await Promise.all(
diagnostics.map(async d => {
let ansiDiagnostic = await prettyDiagnostic(d, options);
return {
message: ansiHtml(ansiDiagnostic.message),
stack: ansiDiagnostic.stack ? ansiHtml(ansiDiagnostic.stack) : null,
frames: ansiDiagnostic.frames.map(f => ({
location: f.location,
code: ansiHtml(f.code),
})),
hints: ansiDiagnostic.hints.map(hint => ansiHtml(hint)),
documentation: d.documentationURL ?? '',
};
}),
);
}
respond(req: Request, res: Response): mixed {
if (this.middleware.some(handler => handler(req, res))) return;
let {pathname, search} = url.parse(req.originalUrl || req.url);
if (pathname == null) {
pathname = '/';
}
if (pathname.startsWith(EDITOR_ENDPOINT) && search) {
let query = new URLSearchParams(search);
let file = query.get('file');
if (file) {
// File location might start with /__parcel_source_root if it came from a source map.
if (file.startsWith(SOURCES_ENDPOINT)) {
file = file.slice(SOURCES_ENDPOINT.length + 1);
}
launchEditor(file);
}
res.end();
} else if (this.errors) {
return this.send500(req, res);
} else if (path.extname(pathname) === '') {
// If the URL doesn't start with the public path, or the URL doesn't
// have a file extension, send the main HTML bundle.
return this.sendIndex(req, res);
} else if (pathname.startsWith(SOURCES_ENDPOINT)) {
req.url = pathname.slice(SOURCES_ENDPOINT.length);
return this.serve(
this.options.inputFS,
this.options.projectRoot,
req,
res,
() => this.send404(req, res),
);
} else if (pathname.startsWith(this.rootPath)) {
// Otherwise, serve the file from the dist folder
req.url =
this.rootPath === '/' ? pathname : pathname.slice(this.rootPath.length);
if (req.url[0] !== '/') {
req.url = '/' + req.url;
}
return this.serveBundle(req, res, () => this.sendIndex(req, res));
} else {
return this.send404(req, res);
}
}
sendIndex(req: Request, res: Response) {
if (this.bundleGraph) {
// If the main asset is an HTML file, serve it
let htmlBundleFilePaths = this.bundleGraph
.getBundles()
.filter(bundle => path.posix.extname(bundle.name) === '.html')
.map(bundle => {
return `/${relativePath(
this.options.distDir,
bundle.filePath,
false,
)}`;
});
let indexFilePath = null;
let {pathname: reqURL} = url.parse(req.originalUrl || req.url);
if (!reqURL) {
reqURL = '/';
}
if (htmlBundleFilePaths.length === 1) {
indexFilePath = htmlBundleFilePaths[0];
} else {
let bestMatch = null;
for (let bundle of htmlBundleFilePaths) {
let bundleDir = path.posix.dirname(bundle);
let bundleDirSubdir = bundleDir === '/' ? bundleDir : bundleDir + '/';
let withoutExtension = path.posix.basename(
bundle,
path.posix.extname(bundle),
);
let isIndex = withoutExtension === 'index';
let matchesIsIndex = null;
if (
isIndex &&
(reqURL.startsWith(bundleDirSubdir) || reqURL === bundleDir)
) {
// bundle is /bar/index.html and (/bar or something inside of /bar/** was requested was requested)
matchesIsIndex = true;
} else if (reqURL == path.posix.join(bundleDir, withoutExtension)) {
// bundle is /bar/foo.html and /bar/foo was requested
matchesIsIndex = false;
}
if (matchesIsIndex != null) {
let depth = bundle.match(SLASH_REGEX)?.length ?? 0;
if (
bestMatch == null ||
// This one is more specific (deeper)
bestMatch.depth < depth ||
// This one is just as deep, but the bundle name matches and not just index.html
(bestMatch.depth === depth && bestMatch.isIndex)
) {
bestMatch = {bundle, depth, isIndex: matchesIsIndex};
}
}
}
indexFilePath = bestMatch?.['bundle'] ?? htmlBundleFilePaths[0];
}
if (indexFilePath) {
req.url = indexFilePath;
this.serveBundle(req, res, () => this.send404(req, res));
} else {
this.send404(req, res);
}
} else {
this.send404(req, res);
}
}
async serveBundle(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
let bundleGraph = this.bundleGraph;
if (bundleGraph) {
let {pathname} = url.parse(req.url);
if (!pathname) {
this.send500(req, res);
return;
}
let requestedPath = path.normalize(pathname.slice(1));
let bundle = bundleGraph
.getBundles()
.find(
b =>
path.relative(this.options.distDir, b.filePath) === requestedPath,
);
if (!bundle) {
this.serveDist(req, res, next);
return;
}
invariant(this.requestBundle != null);
try {
await this.requestBundle(bundle);
} catch (err) {
this.send500(req, res);
return;
}
this.serveDist(req, res, next);
} else {
this.send404(req, res);
}
}
serveDist(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> | Promise<mixed> {
return this.serve(
this.options.outputFS,
this.options.distDir,
req,
res,
next,
);
}
async serve(
fs: FileSystem,
root: FilePath,
req: Request,
res: Response,
next: NextFunction,
): Promise<mixed> {
if (req.method !== 'GET' && req.method !== 'HEAD') {
// method not allowed
res.statusCode = 405;
res.setHeader('Allow', 'GET, HEAD');
res.setHeader('Content-Length', '0');
res.end();
return;
}
try {
var filePath = url.parse(req.url).pathname || '';
filePath = decodeURIComponent(filePath);
} catch (err) {
return this.sendError(res, 400);
}
filePath = path.normalize('.' + path.sep + filePath);
// malicious path
if (filePath.includes(path.sep + '..' + path.sep)) {
return this.sendError(res, 403);
}
// join / normalize from the root dir
if (!path.isAbsolute(filePath)) {
filePath = path.normalize(path.join(root, filePath));
}
try {
var stat = await fs.stat(filePath);
} catch (err) {
if (err.code === 'ENOENT') {
return next(req, res);
}
return this.sendError(res, 500);
}
// Fall back to next handler if not a file
if (!stat || !stat.isFile()) {
return next(req, res);
}
if (fresh(req.headers, {'last-modified': stat.mtime.toUTCString()})) {
res.statusCode = 304;
res.end();
return;
}
return serveHandler(
req,
res,
{
public: root,
cleanUrls: false,
},
{
lstat: path => fs.stat(path),
realpath: path => fs.realpath(path),
createReadStream: (path, options) => fs.createReadStream(path, options),
readdir: path => fs.readdir(path),
},
);
}
sendError(res: Response, statusCode: number) {
res.statusCode = statusCode;
res.end();
}
send404(req: Request, res: Response) {
res.statusCode = 404;
res.end(TEMPLATE_404);
}
send500(req: Request, res: Response): void | Response {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.writeHead(500);
if (this.errors) {
return res.end(
ejs.render(TEMPLATE_500, {
errors: this.errors,
hmrOptions: this.options.hmrOptions,
}),
);
}
}
logAccessIfVerbose(req: Request) {
this.options.logger.verbose({
message: `Request: ${req.headers.host}${req.originalUrl || req.url}`,
});
}
/**
* Load proxy table from package.json and apply them.
*/
async applyProxyTable(app: any): Promise<Server> {
// avoid skipping project root
const fileInRoot: string = path.join(this.options.projectRoot, 'index');
const configFilePath = await resolveConfig(
this.options.inputFS,
fileInRoot,
[
'.proxyrc.cjs',
'.proxyrc.mjs',
'.proxyrc.js',
'.proxyrc',
'.proxyrc.json',
],
this.options.projectRoot,
);
if (!configFilePath) {
return this;
}
const filename = path.basename(configFilePath);
if (
filename === '.proxyrc.js' ||
filename === '.proxyrc.cjs' ||
filename === '.proxyrc.mjs'
) {
// $FlowFixMe
// let cfg = (await import(configFilePath)).default;
let cfg = await this.options.packageManager.require(
configFilePath,
fileInRoot,
);
if (
// $FlowFixMe
Object.prototype.toString.call(cfg) === '[object Module]'
) {
cfg = cfg.default;
}
if (typeof cfg !== 'function') {
this.options.logger.warn({
message: `Proxy configuration file '${filename}' should export a function. Skipping...`,
});
return this;
}
cfg(app);
} else if (filename === '.proxyrc' || filename === '.proxyrc.json') {
let conf = await readConfig(this.options.inputFS, configFilePath);
if (!conf) {
return this;
}
let cfg = conf.config;
if (typeof cfg !== 'object') {
this.options.logger.warn({
message:
"Proxy table in '.proxyrc' should be of object type. Skipping...",
});
return this;
}
for (const [context, options] of Object.entries(cfg)) {
// each key is interpreted as context, and value as middleware options
app.use(createProxyMiddleware(context, options));
}
}
return this;
}
async start(): Promise<HTTPServer> {
const finalHandler = (req: Request, res: Response) => {
this.logAccessIfVerbose(req);
// Wait for the parcelInstance to finish bundling if needed
if (this.pending) {
this.pendingRequests.push([req, res]);
} else {
this.respond(req, res);
}
};
const app = connect();
app.use((req, res, next) => {
setHeaders(res);
next();
});
await this.applyProxyTable(app);
app.use(finalHandler);
let {server, stop} = await createHTTPServer({
cacheDir: this.options.cacheDir,
https: this.options.https,
inputFS: this.options.inputFS,
listener: app,
outputFS: this.options.outputFS,
host: this.options.host,
});
this.stopServer = stop;
server.listen(this.options.port, this.options.host);
return new Promise((resolve, reject) => {
server.once('error', err => {
this.options.logger.error(
({
message: serverErrors(err, this.options.port),
}: Diagnostic),
);
reject(err);
});
server.once('listening', () => {
resolve(server);
});
});
}
async stop(): Promise<void> {
invariant(this.stopServer != null);
await this.stopServer();
this.stopServer = null;
}
}

View File

@@ -0,0 +1,131 @@
// @flow
import {Reporter} from '@parcel/plugin';
import HMRServer from './HMRServer';
import Server from './Server';
let servers: Map<number, Server> = new Map();
let hmrServers: Map<number, HMRServer> = new Map();
export default (new Reporter({
async report({event, options, logger}) {
let {serveOptions, hmrOptions} = options;
let server = serveOptions ? servers.get(serveOptions.port) : undefined;
let hmrPort =
(hmrOptions && hmrOptions.port) || (serveOptions && serveOptions.port);
let hmrServer = hmrPort ? hmrServers.get(hmrPort) : undefined;
switch (event.type) {
case 'watchStart': {
if (serveOptions) {
// If there's already a server when watching has just started, something
// is wrong.
if (server) {
return logger.warn({
message: 'Trying to create the devserver but it already exists.',
});
}
let serverOptions = {
...serveOptions,
projectRoot: options.projectRoot,
cacheDir: options.cacheDir,
// Override the target's publicUrl as that is likely meant for production.
// This could be configurable in the future.
publicUrl: serveOptions.publicUrl ?? '/',
inputFS: options.inputFS,
outputFS: options.outputFS,
packageManager: options.packageManager,
logger,
hmrOptions,
};
server = new Server(serverOptions);
servers.set(serveOptions.port, server);
const devServer = await server.start();
if (hmrOptions && hmrOptions.port === serveOptions.port) {
let hmrServerOptions = {
port: serveOptions.port,
host: hmrOptions.host,
devServer,
addMiddleware: handler => {
server?.middleware.push(handler);
},
logger,
};
hmrServer = new HMRServer(hmrServerOptions);
hmrServers.set(serveOptions.port, hmrServer);
await hmrServer.start();
return;
}
}
let port = hmrOptions?.port;
if (typeof port === 'number') {
let hmrServerOptions = {port, host: hmrOptions?.host, logger};
hmrServer = new HMRServer(hmrServerOptions);
hmrServers.set(port, hmrServer);
await hmrServer.start();
}
break;
}
case 'watchEnd':
if (serveOptions) {
if (!server) {
return logger.warn({
message:
'Could not shutdown devserver because it does not exist.',
});
}
await server.stop();
servers.delete(server.options.port);
}
if (hmrOptions && hmrServer) {
await hmrServer.stop();
// $FlowFixMe[prop-missing]
hmrServers.delete(hmrServer.wss.options.port);
}
break;
case 'buildStart':
if (server) {
server.buildStart();
}
break;
case 'buildProgress':
if (
event.phase === 'bundled' &&
hmrServer &&
// Only send HMR updates before packaging if the built in dev server is used to ensure that
// no stale bundles are served. Otherwise emit it for 'buildSuccess'.
options.serveOptions !== false
) {
await hmrServer.emitUpdate(event);
}
break;
case 'buildSuccess':
if (serveOptions) {
if (!server) {
return logger.warn({
message:
'Could not send success event to devserver because it does not exist.',
});
}
server.buildSuccess(event.bundleGraph, event.requestBundle);
}
if (hmrServer && options.serveOptions === false) {
await hmrServer.emitUpdate(event);
}
break;
case 'buildFailure':
// On buildFailure watchStart sometimes has not been called yet
// do not throw an additional warning here
if (server) {
await server.buildError(options, event.diagnostics);
}
if (hmrServer) {
await hmrServer.emitError(options, event.diagnostics);
}
break;
}
},
}): Reporter);

View File

@@ -0,0 +1,21 @@
// @flow
export type ServerError = Error & {|
code: string,
|};
const serverErrorList = {
EACCES: "You don't have access to bind the server to port {port}.",
EADDRINUSE: 'There is already a process listening on port {port}.',
};
export default function serverErrors(err: ServerError, port: number): string {
let desc = `Error: ${
err.code
} occurred while setting up server on port ${port.toString()}.`;
if (serverErrorList[err.code]) {
desc = serverErrorList[err.code].replace(/{port}/g, port);
}
return desc;
}

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Page not found</title>
<style>
body {
margin: 0;
font-family: sans-serif;
}
.error-title {
color: #282c33;
font-size: 3rem;
padding: 25px;
padding-bottom: 10px;
margin: 0;
}
.short-desc {
color: #282c33;
font-size: 2rem;
padding: 0 25px;
margin: 0;
}
</style>
</head>
<body>
<h1 class="error-title">404</h1>
<h2 class="short-desc">Page not found.</h2>
</body>
</html>

View File

@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>🚨 Build Error</title>
<style>
html {
color-scheme: dark light;
background-color: #282c33;
}
body {
margin: 0;
font-family: sans-serif;
}
.title-heading {
font-size: 2rem;
background-color: #fe0140;
color: #ffffff;
margin: 0 0 20px 0;
padding: 10px;
}
.error-message {
font-size: 1.2rem;
color: salmon;
margin: 10px;
font-family: Menlo, Consolas, monospace;
white-space: pre;
}
.error-hints-container {
margin: 5px 0 20px 0;
list-style: none;
padding: 0;
}
.error-hints-container li:before {
content: '💡 ';
}
.error-hint {
color: #5e8af7;
font-size: 0.8rem;
padding: 0 0 0 5px;
font-family: Menlo, Consolas, monospace;
}
.error-stack-trace {
padding: 20px 10px;
background-color: #282c33;
color: #c5ccdb;
font-family: Menlo, Consolas, monospace;
font-size: 0.8rem;
white-space: pre;
}
.documentation {
padding: 0 5px;
font-family: Menlo, Consolas, monospace;
font-size: 0.8rem;
}
.documentation a {
color: violet;
}
</style>
</head>
<body>
<h1 class="title-heading">🚨 Parcel encountered errors</h1>
<% errors.forEach(function(error){ %>
<h2 class="error-message"><%- error.message %></h2>
<div class="error-stack-trace"><% if (error.frames?.length) { %><% error.frames.forEach(function(frame){ %><a href="/__parcel_launch_editor?file=<%- encodeURIComponent(frame.location) %>" style="text-decoration: underline; color: #888" onclick="fetch(this.href); return false"><%- frame.location %></a>
<%- frame.code %><% }); %><% } else { %><%- error.stack %><% } %></div>
<ul class="error-hints-container">
<% error.hints.forEach(function(hint){ %>
<li class="error-hint"><%- hint %></li>
<% }); %>
</ul>
<% if (error.documentation) { %>
<div class="documentation">📝 <a href="<%- error.documentation %>" target="_blank">Learn more</a></div>
<% } %>
<% }); %>
<% if (hmrOptions) { %>
<script>
// Reload the page when an HMR update occurs.
var protocol =
(location.protocol == 'https:' &&
!['localhost', '127.0.0.1', '0.0.0.0'].includes(hostname))
? 'wss'
: 'ws';
var hostname = <%- JSON.stringify(hmrOptions.host || null) %> || location.protocol.indexOf('http') === 0 ? location.hostname : 'localhost';
var port = <%- JSON.stringify(hmrOptions.port || null) %> || location.port;
var ws = new WebSocket(protocol + '://' + hostname + (port ? ':' + port : '') + '/');
var receivedInitialMessage = false;
ws.onmessage = (e) => {
let data = JSON.parse(e.data);
// The HMR server sends the pending error immediately on connect. Ignore this.
if (data.type == 'error' && !receivedInitialMessage) {
receivedInitialMessage = true;
return;
}
location.reload();
};
</script>
<% } %>
</body>
</html>

View File

@@ -0,0 +1,48 @@
// @flow
import type {ServerOptions, PluginLogger, HMROptions} from '@parcel/types';
import type {FileSystem} from '@parcel/fs';
import type {HTTPServer} from '@parcel/utils';
import type {PackageManager} from '@parcel/package-manager';
import {
IncomingMessage as HTTPIncomingMessage,
ServerResponse as HTTPServerResponse,
} from 'http';
import {
IncomingMessage as HTTPSIncomingMessage,
ServerResponse as HTTPSServerResponse,
} from 'https';
interface HTTPRequest extends HTTPIncomingMessage {
originalUrl?: string;
}
interface HTTPSRequest extends HTTPSIncomingMessage {
originalUrl?: string;
}
export type Request = HTTPRequest | HTTPSRequest;
export type Response = HTTPServerResponse | HTTPSServerResponse;
export type DevServerOptions = {|
...ServerOptions,
projectRoot: string,
publicUrl: string,
cacheDir: string,
inputFS: FileSystem,
outputFS: FileSystem,
logger: PluginLogger,
hmrOptions: ?HMROptions,
packageManager: PackageManager,
|};
// TODO: Figure out if there is a node.js type that could be imported with a complete ServerError
export type ServerError = Error & {|
code: string,
|};
export type HMRServerOptions = {|
devServer?: HTTPServer,
addMiddleware?: (
handler: (req: Request, res: Response) => boolean
) => void,
port: number,
host: ?string,
logger: PluginLogger,
|};