Take a look at the sample code.

Here are three files, index.js is the main entry file:

// filename: index.js
import foo from './foo.js'
foo();

//filename: foo.js
import message from './message.js'
function foo() {
  console.log(message);
}

// filename: message.js
const message = 'hello world'
export default message;
Copy the code

Next, we’ll create a bundle.js package for these three files. The result of the package is a JS file, which will output ‘Hello World’ when run.

Bundle. js is what Webpack does. In our example, index.js is equivalent to the webpack entry file, which is configured in the webpack.config.js entry.

Let’s implement bundle.js.

First, of course, is to read the main entry file:

function createAssert(filename) {
  const content = fs.readFileSync(filename, {
    encoding: 'utf-8'
  });
  return content;
} 

const content = createAssert('./example/index.js');
Copy the code

Next, all you need to do is find the file where the import syntax is introduced, in the figure above, foo.js, and also find the dependencies of foo.js, recursively.

Import foo from ‘./foo.js’. Import foo from ‘./foo.js’.

Parsing this line of code into an AST becomes:

The next idea is to convert the above code into an AST and then fetch the field in the box above. Also read dependent files:

const fs = require('fs');
const babylon = require('babylon');
const traverse = require('babel-traverse').default;

function createAssert(filename) {
  const dependencies = [];
  const content = fs.readFileSync(filename, {
    encoding: 'utf-8'
  });

  const ast = babylon.parse(content, {
    sourceType: 'module'}); traverse(ast, {ImportDeclaration: ({node}) = >{ dependencies.push(node.source.value); }})console.log(dependencies); // [ './foo.js' ]
  return content;
}
Copy the code

We do this by reading the current file and adding the dependencies of the current file to an array called Dependencies.

However, createAssert doesn’t just return the source code, so let’s make it better:

let id = 0;

function getId() { return id++; }

function createAssert(filename) {
  const dependencies = [];
  const content = fs.readFileSync(filename, {
    encoding: 'utf-8'
  });

  const ast = babylon.parse(content, {
    sourceType: 'module'}); traverse(ast, {ImportDeclaration: ({ node }) = >{ dependencies.push(node.source.value); }})return {
    id: getId(),
    code: content,
    filename,
    dependencies,
    mapping: {}}; }Copy the code

If you call the main entry file index.js, you get this (ignoring the mapping) :

We can’t just do this for the main entry file, we need to do this for all files in the main entry chain. CreateAssert does this for one file. Based on this function, we create a function called crateGraph that makes recursive calls:

function createGraph(entry) {
  const modules = [];

  createGraphImpl(
    path.resolve(__dirname, entry),
  );

  function createGraphImpl(absoluteFilePath) {
    const assert = createAssert(absoluteFilePath);
    modules.push(assert);

    assert.dependencies.forEach(relativePath= > {
      const absolutePath = path.resolve(
        path.dirname(assert.filename),
        relativePath
      );
      const child = createGraphImpl(absolutePath, relativePath);
      assert.mapping[relativePath] = child.id;
    });

    return assert
  }

  return modules;
}
Copy the code

Run this function and you get something like this:

You’ll notice that in the screenshot, the code for each item in the array is our source code, but there’s still this import statement in there, so we’ll use Babel to convert it to commonJS.

To do this, change the code returned by createAssert with Babel:

const code = transformFromAst(ast, null, {
  presets: ['env'],
}).code
Copy the code

Cut off one of these terms and the result becomes:

The next step is going to be a little hard to understand at first, but the key is that we’re going to rewrite the require function, so let’s look at it first:

Let’s create a new function, bundle, to handle the results of createGraph.

function bundle(graph) {
  let moduleStr = ' ';

  graph.forEach(module= > {
    moduleStr += `
    The ${module.id}Function (require, module, exports) {function(require, module, exports) {function(require, module, exports) {The ${module.code}
      },
      The ${JSON.stringify(module.mapping)}
    ],
    `
  })

  const result = ` (function(modules){ function require(id) { const [fn, mapping] = modules[id]; const module = { exports: {}} // fn(localRequire, module, module.exports) function localRequire(name) { return require(mapping[name]) } return module.exports; } require(0); ({})${moduleStr}}) 
  `
  return result;
}
Copy the code

The final use is:

const graph = createGraph('./example/index.js');
const res = bundle(graph);
Copy the code

The res is the result of the final package, copied to the console to run, and you can see the output:

So the basic function succeeds.

(I’m running a little late today and will continue to add dependency loops and package caching issues tomorrow)