浅析node怎样链接多个JS模块
时间:2023-02-07 17:52
有时候是不是会有这样的疑问:纷繁的功能文件,到最后是怎么组合成起来并且在浏览器中展示的?为什么需要 node 环境?下面本篇文章给大家介绍一下node是怎样把多个JS模块链接在一起的?希望对大家有所帮助! 浏览器本身只能做一些展示及用户交互的功能,对于系统操作的能力很有限,那么,浏览器内置的运行环境显然不满足一些更为人性化的开发模式,比如:更好的区分功能模块、实现文件的操作。那么,带来的缺陷就很明显,比如:各个 JS 文件比较分散,需要在 html 页面里面单独引入,如果某个 JS 文件需要其他的 JS 库,那么很可能会因为 html 页面未引入而报错,在功能庞大的项目里,手动的管理这些功能文件确实让人有点捉襟见肘。 那么, node 到底是怎么更友好的提供开发的呢?其实,上面也说了,人为的管理文件依赖不但会消耗大量的精力,还会存在疏漏,那么,是不是以用自动化的方式进行管理就会好很多?是的,在 node 的运行环境里拓宽了对系统的操作能力,也就是说,或许以前开发者也想通过一些代码来完成那些机械琐碎的工作,但是,只有想法没有操作权限,最后只能望洋兴叹。现在,可以以 node 的一些扩展功能对文件进行先前加工与整理,再添加一些自动化的代码,最后转换为一个浏览器可识别的、完整的 JS 文件,这样一来,多个文件的内容,便可以汇集到一个文件。【相关教程推荐:nodejs视频教程、编程教学】 先创建一些 JS 文件,如下图所示: 这些文件都是手动创建,babel-core 这个文件是从全局的 node_modules 里面复制出来的,如下图所示: 为什么要复制出来呢?这是因为,任何脚手架干的事其实都是为了快速搭建,但是,怎么能理解它干的什么事呢?那干脆就直接复制吧,本身,node 除了一些内置的模块,其他的都需要通过指明 require 路径的方式来找到相关模块,如下图所示: 通过 require('./babel-core') 方法,解析一个功能模块下的方法。 1、编写入口文件,转换ES6代码 entrance.js 作为入口文件,作用就是设定工作从哪开始?怎么开始?那么,这里的工作指的就是转换ES6代码,以提供浏览器使用。 上面的代码很简单,最终的目的就是将 module 类型的 person.js 文件转换为 ES5。 终端运行入口文件,如下所示: 打印一下代码,如下图所示: 可以,看到打印的代码,里面都是浏览器能识别的代码,按照常理,看看能不能直接运行一下? 下面将这段代码通过 fs 功能写入一个 js 文件并让一个页面引用,来看看效果: 再次运行命令,如图所示: 浏览器运行结构,如图所示: 其实代码生成完就有很明显的错误,未声明变量,怎么会不报错呢?这时候,在入口文件输入之前就需要添加一些自定义辅助代码,来解决一下这个报错。 解决的方式也很简单,将原 code 的未声明的 exports 变量通过自执行函数的方式包裹一下,再返回给指定对象。 看一下输出完善后的 main.js 文件 浏览器运行,如图所示: 现在浏览器运行正常了。 2、处理 import 逻辑 既然是模块,肯定会存在一个模块依赖另一个或其他很多个模块的情况。这里先不着急,先看看person 模块引入单一 animal 模块后的代码是怎样的? animal 模块很简单,仅仅是一个对象导出 person 模块引入 看下转换后的代码 可以看到,转换后会多一个未声明的 require 方法,内部声明的 _interopRequireDefault 方法已声明,是对 animal 导出部分进行了一个包裹,让其后续的代码取值 default 的时候保证其属性存在! 下面就需要对 require 方法进行相关的处理,让其转为返回一个可识别、可解析、完整的对象。 是不是可以将之前的逻辑对 animal 模块重新执行一遍获取到 animal 的代码转换后的对象就行了? 但是,这里先要解决一个问题,就是对于 animal 模块的路径需要提前获取并进行代码转换,这时候给予可以利用 babel-traverse 工具对 AST 进行处理。 说到这里,先看一下 JS 转换为 AST 是什么内容? 这里简单放一张截图,其实是一个 JSON 对象,存储着相关的代码信息,有代码位置的、指令内容的、变量的等等。 拿到它的目的其实就是找到import 对应节点下的引入其他模块的路径 通过 babel-traverse 找到 AST 里面 import 对应的信息 输出看下节点打印的结构 可以看到 node.source.value 就是 animal 模块的路径,需要的就是它。 扩展入口文件功能,解析 import 下的 JS 模块, 添加 require 方法 这样转换完的 main.js 给不会报错了,但是,这里需要解决怎么让 require 方法返回 animal 对象 下面就需要添加 require 方法进行 animal 对象的返回逻辑 上面的代码其实没有什么特别难理解的部分,里面的自执行闭包看着乱,最终的目的也很清晰,就是找到对应模块下的文件 code 代码进行自运行返回一个对应的模块对象即可。 看下转换后的 main.js 代码 刷新浏览器,打印结果如下: 可以看到,pet 属性被赋予了新值。 古代钻木取火远比现代打火机烤面包的意义深远的多。这个世界做过的事情没有对或错之分,但有做与不做之别。代码拙劣,大神勿笑[抱拳][抱拳][抱拳] 更多node相关知识,请访问:nodejs 教程! 以上就是浅析node怎样链接多个JS模块的详细内容,更多请关注gxlsystem.com其它相关文章!一、个人理解
二、创建文件
//文件管理模块
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}`)
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.mkdir('cache',(err)=>{
if(!err){
fs.writeFile('cache/main.js',code,(err)=>{
if(!err){
console.log('文件创建完成')
}
})
}
})
//完善不严谨的code代码
function perfectCode(code){
let exportsCode = `
var exports = (function(exports){
${code}
return exports
})({})
console.log(exports.default)`
return exportsCode
}
//重新定义code
code = perfectCode(code)
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)
let animal = {name:'dog'}
export default animal
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;
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'
}
}
//完善代码
function perfectCode(code){
let exportsCode = `
//添加require方法
let require = function(path){
return {}
}
let exports = (function(exports,require){
${code}
return exports
})({},require)
`
return exportsCode
}
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)
//引入模块路径
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
}
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)
三、完整的入口文件代码
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('文件创建完成')
}
})
}
})
四、总结与思考