1.创建一个项目文件夹,包含lib,dist,src三个文件
2.创建配置文件webpack.config.js,和bundle.js文件
3.lib里创建webpack.js文件,用来封装webpack类
4.src里创建一个index.js文件,作文打包的入口文件
##src--index.js
import a from "./a.js"
console.log("hello webpack");
##webpack的基础配置--webpack.config.js const path = require("path") module.exports = { mode:"development", entry:"./src/index.js", output:{ path:path.resolve(__dirname,"./dist"), filename:"main.js" } }
##bundle.js //打包入口 const options = require("./webpack.config.js") const webpack = require("./lib/webpack.js") new webpack(options).run()
##webpack.js
这里面要做的事儿:
1.首先接收一份配置(webpack.config.js)
2.分析出入口模块的位置
2.1读取入口模块的内容,分析哪些是依赖,哪些是源码
2.2处理需要编译的代码,使其变为浏览器能执行的代码(es6,jsx等的编译等)
2.3分析其他模块
3.将第二部处理出的内容变为对象数据结构
3.1包含模块路径
3.2包含处理好的内容
4.创建bundle.js:启动器函数,来补充代码里有可能出现的module exports require,让浏览器能够顺利的执行
首先分析入口文件,这里我们要安装几个插件:
安装babel/parser:npm i @babel/parser --save 分析文件中哪些是源码哪些是依赖
安装babel/traverse:npm i @babel/traverse --save 提取抽象语法树里的节点信息
安装babel/core babel/preset-env:npm i @babel/core @babel/preset-env --save 对源码js进行解析处理
babel/core:核心库
babel/preset-env:做代码转换,比如解析jsx,es6的Promise等等
##分析入口文件,解析依赖及源码,并进行编译 const fs = require("fs"); const path = require("path"); const parser = require("@babel/parser");//引入parser const traverse = require("@babel/traverse").default;//引入traverse const {transformFromAst} = require("@babel/core")//处理ast抽象语法树 module.exports = class webpack { constructor(options){ let {entry,output} = options //入口信息 this.entry = entry; //出口信息 this.output = output;
//缓存模块信息
this.module = [];
} run(){ //开始分析入口模块内容 this.parse(this.entry); } parse(entryFile){ //拿到文件内容 const content = fs.readFileSync(entryFile,"utf-8"); //分析-哪些是源码-哪些是依赖 我们借助babel插件:安装:npm i @babel/parser --save //console.log(content) //使用parser分析内容,返回ast 抽象语法树 const ast = parser.parse(content,{ sourceType:"module" }) //console.log(ast.program.body)通过上面的截图 我们可以看到打印的数据 //提取依赖路径信息 const dependencies = {} //key为相对于相对入口路径,val为填充后的路径 traverse(ast,{ ImportDeclaration({node}){ //node.source.value 是相对于入口的相对路径 //做路径填充 path.dirname(entryFile) const newPathName = "./" + path.join( path.dirname(entryFile), node.source.value ) dependencies[node.source.value] = newPathName } }) //代码编译 const {code} = transformFromAst(ast,null,{ //此api处理ast抽象语法树
presets:["@babel/preset-env"] }); //console.log(code) 解析后的代码依然不能再浏览器中执行 “require is not defined” 所以接下来我门要告诉浏览器 require是干嘛的 //信息汇总返回 return { entryFile, dependencies, code } } }
将上面得到得数据做一个递归,即对所有依赖文件做了分析,并将拿到得数组转换为对象数据结构
run(){ //开始分析入口模块内容 const info = this.parse(this.entry); console.log(info) //入口数据放入模块 this.module.push(info); //做递归,拿到所有得地址依赖做parse() for (let i = 0; i < this.module.length; i++) { const item = this.module[i]; const { dependencies } = item; if (dependencies) { for (let j in dependencies) { this.module.push(this.parse(dependencies[j])); } } //console.log(this.module) } //数据结构转换-数组转换成对象 const obj = {}; this.module.forEach(item => { obj[item.entryFile] = { dependencies:item.dependencies, code:item.code } }) }
生成bundle
file(code) { //生成bundle.js => dist/main.js const filePath = path.join(this.output.path,this.output.filename); const newCode = JSON.stringify(code); const bundle = `(function(graph){ function require(module){
//拿到绝对路径 function localRequire(relativePath){ return require(graph[module].dependencies[relativePath]) } var exports = {}; (function(require,exports,code){ eval(code) })(localRequire,exports,graph[module].code) return exports } require('${this.entry}') })(${newCode})` //写文件操作 fs.writeFileSync(filePath,bundle,"utf-8") }
然后执行 node bundle.js 转义后得代码 放到浏览器里可执行即正确
----------------
完整代码
const fs = require("fs"); const path = require("path"); const parser = require("@babel/parser");//引入parser const traverse = require("@babel/traverse").default;//引入traverse const {transformFromAst} = require("@babel/core")//处理ast抽象语法树 module.exports = class webpack { constructor(options){ let {entry,output} = options //入口信息 this.entry = entry; //出口信息 this.output = output; //缓存模块信息 this.module = []; } run(){ //开始分析入口模块内容 const info = this.parse(this.entry); console.log(info) //入口数据放入模块 this.module.push(info); //做递归,拿到所有得地址依赖做parse() for (let i = 0; i < this.module.length; i++) { const item = this.module[i]; const { dependencies } = item; if (dependencies) { for (let j in dependencies) { this.module.push(this.parse(dependencies[j])); } } //console.log(this.module) } //数据结构转换-数组转换成对象 const obj = {}; this.module.forEach(item => { obj[item.entryFile] = { dependencies:item.dependencies, code:item.code } }) //生成文件 this.file(obj) } parse(entryFile){ //拿到文件内容 const content = fs.readFileSync(entryFile,"utf-8"); //分析-哪些是源码-哪些是依赖 我们借助babel插件:安装:npm i @babel/parser --save //console.log(content) //使用parser分析内容,返回ast 抽象语法树 const ast = parser.parse(content,{ sourceType:"module" }) //console.log(ast.program.body) //提取依赖路径信息 const dependencies = {} //key为相对于相对入口路径,val为填充后的路径 traverse(ast,{ ImportDeclaration({node}){ //node.source.value 是相对于入口的相对路径 //做路径填充 path.dirname(entryFile) const newPathName = "./" + path.join( path.dirname(entryFile), node.source.value ) dependencies[node.source.value] = newPathName } }) //代码编译 const {code} = transformFromAst(ast,null,{ //此api处理ast抽象语法树 presets:["@babel/preset-env"] }); //console.log(code) //信息汇总返回 return { entryFile, dependencies, code } } file(code) { //生成bundle.js => dist/main.js const filePath = path.join(this.output.path,this.output.filename); const newCode = JSON.stringify(code); const bundle = `(function(graph){ function require(module){ function localRequire(relativePath){ return require(graph[module].dependencies[relativePath]) } var exports = {}; (function(require,exports,code){ eval(code) })(localRequire,exports,graph[module].code) return exports } require('${this.entry}') })(${newCode})` //写文件操作 fs.writeFileSync(filePath,bundle,"utf-8") } }