webpack-Bundler源码编写(模块分析)

为了简单理解webpack原理。

新建项目:webpack_bundler

// 目录
src
    index.js
    message.js
    word.js
bundler.js

word.js:

export const word=‘hello‘;

message.js:

import {word} from ‘./word.js‘;
    const message=`say ${word}`;
    export default message;

index.js:

import message from ‘./message.js‘;
    console.log(message);

bundler.js:

const fs=require(‘fs‘);
const moduleAnalyser=(filename)=>{
    const content=fs.readFileSync(filename,‘utf-8‘);
    console.log(content);
}

moduleAnalyser(‘./src/index.js‘);

在命令行中执行:node bundler.js,会输出index.js的内容:

import message from ‘./message.js‘;

console.log(message);

我们可以使用babel的一个第三方工具来帮我们分析代码 https://babel.docschina.org/docs/en/babel-parser

npm install @babel/parser --save

修改bundler.js后运行node bundler.js:

const fs=require(‘fs‘);
const parser=require(‘@babel/parser‘);

const moduleAnalyser=(filename)=>{
    const content=fs.readFileSync(filename,‘utf-8‘);
    console.log(parser.parse(content,{
        sourceType:‘module‘ // 当我们使用ES module语法时需要加上
    }));
}

moduleAnalyser(‘./src/index.js‘);

命令行就会输出一个抽象语法树:

Node {
  type: ‘File‘,
  start: 0,
  end: 58,
  loc:
   SourceLocation {
     start: Position { line: 1, column: 0 },
     end: Position { line: 3, column: 21 } },
  errors: [],
  program:  // 表示当前运行的程序
   Node {
     type: ‘Program‘,
     start: 0,
     end: 58,
     loc: SourceLocation { start: [Position], end: [Position] },
     sourceType: ‘module‘,
     interpreter: null,
     body: [ [Node], [Node] ],
     directives: [] },
  comments: [] }

接下来我们看一下program下面的body:

const ast=parser.parse(content,{
    sourceType:‘module‘
});
console.log(ast.program.body);

输出:

[ Node {
    type: ‘ImportDeclaration‘,  // 引入的声明--import message from ‘./message.js‘;
    start: 0,
    end: 35,
    loc: SourceLocation { start: [Position], end: [Position] },
    specifiers: [ [Node] ],
    source:
     Node {
       type: ‘StringLiteral‘,
       start: 20,
       end: 34,
       loc: [SourceLocation],
       extra: [Object],
       value: ‘./message.js‘ } },
  Node {
    type: ‘ExpressionStatement‘,    // 表达式语句--console.log(message);
    start: 37,
    end: 58,
    loc: SourceLocation { start: [Position], end: [Position] },
    expression:
     Node {
       type: ‘CallExpression‘,
       start: 37,
       end: 57,
       loc: [SourceLocation],
       callee: [Node],
       arguments: [Array] } } ]

这样我们就可以拿到所有引入的声明来分析依赖关系,babel还给我们提供了一个模块来帮我们快速找到import节点:

npm install @babel/traverse --save

修改一下bundler.js:

const fs=require(‘fs‘);
const parser=require(‘@babel/parser‘);
const traverse=require(‘@babel/traverse‘).default;

const moduleAnalyser=(filename)=>{
    const content=fs.readFileSync(filename,‘utf-8‘);
    const ast=parser.parse(content,{
        sourceType:‘module‘
    });
    traverse(ast,{
        ImportDeclaration({node}){
            console.log(node);
        }
    });
}

moduleAnalyser(‘./src/index.js‘);

重新运行node bundler.js输出:

Node {
  type: ‘ImportDeclaration‘,
  start: 0,
  end: 35,
  loc:
   SourceLocation {
     start: Position { line: 1, column: 0 },
     end: Position { line: 1, column: 35 } },
  specifiers:
   [ Node {
       type: ‘ImportDefaultSpecifier‘,
       start: 7,
       end: 14,
       loc: [SourceLocation],
       local: [Node] } ],
  source:
   Node {
     type: ‘StringLiteral‘,
     start: 20,
     end: 34,
     loc: SourceLocation { start: [Position], end: [Position] },
     extra: { rawValue: ‘./message.js‘, raw: ‘\‘./message.js\‘‘ },
     value: ‘./message.js‘ } }

这样我们就拿到了所有的依赖,上面的source的value就是我们依赖的文件。 再稍微修改一下bundler.js:

let dependencies=[];
traverse(ast,{
    ImportDeclaration({node}){
        dependencies.push(node.source.value);
    }
});
console.log(dependencies);

会输出:[ ‘./message.js‘ ]

还有一个问题是我们现在拿到的文件路径是相对于src下的路径,我们在开发中需要的是跟目录下的路径,所以还需要做一点改进:

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

const moduleAnalyser=(filename)=>{
    const content=fs.readFileSync(filename,‘utf-8‘);
    const ast=parser.parse(content,{
        sourceType:‘module‘
    });
    let dependencies=[];
    traverse(ast,{
        ImportDeclaration({node}){
            const dirname=path.dirname(filename);   // ./src
            const newFile=‘./‘+path.join(dirname,node.source.value);
            dependencies.push(newFile);
        }
    });
    console.log(dependencies);
}

moduleAnalyser(‘./src/index.js‘);

命令行输出:[ ‘./src/message.js‘ ]

但是如果我们只存绝对路径二没有相对路径的话最后打包还是会比较麻烦,所以最好是把两个路径都存下来:

let dependencies={};
traverse(ast,{
    ImportDeclaration({node}){
        const dirname=path.dirname(filename);
        const newFile=path.join(dirname,node.source.value);
        dependencies[node.source.value]=newFile;
    }
});
console.log(dependencies);

命令行输出:{ ‘./message.js‘: ‘src/message.js‘ }

这就是我们想要的结果,最后只要返回就可以了: return { filename,dependencies }

浏览器无法运行这样的代码,所以我们还需要将它转换成浏览器可以运行的代码,我们还可以借助babel的一个模块:

npm install @babel/core --save

还需要转换成es5:

npm install @babel/preset-env --save

修改bundler.js:

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

const moduleAnalyser=(filename)=>{
    const content=fs.readFileSync(filename,‘utf-8‘);
    const ast=parser.parse(content,{
        sourceType:‘module‘
    });
    let dependencies={};
    traverse(ast,{
        ImportDeclaration({node}){
            const dirname=path.dirname(filename);
            const newFile=path.join(dirname,node.source.value);
            dependencies[node.source.value]=newFile;
        }
    });
    const {code}=babel.transformFromAst(ast,null,{
        presets:[‘@babel/preset-env‘]
    });
    console.log(code);  // 浏览器可以运行的代码
    return {
        filename,dependencies,code
    }
}

moduleAnalyser(‘./src/index.js‘);

命令行输出:

"use strict";

var _message = _interopRequireDefault(require("./message.js"));

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

console.log(_message["default"]);

这就转换成浏览器可以运行的代码了。

原文地址:https://www.cnblogs.com/jingouli/p/12336344.html

时间: 2024-08-25 05:28:45

webpack-Bundler源码编写(模块分析)的相关文章

jQuery源码 Ajax模块分析

写在前面: 先讲讲ajax中的相关函数,然后结合函数功能来具体分析源代码. 相关函数: >>ajax全局事件处理程序 .ajaxStart(handler) 注册一个ajaxStart事件处理器.当一个Ajax请求开始,并且同时无其它未完成的Ajax请求时,jQuery触发ajaxStart事件. .ajaxSend(handler) 注册一个ajaxSend事件处理器.当一个Ajax请求被发送时触发ajaxSend事件. .ajaxSuccess(handler) 注册一个ajaxSucce

从Handler+Message+Looper源码带你分析Android系统的消息处理机制

引言 [转载请注明出处:从Handler+Message+Looper源码带你分析Android系统的消息处理机制 CSDN 废墟的树] 作为Android开发者,相信很多人都使用过Android的Handler类来处理异步任务.那么Handler类是怎么构成一个异步任务处理机制的呢?这篇 博客带你从源码分析Android的消息循环处理机制,便于深入的理解. 这里不得不从"一个Bug引发的思考"开始研究Android的消息循环处理机制.说来话长,在某一次的项目中,原本打算开启一个工作线

HTTP请求库——axios源码阅读与分析

概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的一个HTTP请求库,目前在GitHub中已经拥有了超过40K的star,受到了各位大佬的推荐. 今天,我们就来看下,axios到底是如何设计的,其中又有哪些值得我们学习的地方.我在写这边文章时,axios的版本为0.18.0.我们就以这个版本的代码为例,来进行具体的源码阅读和分析.当前axios所有

Spring Boot源码中模块详解

Spring Boot源码中模块详解 一.源码 spring boot2.1版本源码地址:https://github.com/spring-projects/spring-boot/tree/2.1.x 二.模块 Spring Boot 包含许多模块,以下是一些简单的概述: 1,spring-boot 为Spring Boot其他部分功能提供主要的lib包,其中包含:(1)SpringApplication类提供了静态便利的方法使编写独立的SpringApplication更加容易.它唯一的任

qt creator源码全方面分析(2-0)

目录 Extending Qt Creator Manual 生成领域特定的代码和模板 代码片段 文件和项目模板 自定义向导 支持其他文件类型 MIME类型 高亮和缩进 自定义文本编辑器 其他自定义编辑器 运行外部工具 简单的外部工具 复杂的外部工具 所有主题 Extending Qt Creator Manual Qt Creator是为Qt开发人员的需求量身定制的跨平台集成开发环境(IDE). Qt Creator可以通过多种方式扩展. 例如,Qt Creator架构基于插件加载器,这意味着

qt creator源码全方面分析(2-10)

目录 Creating Plugins Creating Plugins Qt Creator的核心是一个插件加载程序,加载并运行一组插件,实际上是这些插件提供了您从Qt Creator IDE中了解的功能.甚至应用程序主窗口和菜单都由插件提供的.插件使用不同的方式,供其他插件访问本插件的功能,并允许它们扩展应用程序的某些方面. 例如,Core插件是Qt Creator完全运行所必须具备的非常基本的插件,它提供了主窗口,以及相关API,用于添加菜单项,模式,编辑器类型,导航面板和许多其他内容.

安卓图表引擎AChartEngine(二) - 示例源码概述和分析

首先看一下示例中类之间的关系: 1. ChartDemo这个类是整个应用程序的入口,运行之后的效果显示一个list. 2. IDemoChart接口,这个接口定义了三个方法, getName()返回值是listitem上显示的标题; getDesc()返回值是listitem上显示的描述内容. excute(context)返回值是一个Intent,当点击listitem后跳转到此Intent. 3. AbstractDemoChart类是一个抽象类,实现接口IDemoChart接口,这个类中封

Android之源码之模块编译和调试

Android之源码之模块编译调试 (一) 进行源码模块修改进行编译的调试 1.首先是从git或者svn上拉一套完整的工程下来,然后全编一下,一般这个时间比较长,大概会得2,3个小时左右, 2,编译成功之后,进到源码的中要修改的模块,例如:package/apps/Contacts/模块下,对要修改的文件进行修改 3.然后在终端Terminal回到项目的根目录下,Android6.0,M的代码,原始的命令是要先source的,如:source xxx.sh xxx_project 4.然后在这个

从源码的角度分析ViewGruop的事件分发

从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,是Android中所有布局的父类或间接父类,像LinearLayout.RelativeLayout等都是继承自ViewGroup的.但ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能.ViewGroup继承结构示意图如

从源码上,分析AsyncTask的实现

Android开发者们应该都知道AsyncTask这个类,它是系统提供的一个异步任务类,可以方便的让我们实现异步操作.在本篇文章中,我将带大家进入源码,简单分析一下AsyncTask的实现. 首先,贴上AsyncTask类的源码: package android.os; import java.util.ArrayDeque; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; impo