迷你版webpack

文章详细分析了Webpack如何处理CommonJS和ES6模块,包括打包代码的结构和内部机制,如require函数的实现、模块缓存、以及如何通过loader处理CSS等非JavaScript资源,同时介绍了如何添加plugins进行扩展。

webpack打包代码

commonJS

模块代码

//module1.js
const module2=require('./module2')
console.log('module2',module2)

//module2.js
const a=1
console.log('module2',a)
module.exports.a=a

-------------------------------------------
打包代码bundle.js

//打包代码稍作修正一(功能不改变)
(() => {

  var __webpack_modules__ = ({
    "./module1.js":
    (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) =>{
      eval(`
          const module2=__webpack_require__("./module2.js");
          console.log('module2',module2.a)
      `);
    },
    "./module2.js":
    (module) =>{
      eval(`
          const a=1;console.log('module2',a);
          module.exports.a=a
      `);
    }
  });

  var __webpack_module_cache__ = {};

  function __webpack_require__(moduleId) {
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = __webpack_module_cache__[moduleId] = {
      exports: {}
    };
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    return module.exports;
  }

  var __webpack_exports__ = __webpack_require__("./module1.js");

 })();


//打包代码稍作修正二(功能不改变)
(function(){
  var cache = {};

  function require(modulePath) {
    module={
      exports:{}
    }
    modules[modulePath](module, module.exports, require);
    return module.exports;
  }

  var modules={
    ["./module1.js"](module, exports, require){
      eval(`
          const module2=require("./module2.js");
          console.log('module2',module2.a)
      `);
    },

    ["./module2.js"](module){
      eval(`
          const a=1;console.log('module2',a);
          module.exports.a=a
      `);
    }
  }

  var exports = require("./module1.js");
 })();

分析

  1. webpack实现了自己的require()函数
  2. webpack采用了立即执行函数
  3. webpack把各个模块的代码放到modules中
  4. 各模块代码字符串形式存储,使用eval()函数执行

commonJs+ES6

模块代码

//module1.js
const module2=require('./module2')
console.log('module1',module2.a)

//module2.js
const module3=require('./utils/modules3')
console.log('module2',module3.a)
module.exports.a=module3.a

//module3.js
export const a=1
console.log('module3',a)

------------------------------------------------
打包代码

//打包代码稍作修正(功能不改变)
(() => { 
	var modules = ({
    "./module1.js":
    ((module, exports, require) => {
        eval(`
          const module2=require("./module2.js");
          console.log('module1',module2.a)
        `);
    }),

    "./module2.js":
    ((module, exports, require) => {
        eval(`
            const module3=require('./utils/modules3.js');
            console.log('module2',module3.a);
            module.exports.a=module3.a
        `);
    }),

    "./utils/modules3.js":
    ((module, exports, require) => {
        "use strict";
        eval(`
              require.r(exports);
              require.d(exports, {"a":() => (a)});
              const a=1;
              console.log('module3',a)
        `);
    })
  });

	var _cache = {};
	
	function require(moduleId) {		
		var cachedModule = _cache[moduleId];
		if (cachedModule !== undefined) {
			return cachedModule.exports;
		}
		var module = _cache[moduleId] = {	
			exports: {}
		};
		modules[moduleId](module, module.exports, require);
		return module.exports;
	}

	(() => {
		require.d = (exports, definition) => {
			for(var key in definition) {
				if(require.o(definition, key) && !require.o(exports, key)) {
					Object.defineProperty(
                        exports, key, 
                        { enumerable: true, get: definition[key] }
                    );
				}
			}
		};
	})();
	
	(() => {
		require.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
	})();
	
	(() => {	
		require.r = (exports) => {
			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
			}
			Object.defineProperty(exports, '__esModule', { value: true });
            console.log(exports)
            // debugger
		};
	})();

	var exports = require("./module1.js");
})()
;

r函数

解开debugger的注释,控制台打印出

Module {__esModule: true, Symbol(Symbol.toStringTag): 'Module'}
    __esModule: true
    Symbol(Symbol.toStringTag): "Module"
   [[Prototype]]: Object

说明:r函数向exports对象添加__esModule、Symbol(Symbol.toStringTag)两个属性,来标注采用了ES6模块化

d、o函数

注释debugger,控制台打印出

d、r函数向exports对象添加a属性

ES6

模块代码

//module1.js
import {a} from './module2'
console.log('module1',a)

//module2.js
export const a=1
console.log('module2',a)

----------------------------------
打包代码

(() => {
  "use strict";
  var modules = ({

    "./module1.js":
      ((module, exports, require) => {

        eval(`require.r(exports);
              var _module2__WEBPACK_IMPORTED_MODULE_0__ = require("./module2.js");
              console.log('module1',_module2__WEBPACK_IMPORTED_MODULE_0__.a)
        `);
      }),

    "./module2.js":

      ((module, exports, require) => {

        eval(`require.r(exports);
              require.d(exports, {"a": () => (a)});
              const a=1;
              console.log('module2',a)`);

      })

  });
  
  var cache = {};

  function require(moduleId) {
    var cachedModule = cache[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }

    var module = cache[moduleId] = {

      exports: {}
    };


    modules[moduleId](module, module.exports, require);


    return module.exports;
  }


  (() => {

    require.d = (exports, definition) => {
      for (var key in definition) {
        if (require.o(definition, key) && !require.o(exports, key)) {
          Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
        }
      }
    };
  })();


  (() => {
    require.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  })();


  (() => {
    require.r = (exports) => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
      }
      Object.defineProperty(exports, '__esModule', { value: true });
    };
  })();
  var exports = require("./module1.js");

})()
  ;

迷你版一

代码

//pack.js
let path=require('path')


let processPath=process.cwd()
let config=require(path.resolve(processPath,'webpack.config.js'))


let Compiler=require('./compiler').Compiler
let compiler=new Compiler(config)
compiler.run()
//compiler.js
const fs= require('fs')
const path=require('path')
const babelParser=require('@babel/parser')
const types=require('@babel/types')
const traverse=require('@babel/traverse').default
const generator=require('@babel/generator').default
const ejs=require('ejs')


class Compiler{
    constructor(config){
        this.config=config
        this.entry=config.entry
        this.entryId
        this.root=process.cwd()
        this.modules={}
    }

    run(){
        const entryPath=path.resolve(this.root,this.entry)
        this.buildMoudle(entryPath,true)
        this.outputFile()
    }

    buildMoudle(absPath,isEntry){
        const regex=/\\/g
        //获取路径
        let relPath='./'+path.relative(this.root,absPath)
        if(isEntry){
            this.entryId=relPath
        }
        // 获取模块字符串
        let source=fs.readFileSync(absPath,'utf-8')
        let {sourceCode,dependcies}=this.parse(source,absPath)
        // 向this.modules添加模块代码
        relPath=relPath.replace(regex,'/')
        this.modules[relPath]=sourceCode
        // 递归子模块
        dependcies.forEach(dep=>{
            this.buildMoudle(dep,false)
        })
    }

    parse(source,absPath){
        const ast=babelParser.parse(source)
        const dependcies=[]
        traverse(ast,{
            CallExpression(obj){
                let node=obj.node
                if(node.callee.name==='require'){
                    let relPathForFather=node.arguments[0].value
                    let absDir=path.dirname(absPath)
                    let childAbsPath=path.resolve(absDir,relPathForFather)
                    dependcies.push(childAbsPath)
                }
            }
        })
        let sourceCode=generator(ast).code
        return {sourceCode,dependcies}
    }


    outputFile(){
        const {path:outputPath,filename}=this.config.output
        let outputFilename=path.join(outputPath,filename)
        let templateStr=fs.readFileSync(path.join(__dirname,"bundleTemplate.ejs"),'utf-8')
        let result=ejs.render(templateStr,{entryId:this.entryId,modules:this.modules})
        if(fs.existsSync(outputPath)===false){
            fs.mkdirSync(outputPath)
        }
        fs.writeFileSync(outputFilename,result)
    }
}


module.exports.Compiler=Compiler
//bundleTemplate.ejs
(function(){
function require(modulePath) {
module={
exports:{}
}
modules[modulePath](module, module.exports, require);
return module.exports;
}

var modules={
<%for(let key in modules){%>
["<%-key%>"](module,exports,require){
eval(`<%-modules[key]%>`)
},
<%}%>
}
var exports = require("<%=entryId%>");
 })();

配置文件

const path=require("path")

module.exports={
    mode:'development',
    entry:"./index.js",
    output:{
        path:path.resolve(__dirname,"dist"),
        filename:"bundle.js"
    }
}

被打包的文件

//index.js
const module1=require("./module/module1.js")
const module2=require("./module/module2.js")
console.log(module1,module2)
//module1.js
const age=18
const sex="男"
module.exports={
    age,
    sex
}
//module2.js
const name="小明"
const school="河南大学"
module.exports={
    name,
    school
}

初始目录结构

打包命令

PS C:\Users\Administrator\Desktop\手写webpack> node ./lib/pack.js

打包代码

(function(){
function require(modulePath) {
module={
exports:{}
}
modules[modulePath](module, module.exports, require);
return module.exports;
}

var modules={

["./index.js"](module,exports,require){
eval(`const module1 = require("./module/module1.js");
const module2 = require("./module/module2.js");
console.log(module1, module2);`)
},

["./module/module1.js"](module,exports,require){
eval(`const age = 18;
const sex = "男";
module.exports = {
  age,
  sex
};`)
},

["./module/module2.js"](module,exports,require){
eval(`const name = "小明";
const school = "河南大学";
module.exports = {
  name,
  school
};`)
},

}
var exports = require("./index.js");
 })();

打包后的目录结构

生成dist/bundle.js

迷你版二——添加loader

代码

//pack.js
let path=require('path')
let processPath=process.cwd()
let config=require(path.resolve(processPath,'webpack.config.js'))
let Compiler=require('./compiler').Compiler
let compiler=new Compiler(config)
compiler.run()
//compiler.js
const fs= require('fs')
const path=require('path')
const babelParser=require('@bable/parser')
const traverse=require('@babel/traverse').default
const generator=require('@babel/generator').default
const ejs=require('ejs')
class Compiler{
    constructor(config){
        this.config=config
        this.entry=config.entry
        this.entryId
        this.root=process.cwd()
        this.modules={}
    }
    run(){
        const entryPath=path.resolve(this.root,this.entry)
        this.buildMoudle(entryPath,true)
        this.outputFile()
    }
    getSource(absPath){
        // return fs.readFileSync(absPath,'utf-8')
        const rules=this.config.module.rules
        let source=fs.readFileSync(absPath,'utf-8')
        for(let i=0;i<rules.length;i++){
            let rule=rules[i]
            let {test,use}=rule
            if(test.test(absPath)){
                for(let j=use.length-1;j>=0;j--){
                    let loader=require(use[j])
                    source=loader(source)
                }
            }
        }
        return source
    }
    buildMoudle(absPath,isEntry){
        const regex=/\\/g
        //获取路径
        let relPath='./'+path.relative(this.root,absPath)
        if(isEntry){
            this.entryId=relPath
        }
        // 获取模块字符串
        let source=this.getSource(absPath)
        let {sourceCode,dependcies}=this.parse(source,absPath)
        // 向this.modules添加模块代码
        relPath=relPath.replace(regex,'/')
        this.modules[relPath]=sourceCode
        // 递归子模块
        dependcies.forEach(dep=>{
            this.buildMoudle(dep,false)
        })
    }
    parse(source,absPath){
        const ast=babelParser.parse(source)
        const dependcies=[]
        traverse(ast,{
            CallExpression(obj){
                let node=obj.node
                if(node.callee.name==='require'){
                    let relPathForFather=node.arguments[0].value
                    let absDir=path.dirname(absPath)
                    let childAbsPath=path.resolve(absDir,relPathForFather)
                    dependcies.push(childAbsPath)
                }
            }
        })
        let sourceCode=generator(ast).code
        return {sourceCode,dependcies}
    }
    outputFile(){
        const {path:outputPath,filename}=this.config.output
        let outputFilename=path.join(outputPath,filename)
        let templateStr=this.getSource(path.join(__dirname,"bundleTemplate.ejs"))
        let result=ejs.render(templateStr,{entryId:this.entryId,modules:this.modules})
        if(fs.existsSync(outputPath)===false){
            fs.mkdirSync(outputPath)
        }
        fs.writeFileSync(outputFilename,result)
    }
}
module.exports.Compiler=Compiler
//bundleTemplate.ejs
(function(){
function require(modulePath) {
module={
exports:{}
}
modules[modulePath](module, module.exports, require);
return module.exports;
}

var modules={
<%for(let key in modules){%>
["<%-key%>"](module,exports,require){
eval(`<%-modules[key]%>`)
},
<%}%>
}
var exports = require("<%=entryId%>");
 })();
//less-loader.js
const less=require('less')
const fs=require('fs')

function loader(sourceLess){
    let css
    less.render(sourceLess,(err,res)=>{
        css=res.css
        css=css.replace(/\n/g,'\\n')
    })
    return css
}
module.exports=loader
//style-loader.js
function loader(sourceCss){
    let style=`
        let style=document.createElement('style')
        style.innerHTML=${JSON.stringify(sourceCss)}
        document.head.appendChild(style)
    `
    return style
}

module.exports=loader

配置文件

//webpack.config.js
const path=require("path")

module.exports={
    mode:'development',
    entry:"./index.js",
    output:{
        path:path.resolve(__dirname,"dist"),
        filename:"bundle.js"
    },
    module:{
        rules:[
            {
                test:/\.less/,
                use:[
                    path.resolve(__dirname,'loader','style-loader'),
                    path.resolve(__dirname,'loader','less-loader')
                ]
                    
            }
        ]
    }
}

被打包的文件

//index.js
const module1=require("./module/module1.js")
const module2=require("./module/module2.js")
require('./style/reset.less')
console.log(module1,module2)
//module1.js
const age=18
const sex="男"
module.exports={
    age,
    sex
}
//module2.js
const name="小明"
const school="河南大学"
module.exports={
    name,
    school
}
//reset.less
.main{
    width: 100px;
    height: 100px;
    background-color: yellowgreen;
    .content{
        width: 50px;
        height: 50px;
        background-color: skyblue;
    }
}

初始目录结构

打包命令

PS C:\Users\Administrator\Desktop\手写webpack> node .\lib\pack.js

打包代码

//bundle.js
(function(){
function require(modulePath) {
module={
exports:{}
}
modules[modulePath](module, module.exports, require);
return module.exports;
}

var modules={

["./index.js"](module,exports,require){
eval(`const module1 = require("./module/module1.js");
const module2 = require("./module/module2.js");
require('./style/reset.less');
console.log(module1, module2);`)
},

["./module/module1.js"](module,exports,require){
eval(`const age = 18;
const sex = "男";
module.exports = {
  age,
  sex
};`)
},

["./module/module2.js"](module,exports,require){
eval(`const name = "小明";
const school = "河南大学";
module.exports = {
  name,
  school
};`)
},

["./style/reset.less"](module,exports,require){
eval(`let style = document.createElement('style');
style.innerHTML = ".main {\\n  width: 100px;\\n  height: 100px;\\n  background-color: yellowgreen;\\n}\\n.main .content {\\n  width: 50px;\\n  height: 50px;\\n  background-color: skyblue;\\n}\\n";
document.head.appendChild(style);`)
},

}
var exports = require("./index.js");
 })();

改变后的目录结构

生成了dist/bundle.js

运行代码

//index.html

<body>
    <div class="main">
        <div class="content"></div>
    </div>
    <script src="./dist/bundle.js"></script>
</body>

页面效果

迷你版三——添加plugins

代码

//pack.js
let path=require('path')
let processPath=process.cwd()
let config=require(path.resolve(processPath,'webpack.config.js'))
let Compiler=require('./compiler').Compiler
let compiler=new Compiler(config)
compiler.run()
//compiler.js
const fs= require('fs')
const path=require('path')
const babelParser=require('@babel/parser')
const traverse=require('@babel/traverse').default
const generator=require('@babel/generator').default
const ejs=require('ejs')
const {SyncHook}=require('tapable')
class Compiler{
    constructor(config){
        this.config=config
        this.entry=config.entry
        this.entryId
        this.root=process.cwd()
        this.modules={}
        this.hooks={
            beforeBuildModule:new SyncHook(),
            afterBuildModule:new SyncHook(),
            beforeOutputFile:new SyncHook(),
            afterOutputFile:new SyncHook()
        }
        const plugins=config.plugins
        plugins.forEach(plugin=>{
            plugin.subscribe(this)
        })
    }
    run(){
        const entryPath=path.resolve(this.root,this.entry)

        this.hooks.beforeBuildModule.call()
        this.buildMoudle(entryPath,true)
        this.hooks.afterBuildModule.call()

        this.hooks.beforeOutputFile.call()
        this.outputFile()
        this.hooks.afterOutputFile.call()
    }
    getSource(absPath){
        // return fs.readFileSync(absPath,'utf-8')
        const rules=this.config.module.rules
        let source=fs.readFileSync(absPath,'utf-8')
        for(let i=0;i<rules.length;i++){
            let rule=rules[i]
            let {test,use}=rule
            if(test.test(absPath)){
                for(let j=use.length-1;j>=0;j--){
                    let loader=require(use[j])
                    source=loader(source)
                }
            }
        }
        return source
    }
    buildMoudle(absPath,isEntry){
        const regex=/\\/g
        //获取路径
        let relPath='./'+path.relative(this.root,absPath)
        if(isEntry){
            this.entryId=relPath
        }
        // 获取模块字符串
        let source=this.getSource(absPath)
        let {sourceCode,dependcies}=this.parse(source,absPath)
        // 向this.modules添加模块代码
        relPath=relPath.replace(regex,'/')
        this.modules[relPath]=sourceCode
        // 递归子模块
        dependcies.forEach(dep=>{
            this.buildMoudle(dep,false)
        })
    }
    parse(source,absPath){
        const ast=babelParser.parse(source)
        const dependcies=[]
        traverse(ast,{
            CallExpression(obj){
                let node=obj.node
                if(node.callee.name==='require'){
                    let relPathForFather=node.arguments[0].value
                    let absDir=path.dirname(absPath)
                    let childAbsPath=path.resolve(absDir,relPathForFather)
                    dependcies.push(childAbsPath)
                }
            }
        })
        let sourceCode=generator(ast).code
        return {sourceCode,dependcies}
    }
    outputFile(){
        const {path:outputPath,filename}=this.config.output
        let outputFilename=path.join(outputPath,filename)
        let templateStr=this.getSource(path.join(__dirname,"bundleTemplate.ejs"))
        let result=ejs.render(templateStr,{entryId:this.entryId,modules:this.modules})
        if(fs.existsSync(outputPath)===false){
            fs.mkdirSync(outputPath)
        }
        fs.writeFileSync(outputFilename,result)
    }
}
module.exports.Compiler=Compiler
//bundleTemplate.ejs
(function(){
function require(modulePath) {
module={
exports:{}
}
modules[modulePath](module, module.exports, require);
return module.exports;
}

var modules={
<%for(let key in modules){%>
["<%-key%>"](module,exports,require){
eval(`<%-modules[key]%>`)
},
<%}%>
}
var exports = require("<%=entryId%>");
 })();
//less-loader.js
const less=require('less')
const fs=require('fs')

function loader(sourceLess){
    let css
    less.render(sourceLess,(err,res)=>{
        css=res.css
        css=css.replace(/\n/g,'\\n')
    })
    return css
}
module.exports=loader
//style-loader.js
function loader(sourceCss){
    let style=`
        let style=document.createElement('style')
        style.innerHTML=${JSON.stringify(sourceCss)}
        document.head.appendChild(style)
    `
    return style
}

module.exports=loader
//myPlugin.js
class MyPlugin{
    subscribe(compiler){
        compiler.hooks.beforeBuildModule.tap('emit',()=>{
            console.log('构建模块之前')
        })
        compiler.hooks.afterBuildModule.tap('emit',()=>{
            console.log('构建模块之后')
        })
        compiler.hooks.beforeOutputFile.tap('emit',()=>{
            console.log('输出模块之前')
        })
        compiler.hooks.afterOutputFile.tap('emit',()=>{
            console.log('输出模块之后')
        })
    }
}

module.exports=MyPlugin

配置文件

//webpack.config.js
const path=require("path")
const MyPlugin=require('./plugins/myPlugin.js')

module.exports={
    mode:'development',
    entry:"./index.js",
    output:{
        path:path.resolve(__dirname,"dist"),
        filename:"bundle.js"
    },
    module:{
        rules:[
            {
                test:/\.less/,
                use:[
                    path.resolve(__dirname,'loader','style-loader'),
                    path.resolve(__dirname,'loader','less-loader')
                ]
                    
            }
        ]
    },
    plugins:[
        new MyPlugin()
    ]
}

被打包的文件

//index.js
const module1=require("./module/module1.js")
const module2=require("./module/module2.js")
require('./style/reset.less')
console.log(module1,module2)
//module1.js
const age=18
const sex="男"
module.exports={
    age,
    sex
}
//module2.js
const name="小明"
const school="河南大学"
module.exports={
    name,
    school
}
//reset.less
.main{
    width: 100px;
    height: 100px;
    background-color: yellowgreen;
    .content{
        width: 50px;
        height: 50px;
        background-color: skyblue;
    }
}

目录结构

打包命令

PS C:\Users\Administrator\Desktop\手写webpack> node .\lib\pack.js
构建模块之前
构建模块之后
输出模块之前
输出模块之后

打包代码

//bundle.js
(function(){
function require(modulePath) {
module={
exports:{}
}
modules[modulePath](module, module.exports, require);
return module.exports;
}

var modules={

["./index.js"](module,exports,require){
eval(`const module1 = require("./module/module1.js");
const module2 = require("./module/module2.js");
require('./style/reset.less');
console.log(module1, module2);`)
},

["./module/module1.js"](module,exports,require){
eval(`const age = 18;
const sex = "男";
module.exports = {
  age,
  sex
};`)
},

["./module/module2.js"](module,exports,require){
eval(`const name = "小明";
const school = "河南大学";
module.exports = {
  name,
  school
};`)
},

["./style/reset.less"](module,exports,require){
eval(`let style = document.createElement('style');
style.innerHTML = ".main {\\n  width: 100px;\\n  height: 100px;\\n  background-color: yellowgreen;\\n}\\n.main .content {\\n  width: 50px;\\n  height: 50px;\\n  background-color: skyblue;\\n}\\n";
document.head.appendChild(style);`)
},

}
var exports = require("./index.js");
 })();

打包后目录结构

 运行代码

//index.html

<body>
    <div class="main">
        <div class="content"></div>
    </div>
    <script src="./dist/bundle.js"></script>
</body>

页面效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日常充电

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值