新建一个项目,再新建一个src文件夹,里面有三个文件,word.js,message.js,index.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);

这三个模块通过调用,最后打印出say hello。那我们说,如果直接想src目录下的代码运行下浏览器下,是不可以的;浏览器根本就不认识这种语法。所以我们需要通过webpack类似的打包工具帮助我们进行项目的打包。在根目录下创建一个bundler.js,这就是我们要做的打包工具

首先打包工具肯定是用node.js来做开发的,所以需要安装node。用了webpack,肯定是装了node的。
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里面的内容。这里在控制台显示的内容,黑色的文本,不是很好看。我们可以安装一个工具

sudo npm install cli-highlight -g
然后运行 node bundler.js | highlight
这个时候,打印出来的代码就会高亮。
拿到文件内容以后。需要分析这个文件里面有哪些依赖,怎么拿到依赖呢?字符串截取可以,但如果很多个模块引入的时候,字符串截取就会显得非常麻烦。我们安装babel的第三方模块帮助我们去分析依赖,安装
npm install @babel/parser --save
安装好之后我们就可以使用了
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');
这个时候打印出来一个对象。这个对象就是我们听说的一个概念,叫做抽象语法树。也就是ast

这个对象可以很好的表述我当前的这段代码。这个对象里有一个program这样的一个字段,表示当前运行的程序,里面有个body字段,我们可以打印这个字段看一下。

bundler.js
const fs = require('fs'); // 帮助我们获取一些文件的信息
const parser = require('@babel/parser'); // 帮助我们分析代码,引入的文件 // 分析模块
const moduleAnalyser = (filename) => {
  // 读取文件内容
  const content = fs.readFileSync(filename, 'utf-8');
  const ast = parser.parse(content, {
    sourceType: 'module' // 说明是es module的引入方式
  })
  console.log(ast.program.body);
} moduleAnalyser('./src/index.js');

这个时候打印出来body的两个节点,首先第一个节点ImportDeclaration,确实是import一些东西。也就是引入的声明。第二个节点是ExpressionStatement,是一个表达式的声明,console.log确实是一个表达式的语句。所以通过babel.parser可以分析出抽象语法树。通过抽象语法树,我们就可以找到一些声明的语句,而声明的语句,放置的就是入口文件对应的一些依赖关系。我们可以遍历body,找到ImportDeclaration这样的一些内容,如果自己写一些遍历的话,还是有点麻烦,babel还提供给我们一个模块,可以帮助我们快速的找到import的节点。所以我们需要安装这样的一个模块

npm install @babel/traverse --save

bundler.js

const fs = require('fs'); // 帮助我们获取一些文件的信息
const parser = require('@babel/parser'); // 帮助我们分析代码,引入的文件
const traverse = require('@babel/traverse').default;// 因为是export出来的内容,必须加一个default属性才可以 // 分析模块
const moduleAnalyser = (filename) => {
  // 读取文件内容
  const content = fs.readFileSync(filename, 'utf-8');
  // 利用parser.parse获取到ast
  const ast = parser.parse(content, {
    sourceType: 'module' // 说明是es module的引入方式
  });
  // 利用traverse对代码进行一个分析
  const dependencies = [];
  traverse(ast, {
    // 只要抽象语法树有ImportDeclaration就会进入这个方法,node是节点
    ImportDeclaration({ node }){
      dependencies.push(node.source.value);
    }
  });
  console.log(dependencies);
} moduleAnalyser('./src/index.js');

再次运行 node bundler.js | highlight。发现已经打印出了依赖的模块。[ './message.js' ]。

这个时候对入口文件对依赖分析就分析出来了。但是这个路径是相对路径,相对于传进来对index.js。在真正做代码打包的时候,我们这些依赖文件他不能是相对路径。他必须是一个绝对路径。或者如果一定要相对路径,也要相对于bundler这个项目,打包才不会有问题。解决这个问题,引入node的另外一个核心模块,叫做path
const fs = require('fs'); // 帮助我们获取一些文件的信息
const path = require('path'); // 打包的时候需要绝对路径,借助path这个模块
const parser = require('@babel/parser'); // 帮助我们分析代码,引入的文件
const traverse = require('@babel/traverse').default;// 因为是export出来的内容,必须加一个default属性才可以 // 分析模块
const moduleAnalyser = (filename) => {
  // 读取文件内容
  const content = fs.readFileSync(filename, 'utf-8');
  // 利用parser.parse获取到ast
  const ast = parser.parse(content, {
    sourceType: 'module' // 说明是es module的引入方式
  });
  // 利用traverse对代码进行一个分析
  const dependencies = [];
  traverse(ast, {
    // 只要抽象语法树有ImportDeclaration就会进入这个方法,node是节点
    ImportDeclaration({ node }){
      // 拿到filename对应的文件夹路径
      const dirname = path.dirname(filename);
      // 对这个文件夹的路径进行一个转化,将引入的模块转化成绝对路径
      const newFile = path.join(dirname, node.source.value);
      console.log(newFile);
      dependencies.push(node.source.value);
    }
  });
} moduleAnalyser('./src/index.js');

这个时候打印出来的就是引入的模块的绝对路径。入口文件和相对应的依赖,都可以分析出来了。但是我们的代码,浏览器还是不支持的,需要借助babel去解析我们es6的代码。

安装

npm install @babel/core --save
这是babel的核心模块
 
bundler.js
const fs = require('fs'); // 帮助我们获取一些文件的信息
const path = require('path'); // 打包的时候需要绝对路径,借助path这个模块
const parser = require('@babel/parser'); // 帮助我们分析代码,引入的文件
const traverse = require('@babel/traverse').default;// 因为是export出来的内容,必须加一个default属性才可以
const babel = require('@babel/core'); // babel的核心模块,转化代码,转化成浏览器认识的代码 // 分析模块
const moduleAnalyser = (filename) => {
  // 读取文件内容
  const content = fs.readFileSync(filename, 'utf-8');
  // 利用parser.parse获取到ast
  const ast = parser.parse(content, {
    sourceType: 'module' // 说明是es module的引入方式
  });
  // 利用traverse对代码进行一个分析
  const dependencies = {};
  traverse(ast, {
    // 只要抽象语法树有ImportDeclaration就会进入这个方法,node是节点
    ImportDeclaration({ node }){
      // 拿到filename对应的文件夹路径
      const dirname = path.dirname(filename);
      // 对这个文件夹的路径进行一个转化,将引入的模块转化成相对于bundler的相对路径
      const newFile = './' + path.join(dirname, node.source.value);
      // 为了方便,把相对路径,绝对路径都存上,key是相对路径,value是绝对路径
      dependencies[node.source.value] = newFile;
    }
  });
  // 这个方法可以将抽象语法树转化成浏览器可以运行代码。
  const { code } = babel.transformFromAst(ast, null, {
    presets: ['@babel/preset-env'] // 把es6语法翻译成es5语法
  });
  // 返回入口文件和相对应的依赖,都可以分析出来了。
  return {
    filename,
    dependencies,
    code
  }
} const moduleInfo = moduleAnalyser('./src/index.js');
console.log(moduleInfo);

这个时候就分析好了文件该有的内容,入口文件,对应的依赖,翻译好的代码。那么接下来就是分析其他的文件

webpack中bundler源码编写的更多相关文章

  1. webpack中bundler源码编写2

    通过第一部分的学习,我们已经可以分析一个js的文件.这节课我们学习Dependencies Graph,也就是依赖图谱.对所有模块进行分析.先分析index.js.index.js里面引入了messg ...

  2. php中foreach源码分析(编译原理)

    php中foreach源码分析(编译原理) 一.总结 编译原理(lex and yacc)的知识 二.php中foreach源码分析 foreach是PHP中很常用的一个用作数组循环的控制语句.因为它 ...

  3. vue打包时,assets目录 和static目录下文件的处理区别(nodeModule中插件源码修改后,打包后的文件应放在static目录)

    为了回答这个问题,我们首先需要了解Webpack如何处理静态资产.在 *.vue 组件中,所有模板和CSS都会被 vue-html-loader 及 css-loader 解析,并查找资源URL.例如 ...

  4. 【原】Spark中Client源码分析(二)

    继续前一篇的内容.前一篇内容为: Spark中Client源码分析(一)http://www.cnblogs.com/yourarebest/p/5313006.html DriverClient中的 ...

  5. 【原】Spark中Master源码分析(二)

    继续上一篇的内容.上一篇的内容为: Spark中Master源码分析(一) http://www.cnblogs.com/yourarebest/p/5312965.html 4.receive方法, ...

  6. 【原】 Spark中Worker源码分析(二)

    继续前一篇的内容.前一篇内容为: Spark中Worker源码分析(一)http://www.cnblogs.com/yourarebest/p/5300202.html 4.receive方法, r ...

  7. Django缓存机制--rest_framework中节流源码使用的就是django提供的缓存api

    一.配置缓存   https://www.jb51.net/article/124434.htm 二.缓存全站.页面.局部   三.自我控制的简单缓存API API 接口为:django.core.c ...

  8. 深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)

    events模块对外提供了一个 EventEmitter 对象,即:events.EventEmitter. EventEmitter 是NodeJS的核心模块events中的类,用于对NodeJS中 ...

  9. 从 sourcemap 中获取源码

    使用 paazmaya/shuji: Reverse engineering JavaScript and CSS sources from sourcemaps 可以从 sourcemap 中获取源 ...

随机推荐

  1. [BJOI2019] 删数 [dp转贪心结论+线段树]

    题面 传送门 思路 dp部分 以下称合法序列为原题面中可以删空的序列 这个是我在模拟考场上的思路 一开始我是觉得,这个首先可以写成一个dp的形式:$dp[i][j]$表示用$j$个数字填满了目标序列的 ...

  2. Intellij热部署插件JRebel的详细配置及图解

    参考博客地址:https://blog.csdn.net/nyotengu/article/details/80629631 参考博客地址:https://blog.csdn.net/weixin_4 ...

  3. JDBC(连接数据库的四个主要步骤)

    JDBC连接数据库 ?创建一个以JDBC连接数据库的程序,包含7个步骤: 1.加载JDBC驱动程序: 在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.l ...

  4. QT 源码分析--1

    Ref: http://blog.sina.com.cn/s/blog_6e80f1390100qoc0.html 安装qt之后(我使用的是online自动安装),安装目录下有\5.10.1\Src\ ...

  5. AES加密、解密(linux、window加密解密效果一致,支持中文)

    转自: http://sunfish.iteye.com/blog/2169158 import java.io.UnsupportedEncodingException; import java.s ...

  6. LeetCode 783. 二叉搜索树结点最小距离(Minimum Distance Between BST Nodes)

    783. 二叉搜索树结点最小距离 LeetCode783. Minimum Distance Between BST Nodes 题目描述 给定一个二叉搜索树的根结点 root, 返回树中任意两节点的 ...

  7. 【转帖】K8S Deployment 命令

    K8S Deployment 命令 https://www.cnblogs.com/Tempted/p/7831604.html 今天学习了一下 kubectl scale deployment xx ...

  8. [Visual Studio] - 使用 Fiddler 时,禁止监控 VSHub 请求的方法

    背景 VS + Fiddler 调试 WebAPI,监控请求包含大量 VSHub Request.http://localhost:49161/vshub/bb195f2e0d5c4765b9411f ...

  9. 修改主机名和修改主机映射和ssh免登陆

    1.修改主机名 vim /etc/sysconfig/network NETWORKING=yes HOSTNAME=cc3 2.修改主机映射 vi /etc/hosts 127.0.0.1 loca ...

  10. golang使用一个二叉树来实现一个插入排序

    思路不太好理解,请用断点 package main import "fmt" type tree struct { value int left, right *tree } fu ...