开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
背景
JS 库的使用者可能有不同的客户端环境、不同的技术体系,但希望所使用的 JS 库稳定成熟。
JS 库的开发者一般都是希望使用新技术(兼容性问题),但希望给更多的用户提供 JS 库。
两者似乎有点冲突和矛盾了,那么怎样才能调节两者矛盾呢???
目前市面上,主推荐的做法是引入构建流程。
模块化
提到构建,就不得不先说一下模块化。只有对模块化有了清晰的了解,才能助你完成 JS 库的构建。
模块就是一个独立的空间,能引用其他模块,也能被其他模块引用。
原始模块
一个函数即可称为一个模块
。这个是最常见的,最简单的模块。
AMD
AMD 是一种异步模块加载规范,专为浏览器端设计,英文全称 Asynchronous Module Definition,中文名称 异步模块定义。
define(id?: String, dependencies?: String[], factory: Function|Object);
上述这个参数的传递逻辑一定要弄清楚。
浏览器并不支持 AMD 模块,在浏览器端,需要借助 RequireJS 才能加载 AMD 模块。
CommonJS
CommonJS 是一种同步模块加载规范
,目前主要用于 Node.js 环境中。
define(function(require, exports, module) {})
UMD
UMD 是一种通用模块加载规范
,全称 Universal Module Definition,中文名称 通用模块定义。
(function(root, factory) {
if(typeof define === 'function' && define.amd) {
// amd
} else if (typeof exports === 'object') {
// CommonJS
} else {
// 原始模块
}
})(this, function(root) {
function functionName () {}
return functionName
})
UMD 规范是对不同模块规范的简单整合。
ES Module
ES Module 是 ES6 带来的原生系统
。目前部分浏览器已经支持直接使用,而不兼容的浏览器可以通过构建工具来使用。
export function functionName() {}
JS 库可提供两个入口文件:
入口文件 | 支持的模块 |
---|---|
index.js | 原始模块、ADM 模块、 CommonJS 模块、 UMD 模块 |
index.esm.js | ES Module |
打包方案
打包工具
webpack
首先安装 webpack:
npm install webpack webpack-cli --save-dev
配置 webpack.config.js
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
filename: "index.js",
path: path.resolve(__dirname, "dist"),
},
};
执行打包命令
npx webpack
生成 dist/index.js
(function (modules) {
// 此处是 webpack 生成的 冗余代码
})()
webpack 打包会生成很多冗余代码,对于业务代码来说问题不大,但是对于 JS 库来说就不太友好了。
所以说 webpack 适合业务项目,则 rollup 适合库
。也是业界最常见的选择方案。
rollup.js
首先安装 rollup.js
npm install rollup --save-dev
接下来创建如下文件及目录:
config 目录下文件内容如下:
// rollup.cjs.js
module.exports = {
input: 'src/index.js',
output: {
file: 'dist/index.cjs.js',
format: 'cjs'
}
}
// rollup.esm.js
module.exports = {
input: 'src/index.js',
output: {
file: 'dist/index.esm.js',
format: 'es'
}
}
// rollup.umd.js
module.exports = {
input: 'src/index.js',
output: {
file: 'dist/index.umd.js',
format: 'umd',
name: 'rollup_umd_test' // 必填
}
}
package.json 执行的命令:
{
"name": "rollup",
"version": "1.0.0",
"description": "",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"scripts": {
"build:cjs": "rollup -c config/rollup.cjs.js",
"build:esm": "rollup -c config/rollup.esm.js",
"build:umd": "rollup -c config/rollup.umd.js",
"build": "npm run build:cjs && npm run build:esm && npm run build:umd"
},
"author": "",
"license": "ISC",
"devDependencies": {
"rollup": "^3.13.0"
}
}
最后生成的 dist 文件内容如下:
// index.cjs.js
'use strict';
function functionName() {}
exports.functionName = functionName;
// index.esm.js
function functionName() {}
export { functionName };
// index.umd.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.rollup_umd_test = {}));
})(this, (function (exports) {
'use strict';
function functionName() {}
exports.functionName = functionName;
}));
小技巧:看到很多库,都有自己的 横幅说明 和 页脚说明(banner and footer),比如 Vue 的:
const banner =
'/*!\n' +
` * Vue.js v${version}\n` +
` * (c) 2014-${new Date().getFullYear()} Evan You\n` +
' * Released under the MIT License.\n' +
' */'
const footer = '/* follow me on GitHub! @xxxx */'
所以我们也在 config/rollup.js
const pkg = require('../package.json')
const version = pkg.version
const name = pkg.name
const author = pkg.author
const banner =
'/*!\n' +
` * ${name} v${version}\n` +
` * (c) 2016-${new Date().getFullYear()} ${author}\n` +
' * Released under the MIT License.\n' +
' */'
const footer = `/* follow me on GitHub! @${author} */`
exports.banner = banner
exports.footer = footer
接着修改打包配置:
// config/rollup.esm.js
const rollupConfig = require('./rollup')
module.exports = {
input: 'src/index.js',
output: {
file: 'dist/index.esm.js',
format: 'es',
banner: rollupConfig.banner,
footer: rollupConfig.footer
}
}
最后得到的打包结果如下:
// dist/index.esm.js
/*!
* rollup_test v1.0.0
* (c) 2016-2023 RainyNight9
* Released under the MIT License.
*/
function functionName() {}
export { functionName };
/* follow me on GitHub! @RainyNight9 */
这样是不是就有点 cool 了。
rollup 的按需加载
可以通过 rollup 提供的 tree shaking 功能可以自动屏蔽未被使用的功能。
配置项
// rollup.config.js
// can be an array (for multiple inputs)
export default {
// core input options
external,
input, // conditionally required
plugins,
// advanced input options
cache,
onwarn,
preserveEntrySignatures,
strictDeprecations,
// danger zone
acorn,
acornInjectPlugins,
context,
moduleContext,
preserveSymlinks,
shimMissingExports,
treeshake,
// experimental
experimentalCacheExpiry,
perf,
// required (can be an array, for multiple outputs)
output: {
// core output options
dir,
file,
format, // required
globals,
name,
plugins,
// advanced output options
assetFileNames,
banner,
chunkFileNames,
compact,
entryFileNames,
extend,
footer,
hoistTransitiveImports,
inlineDynamicImports,
interop,
intro,
manualChunks,
minifyInternalExports,
outro,
paths,
preserveModules,
preserveModulesRoot,
sourcemap,
sourcemapBaseUrl,
sourcemapExcludeSources,
sourcemapFile,
sourcemapPathTransform,
validate,
// danger zone
amd,
esModule,
exports,
externalLiveBindings,
freeze,
indent,
namespaceToStringTag,
noConflict,
preferConst,
sanitizeFileName,
strict,
systemNullSetters
},
watch: {
buildDelay,
chokidar,
clearScreen,
skipWrite,
exclude,
include
}
};