Skip to content

Commit

Permalink
Merge pull request #28 from metacall/docs/metassr-bundler
Browse files Browse the repository at this point in the history
docs: document metassr-bundler
  • Loading branch information
hulxv authored Sep 29, 2024
2 parents 637d55a + 58a7343 commit 836f05c
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 58 deletions.
111 changes: 61 additions & 50 deletions crates/metassr-bundler/src/bundle.js
Original file line number Diff line number Diff line change
@@ -1,124 +1,135 @@
const { rspack } = require('@rspack/core');
const path = require('path');

/**
* Safely parses a JSON string, returning undefined if parsing fails.
* @param {string} json - The JSON string to parse.
* @returns {Object|undefined} - Parsed object or undefined if parsing fails.
*/
function safelyParseJSON(json) {
try {
return JSON.parse(json)
return JSON.parse(json);
} catch (_) {
return undefined
return undefined;
}
}

// Default configuration object for rspack bundling process
let config = {

output: {
filename: '[name].js',
filename: '[name].js', // Output filename with the entry name
library: {
type: 'commonjs2',
type: 'commonjs2', // Set library type to CommonJS2 (Node.js modules)
},
publicPath: ''
publicPath: '' // Specify the base path for all assets within the application
},
resolve: {
extensions: ['.js', '.jsx', '.tsx', '.ts']
extensions: ['.js', '.jsx', '.tsx', '.ts'] // Extensions that will be resolved
},
optimization: {
minimize: false,
minimize: false, // Disable minimization for easier debugging
},
module: {
rules: [
{
test: /\.(jsx|js)$/,
exclude: /node_modules/,
test: /\.(jsx|js)$/, // Rule for JavaScript and JSX files
exclude: /node_modules/, // Exclude node_modules directory
use: {
loader: 'builtin:swc-loader',
loader: 'builtin:swc-loader', // Use the SWC loader to transpile ES6+ and JSX
options: {
sourceMap: true,
sourceMap: true, // Enable source maps for easier debugging
jsc: {
parser: {
syntax: 'ecmascript',
jsx: true,
syntax: 'ecmascript', // Set parser syntax to ECMAScript
jsx: true, // Enable parsing JSX syntax
},
externalHelpers: false,
preserveAllComments: false,
externalHelpers: false, // Disable external helpers (use inline helpers)
preserveAllComments: false, // Remove comments from output
transform: {
react: {
runtime: 'automatic',
throwIfNamespace: true,
useBuiltins: false,
runtime: 'automatic', // Use React's automatic JSX runtime
throwIfNamespace: true, // Throw error if namespace is used
useBuiltins: false, // Don't include built-in polyfills
},
},
},
},

},
type: 'javascript/auto',
type: 'javascript/auto', // Specify the type as auto (for backward compatibility)
},
{
test: /\.(tsx|ts)$/,
exclude: /node_modules/,
test: /\.(tsx|ts)$/, // Rule for TypeScript and TSX files
exclude: /node_modules/, // Exclude node_modules directory
use: {
loader: 'builtin:swc-loader',
loader: 'builtin:swc-loader', // Use the SWC loader to transpile TS and TSX
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
syntax: 'typescript', // Set parser syntax to TypeScript
tsx: true, // Enable parsing TSX syntax
},
transform: {
react: {
runtime: 'automatic',
throwIfNamespace: true,
useBuiltins: false,
runtime: 'automatic', // Use React's automatic JSX runtime
throwIfNamespace: true, // Throw error if namespace is used
useBuiltins: false, // Don't include built-in polyfills
},
},
},
},
},
type: 'javascript/auto',
type: 'javascript/auto', // Specify the type as auto
},
{
test: /\.(png|svg|jpg)$/,
type: 'asset/inline',
test: /\.(png|svg|jpg)$/, // Rule for image files (PNG, SVG, JPG)
type: 'asset/inline', // Inline assets as Base64 strings
},
],
},
};

}
}

/**
* Bundles web resources using rspack.
* @param {Object|string} entry - The entry point(s) for the bundling process (can be a string or JSON object).
* @param {string} dist - The distribution path where bundled files will be output.
* @returns {Promise} - Resolves when bundling is successful, rejects if there is an error.
*/
async function web_bundling(entry, dist) {

// Create a bundler instance using the config and parameters
const compiler = rspack(
{
...config,
entry: safelyParseJSON(entry) ?? entry,
...config, // Merge with the default config
entry: safelyParseJSON(entry) ?? entry, // Parse entry if it's JSON, otherwise use it as is
output: dist ? {
...config.output,
path: path.join(process.cwd(), dist)
path: path.join(process.cwd(), dist), // Use current working directory and output path
} : config.output,

name: 'Client',
mode: 'development',
devtool: 'source-map',
stats: { preset: 'errors-warnings', timings: true, colors: true },
target: 'web',
// minimize: true,
name: 'Client', // Name of the bundle (Client)
mode: 'production', // Set mode to development (for non-minimized builds)
devtool: 'source-map', // Enable source maps for better debugging
stats: { preset: 'errors-warnings', timings: true, colors: true }, // Customize bundling stats output
target: 'web', // Set the target environment to web (for browser usage)
}

);

// Return a promise that runs the bundling process and resolves or rejects based on the result
return new Promise((resolve, reject) => {
return compiler.run((error, stats) => {
// Handle errors during the bundling process
if (error) {
reject(error.message);
reject(error.message); // Reject with the error message if bundling fails
}

// Check if there are any errors in the bundling stats
if (error || stats?.hasErrors()) {
reject(stats.toString("errors-only"));
reject(stats.toString("errors-only")); // Reject with errors-only details from stats
}
resolve(0);
resolve(0); // Resolve successfully when bundling is complete
});
});
}

module.exports = {
web_bundling
web_bundling // Export the web_bundling function to call it via metacall
};
47 changes: 39 additions & 8 deletions crates/metassr-bundler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,27 @@ impl Default for CompilationWait {
}
}

/// A web bundler that invokes the `web_bundling` function from the Node.js `bundle.js` script
/// using MetaCall. It is designed to bundle web resources like JavaScript and TypeScript files
/// by calling a custom `rspack` configuration.
///
/// The `exec` function blocks the execution until the bundling process completes.
#[derive(Debug)]

pub struct WebBundler<'a> {
/// A map containing the source entry points for bundling.
/// The key represents the entry name, and the value is the file path.
pub targets: HashMap<String, &'a Path>,
/// The output directory where the bundled files will be stored.
pub dist_path: &'a Path,
}

impl<'a> WebBundler<'a> {
/// Creates a new `WebBundler` instance.
///
/// - `targets`: A HashMap where the key is a string representing an entry point, and the value is the file path.
/// - `dist_path`: The path to the directory where the bundled output should be saved.
///
/// Returns a `WebBundler` struct.
pub fn new<S>(targets: &'a HashMap<String, String>, dist_path: &'a S) -> Self
where
S: AsRef<OsStr> + ?Sized,
Expand All @@ -58,57 +71,75 @@ impl<'a> WebBundler<'a> {
dist_path: Path::new(dist_path),
}
}

/// Executes the bundling process by invoking the `web_bundling` function from `bundle.js` via MetaCall.
///
/// It checks if the bundling script has been loaded, then calls the function and waits for the
/// bundling to complete, either resolving successfully or logging an error.
///
/// # Errors
///
/// This function returns an `Err` if the bundling script cannot be loaded or if bundling fails.
pub fn exec(&self) -> Result<()> {
// Lock the mutex to check if the bundling script is already loaded
let mut guard = IS_BUNDLING_SCRIPT_LOADED.lock().unwrap();
if !guard.is_true() {
// If not loaded, attempt to load the script into MetaCall
if let Err(e) = loaders::from_memory("node", BUILD_SCRIPT) {
return Err(anyhow!("Cannot load bundling script: {e:?}"));
}
// Mark the script as loaded
guard.make_true();
}
// Drop the lock on the mutex as it's no longer needed
drop(guard);

// Resolve callback when the bundling process is completed successfully
fn resolve(_: Box<dyn MetacallValue>, _: Box<dyn MetacallValue>) {
let compilation_wait = &*Arc::clone(&IS_COMPLIATION_WAIT);
let mut started = compilation_wait.checker.lock().unwrap();

// Mark the process as completed and notify waiting threads
started.make_true();
// We notify the condvar that the value has changed
compilation_wait.cond.notify_one();
}

// Reject callback for handling errors during the bundling process
fn reject(err: Box<dyn MetacallValue>, _: Box<dyn MetacallValue>) {
let compilation_wait = &*Arc::clone(&IS_COMPLIATION_WAIT);
let mut started = compilation_wait.checker.lock().unwrap();

// Log the bundling error and mark the process as completed
error!("Bundling rejected: {err:?}");

started.make_true();
// We notify the condvar that the value has changed
compilation_wait.cond.notify_one();
}

// Call the `web_bundling` function in the MetaCall script with targets and output path
let future = metacall::<MetacallFuture>(
BUNDLING_FUNC,
[
// Serialize the targets map to a string format
serde_json::to_string(&self.targets)?,
// Get the distribution path as a string
self.dist_path.to_str().unwrap().to_owned(),
],
)
.unwrap();

// Set the resolve and reject handlers for the bundling future
future.then(resolve).catch(reject).await_fut();

// Wait for the thread to start up.
// Lock the mutex and wait for the bundling process to complete
let compilation_wait = Arc::clone(&IS_COMPLIATION_WAIT);

let mut started = compilation_wait.checker.lock().unwrap();

// Waiting till future done
// Block the current thread until the bundling process signals completion
while !started.is_true() {
started = Arc::clone(&IS_COMPLIATION_WAIT).cond.wait(started).unwrap();
}
// Reset checker

// Reset the checker state to false after the process completes
started.make_false();
Ok(())
}
Expand Down

0 comments on commit 836f05c

Please sign in to comment.