您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“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)
刷新瀏覽器,打印結果如下:
可以看到,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如何鏈接多個JS模塊”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。