Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(h5package-opensdk): 0.0.4 #41

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"indent": ["error", 2],
"semi": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unsafe-assignment": 0,
"@typescript-eslint/no-var-requires": 0,
"quotes": ["error", "single"],
Expand All @@ -26,7 +27,7 @@
{
"arrays": "ignore",
"objects": "always",
"imports": "always",
"imports": "only-multiline",
"exports": "ignore",
"functions": "ignore"
}
Expand Down
56 changes: 0 additions & 56 deletions packages/dingtalk-h5package-opensdk/README.md
Original file line number Diff line number Diff line change
@@ -1,57 +1 @@
# `dingtalk-h5package-opensdk`

> H5离线包OpenSDK


## 安装

```

npm install dingtalk-h5package-opensdk --save-dev

```

## 用法


### 命令行用法

```javascript

// 企业自建应用
npx h5package inner --id <企业自建应用agentId> --accesstoken <企业apiToken> --dir ./dist

// 第三方企业应用
npx h5package provider --id <第三方企业应用appId> --accesstoken <企业apiToken> --dir ./dist


```


### 脚本用法

```javascript
import { sdk } from 'dingtalk-h5package-opensdk';

// 初始化参数
sdk.setConfig({
accessToken: '从开放平台获取的apiToken',
});

// 创建H5离线包
const { version } = await sdk.createPackage({
// 应用的ID,二选一即可。
appId: '第三方企业应用appId',
agentId: '企业内部agentId',
input: 'H5离线包资源所在的目录地址', // 在此目录下的所有文件会作为H5离线包的静态资源,压缩上传并创建H5离线包
});

// H5离线包发布上线
await sdk.publishPackage({
// 设置应用的ID,二选一即可。
appId: '第三方企业应用appId',
agentId: '企业内部agentId',
version: version, // 需要发布的离线包的版本号,如0.0.1。
});

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { PackagePacker, urlToPath, pathToUrl } from '../src/PackagePacker';
import path from 'path';


describe('PackagePacker', () => {

it('urlToPath', async () => {
const uc = {
'https://www.dingtalk.com': 'www.dingtalk.com',
'https://www.dingtalk.com/': 'www.dingtalk.com',
'https://www.dingtalk.com/abc': 'www.dingtalk.com/abc',
'https://www.dingtalk.com/abc/': 'www.dingtalk.com/abc',
'https://www.dingtalk.com/abc/index.html': 'www.dingtalk.com/abc/index.html',
'https://www.dingtalk.com:8484/abc/index.html': 'www.dingtalk.com@8484/abc/index.html',
};

Object.keys(uc).forEach((url) => {
expect(urlToPath(url)).toBe(uc[url]);
});
});

it('pathToUrl', async () => {
const uc = {
'www.dingtalk.com': 'https://www.dingtalk.com',
'www.dingtalk.com/abc': 'https://www.dingtalk.com/abc',
'www.dingtalk.com/abc/index.html': 'https://www.dingtalk.com/abc/index.html',
'www.dingtalk.com@8484/abc/index.html': 'https://www.dingtalk.com:8484/abc/index.html',
};

Object.keys(uc).forEach((path) => {
expect(pathToUrl(path)).toBe(uc[path]);
});
});

it('appendFile', async () => {
const packer = new PackagePacker();
await packer.appendFile(
'https://www.example.com/index.html',
path.join(__dirname, './source/index.html')
);
await packer.appendFile(
'https://www.example.com/index.js',
path.join(__dirname, './source/vendor.js')
);
await packer.appendFile(
'https://www.example.com/sub',
path.join(__dirname, './source')
);
await packer.appendUrl(
'https://g.alicdn.com/code/lib/react/0.0.0-0c756fb-f7f79fd/cjs/react.development.js'
);
const dist = await packer.finalize();

console.log(dist);
}, 60000);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@


function hello(){
console.log('hello world');
}
9 changes: 6 additions & 3 deletions packages/dingtalk-h5package-opensdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dingtalk-h5package-opensdk",
"version": "0.0.3",
"version": "2.0.7",
"description": "H5离线包OpenSDK",
"author": "vularr <[email protected]>",
"homepage": "https://github.com/open-dingtalk/dingtalk-design-cli#readme",
Expand All @@ -17,7 +17,7 @@
"dist"
],
"publishConfig": {
"registry": "https://registry.npmjs.org/"
"registry": "https://registry.npmjs.com/"
},
"repository": {
"type": "git",
Expand All @@ -27,7 +27,7 @@
"build": "tsc ",
"lint": "eslint src",
"test": "jest -i",
"prepublishOnly": "npm run lint && npm run build"
"prepublishOnly": "npm run build"
},
"bugs": {
"url": "https://github.com/open-dingtalk/dingtalk-design-cli/issues"
Expand All @@ -45,7 +45,10 @@
"archiver": "^5.3.1",
"axios": "^0.27.2",
"commander": "^9.4.1",
"download": "^8.0.0",
"fs-extra": "^10.1.0",
"glob": "^8.0.3",
"uri-js": "^4.4.1",
"uuid": "^9.0.0"
}
}
153 changes: 153 additions & 0 deletions packages/dingtalk-h5package-opensdk/src/PackagePacker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import archiver from 'archiver';
import fs from 'fs-extra';
import path from 'path';
import download from 'download';
import glob from 'glob';

// 文件路径最大长度是100,扣除"web_assets/"目录,实际最大长度是89;
export const MAX_PATH_LENGTH = 88;

export interface PackerConfig {
host?: string;
miniAppId: string;
accessToken: string;
assets: Record<string, string>;
externalAssets?: string[];
globMappingRules?: Record<string, string>;
}

export function urlToPath(url: string) {
let uri: URL;

try {
uri = new URL(url);
} catch (error) {
throw new Error(`无效的资源地址: ${url}`);
}

if (uri.protocol !== 'https:') {
throw new Error(`无效的资源地址: ${url}, 离线包只支持 https 协议`);
}

const host = uri.port ? `${uri.hostname}@${uri.port}` : uri.hostname;

let p = `${host}${uri.pathname}`;

if (p.endsWith('/')) {
p = p.slice(0, -1);
}

return p;
}

export function pathToUrl(filename: string) {
const sep = '/';
const arr = filename.split(sep);
const host = arr[0];
const pathname = arr.slice(1).join(sep);

let p = `https://${host.replace('@', ':')}/${pathname}`;

if (p.endsWith('/')) {
p = p.slice(0, -1);
}

return p;
}


export class PackagePacker {
private arch: archiver.Archiver;
private prom: Promise<void>;
private output: fs.WriteStream;
private file: string;
private bundleMode = 2;
private includeUrlSet = new Set<string>();
private resourceConfigFileName = 'localresource.json';

constructor(readonly options: {
filename: string;
}) {
this.file = path.join(process.cwd(), options.filename);
this.output = fs.createWriteStream(this.file);
this.arch = archiver('tar', { gzip: true });
this.prom = new Promise((r, c) => {
this.arch.on('error', err =>{
console.error('打包失败', err);
c(err);
});
this.output.on('close', () =>{
// eslint-disable-next-line no-console
console.log('打包完成', this.file);
r();
});
});
this.arch.pipe(this.output);
}

async appendFile(url: string, fileOrDirectory: string) {
const p = path.resolve(fileOrDirectory);
const stat = await fs.stat(p);

if (stat.isDirectory()) {
await this.appendDirectory(url, p);
} else if (stat.isFile()) {
this._file(url, p);
} else {
throw new Error(`无效的文件或目录: ${p}`);
}
}

private _file(url: string, filename: string) {
const fullFilePath = urlToPath(url);

if (fullFilePath.length > MAX_PATH_LENGTH) {
console.warn(
`以下文件被忽略(文件路径长度>${MAX_PATH_LENGTH}): ${path.relative(
process.cwd(),
filename,
)}`
);
return;
}

this.arch.file(filename, { name: fullFilePath, });
this.includeUrlSet.add(url);
}

async appendDirectory(url: string, directory: string) {
const prefixURL = new URL(url);
const files = glob.sync('**/*', { cwd: directory, nodir: true, });

for (const file of files) {
const filename = path.join(directory, file);
const fullUrl = new URL(
path.join(prefixURL.pathname, file),
prefixURL.origin
).toString();

this._file(fullUrl, filename);
}
}

async appendUrl(url: string) {
const content = await download(url);

this.arch.append(content, { name: urlToPath(url) });
}

// 暂时不支持
// async appendGlobMapping(globPartten: string, file: string) {}

async finalize() {
await this.arch.append(JSON.stringify({
bundleMode: this.bundleMode,
includeUrls: Array.from(this.includeUrlSet),
globMappingRules: [],
}), { name: this.resourceConfigFileName, });

await this.arch.finalize();
return Promise.resolve(this.prom)
.then(() => this.file);
}
}
Loading