Implement a webpack

Modularization cannot be used directly in browsers. Although Es Module is now supported, further conversion is needed. webpack can package our modules into bundle files to provide the syntax that the browser can recognize.

add.js file

exports.default = (a, b) => a + b;

index.js file

const add = require('add.js').default;
console.log(add(2, 4));

The above exports and require browsers cannot be recognized. However, we can simulate an exports and require to run on the browser.

const exports = {}; // In CommonJS, exports points to an object
exports.default = function (a, b) {return a + b};
exports.default(2, 4); 

The above code can run normally and get 6, but this is equivalent to add The JS file will be exposed under the global. If add JS file has other variables, which will cause environmental pollution. So we need to extract a function scope.

const exports = {};
(function (exports, code) {
	eval(code);
})(exports, 'exports.default = function (a, b) {return a + b}');

eval is used because the Node uses fs to read the file and reads the string. Therefore, we need to use eval to parse strings.

Next, we complete the require to simulate the import.

function request() {
	const exports = {};
	(function (exports, code) {
		eval(code);
	})(exports, 'exports.default = function (a, b) {return a + b}');
}

In this way, we have completed the imitation of require, but this kind of writing is dead. What we need is to import files according to dependencies.

const list = {
	'add.js': `exports.default = function (a, b) {return a + b}`,
	'index.js': `
		var add = require('add.js').default;
	    console.log(add(2, 4));
	`
}
function request(file) {
	const exports = {};
	(function (exports, code) {
		eval(code);
	})(exports, list[file]);
}
require('index.js');

In this way, we can start from index JS, load index JS file, and then in the process of loading, another require is encountered, and then add.js is loaded JS file, self executing add JS file code.

We can find that the above code exposes list and request globally, and we can continue to use the function scope.

(function (list) {
	function request(file) {
		const exports = {};
		(function (exports, code) {
			eval(code);
		})(exports, list[file]);
		return exports;
	}
	request('index.js');
})({
	'add.js': `exports.default = function (a, b) {return a + b}`,
	'index.js': `
		var add = require('add.js').default;
	    console.log(add(2, 4));
	`
})

Now we have completed a similar that can run in the browser. But our list is dead. We need to automatically read files, automatically collect dependencies and package bundles js . We can use Node to do this.

realization.

Our implementation can be divided into three parts: collecting dependencies, converting ES6 to ES5, replacing exports and require.

We use the import and export keywords of Es Module to import and export.
add.js file

export default (a, b) => a + b;

index.js file

import add from 'add.js';
console.log(add(2, 4));

If we want to collect dependencies, we must first know which files the entry file depends on. According to the import keyword, you can know that you should collect dependencies.

const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core');

function getModuleInfo(file) {

	const body = fs.readFileSync(file, 'utf-8');
	
	const ast = parser.parse(body, {
		sourceType: 'module'
	});
	
	const deps = {};
	
	traverse(ast, {
		ImportDeclaration({node}) {
			const dirname = path.dirname(file);
			const abspath = './' + path.join(dirname, node.source.value);
			deps[node.source.value] = abspath;
		}
	})
	
	const {code} = babel.transformFromAst(ast, null, {
		presets: ['@babel/preset-env']
	});
	
	const moduleInfo = {file, deps, code};
	
	return moduleInfo;
	
}

function parseModules(file) {
	const entry = getModuleInfo(file);
	const temp = [entry];
	const depsGraph = {};
	getDeps(temp, entry);
	temp.forEach(info => {
		depsGraph[info.file] = {
			deps: info.deps,
			code: info.code
		}
	})
	return depsGraph;
}

function getDeps(temp, {deps}) {
    Object.keys(deps).forEach((key) => {
        const child = getModuleInfo(deps[key])
        temp.push(child);
        getDeps(temp, child);
    })
}

function bundle(file) {
    const depsGraph = JSON.stringify(parseModules(file));
    return `
    (function (graph) {
        function require(file) {
            function absRequire(relPath) {
                return require(graph[file].deps[relPath])
            }
            var exports = {};
            (function (require, exports, code) {
                eval(code);
            })(absRequire, exports, graph[file].code)
            return exports;
        }
    })(${depsGraph})
    `;
}
const content = bundle('./src/index.js');
!fs.existsSync('./dist') && fs.mkdirSync('./dist');
fs.writeFileSync('./dist/bundle.js', content);

Keywords: Javascript Front-end Webpack

Added by swebajen on Wed, 26 Jan 2022 20:34:54 +0200