-
Notifications
You must be signed in to change notification settings - Fork 14
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
How can a bundler efficiently work across different sims or entry points? #1559
Comments
Here is one working strategy, it runs a server that detects requests for bundle.js and runs esbuild before serving it. This is working correctly in membrane-channels but has not been generalized for use across other entry points. I'm also not sure how we could combine this with esbuild's live reload at all. Also, it's unclear how we could run this service as the server on phettest (I don't know how that server is configured). // server.js
const http = require( 'http' );
const fs = require( 'fs' );
const path = require( 'path' );
const { execFile } = require( 'child_process' );
const url = require( 'url' );
// Set the port you want the server to listen on.
const PORT = 8080;
// The esbuild command in our example uses --servedir=..
// so we assume our static files live one directory above this script.
const STATIC_ROOT = path.join( __dirname, '..' );
// A very basic mapping from file extension to Content-Type.
function getContentType( filePath ) {
const ext = path.extname( filePath ).toLowerCase();
switch( ext ) {
case '.html':
return 'text/html';
case '.js':
return 'application/javascript';
case '.css':
return 'text/css';
case '.json':
return 'application/json';
case '.map':
return 'application/json';
default:
return 'text/plain';
}
}
const server = http.createServer( ( req, res ) => {
const parsedUrl = url.parse( req.url );
let pathname = parsedUrl.pathname;
// If the request is for the root path, serve index.html.
if ( pathname === '/' ) {
pathname = '/index.html';
}
// If the requested file is "bundle.js", trigger the esbuild process.
if ( pathname.endsWith( 'bundle.js' ) ) {
console.log( `Received request for ${pathname}. Triggering esbuild...` );
// Define the esbuild arguments based on your exemplar.
// Note that we remove the --watch, --servedir, and --serve flags
// because we're doing an on-demand build.
const esbuildArgs = [
'./membrane-channels/js/membrane-channels-main.ts', // entry file
'--bundle',
'--format=iife',
'--global-name=MembraneChannelsBundle',
'--outfile=dist/bundle.js',
'--sourcemap'
];
// We use 'npx' to run esbuild.
execFile( 'npx', [ 'esbuild', ...esbuildArgs ], { cwd: STATIC_ROOT }, ( err, stdout, stderr ) => {
if ( err ) {
console.error( 'Esbuild error:', stderr );
res.statusCode = 500;
res.setHeader( 'Content-Type', 'text/plain' );
res.end( 'Build failed:\n' + stderr );
return;
}
console.log( 'Esbuild completed successfully. Serving bundle.js.' );
// The output file is generated at "dist/bundle.js" relative to STATIC_ROOT.
const bundlePath = path.join( STATIC_ROOT, 'dist', 'bundle.js' );
fs.readFile( bundlePath, ( err, data ) => {
if ( err ) {
console.error( 'Error reading bundle.js:', err );
res.statusCode = 500;
res.setHeader( 'Content-Type', 'text/plain' );
res.end( 'Error reading bundle file.' );
return;
}
// Serve the file with no caching.
res.statusCode = 200;
res.setHeader( 'Content-Type', 'application/javascript' );
res.setHeader( 'Cache-Control', 'no-store' );
res.end( data );
} );
} );
}
else {
// For all other requests, serve static files from STATIC_ROOT.
const filePath = path.join( STATIC_ROOT, pathname );
fs.readFile( filePath, ( err, data ) => {
if ( err ) {
console.error( `File not found: ${filePath}` );
res.statusCode = 404;
res.setHeader( 'Content-Type', 'text/plain' );
res.end( 'File not found.' );
}
else {
res.statusCode = 200;
res.setHeader( 'Content-Type', getContentType( filePath ) );
res.end( data );
}
} );
}
} );
server.listen( PORT, () => {
console.log( `Server running at http://localhost:${PORT}/` );
} ); |
This version works for any sim main, and is very snappy: // server.js
const http = require( 'http' );
const fs = require( 'fs' );
const path = require( 'path' );
const { execFile } = require( 'child_process' );
const url = require( 'url' );
// Set the port you want the server to listen on.
const PORT = 8080;
// The esbuild command in our example uses --servedir=..
// so we assume our static files live one directory above this script.
const STATIC_ROOT = path.join( __dirname, '..' );
// A very basic mapping from file extension to Content-Type.
function getContentType( filePath ) {
const ext = path.extname( filePath ).toLowerCase();
switch( ext ) {
case '.html':
return 'text/html';
case '.js':
return 'application/javascript';
case '.css':
return 'text/css';
case '.json':
return 'application/json';
case '.map':
return 'application/json';
default:
return 'text/plain';
}
}
const server = http.createServer( ( req, res ) => {
const parsedUrl = url.parse( req.url );
let pathname = parsedUrl.pathname;
// If the request is for the root path, serve index.html.
if ( pathname === '/' ) {
pathname = '/index.html';
}
// If the requested file is "bundle.js", trigger the esbuild process.
if ( pathname.endsWith( 'bundle.js' ) ) {
console.log( `Received request for ${pathname}. Triggering esbuild...` );
const sim = pathname.split( '/' )[ 1 ];
console.log( 'sim = ', sim );
// Define the esbuild arguments based on your exemplar.
// Note that we remove the --watch, --servedir, and --serve flags
// because we're doing an on-demand build.
const esbuildArgs = [
`./${sim}/js/${sim}-main.ts`, // entry file
'--bundle',
'--format=iife',
'--global-name=MembraneChannelsBundle',
'--outfile=dist/bundle.js',
'--sourcemap'
];
//mkdir dist
// fs.mkdir( path.join( STATIC_ROOT, 'dist' ), { recursive: true }, ( err ) => {
// if ( err ) {
// console.error( 'Error creating dist directory:', err );
// res.statusCode = 500;
// res.setHeader( 'Content-Type', 'text/plain' );
// res.end( 'Error creating dist directory.' );
// return;
// }
// } );
// We use 'npx' to run esbuild.
execFile( 'npx', [ 'esbuild', ...esbuildArgs ], { cwd: STATIC_ROOT }, ( err, stdout, stderr ) => {
if ( err ) {
console.error( 'Esbuild error:', stderr );
res.statusCode = 500;
res.setHeader( 'Content-Type', 'text/plain' );
res.end( 'Build failed:\n' + stderr );
return;
}
console.log( 'Esbuild completed successfully. Serving bundle.js.' );
// The output file is generated at "dist/bundle.js" relative to STATIC_ROOT.
const bundlePath = path.join( STATIC_ROOT, 'dist', 'bundle.js' );
fs.readFile( bundlePath, ( err, data ) => {
if ( err ) {
console.error( 'Error reading bundle.js:', err );
res.statusCode = 500;
res.setHeader( 'Content-Type', 'text/plain' );
res.end( 'Error reading bundle file.' );
return;
}
// Serve the file with no caching.
res.statusCode = 200;
res.setHeader( 'Content-Type', 'application/javascript' );
res.setHeader( 'Cache-Control', 'no-store' );
res.end( data );
} );
} );
}
else {
// For all other requests, serve static files from STATIC_ROOT.
const filePath = path.join( STATIC_ROOT, pathname );
fs.readFile( filePath, ( err, data ) => {
if ( err ) {
console.error( `File not found: ${filePath}` );
res.statusCode = 404;
res.setHeader( 'Content-Type', 'text/plain' );
res.end( 'File not found.' );
}
else {
res.statusCode = 200;
res.setHeader( 'Content-Type', getContentType( filePath ) );
res.end( data );
}
} );
}
} );
server.listen( PORT, () => {
console.log( `Server running at http://localhost:${PORT}/` );
} ); |
In phetsims/membrane-channels#6, we exercised esbuild to iterate quickly on membrane-channels development. One recurring question has been: how do we use a bundler across many entry points? For instance, if I need to run a test in sim1, then run a test in sim2, how can bundle.js be up-to-date for both without too many watch processes?
The text was updated successfully, but these errors were encountered: