//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就进行了相关处理。
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
注意路径问题
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
//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
//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
Document
//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
//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
Document