Rumah  >  Artikel  >  hujung hadapan web  >  Analisis ringkas tentang cara nod memautkan berbilang modul JS

Analisis ringkas tentang cara nod memautkan berbilang modul JS

青灯夜游
青灯夜游ke hadapan
2023-02-07 17:52:372248semak imbas

Adakah anda kadangkala mempunyai soalan seperti: Bagaimanakah pelbagai fail berfungsi digabungkan dan dipaparkan dalam penyemak imbas? Mengapa kita memerlukan persekitaran nod? Artikel berikut akan memperkenalkan kepada anda cara nod memautkan berbilang modul JS bersama-sama. Semoga ia membantu semua orang!

Analisis ringkas tentang cara nod memautkan berbilang modul JS

1 Pemahaman peribadi

Pelayar itu sendiri hanya boleh melakukan beberapa fungsi paparan dan interaksi pengguna, dan keupayaannya untuk mengendalikan sistem adalah sangat terhad , menyemak imbas Persekitaran berjalan terbina dalam pelayan jelas tidak memenuhi beberapa mod pembangunan yang lebih berperikemanusiaan, seperti modul berfungsi yang lebih membezakan dan merealisasikan operasi fail. Kemudian, kelemahannya adalah jelas, contohnya: setiap fail JS bertaburan dan perlu diperkenalkan secara berasingan dalam halaman html Jika fail JS tertentu memerlukan Untuk pustaka JS yang lain, kemungkinan besar ralat akan dilaporkan kerana halaman html tidak diperkenalkan dalam projek dengan fungsi yang besar, ia sememangnya agak menggembirakan untuk secara manual menguruskan fail fungsi ini.

Jadi, bagaimanakah nod menyediakan pembangunan dengan cara yang lebih mesra? Malah, seperti yang dinyatakan di atas, pengurusan pergantungan fail secara manual bukan sahaja akan menggunakan banyak tenaga, tetapi juga menyebabkan ketinggalan Jadi, adakah lebih baik untuk mengurusnya secara automatik? Ya, persekitaran pengendalian nod telah meluaskan keupayaan untuk mengendalikan sistem Dalam erti kata lain, mungkin pembangun pada masa lalu juga ingin menyelesaikan tugas mekanikal dan remeh tersebut melalui beberapa kod, tetapi mereka hanya mempunyai idea tetapi. tiada operasi, akhirnya kita hanya boleh melihat ke belakang dan mengeluh. Kini, anda boleh menggunakan beberapa fungsi lanjutan nod untuk pra-memproses dan menyusun fail, menambah beberapa kod automatik dan akhirnya menukarnya menjadi fail JS lengkap yang boleh dikenali oleh pelayar , dengan cara ini, kandungan berbilang fail boleh dikumpulkan ke dalam satu fail. [Tutorial berkaitan yang disyorkan: tutorial video nodejs, Pengajaran pengaturcaraan]

2 Cipta fail

Mula-mula buat beberapa JS. fail, seperti yang ditunjukkan di bawah:

Analisis ringkas tentang cara nod memautkan berbilang modul JS

Fail ini dicipta secara manual, babel-core Fail ini daripada node_modules global satu salinan di dalam adalah seperti yang ditunjukkan di bawah:

Analisis ringkas tentang cara nod memautkan berbilang modul JS

Mengapa ia perlu disalin? Ini kerana apa sahaja yang dilakukan oleh perancah sebenarnya adalah untuk membinanya dengan cepat, tetapi bagaimana kita boleh memahami apa yang dilakukannya? Kemudian salin terus sahaja nod Kecuali beberapa modul terbina dalam, yang lain perlu mencari modul yang berkaitan dengan menentukan laluan require, seperti yang ditunjukkan dalam rajah di bawah:

Analisis ringkas tentang cara nod memautkan berbilang modul JS

Gunakan kaedah require('./babel-core') untuk menghuraikan kaedah di bawah modul berfungsi.

1 Tulis fail masukan dan tukarkan kod ES6

entrance.js Sebagai fail entri, ia function is to set Di manakah kerja bermula? Bagaimana hendak bermula? Jadi, kerja di sini merujuk kepada menukar kod ES6 untuk menyediakannya untuk kegunaan penyemak imbas.

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

Kod di atas sangat mudah Matlamat utama adalah untuk menukar modul taip person.js kepada ES5.

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

Terminal menjalankan fail kemasukan, seperti ditunjukkan di bawah:

node entrance.js

Cetak kod, seperti yang ditunjukkan di bawah:

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

Ya, lihat print Kod di dalamnya adalah semua kod yang boleh dikenali oleh penyemak imbas Menurut akal, lihat jika anda boleh menjalankannya secara langsung?

Tulis kod ini ke dalam fail js melalui fungsi fs dan biarkan ia dirujuk oleh halaman untuk melihat kesan:

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

Jalankan arahan sekali lagi, seperti yang ditunjukkan dalam gambar:

Analisis ringkas tentang cara nod memautkan berbilang modul JS

Struktur berjalan penyemak imbas, seperti yang ditunjukkan dalam gambar:

Analisis ringkas tentang cara nod memautkan berbilang modul JS

Sebenarnya, terdapat ralat yang jelas selepas kod dijana Pembolehubah tidak diisytiharkan. Pada masa ini, anda perlu menambah beberapa kod tambahan tersuai sebelum memasukkan fail entri untuk menyelesaikan ralat ini. Penyelesaian kepada

juga sangat mudah Balutkan pembolehubah eksport yang tidak diisytiharkan bagi kod asal dalam fungsi laksana sendiri, dan kemudian kembalikan kepada objek yang ditentukan. .

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

Lihat output fail main.js yang telah siap

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)

Pelayar berjalan, seperti yang ditunjukkan dalam rajah:

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

Analisis ringkas tentang cara nod memautkan berbilang modul JS

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

Analisis ringkas tentang cara nod memautkan berbilang modul 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)

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

Analisis ringkas tentang cara nod memautkan berbilang modul 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 教程

Atas ialah kandungan terperinci Analisis ringkas tentang cara nod memautkan berbilang modul JS. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam