Maison  >  Article  >  interface Web  >  Une brève analyse de la façon dont le nœud relie plusieurs modules JS

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

青灯夜游
青灯夜游avant
2023-02-07 17:52:372178parcourir

Parfois, vous vous posez cette question : Comment les différents fichiers fonctionnels sont-ils combinés et affichés dans le navigateur ? Pourquoi avons-nous besoin d’un environnement de nœuds ? L'article suivant vous présentera comment le nœud relie plusieurs modules JS entre eux. J'espère que cela aidera tout le monde !

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

1. Compréhension personnelle

Le navigateur lui-même ne peut effectuer que certaines fonctions d'affichage et d'interaction avec l'utilisateur, et sa capacité à faire fonctionner le système est très limitée. Ensuite, l'environnement d'exploitation intégré du navigateur ne répond évidemment pas. quelques exigences plus humaines en mode développement, telles que : une meilleure distinction des modules fonctionnels et la mise en œuvre des opérations sur les fichiers. Ensuite, les défauts provoqués sont évidents, par exemple : chaque fichier JS est relativement dispersé et doit être introduit séparément dans la page html. Si un certain fichier JS nécessite d'autres bibliothèques JS, alors c'est le cas. il est très probable que html La page n'est pas introduite et une erreur est signalée. Dans un projet avec des fonctions énormes, gérer manuellement ces fichiers de fonctions est vraiment un peu écrasant.

Alors, comment node permet-il de développer de manière plus conviviale ? En effet, comme mentionné ci-dessus, la gestion manuelle des dépendances de fichiers va non seulement consommer beaucoup d'énergie, mais entraînera également des omissions. Alors, serait-il préférable de la gérer de manière automatisée ? Oui, l'environnement d'exploitation de node a élargi la capacité de faire fonctionner le système. En d'autres termes, peut-être que les développeurs du passé voulaient également effectuer ces tâches mécaniques et triviales via certains codes. Cependant, ils n'avaient que des idées mais aucune autorisation d'exploitation. En fin de compte, ils ne peuvent que regarder l'océan et soupirer de joie. Désormais, vous pouvez utiliser certaines fonctions étendues de node pour traiter et organiser les fichiers, ajouter des codes automatisés et enfin les convertir en un fichier JS complet et reconnaissable par le navigateur. De cette manière, plusieurs fichiers peuvent contenir du contenu. rassemblés dans un seul fichier. [Tutoriels associés recommandés : Tutoriel vidéo Nodejs, Enseignement de la programmation]

2. Créer des fichiers

Créez d'abord des fichiers JS, comme indiqué ci-dessous :

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

Ces fichiers sont tous créés manuellement, babel -core Ce fichier est copié à partir du node_modules global, comme le montre la figure ci-dessous :

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

Pourquoi doit-il être copié ? En effet, tout ce que fait l’échafaudage, c’est en réalité le construire rapidement, mais comment pouvons-nous comprendre ce qu’il fait ? Ensuite, copiez-le directement node À l'exception de certains modules intégrés, d'autres doivent trouver les modules pertinents en spécifiant le chemin require, comme le montre la figure ci-dessous :

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

via require(' . /babel-core') méthode, analysant la méthode sous un module fonctionnel.

1. Écrivez le fichier d'entrée et convertissez le code ES6

entrance.js En tant que fichier d'entrée, sa fonction est de définir par où commencer le travail ? Comment commencer ? Ainsi, le travail ici fait référence à la conversion du code ES6 pour le fournir à une utilisation par le navigateur.

//文件管理模块
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}`)

Le code ci-dessus est très simple. Le but ultime est de convertir le fichier module de type person.js en ES5.

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

Le terminal exécute le fichier d'entrée, comme indiqué ci-dessous :

node entrance.js

Imprimez le code, comme indiqué ci-dessous :

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

Oui, lorsque vous voyez le code imprimé, il contient des codes que le navigateur peut reconnaître selon le bon sens. , jetez un oeil. Pouvez-vous l'exécuter directement ?

Écrivez ce code dans un fichier js via la fonction fs et laissez-le être référencé par une page pour voir l'effet :

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

Exécutez à nouveau la commande, comme indiqué dans l'image :

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

Exécutez le navigateur La structure est comme le montre la figure :

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

En fait, il y a une erreur évidente après la génération du code. Les variables ne sont pas déclarées. Comment peut-il ne pas signaler d'erreur ? À ce stade, vous devez ajouter du code auxiliaire personnalisé avant d'entrer dans le fichier d'entrée pour résoudre cette erreur.

La solution est également très simple. Enveloppez la variable exports non déclarée du code d'origine avec une fonction auto-exécutable, puis renvoyez-la à l'objet spécifié.

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

Regardez le résultat du fichier main.js amélioré

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)

Le navigateur s'exécute, comme le montre la figure :

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

现在浏览器运行正常了。

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 对象,存储着相关的代码信息,有代码位置的、指令内容的、变量的等等。

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

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

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

通过 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)

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

Une brève analyse de la façon dont le nœud relie plusieurs modules JS

可以看到,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 教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer