JavaScript UMD 插件编写规范
UMD 的实现很简单,先判断是否支持 Node.js 模块格式(exports 是否存在),存在则使用 Node.js 模块格式。 再判断是否支持 AMD(define 是否存在),存在则使用 AMD 方式加载模块。前两个都不存在,则将模块公开到全局(window 或 global)。
编写 UMD 时有一些注意事项,如果不注意,会导致生产环境报错。
如何兼容 CommonJS, AMD, CMD 和浏览器原生 JS
CommonJS
CommonJS
有三个全局变量 module
、exports
和 require
。但是由于 AMD
也有 require
这个全局变量,故不使用这个变量来进行检测。
如果想要对外提供接口的话,可以将接口绑定到 exports
(即 module.exports
) 上。
js
function MyModule() {
// ...
}
if (typeof module !== `undefined` && typeof exports === `object`) {
module.exports = MyModule;
}
CMD
CMD
规范中定义了 define
函数有一个公有属性 define.cmd
。
CMD
模块中有两种方式提供对外的接口,一种是 exports.MyModule = ...
,一种是使用 return
进行返回。
AMD
AMD
规范中,define
函数同样有一个公有属性 define.amd
。
AMD
中的参数便是这个模块的依赖。那么如何在 AMD
中提供接口呢?它是返回一个对象,这个对象就作为这个模块的接口,故我们可以这样写:
js
function MyModule() {
// ...
}
if (typeof define === `function` && define.amd) {
define(function () {
return MyModule;
});
}
总结
我们除了提供 AMD
模块接口,CMD
模块接口,还得提供原生的 JavaScript 接口。 由于 CMD
和 AMD
都可以使用 return
来定义对外接口,故可以合并成一句代码。
js
(function () {
function MyModule() {
// ...
}
var moduleName = MyModule;
if (typeof module !== "undefined" && typeof exports === "object") {
module.exports = moduleName;
} else if (typeof define === "function" && (define.amd || define.cmd)) {
define(function () {
return moduleName;
});
} else {
this.moduleName = moduleName;
}
}.call(this || (typeof window !== "undefined" ? window : global)));
Webpack 编译 UMD 包需要注意的事项
output.library
这个名字表示在浏览器直接引入时,挂载到 window
对象上的名字。
output.globalObject
[重要!]:全局对象配置,浏览器中是 window
,Node
中是 global
。如果编写的 UMD
需要兼容两种环境,定义为 this
。
别忘记将 ES6 转换成 ES5 再发布
有很多人没有注意这一点,导致线上环境报错。因为 webpack
的 babel-loader
默认不会编译 node_modules
中的代码。
最终配置
js
const webpack = require("webpack");
const path = require("path");
const libraryName = "Datetime";
const filename = "Datetime";
const config = {
entry: __dirname + "/lib/index.js",
devtool: "source-map",
output: {
path: __dirname + "/dist",
publicPath: "/dist",
library: libraryName,
libraryTarget: "umd", // 这里一定要注明 umd
filename: filename + ".umd.js",
globalObject: "this", // 这个 this 非常重要,这样才能使浏览器和 Node 环境同时兼容!
},
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/,
options: {
presets: ["env"], // 重要,转换成 ES5,防止某些不支持 ES6 的环境
},
},
],
},
resolve: {
extensions: ["*", ".js"],
},
};
module.exports = config;
依赖如下:
json
{
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0",
"babel-preset-stage-3": "^6.24.1",
"prettier": "^1.16.4",
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2"
}
}
从 TS 源码打包 UMD
依赖如下:
json
{
"devDependencies": {
"prettier": "^1.16.4",
"ts-loader": "^6.2.0",
"typescript": "^3.6.4",
"webpack": "^4.27.1",
"webpack-cli": "^3.3.2"
}
}
tsconfig.json
json
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"moduleResolution": "node",
"removeComments": true,
"sourceMap": true,
"lib": ["dom", "es6"]
}
}
webpack.config.js
js
const webpack = require("webpack");
const path = require("path");
const libraryName = "DataUtils";
const filename = "DataUtils";
const config = {
entry: __dirname + "/lib/index.ts",
devtool: "source-map",
output: {
path: __dirname + "/dist",
publicPath: "/dist",
library: libraryName,
libraryTarget: "umd",
// libraryExport: "default",
filename: filename + ".umd.js",
globalObject: "this",
},
module: {
rules: [
{
test: /\.ts?$/,
use: [
{
loader: "ts-loader",
options: {
configFile: path.resolve(__dirname, "./tsconfig.json"),
},
},
],
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ["*", ".ts"],
},
};
module.exports = config;