loader本质上就是一个函数
使用自己本地的loader
jsuse:[
{loader:path.resolve(__dirname,'./loader/a')},
{loader:path.resolve(__dirname,'./loader/b'),}
]
使用resolveLoader参数(webpack配置项):
jsresolveLoader: {
modules: ['node_modules','./loader']
}
use:[
{loader:'a'},
{loader:'b'}
]
jsconst loaderUtils=require('loader-utils')
module.exports=function (source) { // option参数获取
const options=loaderUtils.getOptions(this)
}
module.exports=function (source) { // callback,不需要手动return
const result=source.replace(/1/g,'2').replace(/console.log\(.*\)/g,'')
this.callback(null,result)
}
module.exports=function (source) { // 提前执行async,告知是异步返回
const result=source.replace(/1/g,'2').replace(/console.log\(.*\)/g,'')
const callback=this.async()
setTimeout(()=>{
callback(null,result)
},3000)
}
plugin本质上就是一个类
webpack plugin会自动使用apply函数,参数compiler下的hooks是plugin的各种生命周期。 tapAsync时,需要手动调用下callback,tap时不需要。constructor可以获取参数。
jsclass A {
constructor(options){
this.name=options.name
}
apply(compiler){
compiler.hooks.compile.tap('A',()=>{
console.log('每次编译都会被执行')
})
compiler.hooks.emit.tapAsync('A',(compilation,cb)=>{
compilation.assets[`${this.name}.txt`]={
source:function(){
return '4'
},
size(){
return 6
}
}
cb()
})
}
}
module.exports=A
readFileSync
fs.readFileSync 会以你当前npm的执行路径为当前路径,而不是文件路径 readFile必须填写第二个参数(callback),不填写则报错
@babel/parser
content为文本,可以将文本输出为AST语法解析树
jsconst parser=require('@babel/parser')
const AST=parser.parse(content,{
sourceType:'module'
})
@babel/traverse
根据你提供的语法解析树,筛选出你需要的树
jsconst traverse=require('@babel/traverse').default
traverse(AST,{
ImportDeclaration({node}){
console.log(node)
}
})
@babel/core
将语法树转换为es5代码
jsconst babel=require('@babel/core')
const {code}=babel.transformFromAst(AST,null,{
presets:["@babel/preset-env"]
})
webpack bundle实现
jsconst fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const core = require('@babel/core')
const moduleSync = function (entry) { //给定入口文件,输出文件解析后的代码以及相关依赖
const content = fs.readFileSync(entry, 'utf-8') //读取文件的JS内容
const AST = parser.parse(content, { //将JS内容导出为AST抽象语法树
sourceType: 'unambiguous'
})
const dependencies = {}
traverse(AST, { //从AST抽象语法树中筛选出import相关内容
ImportDeclaration({node}) {
const dirname = path.dirname(entry) //获取当前JS的所在文件夹(相当于当前node执行路径)
const file = path.join(dirname, node.source.value) //关联资源的路径(相当于当前node执行路径)
dependencies[node.source.value] = file
}
})
const {code} = core.transformFromAst(AST, null, {
presets: ["@babel/preset-env"]
})
return {
filename: entry,
dependencies,
code
}
}
const transformGraph = function (moduleArr) { //将数组格式转换为对象格式,后续方便会用
let graph = {}
moduleArr.forEach(item => {
const key = item.filename
graph[key] = {
dependencies: item.dependencies,
code: item.code
}
})
return graph
}
const makeDependGraph = function (entry) { //生成入口文件和其所有的依赖项
let entryModule = moduleSync(entry)
let moduleArr = [entryModule]
for (let i = 0; i < moduleArr.length; i++) { //for循环每次增加后,都可以在进行一次,而forEach不行
const dependencies = moduleArr[i].dependencies
for (let j in dependencies) { //对依赖项进行遍历,如果存在,则再次对其进行统计其依赖项
moduleArr.push(moduleSync(dependencies[j]))
}
}
return transformGraph(moduleArr)
}
const generateCode=function(entry){ //将对象转换为code代码
let graph=JSON.stringify(makeDependGraph(entry))
return `(function(graph){
function require(entry){
function localRequire(relativePath){
return require(graph[entry].dependencies[relativePath])
}
var exports={};
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[entry].code)
}
require('${entry}')
})(${graph})`
}
let result = generateCode('src/index.js')
fs.writeFileSync('./index.js',result)
本文作者:BARM
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!