function createAsset(filename) {
  // Read the content of the file as a string.
  const content = fs.readFileSync(filename, 'utf-8');

  // Now we try to figure out which files this file depends on. We can do that
  // by looking at its content for import strings. However, this is a pretty
  // clunky approach, so instead, we will use a JavaScript parser.
  //
  // JavaScript parsers are tools that can read and understand JavaScript code.
  // They generate a more abstract model called an AST (abstract syntax tree).

  // I strongly suggest that you look at AST Explorer (https://astexplorer.net)
  // to see how an AST looks like.
  //
  // The AST contains a lot of information about our code. We can query it to
  // understand what our code is trying to do.
  const ast = babylon.parse(content, {
    sourceType: 'module',
  });

  // This array will hold the relative paths of modules this module depends on.
  const dependencies = [];

  // We traverse the AST to try and understand which modules this module depends
  // on. To do that, we check every import declaration in the AST.
  traverse(ast, {
    // EcmaScript modules are fairly easy because they are static. This means
    // that you can't import a variable, or conditionally import another module.
    // Every time we see an import statement we can just count its value as a
    // dependency.
    ImportDeclaration: ({node}) => {
      // We push the value that we import into the dependencies array.
      dependencies.push(node.source.value);
    },
  });

  // We also assign a unique identifier to this module by incrementing a simple
  // counter.
  const id = ID++;

  // We use EcmaScript modules and other JavaScript features that may not be
  // supported on all browsers. To make sure our bundle runs in all browsers we
  // will transpile it with Babel (see https://babeljs.io).
  //
  // The `presets` option is a set of rules that tell Babel how to transpile
  // our code. We use `babel-preset-env` to transpile our code to something
  // that most browsers can run.
  const {code} = transformFromAst(ast, null, {
    presets: ['env'],
  });

  // Return all information about this module.
  return {
    id,
    filename,
    dependencies,
    code,
  };
}
Module bundlers compile small pieces of code into something larger and more
complex that can run in a web browser. These small pieces are just JavaScript
files, and dependencies between them are expressed by a module system
(https://webpack.js.org/concepts/modules).

Module bundlers have this concept of an entry file. Instead of adding a few
script tags in the browser and letting them run, we let the bundler know
which file is the main file of our application. This is the file that should
bootstrap our entire application.

Our bundler will start from that entry file, and it will try to understand
which files it depends on. Then, it will try to understand which files its
dependencies depend on. It will keep doing that until it figures out about
every module in our application, and how they depend on one another.

This understanding of a project is called the dependency graph.

In this example, we will create a dependency graph and use it to package
all of its modules in one bundle.

Let's begin :)

Please note: This is a very simplified example. Handling cases such as
circular dependencies, caching module exports, parsing each module just once
and others are skipped to make this example as simple as possible.