This article will implement a base version of Webpack that can package dependent JS files.

Packages to install for this article:

File directory:

Contents of the document:

  • 1, the root directory of index.js entry js file:

    //.index.js: entry js file
    import { add, mul } from './math/index.js'
    console.log('1 + 1:, add(1.1));
    console.log('2 * 2:, mul(2.2));
    Copy the code
  • All files in the root math folder are referenced directly or indirectly by the entry file:

    // ./math/index.js
    import add from './tools/add.js'
    import mul from './tools/mul.js'
    
    console.log('Welcome to the Math tool method! ');
    export { add, mul }
    Copy the code
    // ./math/tools/add.js
    export default function add(. args) {
        return args.reduce((p, c) = > p + c)
    }
    Copy the code
    // ./math/tools/mul.js
    export default function add(. args) {
        return args.reduce((p, c) = > p * c)
    }
    Copy the code
  • 3. The dist folder in the root directory is the output location of the packaged bundle

Webpack implementation:

  • 4, the root directory of webpack.js is our implementation of webpack (code fully annotated, according to the comments read)

    const fs = require('fs')
    const { dirname, join } = require('path')
    Parse transforms ES6 code into an abstract syntax tree
    const parser = require('@babel/parser')
    // Traverse ES6 code's abstract syntax tree with traverse to find all dependent file paths for the current JS file
    const traverse = require('@babel/traverse').default
    // Use transformFromAstSync to transform the ES5 abstract syntax tree into ES5 code
    const { transformFromAstSync } = require('@babel/core')
    
    function getModule(path) {
        // 1, read the corresponding file contents according to the file path
        const content = fs.readFileSync(path, 'utf-8')
        // 2, convert ES6 to abstract syntax tree (AST)
        const ast = parser.parse(content, { sourceType: 'module' })
        // 3, in the abstract syntax tree to find the current file depends on the file path save
        const dependencies = []
        traverse(ast, { ImportDeclaration: ({ node }) = > { dependencies.push(node.source.value) } })
        // 4, convert the syntax tree of the current file into ES5 code, and add the function body wrap to simulate the behavior of require in CJS
        const code = `function (require,module,exports){${transformFromAstSync(ast, null, { presets: ['@babel/preset-env'] }).code}} `
        // 5, return the module information of the current file: file path, all dependent file paths, function body wrapped ES6 after ES5 code
        return { path, dependencies, code }
    }
    
    function getGraph(module, path) {
        // 1, first create the path to the current module and code mapping:
        // `'./index.js' : some code ~ ,`
        const initialMappingCodeToPath = ` '${path}':The ${module.code}, `
        // 2, recursively handles the dependencies of the current module, creating path and code mappings for all modules, and finally merging them together (strings), like the following
        / / `
        // './index.js' : some string_code ~ ,
        // './math/index.js' : some string_code ~ ,
        // './tools/add.js' : some string_code ~ ,
        // './tools/mul.js' : some string_code ~ ,
        / / `
        return module.dependencies.reduce(
            (mappingCodeToPath, depPath) = > {
                // 2.1, obtain the absolute path of dependent modules based on the path of the current module
                const depAbspath = join(dirname(module.path), depPath)
                // 2.2, get the dependency module path and code mapping relationship
                const depMappingCodeToPath = getGraph(getModule(depAbspath), depPath)
                // 2.3, merge the path and code mappings of all modules to generate dependency diagrams
                return mappingCodeToPath + depMappingCodeToPath
            },
            // 2.4, reduce method initial module path and code mapping relationship
            initialMappingCodeToPath
        )
    }
    
    function getBundle(entryPath) {
        Import file path import file all dependent file path import file ES6 to ES5 code
        const entryModule = getModule(entryPath)
        // 1.2, then recursively create the dependency graph (string) of all modules from the entry file, each module dependency is the mapping between the module path and the internal code of the module:
        / / `
        / / {
        // './index.js' : some string_code ~ ,
        // './math/index.js' : some string_code ~ ,
        // './tools/add.js' : some string_code ~ ,
        // './tools/mul.js' : some string_code ~
        / /}
        / / `
        const graph = ` {${getGraph(entryModule, entryPath)}} `
        // 1.3, returns an immediate-execute function string, which is the packaged code block:
        // Implement the CJS class require within the function
        // When executing the function immediately, we will start require from the entry file path, execute the internal code of the entry file, and encounter the require dependency module because it injected the implementation of the CJS class require
        // So the current dependent module code will continue to execute, and eventually all module code will complete
        return `(function(modules){
            function require(path){
                const module= {exports:{}}
                modules[path](require,module,module.exports)
                return module.exports
            }
           require('${entryPath}')
        })(${graph}) `
    
    }
    // 1, start packing
    // Pass in the entry file path and start packing
    const bundle = getBundle('./index.js')
    
    // 4, according to Webpack, the packaged code will be injected into bundle.js under the dist folder to complete the packaging! fs.existsSync("./dist") && fs.mkdirSync("./dist"); // Create dist folder if there is no dist folder
    fs.writeFileSync("./dist/bundle.js", bundle);       // Write the packaged contents to bundle.js under the dist folder
    
    Copy the code

Packaged JS code:

(function(modules){
        function require(path){
            const module= {exports:{}}
            modules[path](require.module.module.exports)
            return module.exports
        }
       require('./index.js') ({})'./index.js':function (require.module.exports){"use strict";

var _index = require("./math/index.js");

console.log('1 + 1:, (0, _index.add)(1.1));
console.log('2 * 2:, (0, _index.mul)(2.2)); },'./math/index.js':function (require.module.exports){"use strict";

Object.defineProperty(exports."__esModule", {
  value: true
});
Object.defineProperty(exports."add", {
  enumerable: true.get: function get() {
    return _add["default"]; }});Object.defineProperty(exports."mul", {
  enumerable: true.get: function get() {
    return _mul["default"]; }});var _add = _interopRequireDefault(require("./tools/add.js"));

var _mul = _interopRequireDefault(require("./tools/mul.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

console.log('Welcome to the Math tool method! '); },'./tools/add.js':function (require.module.exports){"use strict";

Object.defineProperty(exports."__esModule", {
  value: true
});
exports["default"] = add;

function add() {
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  return args.reduce(function (p, c) {
    return p + c;
  });
}},'./tools/mul.js':function (require.module.exports){"use strict";

Object.defineProperty(exports."__esModule", {
  value: true
});
exports["default"] = add;

function add() {
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  return args.reduce(function (p, c) {
    returnp * c; }); }}})Copy the code