技术选型
现代前端开发,不再是打开一个文本,一行行手敲代码,自己做代码压缩的时代了。为什么一定要选择打包工具来开发SDK?
- 不一定非要使用构建工具来开发,使用构建工具主要是为了使用它强大的生态系统。比如代码风格检测、本地服务、同时构建多种规范的产物等等,方便我们的开发。
为什么选是 rollup 而不是 webpack?
- 随着 rollup 和 webpack 的版本更新,二者之间的差异性特性越来越小。
- rollup 配置简单,支持同时打包输出多种规范的产物(iife、cjs、umd、esm、amd、system)。
- webpack 功能强大社区丰富,更加适合大型应用;不支持打包输出为es module,而且产物不是很纯净。
- 构建App应用时,webpack比较合适;如果是类库(纯js项目),rollup更加适合。
- webpack在打包的代码中,会引入一些webpack自身的代码,增加包大小,拖慢SDK的实际启动时间。对于包大小、执行速度有要求的SDK,rollup是更好的选择。
完整的开发流程
- 初始化项目
- 创建合理的目录结构
- 配置 eslint 统一代码风格
- 配置 babel
- 配置 git 提交的校验钩子
- 开始编写代码
- watch 模式开发(本地服务)
- 完善 package.json 必要字段
- 配置合适的 npm script
- 本地测试开发的 npm 包
- 发布包到 npm
- 提交代码到 git 仓库
合理的包结构
├── README.md // 对外接入文档,会在npm展示
├── docs // 文档说明
├── lib // 产物输出目录
├── package-lock.json
├── package.json
├── test // 测试用的demo
├── src // 源码
├── .babelrc // babel配置,将es6转义为es5
├── .eslintrc.json // eslint配置,规范代码格式
├── .gitignore // 不上传git的目录配置
├── .eslintignore // eslint不处理的目录配置
└── rollup.config.js // rollup打包配置
使用rollup开发
示例代码
初始化
mkdir sdk-demo
cd sdk-demo
npm init -y // 创建package.json文件
配置 rollup
- 根据开发环境区分不同的配置
- 设置对应的 npm script
- 输出cjs规范的产物
import { terser } from "rollup-plugin-terser"; // 压缩代码
import replace from 'rollup-plugin-replace'; // 替换全局变量
import { babel } from '@rollup/plugin-babel'; // rollup 的 babel 插件,ES6转ES5
import eslint from '@rollup/plugin-eslint';
import packageConfig from './package.json';
const NODE_ENV = process.env.NODE_ENV;
const ENV = JSON.stringify(NODE_ENV || 'development');
const VERSION = packageConfig.version;
const banner =
'/*!\n' +
` * sdk-demo v${VERSION}-${ENV}\n` +
' * Released under the MIT License.\n' +
' */'
const plugins = [
eslint({
throwOnError: true,
throwOnWarning: true,
include: 'src/**'
}),
replace({
exclude: 'node_modules/**',
ENV,
VERSION: `'${VERSION}'`
}),
babel({
include: 'src/**/*',
exclude: 'node_modules/**' // 仅仅转译我们的源码
})
];
// 非开发环境压缩代码
if ('development' !== NODE_ENV) {
plugins.push(terser({
compress: {
pure_getters: true, // 默认是 false. 如果你传入true,UglifyJS会假设对象属性的引用(例如foo.bar 或 foo["bar"])没有函数副作用
unsafe: true, // 默认是 false. 使用 "unsafe"转换. 参考https://github.com/LiPinghai/UglifyJSDocCN#unsafecompress%E9%85%8D%E7%BD%AE
unsafe_comps: true,
dead_code: true,
drop_console: true,
drop_debugger: true,
global_defs: {
DEBUG: false,
},
pure_funcs: ["error", "warn"]
}
}));
}
const config = [
{
input: 'src/index.js',
output: {
name: 'sdkdemo',
file: 'lib/index.js',
format: 'cjs'
},
plugins,
banner
}
];
export default config;
配置eslint
{
"extends": [
"eslint:recommended",
"prettier"
],
"parser": "@babel/eslint-parser",
"env": {
"browser": true,
"node": true,
"es6": true,
"commonjs": true
},
"globals": { // 下面都是小程序相关设置,如果不是小程序的SDK,可以删掉
"__DEV__": true,
"__WECHAT__": true,
"__ALIPAY__": true,
"App": true,
"Page": true,
"Component": true,
"Behavior": true,
"wx": true,
"getApp": true,
"getCurrentPages": true
},
"parserOptions": {
"ecmaVersion": 6,
"ecmaFeatures": {
"experimentalObjectRestSpread": true
},
"sourceType": "module",
"allowSyntheticDefaultImports": true
},
"rules": {
"semi": 2,
"no-dupe-args": 2,
"no-const-assign": 2,
"no-duplicate-case": 2
}
}
配置babel
{
"presets": [
[
"@babel/preset-env",
{
"modules": false, // 设置为false,否则 Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS。
"targets": {
"ie": "10"
}
}
]
]
}
添加忽略文件
.gitignore
node_modules
.vscode
.DS_STORE
lib
.eslintignore
node_modules/**/*.js
lib/*.js
package.json
{
"name": "sdk-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "rollup -c ./rollup.config.js",
"build:prod": "export NODE_ENV=production && rollup -c ./rollup.config.js",
"build:dev": "export NODE_ENV=development && rollup -c ./rollup.config.js",
"build:analyze": "export BUNDLE_ANALYZE=true && npm run build",
"test": "echo success",
"lint": "echo success"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.15.5",
"@babel/eslint-parser": "^7.15.4",
"@babel/preset-env": "^7.15.6",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-eslint": "^8.0.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^7.0.2",
"lint-staged": "^11.1.2",
"prettier": "^2.4.0",
"rollup": "^2.56.3",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-terser": "^7.0.2"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js}": [
"eslint --fix src",
"git add"
]
}
}
开发逻辑代码
- 对外暴露的API
import import DemoDelegate from './demo';
const Demo = {};
Demo.getName = function() {
DemoDelegate.getName();
};
Demo.getAge = function() {
DemoDelegate.getAge();
};
export default Demo;
- 内部逻辑代码
class Demo {
constructor() {
this.name = 'bing dwen dwen';
this.age = 1;
}
getName() {
console.log(this.name);
}
_setName(name) {
this.name = name;
}
getAge() {
console.log(this.age);
}
_setAge(age) {
this.age = age;
}
}
const instance = new Demo();
export default instance; // 输出实例
打包发布
JS SDK的接入方式一般分两种形式:
- 一种是npm,使用import/require方式引入。
import react from 'React'
- 一种是cdn链接,使用
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
使用
以xxxsdk为例
// 安装依赖
npm install xxxsdk
// 使用SDK
import xxx from 'xxxsdk'