Heim  >  Artikel  >  Web-Frontend  >  Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

青灯夜游
青灯夜游nach vorne
2023-02-07 17:52:372178Durchsuche

Manchmal haben Sie diese Frage: Wie werden die verschiedenen Funktionsdateien zusammengefasst und im Browser angezeigt? Warum brauchen wir eine Knotenumgebung? Im folgenden Artikel erfahren Sie, wie Node mehrere JS-Module miteinander verknüpft. Hoffe, es hilft allen!

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

1. Persönliches Verständnis

Der Browser selbst kann nur einige Anzeige- und Benutzerinteraktionsfunktionen ausführen, und seine Fähigkeit, das System zu bedienen, ist dann offensichtlich nicht erfüllt einige humanere Anforderungen an den Entwicklungsmodus, wie zum Beispiel: bessere Unterscheidung von Funktionsmodulen und Implementierung von Dateioperationen. Dann liegen die Nachteile auf der Hand, zum Beispiel: Jede JS-Datei ist relativ verstreut und muss separat in die html-Seite eingefügt werden. Wenn eine bestimmte JS-Datei andere JS-Bibliotheken erfordert, ist das wahrscheinlich der Fall html Die Seite wird nicht eingeführt und es wird ein Fehler gemeldet. In einem Projekt mit riesigen Funktionen ist die manuelle Verwaltung dieser Funktionsdateien wirklich etwas überwältigend.

Wie sorgt node für eine benutzerfreundlichere Entwicklung? Tatsächlich wird die manuelle Verwaltung von Dateiabhängigkeiten, wie oben erwähnt, nicht nur viel Energie verbrauchen, sondern auch zu Auslassungen führen. Wäre es also viel besser, sie automatisiert zu verwalten? Ja, die Betriebsumgebung von node hat die Möglichkeiten zur Bedienung des Systems erweitert. Mit anderen Worten: Vielleicht wollten Entwickler in der Vergangenheit auch diese mechanischen und trivialen Aufgaben durch einige Codes erledigen, hatten jedoch nur Ideen, aber keine Betriebsberechtigungen Am Ende können sie nur auf das Meer schauen und vor Freude seufzen. Jetzt können Sie einige erweiterte Funktionen von node verwenden, um die Dateien zu verarbeiten und zu organisieren, einige automatisierte Codes hinzuzufügen und sie schließlich in eine vom Browser erkennbare, vollständige JS-Datei umzuwandeln. Auf diese Weise können mehrere Dateien gespeichert werden in einer Datei gesammelt. [Empfohlene verwandte Tutorials: nodejs-Video-Tutorial, Programmierlehre]

2. Dateien erstellen

Erstellen Sie zunächst einige JS-Dateien, wie unten gezeigt:

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

Diese Dateien werden alle manuell erstellt, babel -core Diese Datei wird von den globalen node_modules kopiert, wie in der Abbildung unten gezeigt:

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

Warum sollte sie kopiert werden? Das liegt daran, dass Gerüste bei allem, was sie tun, eigentlich schnell aufgebaut werden sollen, aber wie können wir verstehen, was sie tun? Dann kopieren Sie es einfach direkt. node Mit Ausnahme einiger integrierter Module müssen andere die relevanten Module durch Angabe des require-Pfads finden, wie in der Abbildung unten gezeigt:

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

bis require(' . /babel-core')-Methode, Parsen der Methode unter einem Funktionsmodul.

1. Schreiben Sie die Eintragsdatei und konvertieren Sie den ES6-Code

entrance.js Als Eintragsdatei besteht ihre Funktion darin, festzulegen, wo mit der Arbeit begonnen werden soll? Wie fange ich an? Die Arbeit hier bezieht sich also auf die Konvertierung von ES6-Code, um ihn für die Verwendung durch den Browser bereitzustellen.

//文件管理模块
const fs = require('fs');
//解析文件为AST模块
const babylon = require('babylon');
//AST转换模块
const { transformFromAst } = require('./babel-core');
//获取JS文件内容
let content = fs.readFileSync('./person.js','utf-8')
//转换为AST结构,设定解析的文件为 module 类型
let ast = babylon.parse(content,{
    sourceType:'module'
})
//将ES6转换为ES5浏览器可识别代码
le t { code } = transformFromAst(ast, null, {
    presets: ['es2015']
});
//输出内容
console.log('code:\n' + `${code}`)

Der obige Code ist sehr einfach. Das ultimative Ziel besteht darin, die Datei vom Typ module person.js in ES5 zu konvertieren.

let person = {name:'wsl'}
export default person

Das Terminal führt die Eingabedatei aus, wie unten gezeigt:

node entrance.js

Drucken Sie den Code aus, wie unten gezeigt:

"use strict";
//声明了一个 __esModule 为 true 的属性
Object.defineProperty(exports, "__esModule", {
  value: true
});
var person = { name: 'wsl' };
exports.default = person;

Ja, wenn Sie den gedruckten Code sehen, enthält er Codes, die der Browser laut gesundem Menschenverstand erkennen kann , schau mal. Kannst du es direkt ausführen?

Schreiben Sie diesen Code über die Funktion fs in eine js-Datei und lassen Sie ihn von einer Seite referenzieren, um den Effekt zu sehen:

fs.mkdir('cache',(err)=>{
    if(!err){
        fs.writeFile('cache/main.js',code,(err)=>{
            if(!err){
                console.log('文件创建完成')
            }
        })
    }
})

Führen Sie den Befehl erneut aus, wie im Bild gezeigt:

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

Ausführen Die Struktur des Browsers ist wie in der Abbildung dargestellt:

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

Tatsächlich liegt nach der Codegenerierung ein offensichtlicher Fehler vor. Wie kann kein Fehler gemeldet werden? Zu diesem Zeitpunkt müssen Sie einen benutzerdefinierten Hilfscode hinzufügen, bevor Sie die Eingabedatei eingeben, um diesen Fehler zu beheben.

Die Lösung ist auch sehr einfach. Wickeln Sie die nicht deklarierte exports-Variable des ursprünglichen Codes mit einer selbstausführenden Funktion ein und geben Sie sie dann an das angegebene Objekt zurück.

//完善不严谨的code代码
function perfectCode(code){
    let exportsCode = `
    var exports = (function(exports){
    ${code}
    return exports
    })({})
    console.log(exports.default)`
    return exportsCode
}
//重新定义code
code = perfectCode(code)

Sehen Sie sich die Ausgabe der verbesserten main.js-Datei an

var exports = (function(exports){
    "use strict";
    Object.defineProperty(exports, "__esModule", {
    value: true
    });
    var person = { name: 'wsl' };
    exports.default = person;
    return exports
})({})
console.log(exports.default)

Der Browser wird wie in der Abbildung gezeigt ausgeführt:

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

现在浏览器运行正常了。

2、处理 import 逻辑

既然是模块,肯定会存在一个模块依赖另一个或其他很多个模块的情况。这里先不着急,先看看person 模块引入单一 animal 模块后的代码是怎样的?

animal 模块很简单,仅仅是一个对象导出

let animal = {name:'dog'}
export default animal

person 模块引入

import animal from './animal'
let person = {name:'wsl',pet:animal}
export default person

看下转换后的代码

"use strict";
Object.defineProperty(exports, "__esModule", {
  value: true
});
var _animal = require("./animal");
var _animal2 = _interopRequireDefault(_animal);
function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : { default: obj };
}
var person = { name: 'wsl', pet: _animal2.default };
exports.default = person;

可以看到,转换后会多一个未声明的 require 方法,内部声明的 _interopRequireDefault 方法已声明,是对 animal 导出部分进行了一个包裹,让其后续的代码取值 default 的时候保证其属性存在!

下面就需要对 require 方法进行相关的处理,让其转为返回一个可识别、可解析、完整的对象。

是不是可以将之前的逻辑对 animal 模块重新执行一遍获取到 animal 的代码转换后的对象就行了?

但是,这里先要解决一个问题,就是对于 animal 模块的路径需要提前获取并进行代码转换,这时候给予可以利用 babel-traverse 工具对 AST 进行处理。

说到这里,先看一下 JS 转换为 AST 是什么内容?

这里简单放一张截图,其实是一个 JSON 对象,存储着相关的代码信息,有代码位置的、指令内容的、变量的等等。

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

拿到它的目的其实就是找到import 对应节点下的引入其他模块的路径

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

通过 babel-traverse 找到 AST 里面 import 对应的信息

const traverse = require('babel-traverse').default;
//遍历找到 import 节点
traverse(ast,{
    ImportDeclaration:({ node })=>{
        console.log(node)
    }
})

输出看下节点打印的结构

Node {
  type: 'ImportDeclaration',
  start: 0,
  end: 29,
  loc: SourceLocation {
    start: Position { line: 1, column: 0 },
    end: Position { line: 1, column: 29 }
  },
  specifiers: [
    Node {
      type: 'ImportDefaultSpecifier',
      start: 7,
      end: 13,
      loc: [SourceLocation],
      local: [Node]
    }
  ],
  source: Node {
    type: 'StringLiteral',
    start: 19,
    end: 29,
    loc: SourceLocation { start: [Position], end: [Position] },
    extra: { rawValue: './animal', raw: "'./animal'" },
    value: './animal'
  }
}

可以看到 node.source.value 就是 animal 模块的路径,需要的就是它。

扩展入口文件功能,解析 import 下的 JS 模块,

添加 require 方法

//完善代码
function perfectCode(code){
    let exportsCode = `
        //添加require方法
        let require = function(path){
            return {}
        }

        let exports = (function(exports,require){
            ${code}
            return exports
        })({},require)
    `
    return exportsCode
}

这样转换完的 main.js 给不会报错了,但是,这里需要解决怎么让 require 方法返回 animal 对象

let require = function(path){
    return {}
}

let exports = (function(exports,require){
    "use strict";
    Object.defineProperty(exports, "__esModule", {
        value: true
    });

    var _animal = require("./animal");
    var _animal2 = _interopRequireDefault(_animal);
    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
    var person = { name: 'wsl', pet: _animal2.default };
    exports.default = person;
    return exports
})({},require)

下面就需要添加 require 方法进行 animal 对象的返回逻辑

//引入模块路径
let importFilesPaths = []
//引入路径下的模块代码
let importFilesCodes = {}

//获取import节点,保存模块路径
traverse(ast,{
    ImportDeclaration:({ node })=>{
        importFilesPaths.push(node.source.value)
    }
})

//解析import逻辑
function perfectImport(){
    //遍历解析import里面对应路径下的模块代码
    importFilesPaths.forEach((path)=>{
        let content = fs.readFileSync(path + '.js','utf-8')
        let ast = babylon.parse(content,{
            sourceType:'module'
        })
        let { code } = transformFromAst(ast, null, {
            presets: ['es2015']
        });
        //转换code
        code = perfectImportCode(code)
        importFilesCodes[path] = code
    })
}

//完善import代码
function perfectImportCode(code){
    let exportsCode = `(
        function(){
                let require = function(path){
                    let exports = (function(){ return eval(${JSON.stringify(importFilesCodes)}[path])})()
                    return exports
                }
                return (function(exports,require){${code}
                    return exports
                })({},require)
            }
        )()
    `
    return exportsCode
}

//完善最终输出代码
function perfectCode(code){
    let exportsCode = `
        let require = function(path){
            let exports = (function(){ return eval(${JSON.stringify(importFilesCodes)}[path])})()
            return exports
        }
        let exports = (function(exports,require){
            ${code}
            return exports
        })({},require)
        console.log(exports.default)
    `
    return exportsCode
}

上面的代码其实没有什么特别难理解的部分,里面的自执行闭包看着乱,最终的目的也很清晰,就是找到对应模块下的文件 code 代码进行自运行返回一个对应的模块对象即可。

看下转换后的 main.js 代码

let require = function(path){
    let exports = (function(){ return eval({"./animal":"(\n function(){\n let require = function(path){\n let exports = (function(){ return eval({}[path])})()\n return exports\n }\n return (function(exports,require){\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar animal = { name: 'dog' };\n\nexports.default = animal; \n return exports\n })({},require)\n }\n )()\n "}[path])})()
    return exports
}

let exports = (function(exports,require){
    "use strict";
    Object.defineProperty(exports, "__esModule", {
    value: true
    });

    var _animal = require("./animal");
    var _animal2 = _interopRequireDefault(_animal);

    function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }


    var person = { name: 'wsl', pet: _animal2.default };
    exports.default = person;
    return exports
})({},require)
console.log(exports.default)

刷新浏览器,打印结果如下:

Eine kurze Analyse, wie Node mehrere JS-Module verknüpft

可以看到,pet 属性被赋予了新值。

三、完整的入口文件代码

const fs = require('fs');
const babylon = require('babylon');
const traverse = require('babel-traverse').default;
const { transformFromAst } = require('./babel-core');
//解析person文件
let content = fs.readFileSync('./person.js','utf-8')
let ast = babylon.parse(content,{
    sourceType:'module'
})

//引入模块路径
let importFilesPaths = []
//引入路径下的模块代码
let importFilesCodes = {}

//保存import引入节点
traverse(ast,{
    ImportDeclaration:({ node })=>{
        importFilesPaths.push(node.source.value)
    }
})

//person.js 对应的code
let { code } = transformFromAst(ast, null, {
    presets: ['es2015']
});

//解析import逻辑
function perfectImport(){
    importFilesPaths.forEach((path)=>{
        let content = fs.readFileSync(path + '.js','utf-8')
        let ast = babylon.parse(content,{
            sourceType:'module'
        })
        let { code } = transformFromAst(ast, null, {
            presets: ['es2015']
        });
        code = perfectImportCode(code)
        importFilesCodes[path] = code
    })
}

//完善import代码
function perfectImportCode(code){
let exportsCode = `
    (
    function(){
        let require = function(path){
        let exports = (function(){ return eval(${JSON.stringify(importFilesCodes)}[path])})()
            return exports
        }
        return (function(exports,require){${code}
            return exports
        })({},require)
        }
    )()
    `
    return exportsCode
}

//开始解析import逻辑
perfectImport()

//完善最终代码
function perfectCode(code){
    let exportsCode = `
        let require = function(path){
            let exports = (function(){ return eval(${JSON.stringify(importFilesCodes)}[path])})()
            return exports
        }
        let exports = (function(exports,require){
            ${code}
            return exports
        })({},require)
        console.log(exports.default)
    `
    return exportsCode
}

//最后的代码
code = perfectCode(code)

//删除文件操作
const deleteFile = (path)=>{
    if(fs.existsSync(path)){
        let files = []
        files = fs.readdirSync(path)
        files.forEach((filePath)=>{
            let currentPath = path + '/' + filePath
            if(fs.statSync(currentPath).isDirectory()){
                deleteFile(currentPath)
            } else {
                fs.unlinkSync(currentPath)
            }
        })
        fs.rmdirSync(path)
    }
}

deleteFile('cache')

//写入文件操作
fs.mkdir('cache',(err)=>{
        if(!err){
            fs.writeFile('cache/main.js',code,(err)=>{
            if(!err){
                console.log('文件创建完成')
            }
        })
    }
})

四、总结与思考

古代钻木取火远比现代打火机烤面包的意义深远的多。这个世界做过的事情没有对或错之分,但有做与不做之别。代码拙劣,大神勿笑[抱拳][抱拳][抱拳]

更多node相关知识,请访问:nodejs 教程

Das obige ist der detaillierte Inhalt vonEine kurze Analyse, wie Node mehrere JS-Module verknüpft. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen