ホームページ  >  記事  >  ウェブフロントエンド  >  ノードが複数の JS モジュールをリンクする方法の簡単な分析

ノードが複数の JS モジュールをリンクする方法の簡単な分析

青灯夜游
青灯夜游転載
2023-02-07 17:52:372178ブラウズ

時々、次のような疑問が生じることがあります。さまざまな機能ファイルはどのように結合され、ブラウザーに表示されるのでしょうか?ノード環境が必要なのはなぜですか?次の記事では、ノードが複数の JS モジュールをリンクする方法を紹介します。お役に立てれば幸いです!

ノードが複数の JS モジュールをリンクする方法の簡単な分析

1. 個人的な理解

ブラウザ自体は、一部の表示機能とユーザー対話機能しか実行できず、システムを操作できる機能は非常に限られています。 、ブラウジング サーバーの組み込み実行環境は、機能モジュールをより適切に区別したり、ファイル操作を実現したりするなど、より人間的な開発モードを明らかに満たしていません。その場合、たとえば、各 JS ファイルが比較的分散しているため、html ページに個別に導入する必要があるなど、欠陥は明らかです。他の JS ライブラリの場合、html ページが導入されていないため、エラーが報告される可能性が高く、巨大な関数を持つプロジェクトでは、これらの関数ファイルを手動で管理するのは確かに困難ですちょっと圧倒されます。 それでは、

node

はどのようにしてよりフレンドリーな方法で開発を提供できるのでしょうか?実際、上で述べたように、ファイルの依存関係を手動で管理すると、多くのエネルギーを消費するだけでなく、漏れも発生します。そう、node の動作環境によって、システムの運用能力が広がったということ、つまり、昔の開発者も、そういった機械的で些細な作業をコードで完結させたいと考えていたのかもしれませんが、アイデアだけはあったのですが、何も操作せず、権限を与えても、結局のところ、私たちは振り返ってため息をつくことしかできません。これで、node の拡張機能を使用してファイルを処理および整理し、自動コードを追加して、最終的にブラウザで認識できる完全な JS ファイルに変換できるようになります。 . このようにして、複数のファイルの内容を 1 つのファイルにまとめることができます。 [関連チュートリアルの推奨事項: nodejs ビデオ チュートリアル プログラミング教育 ]2. ファイルの作成

最初に

JS

を作成します以下に示すように、ファイル:

ノードが複数の JS モジュールをリンクする方法の簡単な分析これらのファイルは手動で作成されます。

babel-core

このファイルはグローバル node_modules## からのものです # コピーされました以下に示すように、内部から:

なぜこれをコピーするのでしょうか?これは、足場が何をするとしても、実際には足場を素早く構築するためのものですが、その役割をどのように理解できるのでしょうか? ノードが複数の JS モジュールをリンクする方法の簡単な分析node

一部の組み込みモジュールを除き、他のモジュールは、次の図に示すように、

require パスを指定して関連モジュールを見つける必要があります。

require('./babel-core')ノードが複数の JS モジュールをリンクする方法の簡単な分析 メソッドを使用して、機能モジュールの下のメソッドを解析します。

1. エントリーファイルを記述し、ES6 コードを変換

##entrance.js エントリーファイルとして、その機能作業をどこから開始するかを設定することです。どうやって始めればいいですか?したがって、ここでの作業は、ES6 コードを変換してブラウザーで使用できるようにすることを指します。

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

上記のコードは非常に単純で、最終的な目標は、

module タイプの person.js

ファイルを

ES5 に変換することです。

let person = {name:'wsl'}
export default person
ターミナルは、以下に示すようにエントリ ファイルを実行します。
node entrance.js
以下に示すように、コードを出力します。
"use strict";
//声明了一个 __esModule 为 true 的属性
Object.defineProperty(exports, "__esModule", {
  value: true
});
var person = { name: 'wsl' };
exports.default = person;

はい、出力されたコードが表示されます。常識に従ってブラウザが認識できるコードです。直接実行できるかどうかを確認してください。

このコードを

fs

関数を使用して

js

ファイルに書き込み、ページから参照して効果を確認します。

fs.mkdir('cache',(err)=>{
    if(!err){
        fs.writeFile('cache/main.js',code,(err)=>{
            if(!err){
                console.log('文件创建完成')
            }
        })
    }
})
もう一度次の図に示すように、コマンドを実行します。

#図に示すように、ブラウザの実行構造:

ノードが複数の JS モジュールをリンクする方法の簡単な分析

実際のコード 生成後に明らかなエラーがある 変数が宣言されていない エラーを報告しないのはどうすればよいでしょうか?現時点では、このエラーを解決するには、エントリ ファイルを入力する前にカスタム補助コードを追加する必要があります。

解決策も非常に簡単で、元の ノードが複数の JS モジュールをリンクする方法の簡単な分析code

の未宣言の

exports

変数を自己実行関数でラップし、指定されたオブジェクトに返します。

//完善不严谨的code代码
function perfectCode(code){
    let exportsCode = `
    var exports = (function(exports){
    ${code}
    return exports
    })({})
    console.log(exports.default)`
    return exportsCode
}
//重新定义code
code = perfectCode(code)
出力が改善されたら、main.js ファイルを確認します。
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)

図に示すように、ブラウザを実行します。

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

ノードが複数の JS モジュールをリンクする方法の簡単な分析

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

ノードが複数の 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)

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

ノードが複数の 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 教程

以上がノードが複数の JS モジュールをリンクする方法の簡単な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。