webpack——手写迷你版

迷你版一

代码

myWebpack打包工具文件

//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 babylon=require('babylon')
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=babylon.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%>");
 })();

注意点——斜杠与反斜杠

①window系统中,路径划分使用反斜杠\,

②javaScript、java、linux中,路径划分使用斜杠/,转义使用反斜杠\。

③有些时候会因为上面的差别而报错,需要自行处理,比如compiler.js的bulidModule就进行了相关处理。

myWebpack配置文件

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
}

目录结构

webpack——手写迷你版_第1张图片

打包结果

打包命令

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

注意路径问题

node命令执行的目录就是webpack.config.js所在的目录。

原因

pack.js中,process.cwd()返回值是运行node命令所在的directory目录,而webpack.config.js就是在directory目录下寻找。

打包代码

(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

webpack——手写迷你版_第2张图片

迷你版二——添加loader

代码

myWebpack打包工具文件

//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 babylon=require('babylon')
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=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=babylon.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

myWebpack配置文件

//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;
    }
}

目录结构

webpack——手写迷你版_第3张图片

打包结果 

打包命令

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

webpack——手写迷你版_第4张图片

运行结果

index页面代码

//index.html



    
    
    
    Document


    

页面效果

webpack——手写迷你版_第5张图片

 

迷你版三——添加plugins

代码

mywebpack打包工具文件

//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 babylon=require('babylon')
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=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=babylon.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

myWebpack配置文件

//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;
    }
}

目录结构

webpack——手写迷你版_第6张图片

打包结果

打包命令

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");
 })();

打包后目录结构

webpack——手写迷你版_第7张图片

 

 运行结果

index页面代码

//index.html



    
    
    
    Document


    

页面效果

webpack——手写迷你版_第8张图片

你可能感兴趣的:(webpack,webpack,javascript,前端)