webpack打包原理

1.创建一个项目文件夹,包含lib,dist,src三个文件

2.创建配置文件webpack.config.js,和bundle.js文件

3.lib里创建webpack.js文件,用来封装webpack类

4.src里创建一个index.js文件,作文打包的入口文件

##src--index.js
  import a from "./a.js"
  console.log("hello webpack");
##webpack的基础配置--webpack.config.js
const path = require("path")

module.exports = {
    mode:"development",
    entry:"./src/index.js",
    output:{
        path:path.resolve(__dirname,"./dist"),
        filename:"main.js"
    }
}
##bundle.js
//打包入口
const options = require("./webpack.config.js")
const webpack = require("./lib/webpack.js")
new webpack(options).run()
##webpack.js
这里面要做的事儿:
1.首先接收一份配置(webpack.config.js)
2.分析出入口模块的位置
  2.1读取入口模块的内容,分析哪些是依赖,哪些是源码
2.2处理需要编译的代码,使其变为浏览器能执行的代码(es6,jsx等的编译等)
  2.3分析其他模块
3.将第二部处理出的内容变为对象数据结构
  3.1包含模块路径
3.2包含处理好的内容
4.创建bundle.js:启动器函数,来补充代码里有可能出现的module exports require,让浏览器能够顺利的执行

首先分析入口文件,这里我们要安装几个插件:

安装babel/parser:npm i @babel/parser --save        分析文件中哪些是源码哪些是依赖

安装babel/traverse:npm i @babel/traverse --save  提取抽象语法树里的节点信息

安装babel/core babel/preset-env:npm i @babel/core @babel/preset-env --save   对源码js进行解析处理

  babel/core:核心库

  babel/preset-env:做代码转换,比如解析jsx,es6的Promise等等

##分析入口文件,解析依赖及源码,并进行编译
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");//引入parser
const traverse = require("@babel/traverse").default;//引入traverse
const {transformFromAst} = require("@babel/core")//处理ast抽象语法树

module.exports = class webpack {
    constructor(options){
        let {entry,output} = options
        //入口信息
        this.entry = entry;
        //出口信息
        this.output = output;
     //缓存模块信息
    
this.module = [];
    }
    run(){
        //开始分析入口模块内容
        this.parse(this.entry);
    }
    parse(entryFile){
        //拿到文件内容
        const content = fs.readFileSync(entryFile,"utf-8");
        //分析-哪些是源码-哪些是依赖 我们借助babel插件:安装:npm i @babel/parser --save
        //console.log(content)
        //使用parser分析内容,返回ast 抽象语法树
        const ast = parser.parse(content,{
            sourceType:"module"
        })
        //console.log(ast.program.body)通过上面的截图 我们可以看到打印的数据
        //提取依赖路径信息
        const dependencies = {}   //key为相对于相对入口路径,val为填充后的路径   
        traverse(ast,{
            ImportDeclaration({node}){
                //node.source.value 是相对于入口的相对路径
                //做路径填充 path.dirname(entryFile)
                const newPathName =  "./" + path.join(
                    path.dirname(entryFile),
                    node.source.value
                )
                dependencies[node.source.value] = newPathName
            }
        })
        //代码编译
        const {code} = transformFromAst(ast,null,{ //此api处理ast抽象语法树
            presets:["@babel/preset-env"]
        });
        //console.log(code)  解析后的代码依然不能再浏览器中执行   “require is not defined”   所以接下来我门要告诉浏览器  require是干嘛的
        //信息汇总返回
        return {
            entryFile,
            dependencies,
            code
        }    
    }
}

 将上面得到得数据做一个递归,即对所有依赖文件做了分析,并将拿到得数组转换为对象数据结构

run(){
        //开始分析入口模块内容
        const info = this.parse(this.entry);
        console.log(info)
        //入口数据放入模块
        this.module.push(info);
        //做递归,拿到所有得地址依赖做parse()
        for (let i = 0; i < this.module.length; i++) {
            const item = this.module[i];
            const { dependencies } = item;
            if (dependencies) {
              for (let j in dependencies) {
                this.module.push(this.parse(dependencies[j]));
              }
            }
            //console.log(this.module)
            
        }
        //数据结构转换-数组转换成对象
        const obj = {};
        this.module.forEach(item => {
            obj[item.entryFile] = {
                dependencies:item.dependencies,
                code:item.code 
            }
        })
    }

生成bundle

file(code) {
        //生成bundle.js  => dist/main.js
        const filePath = path.join(this.output.path,this.output.filename);
        const newCode = JSON.stringify(code);
        const bundle = `(function(graph){
            function require(module){
          //拿到绝对路径
function localRequire(relativePath){ return require(graph[module].dependencies[relativePath]) } var exports = {}; (function(require,exports,code){ eval(code) })(localRequire,exports,graph[module].code) return exports } require('${this.entry}') })(${newCode})` //写文件操作 fs.writeFileSync(filePath,bundle,"utf-8") }

然后执行  node bundle.js  转义后得代码 放到浏览器里可执行即正确

----------------

完整代码

const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");//引入parser
const traverse = require("@babel/traverse").default;//引入traverse
const {transformFromAst} = require("@babel/core")//处理ast抽象语法树

module.exports = class webpack {
    constructor(options){
        let {entry,output} = options
        //入口信息
        this.entry = entry;
        //出口信息
        this.output = output;
        //缓存模块信息
        this.module = [];
    }
    run(){
        //开始分析入口模块内容
        const info = this.parse(this.entry);
        console.log(info)
        //入口数据放入模块
        this.module.push(info);
        //做递归,拿到所有得地址依赖做parse()
        for (let i = 0; i < this.module.length; i++) {
            const item = this.module[i];
            const { dependencies } = item;
            if (dependencies) {
              for (let j in dependencies) {
                this.module.push(this.parse(dependencies[j]));
              }
            }
            //console.log(this.module)
            
        }
        //数据结构转换-数组转换成对象
        const obj = {};
        this.module.forEach(item => {
            obj[item.entryFile] = {
                dependencies:item.dependencies,
                code:item.code 
            }
        })
        //生成文件
        this.file(obj)
    }
    parse(entryFile){
        //拿到文件内容
        const content = fs.readFileSync(entryFile,"utf-8");
        //分析-哪些是源码-哪些是依赖 我们借助babel插件:安装:npm i @babel/parser --save
        //console.log(content)
        //使用parser分析内容,返回ast 抽象语法树
        const ast = parser.parse(content,{
            sourceType:"module"
        })
        //console.log(ast.program.body)
        //提取依赖路径信息
        const dependencies = {}   //key为相对于相对入口路径,val为填充后的路径   
        traverse(ast,{
            ImportDeclaration({node}){
                //node.source.value 是相对于入口的相对路径
                //做路径填充 path.dirname(entryFile)
                const newPathName =  "./" + path.join(
                    path.dirname(entryFile),
                    node.source.value
                )
                dependencies[node.source.value] = newPathName
            }
        })
        //代码编译
        const {code} = transformFromAst(ast,null,{ //此api处理ast抽象语法树
            presets:["@babel/preset-env"]
        });
        //console.log(code)
        //信息汇总返回
        return {
            entryFile,
            dependencies,
            code
        }    
    }
    file(code) {
        //生成bundle.js  => dist/main.js
        const filePath = path.join(this.output.path,this.output.filename);
        const newCode = JSON.stringify(code);
        const bundle = `(function(graph){
            function require(module){
                function localRequire(relativePath){
                    return require(graph[module].dependencies[relativePath])
                }
                var exports = {};
                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[module].code)
                return exports
            }
            require('${this.entry}')
        })(${newCode})`

        //写文件操作
        fs.writeFileSync(filePath,bundle,"utf-8")
    }
}
原文地址:https://www.cnblogs.com/znLam/p/13062966.html