webpack 是目前前端主流的打包工具之一,但对于刚入门的同学来说,webpack 其实还是有点复杂的。可能大部分只会使用 creact-react-app 这种一键式脚手架对项目进行构建(包括我)。那么 webpack 的基础概念到底是什么呢?那么今天就来用 js 手写一个基础版的 webpack。
webpack 简单原理
- 把一堆 js 文件中的 js 进行打包处理,将其合并为一个或者几个浏览器能够识别的文件,然后将其放入到 html 中,从而展示在浏览器中。最直接的体现就是将 ES6 的 js 代码打包成 ES5 的代码,让浏览器进行编译。
使用 ES5 的语法进行打包
- 项目结构
- index.js
let first = require('./first.js').first;
let second = require('./second.js').second;
let message = `${name} is ${action}`;
console.log( message );
复制代码
- first.js
let first = 'Hello';
exports.first = first;
复制代码
- second.js
let third = require('./third').third;
exports.second = `Let's ${third}`;
复制代码
- third.js
exports.third = 'making webpack';
复制代码
此时运行 node index.js
,打印出的结果是:
那么,这就是 webpack 最最基本的实现形式,把所有的模块化代码打包成一个文件,并运行它。但是,要注意:浏览器并不认识 exports、require 代码,它们只能在 node 环境下使用。因此,我们需要手动设定 require 函数和 exports 对象
- 把所有文件的代码都设定成函数
//index.js
function(require, exports) {
let first = require('./first.js').first;
let second = require('./second.js').second;
let message = `${first}, ${second}`;
console.log( message );
}
//first.js
function(require, exports) {
let first = 'Hello';
exports.first = first;
}
//second.js
function(require, exports) {
let third = require('./third.js').third;
exports.second = `Let's ${third}`;
}
//third.js
function(require, exports) {
exports.third = 'making webpack';
}
复制代码
- 期望目标
通过运行一个函数,对这些代码进行处理,最终生成一个 module 对象
modules = {
//fn
0: [function(require, exports) {
let first = require('./first.js').first;
let second = require('./second.js').second;
let message = `${first}, ${second}`;
console.log( message );
},
//mapping
{
'./first.js': 1,
'./second.js': 2
}
],
//fn
1: [function(require, exports) {
let first = 'Hello';
exports.first = first;
},
//mapping
{}
],
//fn
2: [function(require, exports) {
let third = require('./third').third;
exports.second = `Let's ${third}`;
},
//mapping
{
'./third.js': 3
}
],
//fn
3: [function(require, exports) {
exports.third = 'making webpack';
},
//mapping
{}
]
}
复制代码
通过一个函数,来对其进行调用
function package(id) {
let [fn, mapping] = modules[id];
let exports = {};
function require(path) {
return exec(mapping[path]);
}
fn && fn(require, exports);
return exports;
}
package(0)
复制代码
最后将其写入到 dist 目录下的 bundle.js 文件中
- 如何来生成 module 对象?
- 首先需要查看单个文件中,require 了哪些文件
function getDependencies(fileContent) {
let reg = /require\(['"](.+?)['"]\)/g;
let result = null;
let dependencies = [];
while(result = reg.exec(fileContent)) {
dependencies.push(result[1]);
}
console.log('dependencies', dependencies)
return dependencies;
}
//示例
let fileContent = fs.readFileSync('./index.js', 'utf-8');
getDependencies(fileContent)
复制代码
通过正则对内容进行判断,符合规则的,就将其放入 dependencies
数组中,示例返回的结果是:
dependencies [ './first.js', './second.js' ]
复制代码
- 接下来,需要一个函数,根据 filename 生成一个包含
id
、filename
、dependencies
、code
,的一个对象
let ID = 0;
function createObject(filename) {
let fileContent = fs.readFileSync(filename, 'utf-8');
let id = ID++;
const item = {
id: id,
filename: filename,
dependencies: getDependencies(fileContent),
code: `function(require, exports, module) {
${fileContent}
}`
}
console.log('item', item)
return item
}
//示例
createObject(./index.js)
复制代码
结果:
item {
id: 0,
filename: './src/index.js',
dependencies: [ './first.js', './second.js' ],
code: 'function(require, exports, module) { \n' +
" let first = require('./first.js').first;\r\n" +
"let second = require('./second.js').second;\r\n" +
'\r\n' +
'let message = `${first}, ${second}`;\r\n' +
'console.log( message );\r\n' +
'\n' +
' }'
}
复制代码
- 根据
dependencies
对index.js
中所有require
的文件进行相同的createObject
操作
function createQueue(filename) {
let item = createObject(filename);
let queue = [item];
for(let item of queue) {
const dirname = path.dirname(item.filename);
item.mapping = {};
item.dependencies.forEach(relativePath => {
//获取文件的绝对路径
const absolutePath = path.join(dirname, relativePath);
//根据 absolutePath,调用 createObject 方法
const child = createObject(absolutePath);
item.mapping[relativePath] = child.id;
queue.push(child);
});
}
console.log('queue', queue)
return queue;
}
//示例
queue [
{
id: 0,
filename: './src/index.js',
dependencies: [ './first.js', './second.js' ],
code: 'function(require, exports, module) { \n' +
" let first = require('./first.js').first;\r\n" +
"let second = require('./second.js').second;\r\n" +
'\r\n' +
'let message = `${first}, ${second}`;\r\n' +
'console.log( message );\r\n' +
'\n' +
' }',
mapping: { './first.js': 1, './second.js': 2 }
},
{
id: 1,
filename: 'src\\first.js',
dependencies: [],
code: 'function(require, exports, module) { \n' +
" let first = 'Hello';\r\n" +
'\r\n' +
'exports.first = first;\r\n' +
'\n' +
' }',
mapping: {}
},
{
id: 2,
filename: 'src\\second.js',
dependencies: [ './third.js' ],
code: 'function(require, exports, module) { \n' +
" let third = require('./third.js').third;\r\n" +
'\r\n' +
"exports.second = `Let's ${third}`;\r\n" +
'\n' +
' }',
mapping: { './third.js': 3 }
},
{
id: 3,
filename: 'src\\third.js',
dependencies: [],
code: 'function(require, exports, module) { \n' +
" exports.third = 'making webpack';\r\n" +
'\r\n' +
'\n' +
' }',
mapping: {}
}
]
复制代码
- 根据 queue 生成 module 对象为内容的字符串,并将其写入 bundle.js 中
function createBundle(graph) {
let modules = ''
graph.forEach(mod => {
modules += `${mod.id}: [
${mod.code},
${JSON.stringify(mod.mapping)}
],`
})
const result = `
const modules = {${modules}}
function fetch(data){
function exec(id) {
let [fn, mapping] = modules[id];
let module = { exports: {} };
fn && fn(require, module.exports, module);
function require(path) {
return exec(mapping[path]);
}
return module.exports;
}
exec(0)
}
fetch(modules)
`
fs.writeFileSync('../dist/bundle.js', result)
}
复制代码
- 全部代码
const fs = require('fs')
const path = require('path')
let ID = 0
let fileContent = fs.readFileSync('./src/index.js', 'utf-8')
function getDependencies(str) {
let reg = /require\(['"](.+?)['"]\)/g
let result = null
let dependencies = []
while (result = reg.exec(str)) {
dependencies.push(result[1])
}
return dependencies
}
function createObject(filename) {
console.log(filename)
let fileContent = fs.readFileSync(filename, 'utf-8')
console.log(11111111111)
const id = ID++
const item = {
id: id,
filename: filename,
dependencies: getDependencies(fileContent),
code: `function(require, exports, module) {
${fileContent}
}`
}
return item
}
function createQueue(filename) {
let item = createObject(filename)
let queue = [item]
console.log(queue)
for (let item of queue) {
const dirname = path.dirname(item.filename)
item.mapping = {}
item.dependencies.forEach(relativePath => {
//获取文件的绝对路径
console.log(1)
const absolutePath = path.join(dirname, relativePath)
console.log(2)
//根据 absolutePath,调用 createObject 方法
const child = createObject(absolutePath)
console.log(3)
item.mapping[relativePath] = child.id
console.log(4)
queue.push(child)
})
}
return queue
}
function createBundle(graph) {
let modules = ''
graph.forEach(mod => {
modules += `${mod.id}: [
${mod.code},
${JSON.stringify(mod.mapping)}
],`
})
const result = `
const modules = {${modules}}
function fetch(data){
function exec(id) {
let [fn, mapping] = modules[id];
let module = { exports: {} };
fn && fn(require, module.exports, module);
function require(path) {
return exec(mapping[path]);
}
return module.exports;
}
exec(0)
}
fetch(modules)
`
fs.writeFileSync('../dist/bundle.js', result)
}// let ccc = fs.readFileSync('/src/third.js', 'utf-8');
// console.log('ccc', ccc)
let queue = createQueue('./src/index.js')
createBundle(queue)
复制代码
- 运行结果:在 dist/bundle.js 中生成代码
const modules = {0: [
function(require, exports, module) {
let first = require('./first.js').first;
let second = require('./second.js').second;
let message = `${first}, ${second}`;
console.log( message );
},
{"./first.js":1,"./second.js":2}
],1: [
function(require, exports, module) {
let first = 'Hello';
exports.first = first;
},
{}
],2: [
function(require, exports, module) {
let third = require('./third.js').third;
exports.second = `Let's ${third}`;
},
{"./third.js":3}
],3: [
function(require, exports, module) {
exports.third = 'making webpack';
},
{}
],}
function fetch(data){
function exec(id) {
let [fn, mapping] = modules[id];
let module = { exports: {} };
fn && fn(require, module.exports, module);
function require(path) {
return exec(mapping[path]);
}
return module.exports;
}
exec(0)
}
fetch(modules)
//运行结果
Hello, Let's making webpack
复制代码
那么,这就是最最简化版本的 webpack 所做的事情
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END