前端模块化CommonJs、ESM、AMD总结

前端开发模式进化史

前端工程化正是为了应对这些演化中出现的挑战和需求而发展起来的:

  1. 前后端混合:服务端渲染,javascript仅实现交互
  2. 前后端分离:借助 ajax 实现前后端分离、单页应用(SPA)等新模式
  3. 模块化开发:npm 管理模块、Webpack 编译打包资源
  4. 模块化 + MVVM:基于 React 或 Vue 等框架进行组件化开发,不再手动操作 html 元素

模块化思想

模块化就是把逻辑代码拆分成独立的块,各自封装、互相独立,每个块自行决定对外暴露什么,同时自行决定引入执行哪些外部代码。前端工程化工具和模块化开发使前端项目更易于管理,避免依赖混乱,促进代码的重用和维护
前端模块化是前端工程化的一个重要组成部分,前者关注代码的组织和结构,而后者关注整个前端开发过程的自动化和最佳实践。前端工程化借助前端模块化来提高代码的组织和可维护性,从而解决前端开发中的一系列问题。

CommonJs

Node.js 实现了 CommonJS 规范,如果前端要使用,需要用browserify库 来进行转化。

//a.js 导出
module.exports = {
  name: 'ysl',
  age: '27'
}
//导入
let obj = require('./a.js')
console.log(obj)

或者

//a.js
export.name = 'ysl'
export.age = 27

注意:

  1. require第一次加载某个模块时,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。需要清除缓存delete require.cache[require.resolve('./a.js')]再引入。
  2. 引入时拷贝机制,引入输出之后,模块内部的变化影响不了原本已经引入的值。
  3. CommonJs加载是同步加载,如果在浏览器中某个模块加载时间太长,容易出现卡死现象。

AMD

AMD采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行

// moduleA.js
  define(function (){
    var add = function (x,y){
      return x+y;
    };
    return {
      add: add
    };
  });
// index.js
  require(['moduleA'], function (moduleA){
    console.log(moduleA)
       //moduleA就是moduleA.js模块传入的函数执行后返回的对象{add:function}
 });

UMD

通过对 CommonJs、CMD、AMD 进一步处理,它没有自己专有的规范,是集结了 CommonJs、CMD、AMD 的规范于一身。
它可以通过运行时或者编译时让同一个代码模块在使用 CommonJs、CMD 甚至是 AMD 的项目中运行。
未来同一个 JavaScript 包运行在浏览器端、服务区端都只需要遵守同一个写法就行了。

// UMD简单实现
((global, factory) => {
    //如果 当前的上下文有define函数,并且AMD  说明处于AMD 环境下
    if (typeof define === 'function' && define.amd) {
        define(["moduleA"], factory);
    }
    else if (typeof exports === 'object') {//commonjs
        let moduleA = require("moduleA")
        modules.exports = factory(moduleA)
    }
    else {
        global.moduleA = factory(global.moduleA) //直接挂载成 windows 全局变量 
    }
})(this, (moduleA) => {
    //本模块的定义
    return {
    }
})

ESM(ECMAScript Moudules)

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。不再需要UMD模块格式了,将来服务器和浏览器都会支持 ES6 模块格式。
es6模块的语法分为两部分:export 模块导出、 import模块导入

//export 导出
// profile.js 
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
//import 引入
// main.js 
import { firstName, lastName, year } from './profile.js';
function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

//或者用*指定一个对象
// main.js 
import * as total from './profile.js';
function setName(element) {
  element.textContent = total.firstName + ' ' + total.lastName;
}

用到export default命令,为模块指定默认输出。一个模块只能有一个默认输出,因此export default命令只能使用一次。

// profile.js 
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export default { firstName, lastName, year };

// main.js 
import total from './profile.js';
function setName(element) {
  element.textContent = total.firstName + ' ' + total.lastName;
}

注意:
export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

CommonsJs与ES6之间的差别

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJS 模块的require()同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

node和浏览器分别加载es6与commonJs的方式

node加载es6模块的方式

1、 ES6 模块采用.mjs后缀文件名。
2、 项目的package.json文件中,指定type字段为module

(注意:如果没有type字段,或者type字段为commonjs,则.js脚本会被解释成 CommonJS 模块。)

总结为一句话:.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CommonJS 模块加载,.js文件的加载取决于package.json里面type字段的设置。

node默认采用commonJs模块模块的方式
// a.js
module.exports = {name:'ysl}
// main.js
let obj = require('./a.js')
// node环境可以直接使用
浏览器加载es6模块方式

浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性。

<script type\="module" src\="./foo.js"\></script\>
// 浏览器对于带有\`type="module"\`的\`<script\>\`,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了\`<script\>\`标签的\`defer\`属性。
<script type\="module" src\="./foo.js" defer\></script\>
// 效果一致

通过<script>标签加载,可能会出现跨域的现象,可以使用express解决

浏览器加载CommonJs

浏览器直接加载CommonJs会报错,因为浏览器不存在module、exports、require这些环境变量,可以使用Browserify,对模块进行转换

参考
https://segmentfault.com/a/1190000039346572?sort=newest
https://juejin.cn/post/7291186181157535800

<think>好的,我需要回答用户关于前端模块化的概念、实现方法、框架、工具以及最佳实践的问题。首先,我要理解用户的需求,他们可能想知道前端模块化的基本概念,不同的实现方式,常用的框架和工具,以及在实践中应该遵循的最佳方法。 首先,我应该从模块化的概念开始解释。模块化是指将代码分割成独立、可复用的模块,这样可以提高代码的可维护性和复用性。需要引用相关的资料,比如站内引用中的[^1],其中提到模块化系统在前端开发中的重要性。 接下来是模块化的实现方式。需要涵盖不同的模块化规范,比如CommonJSAMD、ES Modules等。要详细说明每个规范的适用场景和特点。例如,CommonJS主要用于Node.js,而AMD适用于浏览器端的异步加载,ES Modules则是现代JavaScript的标准,被广泛支持。 然后是框架和工具部分。用户可能想知道主流框架如React、Vue、Angular如何处理模块化,以及构建工具如Webpack、Rollup、Vite的作用。需要提到这些工具如何支持模块打包、代码分割等功能,并引用相关的资料,比如和[^3],其中提到了模块化策略和工具的选择对项目的重要性。 最佳实践部分需要包括如何合理拆分模块、命名规范、依赖管理、Tree-shaking、按需加载等。还要结合引用中的内容,比如模块化对性能的影响,以及HTTP请求和API调用的最佳实践。 同时,用户可能希望了解如何选择合适的模块化策略和工具,可以引用[^2]提到的评估标准,比如项目需求、团队熟悉度、生态系统支持等因素。 需要注意按照用户的系统级指令,使用正确的中文回答,行内公式用$...$,独立公式用$$...$$,不过在这个问题中可能数学公式较少,但如果有的话要正确应用。同时生成相关问题,帮助用户进一步学习。 最后,确保回答结构清晰,分点说明,每个部分都有对应的引用标注,比如模块化的概念引用[^1],工具部分引用,最佳实践引用等。检查LaTeX语法是否正确,尽管这个问题可能涉及较少数学内容,但如果有的话需正确使用。 现在需要组织这些内容,确保符合用户的要求,并且自然流畅地引用相关资料。同时,生成的相关问题应该与模块化的深入主题相关,例如模块化的发展历程、不同规范的比较、性能优化方法等。</think>### 前端模块化详解 #### 一、模块化概念 模块化是将代码拆分为独立、可复用的单元(模块),每个模块封装特定功能,通过接口与其他模块通信。这一概念解决了代码冗余、全局污染、依赖管理混乱等问题,显著提升可维护性和协作效率。 #### 二、实现方式 1. **CommonJS** - 特点:同步加载,适用于Node.js环境 - 语法:`require()`引入,`module.exports`导出 - 示例: ```javascript // math.js const add = (a, b) => a + b; module.exports = { add }; // app.js const { add } = require('./math.js'); ``` 2. **AMD(Asynchronous Module Definition)** - 特点:浏览器端异步加载,避免阻塞 - 代表库:RequireJS - 示例: ```javascript define(['dep1', 'dep2'], function(dep1, dep2) { return { /* 模块内容 */ }; }); ``` 3. **ES Modules(ESM)** - 特点:现代浏览器原生支持,静态解析 - 语法:`import`/`export` - 示例: ```javascript // utils.js export const log = (msg) => console.log(msg); // main.js import { log } from './utils.js'; ``` #### 三、框架与工具 1. **框架支持** - **React**:通过`import`引入组件,结合Webpack处理依赖 - **Vue**:单文件组件(`.vue`)天然支持模块化 - **Angular**:内置依赖注入系统和模块化架构(`@NgModule`) 2. **构建工具** - **Webpack**:支持多种模块规范,提供代码分割、Tree-shaking - **Rollup**:专注于ESM的打包,适合库开发 - **Vite**:基于ESM的按需编译,极速热更新 #### 四、最佳实践 1. **模块拆分原则** - 单一职责:每个模块只做一件事 - 高内聚低耦合:内部紧密关联,外部接口简洁 2. **依赖管理** - 显式声明依赖(如`package.json`中的`dependencies`) - 避免循环引用 3. **性能优化** - **Tree-shaking**:移除未使用代码(需ESM静态结构) - **按需加载**:动态`import()`实现代码分割 - **缓存策略**:利用文件名哈希(如`[name].[hash].js`) 4. **代码规范** - 统一命名规则(如`camelCase`模块名) - 添加JSDoc注释说明模块功能 #### 五、工具链选择标准 根据项目规模、团队技术栈和部署需求评估: - 小型项目:Vite + ESM - 企业级应用:Webpack + TypeScript - 库开发:Rollup + ESM
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值