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");
})();
分析
- webpack实现了自己的require()函数
- webpack采用了立即执行函数
- webpack把各个模块的代码放到modules中
- 各模块代码字符串形式存储,使用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>
页面效果

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

1065

被折叠的 条评论
为什么被折叠?



