手写webpack

建几个文件add.js,index.js,index.html

// src/add.js
exports.default = function(a, b) {return a + b;}

// src/index.js
var add  = require('./add.js')
console.log(add(1,3))
<!-- src/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>测试模块化开发的代码</title>
  <script src="./index.js"></script>
</head>
<body>
</body>
</html>

运行html
在这里插入图片描述
Es5不使用webpack出现的问题
往往模块是别的库(比如nodejs),用的commonjs来写的,要处理加载模块的问题:
1.强大的eval函数
读取子模块add.js文件后获取到的是字符串

`exports.default = function(a, b) {return a + b;}`
//可以将此语句放入html文件中测试
eval(`exports.default = function(a, b) {return a + b;}`)

此时浏览器会报错
export is not defined

2.手动创建一个exports对象

var exports = {}
eval(`export.default = function(a,b) {return a + b}`)
console.log(exports.default(1,1))

此时出现了正确结果

3.其他情况,变量全局污染
如果在导出的文件中,还要一些其它的变量,比如var a = 1;之类的,就会造成全局污染
解决:为了避免全局污染,我们使用自执行函数包裹起来,它会为其创建一个独立的作用域

var exports = {};
(function(exports, code) {
	eval(code)
})(exports, `exports.default = function(a, b) {return a + b;}`)
console.log(exports.default(1,1))

第二步,实现加载模块

//我们将index.js中调用子模块的方法实现
<script>
	var exports = {}
	(function(exports, code) {
		eval(code)
	})(exports, `exports.default = function(a,b) { return a + b ;}`)

	//index.js的内容
	var add = require('./add.js')
	console.log(add(1,3))
</script>

直接引入提示require未定义
自己模拟实现一个require方法

function require(file) {
	(function(exports, code) {
		eval(code)
	})(exports, `exports.default = function(a, b){ return a + b; }`)
	return exports.deault;
}

var add = require('./add.js');
console.log(add(1,3))

第三步,文件获取
require(’./add.js’)这时的文件是写死的,不能按照参数形式处理

var add = require('./add.js')
console.log(add(1,3))
 // 文件列表对象大概长这样
    {
      "index.js": `
        var add  = require('./add.js')
        console.log(add(1,3))
      `,
      "add.js": `
        exports.default = function(a, b) {return a + b;}
      `
    }

再套一个立即执行函数,将文件列表对象以参数的形式传入

//src/index.html

<script>
	var exports = {};
	(function(list) {
		function require(file) {
			(function(exports, code) {
				eval(code)
			})(exports, list[file])
			return exports.default;
		}
		require('./index.js')
	})({
		"./index.js": `
			var add = require('./add.js')
			console.log(add(1,3))
		`,
		"./add.js":`
			exports.default = function(a,b) { return a + b;}
		`
	})
</script>

浏览器查看结果
简单的webpack雏形已经完成

二. 分析模块间的依赖,获取依赖图
主要解决依赖嵌套问题以及一个模块依赖多个模块问题
步骤分为
1.收集依赖
2.ES6转ES5
3.实现import 和 export

// ./webpack.js
// 引入fs模块,用来读写文件
const fs = require('fs')
// 引入path模块,处理路径问题
const path = require('path')

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

/**
 * 模块分析
 * @param {*} file 
 */
function getModuleInfo(file) {
  // 1. 读取文件
  const body = fs.readFileSync(file, 'utf-8')

  // 2. 转换AST语法树
  const ast = parser.parse(body, {
    sourceType: 'module'  // ES模块
  })
  // console.log("ast:", ast)

  // 3. 收集依赖
  const deps = {}
  traverse(ast, {
    ImportDeclaration({node}) {
      // 获取当前目录名
      const dirname = path.dirname(file)
      // 设置绝对路径
      const abspath = './' + path.join(dirname, node.source.value)
      deps[node.source.value] = abspath
    }
  })
  console.log("deps:", deps) // deps: { './add.js': './src\\add.js' }

  // 4. ES6转换ES5
  const { code } = babel.transformFromAst(ast, null, {
    presets: ["@babel/preset-env"]
  })
  // console.log("code:", code)

  // 5. 输出模块信息
  const moduleInfo = {
    file,
    deps,
    code
  }
  return moduleInfo
}

// const info = getModuleInfo('./src/index.js')
// console.log({info})

// 6. 解析模块间的关系
function parseModules(file) {
  // 从入口开始
  const entry = getModuleInfo(file)
  const temp = [entry]
  // 依赖关系图
  const depsGraph = {}
  // 7. 递归获取依赖关系
  getDeps(temp, entry)

  // 8. 组装依赖
  temp.forEach((moduleInfo) => {
    depsGraph[moduleInfo.file] = {
      deps: moduleInfo.deps,
      code: moduleInfo.code,
    };
  });
  return depsGraph

}


// 递归获取依赖关系
function getDeps(temp, {deps}) {
  Object.keys(deps).forEach(key => {
    const child = getModuleInfo(deps[key])
    temp.push(child)
    getDeps(temp, child)
  })
}

// console.log('graph:', graph)


// 9. 打包
function bundle(file) {
  // 获取依赖图
  const depsGraph = JSON.stringify(parseModules(file))
  // 跟第一个demo中的打包文件,拼接起来
  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
    }
    require('${file}')
  })(${depsGraph})`;
}

const content = bundle('./src/index.js')
console.log(content)

// 判断有没dist目录,没有就创建
!fs.existsSync("./dist") && fs.mkdirSync("./dist");
// 将打包后的文件写入./dist/bundle.js中
fs.writeFileSync("./dist/bundle.js", content);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值