浅析node怎样链接多个JS模块

有时候是不是会有这样的疑问:纷繁的功能文件,到最后是怎么组合成起来并且在浏览器中展示的?为什么需要 node 环境?下面本篇文章给大家介绍一下node是怎样把多个js模块链接在一起的?希望对大家有所帮助!

浅析node怎样链接多个JS模块

一、个人理解

浏览器本身只能做一些展示及用户交互的功能,对于系统操作的能力很有限,那么,浏览器内置的运行环境显然不满足一些更为人性化的开发模式,比如:更好的区分功能模块、实现文件的操作。那么,带来的缺陷就很明显,比如:各个 JS 文件比较分散,需要在 html 页面里面单独引入,如果某个 JS 文件需要其他的 JS 库,那么很可能会因为 html 页面未引入而报错,在功能庞大的项目里,手动的管理这些功能文件确实让人有点捉襟见肘。

那么, node 到底是怎么更友好的提供开发的呢?其实,上面也说了,人为的管理文件依赖不但会消耗大量的精力,还会存在疏漏,那么,是不是以用自动化的方式进行管理就会好很多?是的,在 node 的运行环境里拓宽了对系统的操作能力,也就是说,或许以前开发者也想通过一些代码来完成那些机械琐碎的工作,但是,只有想法没有操作权限,最后只能望洋兴叹。现在,可以以 node 的一些扩展功能对文件进行先前加工与整理,再添加一些自动化的代码,最后转换为一个浏览器可识别的、完整的 JS 文件,这样一来,多个文件的内容,便可以汇集到一个文件。【相关教程推荐:nodejs视频教程、编程教学】

二、创建文件

先创建一些 JS 文件,如下图所示:

image.png

这些文件都是手动创建,babel-core 这个文件是从全局的 node_modules 里面复制出来的,如下图所示:

image.png

为什么要复制出来呢?这是因为,任何脚手架干的事其实都是为了快速搭建,但是,怎么能理解它干的什么事呢?那干脆就直接复制吧,本身,node 除了一些内置的模块,其他的都需要通过指明 require 路径的方式来找到相关模块,如下图所示:

image.png

通过 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('文件创建完成')            }        })    }})

再次运行命令,如图所示:

image.png

浏览器运行结构,如图所示:

image.png

其实代码生成完就有很明显的错误,未声明变量,怎么会不报错呢?这时候,在入口文件输入之前就需要添加一些自定义辅助代码,来解决一下这个报错。

解决的方式也很简单,将原 code 的未声明的 exports 变量通过自执行函数的方式包裹一下,再返回给指定对象。

//完善不严谨的code代码function perfectCode(code){    let exportsCode = `    var exports = (function(exports){    ${code}    return exports    })({})    console.log(exports.default)`    return exportsCode}//重新定义codecode = 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)

浏览器运行,如图所示:

image.png

现在浏览器运行正常了。

2、处理 import 逻辑

壁纸样机神器 壁纸样机神器

免费壁纸样机生成

壁纸样机神器 0 查看详情 壁纸样机神器

既然是模块,肯定会存在一个模块依赖另一个或其他很多个模块的情况。这里先不着急,先看看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 对象,存储着相关的代码信息,有代码位置的、指令内容的、变量的等等。

image.png

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

image.png

通过 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 exportsn }n return (function(exports,require){"use strict";nnObject.defineProperty(exports, "__esModule", {n value: truen});nnvar animal = { name: 'dog' };nnexports.default = animal; n return exportsn })({},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)

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

image.png

可以看到,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 对应的codelet { 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 教程!

以上就是浅析node怎样链接多个JS模块的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/543874.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
怎么设置电脑桌面图标字体大小
上一篇 2025年11月9日 16:30:52
如何在Java中使用Scanner获取用户输入
下一篇 2025年11月9日 16:31:02

相关推荐

  • 修复Django电商项目中AJAX过滤产品列表图片不显示问题

    在Django电商项目中,当使用AJAX动态加载过滤后的产品列表时,常遇到图片无法正常显示的问题。这通常是由于前端模板中图片加载方式(如data-setbg属性结合JavaScript库)与AJAX动态内容更新机制不兼容所致。解决方案是直接在AJAX返回的HTML中使用标准的标签来渲染图片,确保浏览…

    2026年5月10日
    000
  • 开源免费PHP工具 PHP开发效率提升利器

    推荐开源免费PHP开发工具以提升效率:VS Code、Sublime Text轻量高效,PhpStorm专业强大;调试用Xdebug、Kint、Ray;依赖管理选Composer;代码质量工具包括PHPStan、Psalm、PHP_CodeSniffer;数据库管理可用%ignore_a_1%MyA…

    2026年5月10日
    000
  • 修复点击时按钮抖动:CSS垂直对齐实践

    本文探讨了在Web开发中,交互式按钮(如播放/暂停按钮)在点击时发生意外垂直位移的问题。通过分析CSS样式变化对元素布局的影响,我们发现这是由于按钮不同状态下的边框样式和内边距改变,以及默认的垂直对齐行为共同作用所致。核心解决方案是利用CSS的vertical-align属性,将其设置为middle…

    2026年5月10日
    000
  • 如何在HTML中插入表单元素_HTML表单控件与输入类型使用指南

    HTML表单通过标签构建,包含action和method属性定义数据提交目标与方式,常用input类型如text、password、email等适配不同输入需求,配合label、required、placeholder提升可用性,结合textarea、select、button等控件实现完整交互,是…

    2026年5月10日
    000
  • 前端缓存策略与JavaScript存储管理

    根据数据特性选择合适的存储方式并制定清晰的读写与清理逻辑,能显著提升前端性能;合理运用Cookie、localStorage、sessionStorage、IndexedDB及Cache API,结合缓存策略与定期清理机制,可在保证用户体验的同时避免安全与性能隐患。 前端缓存和JavaScript存…

    2026年5月10日
    100
  • HTML5网页如何实现手势操作 HTML5网页移动端交互的处理技巧

    首先利用原生touch事件实现滑动判断,再通过preventDefault解决滚动冲突,接着引入Hammer.js处理复杂手势,最后通过优化点击区域、避免事件冲突和增加视觉反馈提升体验。 在移动端浏览器中,HTML5网页可以通过触摸事件实现手势操作,提升用户体验。虽然原生JavaScript提供了基…

    2026年5月10日
    000
  • JavaScript 闭包:理解闭包原理与内存泄漏问题

    闭包是函数访问其外部作用域变量的能力,即使外部函数已执行完毕。如 inner 函数引用 outer 中的 count,形成闭包,使变量持久存在。闭包本身无害,但可能因延长变量生命周期导致内存泄漏,例如事件监听器引用大对象时。若未及时清理 DOM 事件或定时器,闭包会阻止垃圾回收,造成内存占用过高。解…

    2026年5月10日
    000
  • JavaScript 动态菜单点击高亮效果实现教程

    本教程详细介绍了如何使用 JavaScript 实现动态菜单的点击高亮功能。通过事件委托和状态管理,当用户点击菜单项时,被点击项会高亮显示(绿色),同时其他菜单项恢复默认样式(白色)。这种方法避免了不必要的DOM操作,提高了性能和代码可维护性,确保了无论点击方向如何,功能都能稳定运行。 动态菜单高亮…

    2026年5月10日
    200
  • JavaScript函数中插入加载动画(Spinner)的正确方法

    本文旨在解决在JavaScript函数中插入加载动画(Spinner)时遇到的异步问题。通过引入async/await和Promise.all,确保在数据处理完成前后正确显示和隐藏加载动画,提升用户体验。我们将提供两种实现方案,并详细解释其原理和优势。 在Web开发中,当执行耗时操作时,显示加载动画…

    2026年5月10日
    000
  • 动态更新圆形进度条:JavaScript成绩计算器集成指南

    本文档旨在指导开发者如何将JavaScript成绩计算系统与动态圆形进度条集成,实现可视化展示平均成绩。我们将详细讲解如何修改现有的JavaScript代码,使其在计算出平均分后,能够动态更新圆形进度条的进度,从而提供更直观的用户体验。本文档包含详细的代码示例和注意事项,帮助开发者轻松实现这一功能。…

    2026年5月10日
    000
  • JavaScript计算器开发:解决数值显示与初始化问题

    本教程深入探讨了使用JavaScript构建计算器时常见的数值显示异常问题,特别是由于类属性未初始化导致的`Cannot read properties of undefined`错误。我们将详细分析问题根源,并通过在构造函数中调用初始化方法来解决该问题,同时优化显示逻辑,确保计算器功能稳定且界面显…

    2026年5月10日
    000
  • 使用 Ajax 和 FormData 实现文件上传及文本数据提交的完整教程

    本文旨在解决在使用 Ajax 和 FormData 进行文件上传时,遇到的 $_POST 和 $_FILES 为空的问题。通过详细的代码示例和解释,我们将展示如何正确地构建 FormData 对象,并通过 Ajax 将文件和文本数据发送到服务器端,同时避免常见的错误配置,确保数据能够成功地被 PHP…

    2026年5月10日
    000
  • JavaScript 高效判断页面所有复选框状态的技巧与实践

    本文旨在提供一套高效且专业的javascript方法,用于判断网页中所有复选框的选中状态。我们将探讨如何利用`array.some()`快速确定是否有未选中的复选框(进而判断是否全部选中),以及如何使用`array.filter()`统计选中和未选中的复选框数量。通过优化dom元素选择和数组操作,提…

    2026年5月10日
    000
  • 解决Persistent UTM代码导致链接意外添加问号的问题

    本文旨在解决在使用JavaScript持久化UTM参数时,链接在没有UTM参数的情况下被意外添加问号的问题。通过分析问题代码,找出错误原因,并提供修正后的代码示例,确保只有当存在UTM参数时,链接才会被添加相应的参数。同时,强调了代码的健壮性和可维护性,避免不必要的修改和潜在的错误。 在使用Java…

    2026年5月10日
    200
  • 从 JavaScript 获取 URL 并在 PHP DataGrid 中使用

    本文档旨在指导开发者如何从 JavaScript 函数中获取 URL,并将其动态应用于 PHP DataGrid。通过前端 JavaScript 动态生成 API 地址,并将其传递给后端的 PHP DataGrid,实现数据根据用户会话动态加载。 动态配置 DataGrid 的 URL 在构建动态 …

    2026年5月10日
    000
  • JavaScript 中使用多个 querySelector 更新页面元素

    本文旨在讲解如何在 JavaScript 的 if 语句中使用多个 querySelector 来更新不同的页面元素,并提供示例代码和注意事项,帮助开发者理解并应用此技术。通过该方法,可以根据特定条件动态修改页面内容,提升用户体验。 使用 querySelector 在 if 语句中更新多个元素 在…

    2026年5月10日
    100
  • HTML5代码如何制作3D效果 HTML5代码中WebGL的入门实例

    最核心的技术是WebGL,通过HTML5的canvas结合JavaScript使用WebGL API渲染3D图形。首先创建包含canvas的HTML页面,获取WebGL上下文,编写GLSL着色器定义顶点位置与颜色,编译着色器并链接成程序,接着设置顶点缓冲区传入三角形坐标和颜色数据,引入gl-matr…

    2026年5月10日
    000
  • 基于两数组数据计算结果排序的 React 教程

    本教程针对 React 应用中需要根据两个独立数组的数据计算结果进行排序的场景,提供了一种高效的解决方案。通过使用 JavaScript 的 `reduce` 和 `map` 方法,将两个数组根据唯一标识符进行合并,从而简化排序逻辑,提高代码的可读性和可维护性。避免了复杂的嵌套循环或同步迭代,提供了…

    2026年5月10日
    000
  • 控制HTML Canvas颜色空间输出24位深度TIFF图像

    本教程详细介绍了如何在web前端环境中,特别是结合`html2canvas`和`canvas-to-tiff`库时,通过明确设置html canvas的颜色空间为`srgb`,从而确保输出24位深度的tiff图像。文章将提供具体的javascript代码示例,并解释其原理,帮助开发者解决canvas…

    2026年5月10日
    100
  • PHP安全文件下载:防止直链与保护资源

    本文旨在解决通过检查元素获取直链下载文件的问题,并提供一种安全的PHP服务器端文件交付方案。核心思想是利用PHP作为文件代理,通过设置HTTP响应头直接将文件发送给用户,从而隐藏文件的实际存储路径,有效防止未经授权的直接链接访问。 客户端下载链接的风险与局限性 在构建下载页面时,开发者常常面临一个挑…

    2026年5月10日
    100

发表回复

登录后才能评论
关注微信