webpack構建原理(實現一個簡易的webpack構建器)

webpack構建原理(實現一個簡易的webpack構建器)

webpack的構建原理:

  • webpack的構建原理所在的核心文件:./lib/webpack-structure.js
  • webpack配置文件:./webpack.config.js
  • webpack的執行文件:./bundle.js
  • 源碼所在的文件:./src/index.js
  • 源碼依賴的文件:./src/expo.js

實現代碼:

  • 工程結構:
    在這裏插入圖片描述
  • webpack-structure.js
const fs = require("fs")
const parser = require("@babel/parser")
const traverse = require("@babel/traverse").default
const path = require("path")
const {transformFromAst} = require("@babel/core")

module.exports = class Webpack {
    constructor(options) {
        const {entry, output} = options;
        this.entry = entry;
        this.output = output;

        // 構建結果存儲
        this.modules = []
    }

    run() {
        // 1、處理入口文件
        const info = this.parse(this.entry)
        this.modules.push(info);

        ///2、處理入口文件的相關依賴
        for (let i = 0; i < this.modules.length; i++) {
            const item = this.modules[i]
            const {dependencies} = item
            if (dependencies) {
                for (let key in dependencies) {
                    this.modules.push(this.parse(dependencies[key]))
                }
            }
        }

        ///3、結果數組結構轉換爲對象
        const obj = {}
        this.modules.forEach(item => {
            obj[item.entryFile] = {
                dependencies: item.dependencies,
                code: item.code
            }
        })

        // console.log("webpack構建後的文件輸出:\n", obj);

        ///4、生成瀏覽器可執行的代碼字符串
        this.generateFile(obj)

    }
	/**
     * 解析入口文件以及相關依賴模塊
     * @param entryFile
     * @return {{code: *, entryFile: *, dependencies: {}}}
     */
    parse(entryFile) {

        // 拿到入口文件的內容
        const content = fs.readFileSync(entryFile, "utf-8")

        // 把內容抽象成語法樹,分析哪些是依賴
        const ast = parser.parse(content, {
            sourceType: "module"
        })

        // 提取依賴模塊的路徑
        const dependencies = {}
        traverse(ast, {
            ImportDeclaration({node}) {
                const newPathName = `${path.dirname(entryFile)}/${node.source.value.split("/")[1]}`;
                dependencies[node.source.value] = newPathName
            }
        })

        // 分析編譯內容
        const {code} = transformFromAst(ast, null, {
            presets: ["@babel/preset-env"]
        })

        // 返回處理後的相關信息
        return {
            entryFile,
            dependencies,
            code
        }
    }
	/**
     * 文件編譯輸出
     * @param code
     */
    generateFile(code) {

        // 生成bundle.js  => ./dist/bundle.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})`

        this.hasDir(this.output.path).then(msg => {

            fs.writeFileSync(filePath, bundle, "utf-8")

        }).catch(error => {

            console.log(error);
            return this.createDir(this.output.path)

        }).then(msg => {

            console.log(msg)
            fs.writeFileSync(filePath, bundle, "utf-8")
            console.log(`webpack編譯文件成功,內容輸出到文件${filePath}`)

        }).catch(error => {

            console.log(error);

        })
    }
}
/**
     * 判斷輸出目錄是否存在,不存在則創建
     * @param path
     * @return {Promise<unknown>}
     */
    hasDir(path) {
        return new Promise((resolve, reject) => {
            fs.stat(path, (err, msg) => {
                if (err) {
                    reject(`目錄${path}不存在,系統開始自動創建目錄...`)
                } else {
                    resolve()
                }
            })
        })
    }

    /**
     * 創建目錄
     * @param path
     * @return {Promise<unknown>}
     */
    createDir(path) {
        return new Promise((resolve, reject) => {
            let err = fs.mkdirSync(path, {})
            if (err) {
                reject(`創建目錄${path}失敗...`)
            } else {
                resolve(`系統創建目錄${path}成功,webpack開始編譯文件...`)
            }
        })
    }
  • webpack.config.js
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'main.js'
    }
};
  • bundle.js
// 獲取webpack的配置文件
const options = require("./webpack.config.js")

// 引入webpack構建原理的核心文件
const Webpack = require("./lib/webpack-structure.js")

new Webpack(options).run()
  • index.js
import {add} from "./expo.js"

add(1, 2)

console.log("hello webpack");
  • expo.js
export const add = function (a, b) {
    return a + b
}

執行node bundle.js的結果

  • 在dist目錄下生成main.js文件
(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('./src/index.js')
            
        })({"./src/index.js":{"dependencies":{"./expo.js":"./src/expo.js"},"code":"\"use strict\";\n\nvar _expo = require(\"./expo.js\");\n\n(0, _expo.add)(1, 2);\nconsole.log(\"hello webpack\");"},"./src/expo.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\nexports.mini = exports.add = void 0;\n\nvar add = function add(a, b) {\n  return a + b;\n};\n\nexports.add = add;\n\nvar mini = function mini(a, b) {\n  return a - b;\n};\n\nexports.mini = mini;"}})
  • 將main.js文件中的代碼拷貝到瀏覽器的控制檯執行,成功輸出“hello webpack”
    在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章