手写webpack (2)

  1. 在m-pack 通过path  引入 webpack.config.js
    
let config = require(path.resolve('webpack.config.js'));
console.log(config)
  输出:
{ mode: 'development',
  entry: './src/index.js',
  output:
   { path: 'C:\\Users\\zxw\\Desktop\\js\\handwritten_webback\\webpack-dev\\dist',
     filename: 'bundle.js' } }
     webpack.config.js
 
let path = require('path');
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
}

 

可以看出, config 是 webpack.config.js 里面导出的配置
2. 在bin  创建 Compiler.js  在  m-pack 引入 然后 通过 new  实例化引入的  Compiler 最后 通过 run 方法启动
  
#! /usr/bin/env node
console.log('start 2020')
 
 
let path = require('path')
//配置文件
let config = require(path.resolve('webpack.config.js'));
console.log(config)
let Compiler = require("../bin/Compiler")
let compiler = new Compiler(config);
compiler.hooks.entryOption.call();
compiler.run()

 

 
3.编写 Compiler 这个文件
  •   在constructor 接受传入的配置 
  •   this.entry 是传入配置的入口文件
  •   this.config 将配置挂载到全局上
  •   this.root = process.cwd(); 是项目的工作的目录 如: C:\Users\zxw\Desktop\js\handwritten_webback\webpack-dev
  •   this.entryId 用来存放主入口
  •  buildModules 通过入口文件的路径来 获取源码 构建依赖关系
  •  emitFile 用来  最后解析好的源码和依赖关系发射出去
let path = require('path')
class Compiler {
  constructor(config) {
    this.config = config
    // 需要保存的主入口文件路径
    this.entryId; // './src/index.js'
    // modules对象用来存放依赖key和源码value
    this.modules = {};
    // 入口路径  entry: './src/index.js',
    this.entry = config.entry;
    //工作路径 C:\Users\zxw\Desktop\js\handwritten_webback\webpack-dev
    this.root = process.cwd();
    console.log(path.resolve(this.root, this.entry))
  }
  run() {
    // C:\Users\zxw\Desktop\js\handwritten_webback\webpack-dev\src\index.js
    this.buildModules(path.resolve(this.root, this.entry), true);
    // 发射一个文件
    this.emitFile();
  }
  buildModules(modulePath, isEntry) {
  }
  // 发射文件
  emitFile() {
  }
}
module.exports = Compiler;

 

 
  1. buildModules 详解
  •     通过  getSource 拿到模块的内容
    
let source = this.getSource(modulePath)
// let str = require('./a.js')
//  console.log(str)

 

  •   获取模块的入口 
   
   let moduleName = './' + path.relative(this.root, modulePath)
    console.log(moduleName)
./src\index.js
 
 
  •  判断如果是入口文件 就将它赋值给this.entryId 如果不是 进行 解析
  •  调用 parse 方法进行解析 传入 模块的内容 和 父文件 的目录
            path.dirname(moduleName)  输出为  ./src
            parse()方法返回了源码sourceCode和依赖dependencies
        
  buildModules(modulePath, isEntry) {
    console.log(111)
    // 拿到模块的内容
    let source = this.getSource(modulePath)
    // console.log(source) //./src\index.js
    let moduleName = './' + path.relative(this.root, modulePath)
    // console.log(moduleName)
    if (isEntry) {
      // 保存入口
      this.entryId = moduleName
    }
    console.log(path.dirname(moduleName))
    // 解析 需要把source 源码解析
    let { sourceCode, dependencies } = this.parse(source, path.dirname(moduleName))
  }

 

  5.  parse 方法
  parse(source, parentPath) {
    let dependencies = [];
    let ast = babylon.parse(source);
    traverse(ast, {
      CallExpression(p) {
        if (p.node.callee.name === 'require') {
          p.node.callee.name = '__webpack_require__';
          let moduleName = p.node.arguments[0].value;
          moduleName = moduleName + (path.extname(moduleName) ? '' : '.js');
          moduleName = './' + path.join(parentPath, moduleName);
          dependencies.push(moduleName);
          p.node.arguments = [t.stringLiteral(moduleName)]
        }
      }
    });
    let sourceCode = generator(ast).code;
    return { sourceCode, dependencies };
  }
 

 

   引入  @babel/traverse @babel/types @babel/generator  这几个创建解析 ast 
         npm i babylon @babel/traverse @babel/types @babel/generator -D
   
首先,parse()方法是解析模块的依赖关系的。它接收了两个参数,分别是源码source和父级路径。方法内部定义了一个空数组dependencies,用来存放解析到的依赖。接着通过第三方库Babylon生成该源码的抽象语法树ast,又通过traverse解析该语法树,解析语法树的流程为: 首先找到节点node的callee,callee有个name属性。找到name为require即找到该文件的依赖,并'require'其替换为'__webpack_require__',至于为什么替换为这个名字,原因是webpack打包后自己实现的require方式名就是'__webpack_require__'。替换后方便我们之后进行模版操作。
紧接着,又拿到了依赖的文件名a.js,不过这个依赖的文件名不一定都是带有后缀.js的。所以,判断它有没有后缀名,没有的话手动添加.js。
接下来就用到了刚才传递的第二个参数:父级路径'./src'。拿到依赖文件名'a.js'和父级路径'./src',将其拼接成依赖的相对路径'./src/a.js'。并把该依赖存到dependencies依赖数组中。
并且通过babel-types将ast中的把AST中的stringLiteral替换为相对路径。
最后一步,通过替换好的抽象语法树和babel-generator,生成结果。并返回souceCode和dependencies。
在buildModule方法中打印this.module即可看到结果:
{ './src\\index.js': 'let str = __webpack_require__("./src\\\\a.js");\n\nconsole.log(str);' }
可以看到,this.module对象中存放了key(依赖的路径)和value(ast语法树解析后的源码)。表示已经成功解析。但是value中还是有__webpack_require__,因为刚才只解析一次,我们需要递归解析所有的依赖,并放到this.module中。
递归解析依赖很简单,只需要循环解析dependencies数组的元素即可,因为dependencies数组中存放的便是所有依赖。
注意,第二个参数要传fasle。因为主入口路径只有一个。执行第一次该this.buildModule()方法就已经把主入口确定并且保存到this.entryId中。
  buildModules(modulePath, isEntry) {
    console.log(111)
    // 拿到模块的内容
    let source = this.getSource(modulePath)
    // console.log(source) //./src\index.js
    let moduleName = './' + path.relative(this.root, modulePath)
    // console.log(moduleName)
    if (isEntry) {
      // 保存入口
      this.entryId = moduleName
    }
    // 解析 需要把source 源码解析 
    let { sourceCode, dependencies } = this.parse(source, path.dirname(moduleName))
    this.modules[moduleName] = sourceCode
    dependencies.forEach((depend) => {
      this.buildModules(path.join(this.root, depend), false);
    });
    console.log(this.modules);
    // console.log(path.dirname(moduleName))
  }

 

输出:
  
{ './src\\index.js': 'let str = __webpack_require__("./src\\\\a.js");\n\nconsole.log(str);',
  './src\\a.js': 'let b = __webpack_require__("./src\\\\base\\\\b.js");\n\nmodule.exports = b + \'2020\';',
  './src\\base\\b.js': 'module.exports = \'您好a\';' }
{ './src\\index.js': 'let str = __webpack_require__("./src\\\\a.js");\n\nconsole.log(str);',
  './src\\a.js': 'let b = __webpack_require__("./src\\\\base\\\\b.js");\n\nmodule.exports = b + \'2020\';',
  './src\\base\\b.js': 'module.exports = \'您好a\';' }
{ './src\\index.js': 'let str = __webpack_require__("./src\\\\a.js");\n\nconsole.log(str);',
  './src\\a.js': 'let b = __webpack_require__("./src\\\\base\\\\b.js");\n\nmodule.exports = b + \'2020\';',
  './src\\base\\b.js': 'module.exports = \'您好a\';' }

  

所有的依赖关系,index.js, a.js, .js 都被完整的保存到this.modules对象中。
 
笔记:
https://app.yinxiang.com/fx/9f6e2d40-c0b8-4cf6-9366-c091db2446e1
代码:
posted @ 2020-03-21 21:51  1点  阅读(215)  评论(0编辑  收藏  举报