文章首發於:github.com/USTB-musion…javascript
模塊化簡單來講就是是指把一個複雜的系統分解到多個模塊以方便編碼。JS模塊化的大體流程爲:CommonJS(服務端) -> AMD(瀏覽器端)-> UMD(兼容了CommonJS和AMD) -> ES Module(ES6標準)。本文將從它們的用法進行介紹,簡單實現其原理。並簡易實現一個模塊化打包工具webpack。html
本文將從如下幾部分進行總結:前端
CommonJS 是一種使用普遍的JavaScript模塊化規範,核心思想是經過require方法來同步地加載依賴的其餘模塊,經過 module.exports 導出須要暴露的接口。java
採用CommonJS來進行導入導出的代碼用法以下:node
// 用require方法導入
const someFun= require('./moduleA');
someFun();
// 用module.exports導出
module.exports = someFunc;
複製代碼
a.js:webpack
console.log('aaa');
exports.name = '這是a模塊的內容';
複製代碼
b.js:git
let fs = require('fs');
let path = require('path');
let b = req('./a.js');
// req即便CommonJS中的require方法
function req(mod) {
let filename = path.join(__dirname, mod);
let content = fs.readFileSync(filename, 'utf8');
/**
* 最後一個參數是函數的內容體,至關於如下函數
*function fn(exports, module, require, __dirname, __filename) {
* module.exports = '這是另一個文件導出的內容'
* return module.exports
*}
*/
let fn = new Function('exports', 'require', 'module', '__filename', '__dirname', content + '\n return module.exports;');
let module = {
exports: {}
};
return fn(module.exports, req, module, __filename, __dirname);
}
複製代碼
AMD 也是一種 JavaScript 模塊化規範,與 CommonJS 最大的不一樣在於它採用異步的方式去加載依賴的模塊。 AMD 規範主要是爲了解決針對瀏覽器環境的模塊化問題,最具表明性的實現是 requirejs。github
// 使用define方法定義一個模塊
define('a', [], function () {
return 'a';
});
define('b', ['a'], function (a) {
return a + 'b';
});
// 使用require來導入和使用
require(['b'], function (b) {
console.log(b);
});
複製代碼
let factories = {};
/**
* 實現AMD的define方法
* @param moduleName 模塊的名字
* @param dependencies 依賴
* @param factory 工廠函數
*/
function define(modName, dependencies, factory) {
factory.dependencies = dependencies;
factories[modName] = factory;
}
/**
* 實現AMD的require方法
* @param mods 引入的模塊
* @param callback 回調函數
*/
function require(modNames, callback) {
let loadedModNames = modNames.map(function (modName) {
let factory = factories[modName];
let dependencies = factory.dependencies;
let exports;
require(dependencies, function (...dependencyMods) {
exports = factory.apply(null, dependencyMods);
});
return exports;
})
callback.apply(null, loadedModNames);
}
複製代碼
ES2015 模塊化是ECMA提出的JavaScript模塊化規範,它在語言的層面上實現了模塊化。瀏覽器廠商和Node.js 都宣佈要原生支持該規範。它將逐漸取代CommonJS和AMD規範,成爲瀏覽器和服務器通用的模塊解決方案。 採用 ES2015 模塊化導入及導出時的代碼以下:web
// 使用import導入
import { name } from './person.js';
// 使用export導出
export const name = 'musion';
複製代碼
ES2015模塊雖然是終極模塊化方案,但它的缺點在於目前沒法直接運行在大部分 JavaScript 運行環境下,必須經過構建工具轉換成標準的 ES5 後才能正常運行。編程
自動化構建就是作這件事情,把源代碼轉換成發佈到線上的可執行 JavaScrip、CSS、HTML 代碼,包括以下內容:
webpack 是一個打包模塊化 JavaScript 的工具,在 webpack 裏一切文件皆模塊,經過 Loader 轉換文件,經過 Plugin 注入鉤子,最後輸出由多個模塊組合成的文件。Webpack 專一於構建模塊化項目。
一切文件: JavaScript、CSS、SCSS、圖片、模板,在 Webpack 眼中都是一個個模塊,這樣的好處是能清晰的描述出各個模塊之間的依賴關係,以方便 Webpack 對模塊進行組合和打包。 通過 Webpack 的處理,最終會輸出瀏覽器能使用的靜態資源。
用webpack來進行打包構建的時候,查看打包📦以後的文件,刪掉多餘的代碼,剩下的核心代碼以下:
(function (modules) {
function require(moduleId) {
var module = {
exports: {}
};
// moduleId -> 模塊的名字
modules[moduleId].call(module.exports, module, module.exports, require);
return module.exports;
}
return require("./index.js");
}) ({
"./index.js":
(function (module, exports, require) {
eval("console.log('hello');\n\n");
})
});
複製代碼
#! /usr/bin/env node
// 這個文件用來描述如何打包
const pathLib = require('path');
const fs = require('fs');
let ejs = require('ejs');
let cwd = process.cwd();
let { entry, output: { filename, path } } = require(pathLib.join(cwd, './webpack.config.js'));
let script = fs.readFileSync(entry, 'utf8');
let bundle = `
(function (modules) {
function require(moduleId) {
var module = {
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, require);
return module.exports;
}
return require("<%-entry%>");
})
({
"<%-entry%>":
(function (module, exports, require) {
eval("<%-script%>");
})
});
`
let bundlejs = ejs.render(bundle, {
entry,
script
});
try {
fs.writeFileSync(pathLib.join(path, filename), bundlejs);
} catch (e) {
console.error('編譯失敗 ', e);
}
console.log('compile sucessfully!');
複製代碼
#! /usr/bin/env node
// 這個文件用來描述如何打包
const pathLib = require('path');
const fs = require('fs');
let ejs = require('ejs');
let cwd = process.cwd();
let { entry, output: { filename, path } } = require(pathLib.join(cwd, './webpack.config.js'));
let script = fs.readFileSync(entry, 'utf8');
let modules = [];
script.replace(/require\(['"](.+?)['"]\)/g, function () { let name = arguments[1]; let script = fs.readFileSync(name, 'utf8'); modules.push({ name, script }); }); let bundle = ` (function (modules) { function require(moduleId) { var module = { exports: {} }; modules[moduleId].call(module.exports, module, module.exports, require); return module.exports; } return require("<%-entry%>"); }) ({ "<%-entry%>": (function (module, exports, require) { eval(\`<%-script%>\`); }) <%if(modules.length>0){%>,<%}%> <%for(let i=0;i<modules.length;i++){ let module = modules[i];%> "<%-module.name%>": (function (module, exports, require) { eval(\`<%-module.script%>\`); }) <% }%> }); ` let bundlejs = ejs.render(bundle, { entry, script, modules }); try { fs.writeFileSync(pathLib.join(path, filename), bundlejs); } catch (e) { console.error('編譯失敗 ', e); } console.log('compile sucessfully!'); 複製代碼