手撸webpack基础原理

2022-10-25 14:04:05 浏览数 (1)

webpack打包思路

  • 读取配置 入口(从哪个文件开始分析?) 出口 (放到什么位置?叫什么名字?) 其他 暂时忽略...
  • 入口函数,run开始编=》chunk

chunk包含了模块的依赖关系、依赖图谱

从入口文件开始,

  1. 进入模块,
  2. 处理模块依赖

进入依赖的模块、处理依赖模块的依赖、处理依赖模块内容 所有依赖模块递归处理

  1. 处理模块内容

处理文件内容 借助babel工具,帮助我们把内容编译成AST语法数,提取模块路径 借助babel语法转义

创建 生成bundle文件

依赖图普为实参(对象格式) webpack启动函数, 配备上下文中,require和exports

实操

初始化工程

代码语言:javascript复制
npm init -y

根目录创建webpack.config.js

代码语言:javascript复制
//webpack基础配置项
const path = require('path')
module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'main.js'
    },
    mode: 'develoment'
}

根目录下创建src目录,./src/目录下创建index.js、a.js、b.js、c.js

代码语言:javascript复制
// index.js
import str1 from './a.js'
import str2 from './b.js'
console.log('hellow'   str1   str2);

// a.js
const a = 'aaa你好'
export default a

// b.js
import strc from './c.js'
const a = 'bbb好的'   strc
export default a

// c.js
const c = 'ccc罗';
export default c;

根目录创建bundle.js,该文件主要写node代码,执行webpack打包脚本

代码语言:javascript复制
// 读取配置
// 引入webpack
// 执行webpack run方法
const webpack = require('./lib/webpack')
const webpackConfig = require('./webpack.config')
new webpack(webpackConfig).run()

根目录创建文件夹 lib,创建webpack.js 路径./lib/wepback.js webpack文件导出一个类

  1. constructor轻松获取到webpack的配置参数
  2. 这个类有一个run方法,首先执行this.parse解析入口文件

首先利用node.js的fs读取到入口文件的内容

代码语言:javascript复制
const fs=require('fs')
class webpack {
    constructor(options) {
        this.$options = options
        this.entry = options.entry;
        this.output = options.output;
        this.modules=[];//将来用来保存this.parse解析后模块
    }

    run() {
        this.parse(this.entry)
    }
    parse(entryFile) {
        const content = fs.readFileSync(entryFile, 'utf-8')
       
    }
}

安装@babel/parser工具,解析静态代码为ast 语法树,Node节点类型,

代码语言:javascript复制
npm install @babel/parser -S
代码语言:javascript复制
//引入
const parser = require('@babel/parser')
// 使用api parser.parse
    parse(entryFile) {
        const content = fs.readFileSync(entryFile, 'utf-8')
        const ast = parser.parse(content, {
            sourceType: 'module'
        })
        console.log(ast)
    }
//console.log(ast)打印结果
Node {
  type: 'File',
  start: 0,
  end: 90,
  loc: SourceLocation {
    start: Position { line: 1, column: 0 },
    end: Position { line: 3, column: 36 },
    filename: undefined,
    identifierName: undefined
  },
  range: undefined,
  leadingComments: undefined,
  trailingComments: undefined,
  innerComments: undefined,
  extra: undefined,
  errors: [],
  program: Node {
    type: 'Program',
    start: 0,
    end: 90,
    loc: SourceLocation {
      start: [Position],
      end: [Position],
      filename: undefined,
      identifierName: undefined
    },
    range: undefined,
    leadingComments: undefined,
    trailingComments: undefined,
    innerComments: undefined,
    extra: undefined,
    sourceType: 'module',
    interpreter: null,
    body: [ [Node], [Node], [Node] ],
    directives: []
  },
  comments: []
}

安装 @babel/traverse

代码语言:javascript复制
npm install --save @babel/traverse

遍历ImportDeclaration类型的节点,获取文件路径 node.source.path 利用path获取 当前文件目录 path.dirname(entry) 拼接得到依赖相对根目录的路径 保存两种路径在对象dependencies中

代码语言:javascript复制
 const path=require('path')
//引入存在一点问题,要加default
 const traverse = require('@babel/traverse').default
//方法
        const dependencies = {}
        traverse(ast, {
            ImportDeclaration({ node }) {
                const pathName = '.\'   path.join(path.dirname(entryFile), node.source.value)
                dependencies[node.source.value] = pathName;
            }
        })

安装@babel/core、@babel/preset-env

代码语言:javascript复制
npm install @babel/core @babel/preset-env -S

处理内容,将ast转换成code

代码语言:javascript复制
       // 处理内容,将ast转换成code
        const code = transformFromAst(ast, null, {
            presets: ["@babel/preset-env"]
        })
// 解析ast结果
{
  metadata: {},
  options: {
    cloneInputAst: true,
    babelrc: false,
    configFile: false,
    passPerPreset: false,
    envName: 'development',
    cwd: 'D:\learn-webpack\webpack-yuanli',
    root: 'D:\learn-webpack\webpack-yuanli',
    plugins: [
      [Plugin], [Plugin], [Plugin], [Plugin],
      [Plugin], [Plugin], [Plugin], [Plugin],
      [Plugin], [Plugin], [Plugin], [Plugin],
      [Plugin], [Plugin], [Plugin], [Plugin],
      [Plugin], [Plugin], [Plugin], [Plugin],
      [Plugin], [Plugin], [Plugin], [Plugin],
      [Plugin], [Plugin], [Plugin], [Plugin],
      [Plugin], [Plugin], [Plugin], [Plugin],
      [Plugin], [Plugin], [Plugin], [Plugin],
      [Plugin], [Plugin], [Plugin], [Plugin]
    ],
    presets: [],
    parserOpts: {
      sourceType: 'module',
      sourceFileName: undefined,
      plugins: [Array]
    },
    generatorOpts: {
      filename: undefined,
      auxiliaryCommentBefore: undefined,
      auxiliaryCommentAfter: undefined,
      retainLines: undefined,
      comments: true,
      shouldPrintComment: undefined,
      compact: 'auto',
      minified: undefined,
      sourceMaps: false,
      sourceRoot: undefined,
      sourceFileName: 'unknown'
    }
  },
  ast: null,
  code: '"use strict";n'  
    'n'  
    'var _a = _interopRequireDefault(require("./a.js"));n'  
    'n'  
    'var _b = _interopRequireDefault(require("./b.js"));n'  
    'n'  
    'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }n'  
    'n'  
    `console.log('hellow'   _a["default"]   _b["default"]);`,
  map: null,
  sourceType: 'script'
}

parse方法最终返回一个对象

代码语言:javascript复制
return {
            entryFile,//模块入口
            dependencies,//模块依赖路径映射关系
            code//模块内容
        }

run方法中接收this.parse的返回值,push都this.modules中

代码语言:javascript复制
        const info = this.parse(this.entry)
        this.modules.push(info);
        for (var i = 0; i < this.modules.length; i  ) {
            const module = this.modules[i];
            // 该模块是否有依赖模块
            if (module.dependencies) {
                for (var j in module.dependencies) {
                    // 每次 执行这条语句,外层循环this.modules就变化,然后完美的完成所有依赖模块的解析
                    this.modules.push(this.parse(module.dependencies[j]))
                }
            }
        }

将modules数组转化成对象

代码语言:javascript复制
   // 将数组转成对象格式
        let fileModules = {}
        this.modules.forEach(item => {
            fileModules[item.entryFile] = {
                dependencies: item.dependencies,
                code: item.code
            }
        })

// 对象转json,方便字符串拼接使用

代码语言:javascript复制
    this.file(JSON.stringify(fileModules))

this.file实现,拼接输出目录路径,生成文件内容,eval执行moudel内部代码,为其提供require方法和exports 对象

代码语言:javascript复制
 file(modules) {
        // 生成代码内容,webpack启动函数
        // 获取输入文件目录路径
        const filePath = path.join(this.output.path, this.output.filename);
        // 生成bundle代码
        const bundle = `(function(modules){
            function require(module){
                function pathRequire(path){
                    return require(modules[module].dependencies[path])
                }
                const exports={};
                (function(require,exports,code){
                    eval(code)
                })(pathRequire,exports,modules[module].code)
                return exports;
            }

            require('${this.entry}')
        })(${modules})`
            // 生成main.js,位置是dist目录
        fs.writeFileSync(filePath, bundle, 'utf-8')

    }

webpack.js最终结构

代码语言:javascript复制
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const { transformFromAst } = require('@babel/core')
    // @babel/core
    // babel-preset-env
class webpack {
    constructor(options) {
        this.$options = options
        this.entry = options.entry;
        this.output = options.output;
        this.modules = [];
    }

    run() {
        const info = this.parse(this.entry)
        this.modules.push(info);
        for (var i = 0; i < this.modules.length; i  ) {
            const module = this.modules[i];
            // 该模块是否有依赖模块
            if (module.dependencies) {
                for (var j in module.dependencies) {
                    // 每次 执行这条语句,外层循环this.modules就变化,然后完美的完成所有依赖模块的解析
                    this.modules.push(this.parse(module.dependencies[j]))
                }
            }
        }
        // 将数组转成对象格式
        let fileModules = {}
        this.modules.forEach(item => {
                fileModules[item.entryFile] = {
                    dependencies: item.dependencies,
                    code: item.code
                }
            })
            // 对象转json,方便字符串拼接使用

        this.file(JSON.stringify(fileModules))
    }
    parse(entryFile) {
        // 读取文件内容
        const content = fs.readFileSync(entryFile, 'utf-8')
            // 解析文件内容为ast
        const ast = parser.parse(content, {
                sourceType: 'module'
            })
            // 获取依赖路径映射图
        const dependencies = {}
        traverse(ast, {
                ImportDeclaration({ node }) {
                    const pathName = '.\'   path.join(path.dirname(entryFile), node.source.value)
                    dependencies[node.source.value] = pathName;
                }
            })
            // 处理内容,将ast转换成code
        const { code } = transformFromAst(ast, null, {
            presets: ["@babel/preset-env"]
        })
        return {
            entryFile,
            dependencies,
            code
        }
    }
    file(modules) {
        // 生成代码内容,webpack启动函数
        // 获取输入文件目录路径
        const filePath = path.join(this.output.path, this.output.filename);
        // 生成bundle代码
        const bundle = `(function(modules){
            function require(module){
                function pathRequire(path){
                    return require(modules[module].dependencies[path])
                }
                const exports={};
                (function(require,exports,code){
                    eval(code)
                })(pathRequire,exports,modules[module].code)
                return exports;
            }

            require('${this.entry}')
        })(${modules})`
            // 生成main.js,位置是dist目录
        fs.writeFileSync(filePath, bundle, 'utf-8')

    }
}

module.exports = webpack;

0 人点赞