Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UMD通用模块定义

UMD(Universal Module Definition,通用模块定义)是一种JavaScript模块模式,旨在创建可以在多种环境中工作的模块。它结合了CommonJS、AMD和全局变量模式的特点,使模块能够在Node.js、浏览器和AMD加载器中都能正常工作。

什么是UMD

UMD是一种模块包装器模式,具有以下特点:

  • 跨平台兼容: 同一个模块可以在不同环境中使用
  • 自动检测: 自动检测当前环境并选择合适的模块系统
  • 向后兼容: 支持旧版本浏览器和旧的模块系统
  • 无依赖: 不需要额外的加载器或构建工具
  • 灵活性: 可以根据需要定制检测逻辑

UMD基本模式

1. 标准UMD模式

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD环境
        define(['dependency1', 'dependency2'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // Node.js/CommonJS环境
        module.exports = factory(require('dependency1'), require('dependency2'));
    } else {
        // 浏览器全局变量环境
        root.MyModule = factory(root.Dependency1, root.Dependency2);
    }
}(typeof self !== 'undefined' ? self : this, function (dependency1, dependency2) {
    'use strict';
    
    // 模块实现
    function MyModule() {
        // 构造函数
    }
    
    MyModule.prototype.method1 = function() {
        return 'method1 result';
    };
    
    MyModule.prototype.method2 = function() {
        return dependency1.someFunction() + dependency2.someOtherFunction();
    };
    
    // 返回模块
    return MyModule;
}));

2. 简化版UMD

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global = global || self, global.MyModule = factory());
}(this, function () {
    'use strict';
    
    // 模块实现
    var MyModule = {
        version: '1.0.0',
        
        init: function() {
            console.log('MyModule initialized');
        },
        
        destroy: function() {
            console.log('MyModule destroyed');
        }
    };
    
    return MyModule;
}));

3. 无依赖UMD模式

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define([], factory);
    } else if (typeof exports === 'object') {
        // Node.js
        module.exports = factory();
    } else {
        // 浏览器全局变量
        root.Utils = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    
    var Utils = {
        // 类型检查工具
        isArray: Array.isArray || function(obj) {
            return Object.prototype.toString.call(obj) === '[object Array]';
        },
        
        isObject: function(obj) {
            return obj !== null && typeof obj === 'object' && !this.isArray(obj);
        },
        
        isFunction: function(obj) {
            return typeof obj === 'function';
        },
        
        // 对象工具
        extend: function(target) {
            var sources = Array.prototype.slice.call(arguments, 1);
            sources.forEach(function(source) {
                for (var key in source) {
                    if (source.hasOwnProperty(key)) {
                        target[key] = source[key];
                    }
                }
            });
            return target;
        },
        
        clone: function(obj) {
            if (!this.isObject(obj)) return obj;
            var cloned = {};
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    cloned[key] = this.isObject(obj[key]) ? this.clone(obj[key]) : obj[key];
                }
            }
            return cloned;
        },
        
        // 数组工具
        unique: function(array) {
            var result = [];
            for (var i = 0; i < array.length; i++) {
                if (result.indexOf(array[i]) === -1) {
                    result.push(array[i]);
                }
            }
            return result;
        },
        
        // 字符串工具
        camelCase: function(str) {
            return str.replace(/-([a-z])/g, function(match, letter) {
                return letter.toUpperCase();
            });
        },
        
        kebabCase: function(str) {
            return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
        }
    };
    
    return Utils;
}));

实际应用示例

1. 数学工具库

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.MathUtils = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    'use strict';
    
    var MathUtils = {
        // 常量
        PI: Math.PI,
        E: Math.E,
        
        // 基础运算
        add: function(a, b) {
            return a + b;
        },
        
        subtract: function(a, b) {
            return a - b;
        },
        
        multiply: function(a, b) {
            return a * b;
        },
        
        divide: function(a, b) {
            if (b === 0) throw new Error('Division by zero');
            return a / b;
        },
        
        // 高级运算
        power: function(base, exponent) {
            return Math.pow(base, exponent);
        },
        
        sqrt: function(n) {
            if (n < 0) throw new Error('Cannot calculate square root of negative number');
            return Math.sqrt(n);
        },
        
        factorial: function(n) {
            if (n < 0) throw new Error('Cannot calculate factorial of negative number');
            if (n === 0 || n === 1) return 1;
            var result = 1;
            for (var i = 2; i <= n; i++) {
                result *= i;
            }
            return result;
        },
        
        // 几何计算
        circleArea: function(radius) {
            return this.PI * radius * radius;
        },
        
        circleCircumference: function(radius) {
            return 2 * this.PI * radius;
        },
        
        rectangleArea: function(width, height) {
            return width * height;
        },
        
        triangleArea: function(base, height) {
            return 0.5 * base * height;
        },
        
        // 统计函数
        average: function(numbers) {
            if (!Array.isArray(numbers) || numbers.length === 0) {
                throw new Error('Input must be a non-empty array');
            }
            var sum = numbers.reduce(function(acc, num) {
                return acc + num;
            }, 0);
            return sum / numbers.length;
        },
        
        median: function(numbers) {
            if (!Array.isArray(numbers) || numbers.length === 0) {
                throw new Error('Input must be a non-empty array');
            }
            var sorted = numbers.slice().sort(function(a, b) { return a - b; });
            var middle = Math.floor(sorted.length / 2);
            
            if (sorted.length % 2 === 0) {
                return (sorted[middle - 1] + sorted[middle]) / 2;
            } else {
                return sorted[middle];
            }
        },
        
        max: function(numbers) {
            if (!Array.isArray(numbers) || numbers.length === 0) {
                throw new Error('Input must be a non-empty array');
            }
            return Math.max.apply(Math, numbers);
        },
        
        min: function(numbers) {
            if (!Array.isArray(numbers) || numbers.length === 0) {
                throw new Error('Input must be a non-empty array');
            }
            return Math.min.apply(Math, numbers);
        }
    };
    
    return MathUtils;
}));

2. 事件发射器

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.EventEmitter = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    'use strict';
    
    function EventEmitter() {
        this._events = {};
        this._maxListeners = 10;
    }
    
    EventEmitter.prototype = {
        constructor: EventEmitter,
        
        // 添加事件监听器
        on: function(event, listener) {
            if (typeof listener !== 'function') {
                throw new TypeError('Listener must be a function');
            }
            
            if (!this._events[event]) {
                this._events[event] = [];
            }
            
            this._events[event].push(listener);
            
            // 检查监听器数量
            if (this._events[event].length > this._maxListeners) {
                console.warn('MaxListenersExceededWarning: Possible memory leak detected. ' +
                    this._events[event].length + ' ' + event + ' listeners added.');
            }
            
            return this;
        },
        
        // 添加一次性事件监听器
        once: function(event, listener) {
            if (typeof listener !== 'function') {
                throw new TypeError('Listener must be a function');
            }
            
            var self = this;
            function onceWrapper() {
                listener.apply(this, arguments);
                self.removeListener(event, onceWrapper);
            }
            
            onceWrapper.listener = listener;
            return this.on(event, onceWrapper);
        },
        
        // 移除事件监听器
        removeListener: function(event, listener) {
            if (typeof listener !== 'function') {
                throw new TypeError('Listener must be a function');
            }
            
            if (!this._events[event]) {
                return this;
            }
            
            var listeners = this._events[event];
            for (var i = listeners.length - 1; i >= 0; i--) {
                if (listeners[i] === listener || listeners[i].listener === listener) {
                    listeners.splice(i, 1);
                    break;
                }
            }
            
            if (listeners.length === 0) {
                delete this._events[event];
            }
            
            return this;
        },
        
        // 移除所有监听器
        removeAllListeners: function(event) {
            if (event) {
                delete this._events[event];
            } else {
                this._events = {};
            }
            return this;
        },
        
        // 触发事件
        emit: function(event) {
            if (!this._events[event]) {
                return false;
            }
            
            var listeners = this._events[event].slice();
            var args = Array.prototype.slice.call(arguments, 1);
            
            for (var i = 0; i < listeners.length; i++) {
                try {
                    listeners[i].apply(this, args);
                } catch (error) {
                    console.error('Error in event listener:', error);
                }
            }
            
            return true;
        },
        
        // 获取监听器列表
        listeners: function(event) {
            return this._events[event] ? this._events[event].slice() : [];
        },
        
        // 获取监听器数量
        listenerCount: function(event) {
            return this._events[event] ? this._events[event].length : 0;
        },
        
        // 设置最大监听器数量
        setMaxListeners: function(n) {
            if (typeof n !== 'number' || n < 0) {
                throw new TypeError('n must be a non-negative number');
            }
            this._maxListeners = n;
            return this;
        },
        
        // 获取最大监听器数量
        getMaxListeners: function() {
            return this._maxListeners;
        }
    };
    
    // 静态方法
    EventEmitter.listenerCount = function(emitter, event) {
        return emitter.listenerCount(event);
    };
    
    return EventEmitter;
}));

3. HTTP客户端

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.HttpClient = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {
    'use strict';
    
    // 检测环境
    var isNode = typeof module === 'object' && module.exports;
    var XMLHttpRequest = isNode ? null : (
        typeof XMLHttpRequest !== 'undefined' ? XMLHttpRequest :
        typeof ActiveXObject !== 'undefined' ? function() {
            return new ActiveXObject('Microsoft.XMLHTTP');
        } : null
    );
    
    function HttpClient(baseURL, defaultOptions) {
        this.baseURL = baseURL || '';
        this.defaultOptions = defaultOptions || {};
        this.interceptors = {
            request: [],
            response: []
        };
    }
    
    HttpClient.prototype = {
        constructor: HttpClient,
        
        // 请求方法
        request: function(options) {
            var self = this;
            
            // 合并配置
            var config = this._mergeConfig(options);
            
            // 应用请求拦截器
            config = this._applyRequestInterceptors(config);
            
            if (isNode) {
                return this._nodeRequest(config);
            } else {
                return this._browserRequest(config);
            }
        },
        
        // GET请求
        get: function(url, options) {
            return this.request(this._extend({ method: 'GET', url: url }, options));
        },
        
        // POST请求
        post: function(url, data, options) {
            return this.request(this._extend({ method: 'POST', url: url, data: data }, options));
        },
        
        // PUT请求
        put: function(url, data, options) {
            return this.request(this._extend({ method: 'PUT', url: url, data: data }, options));
        },
        
        // DELETE请求
        delete: function(url, options) {
            return this.request(this._extend({ method: 'DELETE', url: url }, options));
        },
        
        // 浏览器环境请求
        _browserRequest: function(config) {
            var self = this;
            
            return new Promise(function(resolve, reject) {
                var xhr = new XMLHttpRequest();
                
                xhr.open(config.method, self._buildURL(config.url), true);
                
                // 设置请求头
                if (config.headers) {
                    for (var header in config.headers) {
                        xhr.setRequestHeader(header, config.headers[header]);
                    }
                }
                
                // 设置超时
                if (config.timeout) {
                    xhr.timeout = config.timeout;
                }
                
                xhr.onreadystatechange = function() {
                    if (xhr.readyState === 4) {
                        var response = {
                            data: self._parseResponse(xhr.responseText, xhr.getResponseHeader('Content-Type')),
                            status: xhr.status,
                            statusText: xhr.statusText,
                            headers: self._parseHeaders(xhr.getAllResponseHeaders()),
                            config: config
                        };
                        
                        response = self._applyResponseInterceptors(response);
                        
                        if (xhr.status >= 200 && xhr.status < 300) {
                            resolve(response);
                        } else {
                            reject(new Error('Request failed with status ' + xhr.status));
                        }
                    }
                };
                
                xhr.onerror = function() {
                    reject(new Error('Network Error'));
                };
                
                xhr.ontimeout = function() {
                    reject(new Error('Request Timeout'));
                };
                
                // 发送请求
                var data = config.data ? self._serializeData(config.data) : null;
                xhr.send(data);
            });
        },
        
        // Node.js环境请求
        _nodeRequest: function(config) {
            var self = this;
            
            return new Promise(function(resolve, reject) {
                var http = require('http');
                var https = require('https');
                var url = require('url');
                
                var parsedUrl = url.parse(self._buildURL(config.url));
                var isSecure = parsedUrl.protocol === 'https:';
                var client = isSecure ? https : http;
                
                var requestOptions = {
                    hostname: parsedUrl.hostname,
                    port: parsedUrl.port || (isSecure ? 443 : 80),
                    path: parsedUrl.path,
                    method: config.method,
                    headers: config.headers || {}
                };
                
                var req = client.request(requestOptions, function(res) {
                    var data = '';
                    
                    res.on('data', function(chunk) {
                        data += chunk;
                    });
                    
                    res.on('end', function() {
                        var response = {
                            data: self._parseResponse(data, res.headers['content-type']),
                            status: res.statusCode,
                            statusText: res.statusMessage,
                            headers: res.headers,
                            config: config
                        };
                        
                        response = self._applyResponseInterceptors(response);
                        
                        if (res.statusCode >= 200 && res.statusCode < 300) {
                            resolve(response);
                        } else {
                            reject(new Error('Request failed with status ' + res.statusCode));
                        }
                    });
                });
                
                req.on('error', function(error) {
                    reject(error);
                });
                
                if (config.timeout) {
                    req.setTimeout(config.timeout, function() {
                        req.abort();
                        reject(new Error('Request Timeout'));
                    });
                }
                
                // 发送数据
                if (config.data) {
                    req.write(self._serializeData(config.data));
                }
                
                req.end();
            });
        },
        
        // 添加拦截器
        addRequestInterceptor: function(interceptor) {
            this.interceptors.request.push(interceptor);
        },
        
        addResponseInterceptor: function(interceptor) {
            this.interceptors.response.push(interceptor);
        },
        
        // 辅助方法
        _mergeConfig: function(options) {
            return this._extend({}, this.defaultOptions, options);
        },
        
        _buildURL: function(url) {
            if (url.indexOf('http') === 0) {
                return url;
            }
            return this.baseURL + url;
        },
        
        _extend: function(target) {
            var sources = Array.prototype.slice.call(arguments, 1);
            sources.forEach(function(source) {
                for (var key in source) {
                    if (source.hasOwnProperty(key)) {
                        target[key] = source[key];
                    }
                }
            });
            return target;
        },
        
        _serializeData: function(data) {
            if (typeof data === 'string') return data;
            return JSON.stringify(data);
        },
        
        _parseResponse: function(data, contentType) {
            if (contentType && contentType.indexOf('application/json') !== -1) {
                try {
                    return JSON.parse(data);
                } catch (e) {
                    return data;
                }
            }
            return data;
        },
        
        _parseHeaders: function(headerStr) {
            var headers = {};
            if (!headerStr) return headers;
            
            headerStr.split('\r\n').forEach(function(line) {
                var parts = line.split(': ');
                if (parts.length === 2) {
                    headers[parts[0].toLowerCase()] = parts[1];
                }
            });
            
            return headers;
        },
        
        _applyRequestInterceptors: function(config) {
            var result = config;
            this.interceptors.request.forEach(function(interceptor) {
                result = interceptor(result);
            });
            return result;
        },
        
        _applyResponseInterceptors: function(response) {
            var result = response;
            this.interceptors.response.forEach(function(interceptor) {
                result = interceptor(result);
            });
            return result;
        }
    };
    
    // 静态方法
    HttpClient.create = function(baseURL, defaultOptions) {
        return new HttpClient(baseURL, defaultOptions);
    };
    
    return HttpClient;
}));

UMD变体和优化

1. 返回工厂函数的UMD

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['jquery'], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory(require('jquery'));
    } else {
        root.MyPlugin = factory(root.jQuery);
    }
}(typeof self !== 'undefined' ? self : this, function ($) {
    'use strict';
    
    // 返回工厂函数而不是构造函数
    return function(options) {
        var defaults = {
            width: 300,
            height: 200,
            animation: true
        };
        
        var settings = $.extend({}, defaults, options);
        
        return {
            init: function() {
                console.log('Plugin initialized with settings:', settings);
            },
            
            destroy: function() {
                console.log('Plugin destroyed');
            },
            
            getSettings: function() {
                return settings;
            }
        };
    };
}));

2. 支持ES6模块的UMD

(function (global, factory) {
    if (typeof exports === 'object' && typeof module !== 'undefined') {
        // CommonJS
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD
        define(factory);
    } else {
        // 浏览器全局变量
        (global = global || self).MyModule = factory();
    }
}(this, function () {
    'use strict';
    
    class MyClass {
        constructor(options = {}) {
            this.options = Object.assign({
                debug: false,
                timeout: 5000
            }, options);
        }
        
        async fetchData(url) {
            try {
                const response = await fetch(url);
                return await response.json();
            } catch (error) {
                if (this.options.debug) {
                    console.error('Fetch error:', error);
                }
                throw error;
            }
        }
        
        processData(data) {
            return data.map(item => ({
                ...item,
                processed: true,
                timestamp: Date.now()
            }));
        }
    }
    
    // 同时支持类和工厂函数
    MyClass.create = function(options) {
        return new MyClass(options);
    };
    
    return MyClass;
}));

3. 懒加载UMD

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['exports'], factory);
    } else if (typeof exports === 'object' && typeof module !== 'undefined') {
        factory(exports);
    } else {
        factory((root.LazyModule = {}));
    }
}(typeof self !== 'undefined' ? self : this, function (exports) {
    'use strict';
    
    var modules = {};
    var loading = {};
    
    function loadModule(name, factory) {
        if (!modules[name]) {
            modules[name] = {
                loaded: false,
                factory: factory,
                exports: null
            };
        }
    }
    
    function getModule(name) {
        var module = modules[name];
        if (!module) {
            throw new Error('Module not found: ' + name);
        }
        
        if (!module.loaded) {
            module.exports = module.factory();
            module.loaded = true;
        }
        
        return module.exports;
    }
    
    async function getModuleAsync(name) {
        if (loading[name]) {
            return loading[name];
        }
        
        if (modules[name] && modules[name].loaded) {
            return modules[name].exports;
        }
        
        loading[name] = new Promise(function(resolve) {
            setTimeout(function() {
                resolve(getModule(name));
                delete loading[name];
            }, 0);
        });
        
        return loading[name];
    }
    
    // 预定义一些模块
    loadModule('utils', function() {
        return {
            formatDate: function(date) {
                return date.toISOString().split('T')[0];
            },
            generateId: function() {
                return Math.random().toString(36).substr(2, 9);
            }
        };
    });
    
    loadModule('validator', function() {
        return {
            isEmail: function(email) {
                return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
            },
            isUrl: function(url) {
                try {
                    new URL(url);
                    return true;
                } catch {
                    return false;
                }
            }
        };
    });
    
    // 导出API
    exports.load = loadModule;
    exports.get = getModule;
    exports.getAsync = getModuleAsync;
    exports.list = function() {
        return Object.keys(modules);
    };
}));

构建工具支持

1. Webpack UMD输出

// webpack.config.js
module.exports = {
    entry: './src/index.js',
    output: {
        path: __dirname + '/dist',
        filename: 'my-library.js',
        library: 'MyLibrary',
        libraryTarget: 'umd',
        globalObject: 'typeof self !== "undefined" ? self : this'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    }
};

2. Rollup UMD输出

// rollup.config.js
import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';

export default {
    input: 'src/index.js',
    output: {
        file: 'dist/my-library.umd.js',
        format: 'umd',
        name: 'MyLibrary',
        globals: {
            'lodash': '_',
            'jquery': '$'
        }
    },
    external: ['lodash', 'jquery'],
    plugins: [
        babel({
            exclude: 'node_modules/**',
            babelHelpers: 'bundled'
        }),
        terser()
    ]
};

测试UMD模块

1. 多环境测试

// test/umd-test.js
(function() {
    'use strict';
    
    // 测试不同环境
    function testEnvironments() {
        var results = {
            amd: false,
            commonjs: false,
            global: false
        };
        
        // 模拟AMD环境
        if (typeof define === 'undefined') {
            global.define = function(deps, factory) {
                results.amd = typeof factory() === 'object';
            };
            global.define.amd = true;
            
            // 重新加载模块
            // 这里需要重新执行UMD模块代码
            
            delete global.define;
        }
        
        // 模拟CommonJS环境
        if (typeof module === 'undefined') {
            global.module = { exports: {} };
            global.exports = global.module.exports;
            
            // 重新加载模块
            // 这里需要重新执行UMD模块代码
            
            results.commonjs = typeof global.module.exports === 'object';
            
            delete global.module;
            delete global.exports;
        }
        
        // 测试全局变量环境
        // 重新加载模块
        // 这里需要重新执行UMD模块代码
        results.global = typeof global.MyModule === 'object';
        
        return results;
    }
    
    // 功能测试
    function testFunctionality(module) {
        var tests = {
            instantiation: false,
            methods: false,
            properties: false
        };
        
        try {
            var instance = new module();
            tests.instantiation = true;
            
            if (typeof instance.method1 === 'function') {
                tests.methods = true;
            }
            
            if (instance.hasOwnProperty('someProperty')) {
                tests.properties = true;
            }
        } catch (error) {
            console.error('Test error:', error);
        }
        
        return tests;
    }
    
    console.log('UMD Environment Tests:', testEnvironments());
    // console.log('UMD Functionality Tests:', testFunctionality(MyModule));
})();

优缺点分析

优点

  • 跨平台兼容: 同一代码可在多种环境中使用
  • 无构建依赖: 不需要额外的构建步骤
  • 向后兼容: 支持旧版本浏览器和模块系统
  • 灵活部署: 可以选择最适合的分发方式
  • 简单易用: 使用标准JavaScript语法

缺点

  • 代码冗余: 包装代码增加了文件大小
  • 复杂性: 模块包装逻辑较为复杂
  • 性能开销: 运行时环境检测有微小开销
  • 调试困难: 包装器可能影响调试体验
  • 维护负担: 需要考虑多种环境的兼容性

现代替代方案

1. 构建时转换

// 源码使用ES模块
export class MyClass {
    constructor() {
        // ...
    }
}

export default MyClass;

// 构建工具自动生成UMD版本

2. 条件导出

{
  "name": "my-package",
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "browser": "./dist/index.umd.js",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "browser": "./dist/index.umd.js"
    }
  }
}

总结

UMD作为过渡期的解决方案,解决了模块系统碎片化的问题:

  • 🎯 历史价值: 在模块标准化过程中发挥了重要作用
  • 🎯 实用性: 对于需要广泛兼容的库仍然有用
  • 🎯 现状: 逐渐被ES模块和现代构建工具取代

虽然UMD在现代开发中使用频率降低,但理解其设计原理对于构建跨平台JavaScript库和理解模块化发展历程仍然很有价值。随着ES模块的普及和构建工具的完善,UMD正在从“必需品“转向“兼容性选项“。


下一章: 模块化工具与实践