>  기사  >  웹 프론트엔드  >  노드가 여러 JS 모듈을 연결하는 방법에 대한 간략한 분석

노드가 여러 JS 모듈을 연결하는 방법에 대한 간략한 분석

青灯夜游
青灯夜游앞으로
2023-02-07 17:52:372178검색

때때로 다음과 같은 질문이 있습니다. 다양한 기능 파일은 어떻게 결합되어 브라우저에 표시됩니까? 노드 환경이 필요한 이유는 무엇입니까? 다음 기사에서는 노드가 여러 JS 모듈을 함께 연결하는 방법을 소개합니다. 모두에게 도움이 되기를 바랍니다!

노드가 여러 JS 모듈을 연결하는 방법에 대한 간략한 분석

1. 개인적인 이해

브라우저 자체는 일부 표시 및 사용자 상호 작용 기능만 수행할 수 있으며 시스템 작동 능력도 매우 제한되어 있으므로 브라우저에 내장된 운영 환경은 당연히 충족되지 않습니다. 좀 더 인간적인 요구 사항(예: 기능 모듈 구분 및 파일 작업 구현 향상) 그러면 발생하는 결함은 명백합니다. 예를 들어 각 JS 파일은 상대적으로 분산되어 있으며 html 페이지에서 별도로 소개해야 합니다. 특정 JS 파일에 다른 JS 라이브러리가 필요한 경우에는 다음과 같습니다. html 페이지가 소개되지 않고 오류가 보고될 가능성이 높습니다. 기능이 많은 프로젝트에서 이러한 기능 파일을 수동으로 관리하는 것은 실제로 약간 부담스럽습니다.

그렇다면 node는 어떻게 좀 더 친근한 방식으로 개발을 제공할 수 있을까요? 실제로 위에서 언급한 것처럼 파일 종속성을 수동으로 관리하면 많은 에너지가 소모될 뿐만 아니라 누락도 발생하게 되므로 자동화된 방식으로 관리하는 것이 훨씬 낫지 않을까요? 네, node의 운영 환경은 시스템을 운영할 수 있는 능력을 확장시켰습니다. 즉, 과거의 개발자들도 일부 코드를 통해 이러한 기계적이고 사소한 작업을 완료하고 싶었을 것입니다. 그러나 그들은 아이디어만 있을 뿐 운영 권한은 없었습니다. 결국 그들은 바다를 바라보며 기쁨의 한숨을 쉬게 된다. 이제 node의 일부 확장 기능을 사용하여 파일을 처리 및 구성하고 자동화된 코드를 추가한 다음 최종적으로 브라우저에서 인식할 수 있는 완전한 JS 파일로 변환할 수 있습니다. 이러한 방식으로 여러 파일의 콘텐츠를 만들 수 있습니다. 하나의 파일로 모아졌습니다. [관련 튜토리얼 권장: nodejs 비디오 튜토리얼, Programming Teaching]

2. 파일 생성

먼저 아래와 같이 JS 파일을 생성합니다:

노드가 여러 JS 모듈을 연결하는 방법에 대한 간략한 분석

이 파일들은 모두 수동으로 생성됩니다. babel -core 이 파일은 아래 그림과 같이 전역 node_modules에서 복사됩니다.

노드가 여러 JS 모듈을 연결하는 방법에 대한 간략한 분석

복사해야 하는 이유는 무엇입니까? 비계가 하는 일은 실제로는 빠르게 구축하기 위한 것이지만 그것이 무엇을 하는지 어떻게 이해할 수 있기 때문입니다. 그런 다음 node 일부 내장 모듈을 제외하고 다른 모듈은 아래 그림과 같이 require 경로를 지정하여 관련 모듈을 찾아야 합니다.

노드가 여러 JS 모듈을 연결하는 방법에 대한 간략한 분석

~ require(' . /babel-core') 메서드, 기능 모듈 아래에서 메서드를 구문 분석합니다.

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 모듈을 연결하는 방법에 대한 간략한 분석

Run 브라우저 구조는 그림과 같습니다.

노드가 여러 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제