前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手撸webpack基础原理

手撸webpack基础原理

作者头像
刘嘿哈
发布2022-10-25 14:04:05
1980
发布2022-10-25 14:04:05
举报
文章被收录于专栏:js笔记

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;
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-05-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实操
  • webpack.js最终结构
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档