webpack原理解析(一)实现一个简单的webpack

背景

Webpack 是当下最热门的前端资源模块化管理和打包工具。它能够将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还能够将按需加载的模块进行代码分隔,等到实际须要的时候再异步加载。webpack如此强大,其内部机制究竟是怎样的,今天咱们来一探究竟。前端

准备工做

新建一个目录,执行npm init -y 初始化一个package.json文件node

新建src下index.js,做为入口文件,export.js做为依赖的模块webpack

新建webpack.config.js,这里就按照webpack4的基础配置写一下git

新建bundle.js做为入口进行webpack构建 建立lib下webpack.jsgithub

根据配置读取入口文件

bundle.js:web

//读取到webpackd 的配置文件
const options = require("./webpack.config.js")
const Webpack = require("./lib/webpack.js")
new Webpack(options).run()
复制代码

webpack.js 这里写了一个Webpack类,bundle.js实例化这个类,将参数(引入的基础配置的内容)传进来并执行run方法npm

module.exports = class Webpack {
    constructor(options){
        console.log(options)
    }
    run(){
        console.log("hello")
    }
}
复制代码

此时执行node bundle.js : json

image
已经能够根据基础配置读取到入口模块了

分析出依赖模块

我这里写的index.js是这样:数组

import { sayHi } from './export.js'
sayHi("hfj")
console.log("hello webpack index")
复制代码

此时须要借助node的核心模块-- filesystem来读到内容浏览器

const conts = fs.readFileSync(entryFile,'utf-8')
复制代码

接下来借助@babel/parser(记得本身安装和引入。。)把内容抽象成语法树

const ast = parser.parse(conts, {
            sourceType: "module"
          });
复制代码

打印下ast.program.body

image
看type,哪里是依赖模块,哪里是表达式,已经很明显了,接下来经过@babel/traverse来提取依赖模块:

const dependencies = {}
          traverse(ast,{
            ImportDeclaration({node}){
                const newPath = "./" + path.join(
                    path.dirname(entryFile),
                    node.source.value
                    )
                dependencies[node.source.value] = newPath
                console.log(dependencies)
            }
        })
复制代码

注意下,这里作了一个处理,将依赖模块以对象的形式放到了dependencies里。

编译内容

接下来,经过@babel/core将ast处理成内容

const {code} = transformFromAst(ast,null,{
            presets: ["@babel/preset-env"]
        })
        console.log(code)
复制代码

打印的code以下:

image
内容成功拿到了!

遍历依赖模块

以上是对入口文件进行的编译,接下来还要看入口模块的依赖模块有哪些,分别按照刚才的方法进行内容分析,最后作一个汇总(根据dependence来判断的是否还有依赖模块):

run(){
       const info = this.analysis(this.entry)
        this.modulesArr.push(info)
        for(let i=0;i<this.modulesArr.length;i++) {
            const item = this.modulesArr[i]
            const { dependencies } = item;
            if(dependencies) {
                for(let j in dependencies){
                    this.modulesArr.push(this.analysis(dependencies[j]))
                }
            }
        }
        // console.log(this.modules)
        //数组结构转换
        const obj = {}
        this.modulesArr.forEach((item) => {
            obj[item.entryFile] = {
                dependencies:item.dependencies,
                code:item.code
            }
        })
    }
复制代码

最终是将modulesArr转化成了对象,方便后续处理

输出浏览器可执行的js代码

此处划重点! webpack最终是将模块化的js打包成一个chunk,打包后的js到dist(webpack基础配置里设置的output)

首先用了fs.writeFileSync将生成的bundle写入filePath(filePath以前有在this.output保存) 接下来就是处理obj(上一步骤中的汇总后的模块)了:

核心是用eval()来执行obj中的code,同时须要处理requrie和export require和export经过形参传入,以后执行本身写的reqire和export require处理了模块路径,exports是一个对象,看源码--

const bundle = `(function(graph){
            function require(moduleId){
                function localRequire(relativePath){
                   return require(graph[moduleId].dependencies[relativePath]) 
                }
                var exports = {};
                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[moduleId].code)
                return exports;
            }
            require('${this.entry}')
        })(${newCode})`
复制代码

执行node bundle.js,在dist文件夹下输出以下js:

(function(graph){
            function require(moduleId){
                function localRequire(relativePath){
                   return require(graph[moduleId].dependencies[relativePath]) 
                }
                var exports = {};
                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[moduleId].code)
                return exports;
            }
            require('./src/index.js')
        })({"./src/index.js":{"dependencies":{"./export.js":"./src\\export.js"},"code":"\"use strict\";\n\nvar _export = require(\"./export.js\");\n\n(0, _export.sayHi)(\"hfj\");\nconsole.log(\"hello webpack index\");"},"./src\\export.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.sayHi = void 0;\n\nvar sayHi = function sayHi(params) {\n console.log(\"hello ~\" + params);\n};\n\nexports.sayHi = sayHi;"}})
复制代码

在浏览器执行:输出:

image

完成~

总结

webpack经过入口文件逐层遍历到模块依赖,进行代码分析、代码转换,最终生成可在浏览器运行的打包后的代码

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序须要的每一个模块,而后将全部这些模块打包成一个或多个 bundle。

以上实现了一个简易版本的webpack

下一篇:webpack原理解析(二)loader和plugin机制

若有兴趣,欢迎进一步交流探讨~

附github地址:github.com/manli-tongh…

参考:Webpack官网

关注咱们

相关文章
相关标签/搜索