webpack打包的简单实现

204 阅读3分钟

webpack打包

webpack打包主要进行两个操作通过通过获取入口文件的内容,获取依赖关系

入口文件

  • 获取入口文件的内容
    • 借助fs文件模块获取某个文件的内容
  • 获取依赖关系
    • 借助@babel/parser 生成ast树

    • @babel/traverse

//index.js
import fs from 'fs',   // 通过配置package.json  type:'module'  支持esm模块

import parser from '@babel/parser'

import traverse from '@babel/traverse'

import { transformFromAst } from 'babel-core'
function createAsset(filePath){
    // 1.获取文件内容
  // const source =  fs.readFileSync("./example/main.js",{
   const source =  fs.readFileSync(filePath,{
   
       encoding:'utf-8'
   })
   console.log(source)
   
   //2.获取依赖关系
   
   cosnt ast  = parser.parse(source,{
       sourceType = "module"
   })  // 将文件内容转换成 生成ast树    
       
     cosnt deps = [] // 存储依赖关系数组
     traverse.default(ast,{
       // 访问到'import'相关内容  机会调用这个函数
          ImpostDeclaration({node}){
                console.log(node.source.value)  //  foo.js   获取到依赖关系 
                
                  deps.push(node.source.value)
         }
     })
     
     // esm --> cjs
    const { code } =  transformFromAst(ast,null,{
        presets:["env"],   //需要安装babel-preset-env
    })
     
    return {
        filePath,
        
        //source,  //当前文件的代码 字符串
        code  // 当前文件代码  cjs引入
        deps    // 当前文件所依赖的文件名
    }    
}    


const asset = createAsset9()
console.log(asset)

获取到文件内容和依赖关系之后,合成一个图对象

合成一个图对象

// index.js

import path from 'path'
function createGraph(){
    const mainAsset = createAsset("./example/main,.js")
    
    //基于依赖关系找到下一个文件的内容和依赖关系
    
    const queue = [mainAsset] 
    for( const asset of queue){
            asset.deps.forEach((relativePath)=>{
            
                console.log(relativePath)   //./foo.js
                
                const filePath = path.resolve("./example",relativePath)
                    
                const childAsset = createAsset(filePath)
                
                console.log(children) // 文件内容,deps
                queue.push(child)
                
            })
    }
    return queue
}

打包后的bundle.js

function mainjs(){
    //main.js
    //import foo from './foo.js'   //esm模块规范,import只能用于顶层作用域    
    //  esm  => cjs
    
    const foo = require("./foo.js")
    foo()
    console.log('main.js')
}  

function foojs(){
    //foo.js
    //export function foo(){
    function foo(){
        console.log('foo')
    }    
    module.exports = {
        foo
    }    
} 
    
//bundle.js

function require(filePath){
    const map = {
        "./foo.js":foojs,
        "./main.js":mainjs
     }
  const fn = map[filePath]
  const module = {
      exports :{}
   }   
  fn(require, module,module.exports)
  
  return module.exports;
} 

requier("./main.js")

function mainjs(require,module,exports){
    const { foo } = require("./foo.js")
    foo()
    console.log('main.js')
}  
function foojs(require,module,exports){
    //foo.js
    //export function foo(){
    function foo(){
        console.log('foo')
    }    
    module.exports = {
        foo
    }    
} 

//重构

(function(modules){
    function require(filePath){
        
      const fn = map[filePath]
      const module = {
          exports :{}
       }   
      fn(require, module,module.exports)
  
      return module.exports;
    } 

    requier("./main.js")

} 


})({
    "./foo.js": function (require,module,exports){
                    const { foo } = require("./foo.js")
                    foo()
                    console.log('main.js')
                }  
    "./main.js": function (require,module,exports){
        //foo.js
        //export function foo(){
                    function foo(){
                        console.log('foo')
                    }    
                    module.exports = {
                        foo
                    }    
})

需要改动的就是这个参数了

如何生成bundle.js

利用bundle.ejs模板

//index.js


/* 之前代码*/
funcuon build(graph){
    const template = fs.readFileSync("bundle.ejs",{
        encode:"utf-8"
    })
    const data = graph.map((asset)=>{
        return {
            filePath:asset.filePath,
            code: asset.code
    })
    const code  = ejs.render(template,{data})
    console.log(code)
    
    
    //创建文件
    fs.writeFileSync("./dist/bundle.js",code)
    
}

build(graph)

引入的命名重复的问题

image.png

index.js中

image.png main.js中

image.png

都是引用的相对路径./foo.js,此时就会出现问题

解决方法:使用唯一模块id

//bundle.js
(function(modules){
    function require(id){
        
      const [fn,mapping] = map[filePath]
      const module = {
          exports :{}
       }   
      function localRequire(filePath){
          const id = mapping[filePath]
          return require(id)
       }
      fn(localRequire,module,module.exports)
      
      return module.exports;
    } 

    requier(1)

} 


})({
    2: [function (require,module,exports){
                    const { foo } = require("./foo.js")
                    foo()
                    console.log('main.js')
                },{}  ]
    1:[ function (require,module,exports){
        //foo.js
        //export function foo(){
                    function foo(){
                        console.log('foo')
                    }    
                    module.exports = {
                        foo
                    } ,
                    {
                        "./foo.js":2
                    }
                 ]   
                    
})
//index.js
/*其他代码*/
import fs from "fs";
import path from "path";
import parser from "@babel/parser"; //生成ast
import traverse from "@babel/traverse";
import ejs from "ejs"; //ejs 模板生成器

import { transformFromAst } from "babel-core";
// console.log(traverse);

import { jsonLoader } from "./jsonLoader.js";

let id = 0;

//相当于webpack.config.js
const webpackConfig = {
    module: {
        rules: [{
            test: /\.json$/,
            use: jsonLoader,
        }, ],
    },
};

function createAsset(filePath) {
    //1、获取文件的内容
    // ast  -->  抽象语法树

    let source = fs.readFileSync(filePath, {
        //"./example/main.js",
        encoding: "utf-8", //未加第二个参数之前打印buffer  加厚发音的是文件内容
    });
    // console.log(source); //buffer

    //2.loader转换   init loader
    const loaders = webpackConfig.module.rules;
    const loaderContext = {
        addDeps(dep) {
            console.log("addDeps", dep);
        },
    };

    loaders.forEach(({ test, use }) => {
        if (test.test(filePath)) {
            if (Array.isArray(use)) {
                use.forEach((fn) => {
                    source = fn.call(loaderContext, source);
                });
            } else {
                source = use.call(loaderContext, source);
            }
        }
        console.log(test, use);
    });

    //3、获取依赖关系
    const ast = parser.parse(source, {
        sourceType: "module",
    }); //生产ast 抽象语法树

    // console.log("ast--------------------");
    // console.log(ast);

    //2、获取依赖关系   ---> import

    const deps = [];
    traverse.default(ast, {
        //获取import 转化ast
        ImportDeclaration({ node }) {
            // console.log("DATA-------------");
            // console.log(data); //输出数据
            // console.log("NODE-------------");
            // console.log(data.node); //输出数据
            // console.log(node.source);
            deps.push(node.source.value);
        },
    });

    //esm转换成cjs
    const { code } = transformFromAst(ast, null, {
        presets: ["env"],
    });
    // console.log(code);
    return {
        filePath,
        // source, //文件内容
        code, // esm转为cjs后的文件内容
        deps, //依赖关系
        mapping: {},
        id: id++,
    };
}

// const asset = createAsset();
// console.log(asset);

//用得到的内容和依赖关系   绘制成图
function createGraph() {
    const mainAsset = createAsset("./example/main.js");
    //遍历图的方式
    const queue = [mainAsset];

    for (const asset of queue) {
        asset.deps.forEach((relativePath) => {
            // console.log(relativePath);
            // console.log(path.resolve("./example", relativePath));
            const childAsset = createAsset(path.resolve("./example", relativePath)); //递归查询其他
            // console.log(childAsset);
            asset.mapping[relativePath] = childAsset.id;
            queue.push(childAsset);
        });
    }

    return queue;
}

const graph = createGraph();

function build(graph) {
    const template = fs.readFileSync("./bundle.ejs", {
        encoding: "utf-8",
    });

    const data = graph.map((asset) => {
        return {
            id: asset.id,
            code: asset.code,
            mapping: asset.mapping,
        };
    });

    const code = ejs.render(template, { data }); //根据模板生成代码
    fs.writeFileSync("./dist/bundle.js", code); // 生成文件
    // console.log(code);
}

console.log(graph);
build(graph);