加入收藏 | 设为首页 | 会员中心 | 我要投稿 广西网 (https://www.guangxiwang.cn/)- 分布式数据库、建站、网络、内容创作、业务安全!
当前位置: 首页 > 教程 > 正文

node该如何链接多个JS模块

发布时间:2023-07-28 09:06:13 所属栏目:教程 来源:未知
导读:   为大家详细介绍“node如何链接多个JS模块”,内容详细,步骤清晰,细节处理妥当,希望这篇“node如何链接多个JS模块”文章能帮助大家解决疑惑,下面跟着小编的思路
  为大家详细介绍“node如何链接多个JS模块”,内容详细,步骤清晰,细节处理妥当,希望这篇“node如何链接多个JS模块”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
 
  一、个人理解
 
  浏览器本身只能做一些展示及用户交互的功能,对于系统操作的能力很有限,那么,浏览器内置的运行环境显然不满足一些更为人性化的开发模式,比如:更好的区分功能模块、实现文件的操作。那么,带来的缺陷就很明显,比如:各个 JS 文件比较分散,需要在 html 页面里面单独引入,如果某个 JS 文件需要其他的 JS 库,那么很可能会因为 html 页面未引入而报错,在功能庞大的项目里,手动的管理这些功能文件确实让人有点捉襟见肘。
 
  那么, node 到底是怎么更友好的提供开发的呢?其实,上面也说了,人为的管理文件依赖不但会消耗大量的精力,还会存在疏漏,那么,是不是以用自动化的方式进行管理就会好很多?是的,在 node 的运行环境里拓宽了对系统的操作能力,也就是说,或许以前开发者也想通过一些代码来完成那些机械琐碎的工作,但是,只有想法没有操作权限,最后只能望洋兴叹。现在,可以以 node 的一些扩展功能对文件进行先前加工与整理,再添加一些自动化的代码,最后转换为一个浏览器可识别的、完整的 JS 文件,这样一来,多个文件的内容,便可以汇集到一个文件。
 
  二、创建文件
 
  先创建一些 JS 文件,
 
  这些文件都是手动创建,babel-core 这个文件是从全局的 node_modules 里面复制出来的,

  为什么要复制出来呢?这是因为,任何脚手架干的事其实都是为了快速搭建,但是,怎么能理解它干的什么事呢?那干脆就直接复制吧,本身,node 除了一些内置的模块,其他的都需要通过指明 require 路径的方式来找到相关模块,
 
  通过 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('文件创建完成')
 
              }
 
          })
 
      }
 
  })
 
  再次运行命令,
 
  浏览器运行结构,
 
  其实代码生成完就有很明显的错误,未声明变量,怎么会不报错呢?这时候,在入口文件输入之前就需要添加一些自定义辅助代码,来解决一下这个报错。
 
  解决的方式也很简单,将原 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)
 
  浏览器运行,
 
  现在浏览器运行正常了。
 
  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 对象,存储着相关的代码信息,有代码位置的、指令内容的、变量的等等。
 
  拿到它的目的其实就是找到import 对应节点下的引入其他模块的路径
 
  通过 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)
 
  刷新浏览器,打印结果如下:
 
  node如何链接多个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('文件创建完成')
 
              }
 
          })
 
      }
 
  })
 

(编辑:广西网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!