最简易版 webpack,带你从基础进入 webpack 世界

webpack 是目前前端主流的打包工具之一,但对于刚入门的同学来说,webpack 其实还是有点复杂的。可能大部分只会使用 creact-react-app 这种一键式脚手架对项目进行构建(包括我)。那么 webpack 的基础概念到底是什么呢?那么今天就来用 js 手写一个基础版的 webpack。

webpack 简单原理

  • 把一堆 js 文件中的 js 进行打包处理,将其合并为一个或者几个浏览器能够识别的文件,然后将其放入到 html 中,从而展示在浏览器中。最直接的体现就是将 ES6 的 js 代码打包成 ES5 的代码,让浏览器进行编译。

使用 ES5 的语法进行打包

  1. 项目结构

image.png

  • index.js
let first = require('./first.js').first;
let second = require('./second.js').second;

let message = `${name} is ${action}`;
console.log( message );
复制代码
  • first.js
let first = 'Hello';

exports.first = first;
复制代码
  • second.js
let third = require('./third').third;

exports.second = `Let's ${third}`;
复制代码
  • third.js
exports.third = 'making webpack';
复制代码

此时运行 node index.js,打印出的结果是:
image.png

那么,这就是 webpack 最最基本的实现形式,把所有的模块化代码打包成一个文件,并运行它。但是,要注意:浏览器并不认识 exports、require 代码,它们只能在 node 环境下使用。因此,我们需要手动设定 require 函数和 exports 对象

  1. 把所有文件的代码都设定成函数
//index.js
function(require, exports) {
    let first = require('./first.js').first;
    let second = require('./second.js').second;

    let message = `${first}, ${second}`;
    console.log( message );
}

//first.js
function(require, exports) {
    let first = 'Hello';
    exports.first = first;
}

//second.js
function(require, exports) {
    let third = require('./third.js').third;
    exports.second = `Let's ${third}`;
}

//third.js
function(require, exports) {
    exports.third = 'making webpack';
}
复制代码
  1. 期望目标

通过运行一个函数,对这些代码进行处理,最终生成一个 module 对象

modules = {
//fn
  0: [function(require, exports) {
    let first = require('./first.js').first;
    let second = require('./second.js').second;

    let message = `${first}, ${second}`;
    console.log( message );
  },
//mapping
    {
      './first.js': 1,
      './second.js': 2
    }
  ],

//fn
  1: [function(require, exports) {
    let first = 'Hello';
    exports.first = first;
  },
//mapping
    {}
  ],

//fn
  2: [function(require, exports) {
    let third = require('./third').third;
    exports.second = `Let's ${third}`;
  },
//mapping
    {
      './third.js': 3
    }
  ],
  
//fn
  3: [function(require, exports) {
    exports.third = 'making webpack';
  },
//mapping
    {}
  ] 
}
复制代码

通过一个函数,来对其进行调用

function package(id) {
  let [fn, mapping] = modules[id];
  let exports =  {};
  
  function require(path) {
    return exec(mapping[path]);
  }
  
  fn && fn(require, exports);
  return exports;
}

package(0)
复制代码

最后将其写入到 dist 目录下的 bundle.js 文件中

  1. 如何来生成 module 对象?
  • 首先需要查看单个文件中,require 了哪些文件
function getDependencies(fileContent) {
  let reg = /require\(['"](.+?)['"]\)/g;
  let result = null;
  let dependencies = [];
  while(result = reg.exec(fileContent)) {
    dependencies.push(result[1]);
  }
  console.log('dependencies', dependencies)
  return dependencies;
}
//示例
let fileContent = fs.readFileSync('./index.js', 'utf-8');
getDependencies(fileContent)
复制代码

通过正则对内容进行判断,符合规则的,就将其放入 dependencies 数组中,示例返回的结果是:

dependencies [ './first.js', './second.js' ]
复制代码
  • 接下来,需要一个函数,根据 filename 生成一个包含 idfilenamedependenciescode,的一个对象
let ID = 0;

function createObject(filename) {
  let fileContent = fs.readFileSync(filename, 'utf-8');
  let id = ID++;
  const item = {
    id: id,
    filename: filename,
    dependencies: getDependencies(fileContent),
    code: `function(require, exports, module) { 
        ${fileContent}
    }`
  }
  console.log('item', item)
  return item
}

//示例
createObject(./index.js)
复制代码

结果:

item {
  id: 0,
  filename: './src/index.js',
  dependencies: [ './first.js', './second.js' ],
  code: 'function(require, exports, module) { \n' +
    "        let first = require('./first.js').first;\r\n" +
    "let second = require('./second.js').second;\r\n" +
    '\r\n' +
    'let message = `${first}, ${second}`;\r\n' +
    'console.log( message );\r\n' +
    '\n' +
    '    }'
}
复制代码
  1. 根据 dependenciesindex.js 中所有 require 的文件进行相同的 createObject 操作
function createQueue(filename) {
  let item = createObject(filename);
  let queue = [item];

  for(let item of queue) {
    const dirname = path.dirname(item.filename);
    item.mapping = {};
    item.dependencies.forEach(relativePath => {
    //获取文件的绝对路径
      const absolutePath = path.join(dirname, relativePath);
    //根据 absolutePath,调用 createObject 方法  
      const child = createObject(absolutePath);
      item.mapping[relativePath] = child.id;
      queue.push(child);
    });

  }
  console.log('queue', queue)
  return queue;
}

//示例
queue [
  {
    id: 0,
    filename: './src/index.js',
    dependencies: [ './first.js', './second.js' ],
    code: 'function(require, exports, module) { \n' +
      "        let first = require('./first.js').first;\r\n" +
      "let second = require('./second.js').second;\r\n" +
      '\r\n' +
      'let message = `${first}, ${second}`;\r\n' +
      'console.log( message );\r\n' +
      '\n' +
      '    }',
    mapping: { './first.js': 1, './second.js': 2 }
  },
  {
    id: 1,
    filename: 'src\\first.js',
    dependencies: [],
    code: 'function(require, exports, module) { \n' +
      "        let first = 'Hello';\r\n" +
      '\r\n' +
      'exports.first = first;\r\n' +
      '\n' +
      '    }',
    mapping: {}
  },
  {
    id: 2,
    filename: 'src\\second.js',
    dependencies: [ './third.js' ],
    code: 'function(require, exports, module) { \n' +
      "        let third = require('./third.js').third;\r\n" +
      '\r\n' +
      "exports.second = `Let's ${third}`;\r\n" +
      '\n' +
      '    }',
    mapping: { './third.js': 3 }
  },
  {
    id: 3,
    filename: 'src\\third.js',
    dependencies: [],
    code: 'function(require, exports, module) { \n' +
      "        exports.third = 'making webpack';\r\n" +
      '\r\n' +
      '\n' +
      '    }',
    mapping: {}
  }
]
复制代码
  1. 根据 queue 生成 module 对象为内容的字符串,并将其写入 bundle.js 中
function createBundle(graph) {
  let modules = ''

  graph.forEach(mod => {
    modules += `${mod.id}: [
      ${mod.code},
      ${JSON.stringify(mod.mapping)}
    ],`
  })

  const result = `
    const modules = {${modules}}
    function fetch(data){
      function exec(id) {
      let [fn, mapping] = modules[id];
      let module = { exports: {} };
    
      fn && fn(require, module.exports, module);
      
      function require(path) {
        return exec(mapping[path]);
      }
      
        return module.exports;
      }
      
      exec(0)
    }
    fetch(modules)
  `

  fs.writeFileSync('../dist/bundle.js', result)
}
复制代码
  1. 全部代码
const fs = require('fs')
const path = require('path')
let ID = 0
let fileContent = fs.readFileSync('./src/index.js', 'utf-8')

function getDependencies(str) {
  let reg = /require\(['"](.+?)['"]\)/g
  let result = null
  let dependencies = []
  while (result = reg.exec(str)) {
    dependencies.push(result[1])
  }
  return dependencies
}


function createObject(filename) {
  console.log(filename)
  let fileContent = fs.readFileSync(filename, 'utf-8')
  console.log(11111111111)
  const id = ID++
  const item = {
    id: id,
    filename: filename,
    dependencies: getDependencies(fileContent),
    code: `function(require, exports, module) { 
        ${fileContent}
    }`
  }
  return item
}

function createQueue(filename) {
  let item = createObject(filename)
  let queue = [item]
  console.log(queue)

  for (let item of queue) {
    const dirname = path.dirname(item.filename)
    item.mapping = {}
    item.dependencies.forEach(relativePath => {
      //获取文件的绝对路径
      console.log(1)
      const absolutePath = path.join(dirname, relativePath)
      console.log(2)

      //根据 absolutePath,调用 createObject 方法
      const child = createObject(absolutePath)
      console.log(3)

      item.mapping[relativePath] = child.id
      console.log(4)

      queue.push(child)
    })

  }
  return queue
}

function createBundle(graph) {
  let modules = ''

  graph.forEach(mod => {
    modules += `${mod.id}: [
      ${mod.code},
      ${JSON.stringify(mod.mapping)}
    ],`
  })

  const result = `
    const modules = {${modules}}
    function fetch(data){
      function exec(id) {
      let [fn, mapping] = modules[id];
      let module = { exports: {} };
    
      fn && fn(require, module.exports, module);
      
      function require(path) {
        return exec(mapping[path]);
      }
      
        return module.exports;
      }
      
      exec(0)
    }
    fetch(modules)
  `

  fs.writeFileSync('../dist/bundle.js', result)
}// let ccc = fs.readFileSync('/src/third.js', 'utf-8');
// console.log('ccc', ccc)

let queue = createQueue('./src/index.js')
createBundle(queue)
复制代码
  1. 运行结果:在 dist/bundle.js 中生成代码

    const modules = {0: [
      function(require, exports, module) { 
        let first = require('./first.js').first;
let second = require('./second.js').second;

let message = `${first}, ${second}`;
console.log( message );

    },
      {"./first.js":1,"./second.js":2}
    ],1: [
      function(require, exports, module) { 
        let first = 'Hello';

exports.first = first;

    },
      {}
    ],2: [
      function(require, exports, module) { 
        let third = require('./third.js').third;

exports.second = `Let's ${third}`;

    },
      {"./third.js":3}
    ],3: [
      function(require, exports, module) { 
        exports.third = 'making webpack';


    },
      {}
    ],}
    function fetch(data){
      function exec(id) {
      let [fn, mapping] = modules[id];
      let module = { exports: {} };
    
      fn && fn(require, module.exports, module);
      
      function require(path) {
        return exec(mapping[path]);
      }
      
        return module.exports;
      }
      
      exec(0)
    }
    fetch(modules)
  
//运行结果
Hello, Let's making webpack
复制代码

那么,这就是最最简化版本的 webpack 所做的事情

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享