增加插件更新检测和自动更新 psd2ui 运行库

This commit is contained in:
onvia 2023-08-31 17:53:22 +08:00
parent 3af92fff66
commit 037e598d81
396 changed files with 57227 additions and 1148805 deletions

View File

@ -1022,14 +1022,17 @@
// 镜像图像管理 // 镜像图像管理
this._imageIdKeyMap = new Map(); this._imageIdKeyMap = new Map();
// 当前 psd 所有的图片 // 当前 psd 所有的图片
this._imageArray = new Map(); this._imageMapMd5Key = new Map();
this._imageMapImgNameKey = new Map();
} }
// /** 相同名称不同 md5 图片的后缀id */
// private _sameImgNameId: Record<string, number> = {};
add(psdImage) { add(psdImage) {
var _a; var _a;
// 不忽略导出图片 // 不忽略导出图片
if (!psdImage.isIgnore() && !psdImage.isBind()) { if (!psdImage.isIgnore() && !psdImage.isBind()) {
if (!this._imageArray.has(psdImage.md5)) { if (!this._imageMapMd5Key.has(psdImage.md5)) {
this._imageArray.set(psdImage.md5, psdImage); this._imageMapMd5Key.set(psdImage.md5, psdImage);
} }
} }
if (typeof ((_a = psdImage.attr.comps.img) === null || _a === void 0 ? void 0 : _a.id) != "undefined") { if (typeof ((_a = psdImage.attr.comps.img) === null || _a === void 0 ? void 0 : _a.id) != "undefined") {
@ -1039,9 +1042,33 @@
} }
this._imageIdKeyMap.set(id, psdImage); this._imageIdKeyMap.set(id, psdImage);
} }
this.handleSameImgName(psdImage, psdImage.imgName, 0);
}
/**
* 处理相同名称的图片
*
* @param {PsdImage} psdImage
* @param {string} imgName
* @param {number} idx
* @memberof ImageMgr
*/
handleSameImgName(psdImage, imgName, idx) {
if (this._imageMapImgNameKey.has(imgName)) {
let _psdImage = this._imageMapImgNameKey.get(imgName);
if (_psdImage.md5 != psdImage.md5) {
this.handleSameImgName(psdImage, `${psdImage.imgName}_R${idx}`, idx + 1);
}
else {
psdImage.imgName = imgName;
}
}
else {
psdImage.imgName = imgName;
this._imageMapImgNameKey.set(imgName, psdImage);
}
} }
getAllImage() { getAllImage() {
return this._imageArray; return this._imageMapMd5Key;
} }
/** 尝试获取有编号的图像图层 */ /** 尝试获取有编号的图像图层 */
getSerialNumberImage(psdImage) { getSerialNumberImage(psdImage) {
@ -1059,7 +1086,7 @@
} }
clear() { clear() {
this._imageIdKeyMap.clear(); this._imageIdKeyMap.clear();
this._imageArray.clear(); this._imageMapMd5Key.clear();
} }
static getInstance() { static getInstance() {
if (!this._instance) { if (!this._instance) {

View File

@ -12,6 +12,7 @@
"canvas": "^2.10.2", "canvas": "^2.10.2",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"minimist": "^1.2.7", "minimist": "^1.2.7",
"node-fetch": "^2.7.0",
"pinyin-pro": "^3.16.0" "pinyin-pro": "^3.16.0"
} }
}, },
@ -399,8 +400,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.6.7", "version": "2.7.0",
"license": "MIT", "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": { "dependencies": {
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
}, },
@ -466,8 +468,7 @@
}, },
"node_modules/pinyin-pro": { "node_modules/pinyin-pro": {
"version": "3.16.0", "version": "3.16.0",
"resolved": "https://registry.npmmirror.com/pinyin-pro/-/pinyin-pro-3.16.0.tgz", "license": "MIT"
"integrity": "sha512-U4pMQ/KSMM5JmSb+ZcReCIbgzGl/JaglaHqWjCli0hpA0rDdjRbAO67e6fOa3ZFcJzbqfe6bJkaMMmpiWmkXgQ=="
}, },
"node_modules/readable-stream": { "node_modules/readable-stream": {
"version": "3.6.0", "version": "3.6.0",
@ -880,7 +881,9 @@
"version": "2.17.0" "version": "2.17.0"
}, },
"node-fetch": { "node-fetch": {
"version": "2.6.7", "version": "2.7.0",
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"requires": { "requires": {
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
} }
@ -916,9 +919,7 @@
"version": "1.0.1" "version": "1.0.1"
}, },
"pinyin-pro": { "pinyin-pro": {
"version": "3.16.0", "version": "3.16.0"
"resolved": "https://registry.npmmirror.com/pinyin-pro/-/pinyin-pro-3.16.0.tgz",
"integrity": "sha512-U4pMQ/KSMM5JmSb+ZcReCIbgzGl/JaglaHqWjCli0hpA0rDdjRbAO67e6fOa3ZFcJzbqfe6bJkaMMmpiWmkXgQ=="
}, },
"readable-stream": { "readable-stream": {
"version": "3.6.0", "version": "3.6.0",

View File

@ -1,9 +1,13 @@
{ {
"name": "ccc-tnt-psd2ui", "name": "ccc-tnt-psd2ui",
"version": "0.0.1", "version": "1.0.0",
"description": "The package template for getting started.", "description": "PSD转预制体工具",
"author": "Cocos Creator", "author": "onvia",
"main": "main.js", "repository": {
"type": "git",
"url": "https://gitee.com/onvia/ccc-tnt-psd2ui"
},
"main": "src/main.js",
"main-menu": { "main-menu": {
"i18n:MAIN_MENU.package.title/ccc-tnt-psd2ui/open": { "i18n:MAIN_MENU.package.title/ccc-tnt-psd2ui/open": {
"message": "ccc-tnt-psd2ui:open" "message": "ccc-tnt-psd2ui:open"
@ -23,6 +27,7 @@
"canvas": "^2.10.2", "canvas": "^2.10.2",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"minimist": "^1.2.7", "minimist": "^1.2.7",
"node-fetch": "^2.7.0",
"pinyin-pro": "^3.16.0" "pinyin-pro": "^3.16.0"
} }
} }

View File

@ -58,7 +58,7 @@ Editor.Panel.extend({
let outputInput = root.getElementById("output"); let outputInput = root.getElementById("output");
outputInput.value = str; outputInput.value = str;
} }
Editor.Ipc.sendToMain('ccc-tnt-psd2ui:check-update');
}, },
onDragEnter(event) { onDragEnter(event) {
event.stopPropagation() event.stopPropagation()
@ -98,14 +98,15 @@ Editor.Panel.extend({
// Editor. // Editor.
return; return;
} }
// 参数参考
// https://www.electronjs.org/docs/latest/api/dialog/#dialogshowopendialogbrowserwindow-options
let result = Editor.Dialog.openFile({ let result = Editor.Dialog.openFile({
'multi': true, properties: ['openFile', 'multiSelections'],
'type': "file", type: "file",
'filters': [ filters: [
{ {
'extensions': ["psd"], extensions: ["psd"],
'name': "请选择 PSD" name: "请选择 PSD"
} }
] ]
}); });

View File

@ -1,25 +1,25 @@
'use strict'; 'use strict';
const Electron = require('electron'); const packageJSON = require('../package.json');
const packageJSON = require('./package.json'); const fs = require('fs-extra');
let fs = require('fs');
const path = require("path") const path = require("path")
const Os = require('os'); const Os = require('os');
let child_process = require('child_process'); let child_process = require('child_process');
let exec = child_process.exec; const updater = require('./updater');
let spawn = child_process.spawn; const exec = child_process.exec;
const ENGINE_VER = "v249"; const ENGINE_VER = "v249";
const packagePath = path.join(Editor.Project.path, "packages", packageJSON.name); const pluginPath = path.join(Editor.Project.path, "packages", packageJSON.name);
const projectAssets = path.join(Editor.Project.path, "assets"); const projectAssets = path.join(Editor.Project.path, "assets");
const cacheFile = path.join(Editor.Project.path, "local", "psd-to-prefab-cache.json"); const cacheFile = path.join(Editor.Project.path, "local", "psd-to-prefab-cache.json");
const configFile = path.join(`${packagePath}/config/psd.config.json`); const configFile = path.join(`${pluginPath}/config/psd.config.json`);
const nodejsFile = path.join(packagePath, "bin", `node${Os.platform() == 'darwin' ? "" : ".exe"}`); const nodejsFile = path.join(pluginPath, "bin", `node${Os.platform() == 'darwin' ? "" : ".exe"}`);
const commandFile = path.join(packagePath, "libs", "psd2ui", `command.${Os.platform() == 'darwin' ? "sh" : "bat"}`); const commandFile = path.join(pluginPath, "libs", "psd2ui", `command.${Os.platform() == 'darwin' ? "sh" : "bat"}`);
const psd = path.join(packagePath, "libs", "psd2ui", "index.js"); const psdCore = path.join(pluginPath, "libs", "psd2ui", "index.js");
const packagePath = path.join(pluginPath, "package.json");
let uuid2md5 = new Map(); let uuid2md5 = new Map();
let cacheFileJson = {}; let cacheFileJson = {};
@ -46,18 +46,10 @@ function _exec(options, tasks) {
} }
Editor.log("[ccc-tnt-psd2ui] 命令参数:" + jsonContent); Editor.log("[ccc-tnt-psd2ui] 命令参数:" + jsonContent);
Editor.log("[ccc-tnt-psd2ui] 命令执行中"); Editor.log("[ccc-tnt-psd2ui] 命令执行中,执行完后请手动关闭终端窗口");
let base64 = Buffer.from(jsonContent).toString("base64"); let base64 = Buffer.from(jsonContent).toString("base64");
tasks.push(new Promise((rs) => { tasks.push(new Promise((rs) => {
// Editor.log(`[ccc-tnt-psd2ui] `, `${nodejsFile} ${psd}` + ' ' + `--json ${base64}`);
// exec(`${nodejsFile} ${psd}` + ' ' + `--json ${base64}`, { windowsHide: false }, (err, stdout, stderr) => {
// Editor.log("[ccc-tnt-psd2ui]:\n", stdout);
// if (stderr) {
// Editor.log(stderr);
// }
// rs();
// })
let shellScript = commandFile; // 你的脚本路径 let shellScript = commandFile; // 你的脚本路径
let scriptArgs = `--json ${base64}`; // 你的脚本参数 let scriptArgs = `--json ${base64}`; // 你的脚本参数
@ -68,7 +60,6 @@ function _exec(options, tasks) {
exec(command, (error, stdout, stderr) => { exec(command, (error, stdout, stderr) => {
Editor.log("[ccc-tnt-psd2ui]:\n", stdout); Editor.log("[ccc-tnt-psd2ui]:\n", stdout);
Editor.log("[ccc-tnt-psd2ui]: 程序执行完后请手动关闭终端窗口", );
if (stderr) { if (stderr) {
Editor.log(stderr); Editor.log(stderr);
} }
@ -129,6 +120,60 @@ function genUUID2MD5Mapping() {
} }
} }
async function checkUpdate() {
const result = await updater.checkUpdate();
const remoteVersion = await updater.getRemoteVersion();
if (result === -10 || result === -100) {
Editor.info(`[ccc-tnt-psd2ui]:发现新版本:${remoteVersion}`);
Editor.info(`[ccc-tnt-psd2ui]:下载地址:${packageJSON.repository.url}/releases`);
} else if (result === -1) {
Editor.log(`[ccc-tnt-psd2ui]:更新 psd2ui 运行库`);
updateCore(remoteVersion);
}
}
async function updateCore(remoteVersion) {
// 备份当前版本
Editor.log(`[ccc-tnt-psd2ui]:备份 ${psdCore}`);
let localVersion = updater.getLocalVersion();
try {
let psdCoreFile = await fs.readFile(psdCore);
await fs.writeFile(`${psdCore}.${localVersion}`, psdCoreFile, "binary");
} catch (error) {
Editor.log(`[ccc-tnt-psd2ui]:备份失败,停止更新`, error);
return;
}
Editor.log(`[ccc-tnt-psd2ui]:备份完成,开始下载新版本`);
try {
let fileBuffer = await updater.downloadCoreAsBuffer("psd2ui-tools/dist/index.js");
await fs.writeFile(psdCore, fileBuffer, "binary");
} catch (error) {
Editor.log(`[ccc-tnt-psd2ui]:更新失败`, error);
return;
}
Editor.log(`[ccc-tnt-psd2ui]:更新版本号`);
try {
let packageJSON = await fs.readJson(packagePath);
packageJSON.version = remoteVersion;
await fs.writeJson(packagePath, packageJSON, {
spaces: 4,
encoding: 'utf-8'
});
} catch (error) {
Editor.log(`[ccc-tnt-psd2ui]:更新版本号失败,下次启动会重新进行更新`, error);
}
Editor.log(`[ccc-tnt-psd2ui]:更新完成`);
}
module.exports = { module.exports = {
load() { load() {
genUUID2MD5Mapping(); genUUID2MD5Mapping();
@ -187,8 +232,10 @@ module.exports = {
} }
Promise.all(tasks).then(() => { Promise.all(tasks).then(() => {
if (tasks.length) {
genUUID2MD5Mapping(); genUUID2MD5Mapping();
Editor.log("[ccc-tnt-psd2ui] psd 导出完成,输出位置为:", output ? output : "psd 同级目录"); Editor.log("[ccc-tnt-psd2ui] 任务执行完成\nTips: 预制体输出位置为:", output ? output : "psd 同级目录");
}
}).catch((reason) => { }).catch((reason) => {
Editor.log("[ccc-tnt-psd2ui] 导出失败", reason); Editor.log("[ccc-tnt-psd2ui] 导出失败", reason);
}).finally(() => { }).finally(() => {
@ -222,6 +269,9 @@ module.exports = {
"read-cache"(event, config) { "read-cache"(event, config) {
}, },
"asset-db:assets-deleted": onAssetDeletedListener "asset-db:assets-deleted": onAssetDeletedListener,
"check-update": () => {
checkUpdate();
}
}, },
}; };

View File

@ -0,0 +1,71 @@
const packageJSON = require('../package.json');
let fetch = require('node-fetch');
const updater = {
branch: "master",
async getRemotePackageJson() {
const packageJsonUrl = `${packageJSON.repository.url}/raw/${this.branch}/package.json`;
let res = await fetch(packageJsonUrl, {
method: 'GET',
});
// 请求结果
if (res.status !== 200) {
return null;
}
const json = await res.json()
return json;
},
async getRemoteVersion() {
let json = await this.getRemotePackageJson();
return json?.version || null;
},
getLocalVersion() {
return packageJSON.version;
},
compareVersion(localVersion, remoteVersion) {
const parts1 = localVersion.split('.');
const parts2 = remoteVersion.split('.');
if (parts1.length != parts2.length) {
// 版本号格式不正确,返回 -100
return -100;
}
for (let i = 0; i < 2; i++) {
if (parts1[i] != parts2[i]) {
return parts1[i] < parts2[i] ? -10 : 10;
}
}
if (parts1[2] !== parts2[2]) {
// 最后一位不一致,返回 -1 或 1
return parts1[2] < parts2[2] ? -1 : 1;
}
return 0;
},
async checkUpdate() {
let remoteVersion = await this.getRemoteVersion();
let localVersion = this.getLocalVersion();
let compareResult = this.compareVersion(localVersion, remoteVersion);
return compareResult;
},
async downloadCoreAsBuffer(file) {
const targetUrl = `${packageJSON.repository.url}/raw/${this.branch}/${file}`;
let res = await fetch(targetUrl, {
method: 'GET',
});
let buffer = await res.buffer();
return buffer;
},
}
module.exports = updater;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
declare namespace Editor {
namespace Interface {
// ---- Package ---- start
interface PackageInfo {
debug: boolean;
enable: boolean;
info: PackageJson;
invalid: boolean;
name: string;
path: string;
version: string;
}
interface PackageJson {
name: string;
version: string;
title?: string;
author?: string;
debug?: boolean;
description?: string;
main?: string;
editor?: string;
panel?: any;
contributions?: { [key: string]: any };
}
// ---- Package ---- end
// ---- UI ---- start
interface PanelInfo {
template?: string;
style?: string;
listeners?: { [key: string]: () => {} };
methods?: { [key: string]: Function };
$?: { [key: string]: string };
ready?(): void;
update?(...args: any[]): void;
beforeClose?(): void;
close?(): void;
}
namespace UIKit {
interface UIPanelInfo extends PanelInfo {
// 向上触发事件
dispath(eventName: string, ...arg: any): void;
}
interface EditorElementBase extends HTMLElement {
value: any;
dispath: (name: string, event: any) => void;
}
}
// ---- UI ---- end
}
}

View File

@ -1,2 +0,0 @@
/// <reference path="./editor.d.ts"/>
/// <reference path="./message.d.ts"/>

View File

@ -1,27 +0,0 @@
import * as AssetDB from './packages/asset-db/@types/message';
import * as Scene from './packages/scene/@types/message';
import * as Engine from './packages/engine/@types/message';
import * as Builder from './packages/builder/@types/public/message';
import * as Programming from './packages/programming/@types/message';
// import * as Extension from './packages/extension/@types/message';
declare global {
interface EditorMessageContent {
params: any[],
result: any;
}
interface EditorMessageMap {
[x: string]: EditorMessageContent;
}
interface EditorMessageMaps {
[x: string]: EditorMessageMap;
'asset-db': AssetDB.message;
'scene': Scene.message;
'engine': Engine.message;
'builder': Builder.message;
'programming': Programming.message,
// 'extension': Extension.message;
}
}

View File

@ -1,179 +0,0 @@
import { AssetInfo, QueryAssetsOption, AssetOperationOption, AssetDBOptions, IAssetMeta } from './public';
export interface message extends EditorMessageMap {
'query-ready': {
params: [],
result: boolean,
},
'create-asset': {
params: [
string,
string | Buffer | null,
] | [
string,
string | Buffer | null,
AssetOperationOption,
],
result: AssetInfo | null,
},
'import-asset': {
params: [
string,
string,
] | [
string,
string,
AssetOperationOption,
],
result: AssetInfo | null,
},
'copy-asset': {
params: [
string,
string,
] | [
string,
string,
AssetOperationOption,
],
result: AssetInfo | null,
},
'move-asset': {
params: [
string,
string,
] | [
string,
string,
AssetOperationOption,
],
result: AssetInfo | null,
},
'delete-asset': {
params: [
string,
],
result: AssetInfo | null,
},
'open-asset': {
params: [
string,
],
result: void,
},
'save-asset': {
params: [
string,
string | Buffer,
],
result: AssetInfo | null,
},
'save-asset-meta': {
params: [
string,
string,
],
result: AssetInfo | null,
},
'reimport-asset': {
params: [
string,
],
result: boolean,
},
'refresh-asset': {
params: [
string
],
result: boolean,
},
'query-asset-info': {
params: [
string,
],
result: AssetInfo | null,
},
'query-asset-meta': {
params: [
string,
],
result: IAssetMeta | null,
},
'query-path': {
params: [
string,
],
result: string | null,
},
'query-url': {
params: [
string
],
result: string | null,
},
'query-uuid': {
params: [
string
],
result: string | null,
},
'query-assets': {
params: [] | [
QueryAssetsOption,
],
result: AssetInfo[],
},
'generate-available-url': {
params: [
string,
],
result: string,
},
// private
'query-asset-mtime': {
params: [
string
],
result: string | null,
},
'refresh': {
params: [],
result: void,
},
'open-devtools': {
params: [],
result: void,
},
'query-db-info': {
params: [
string,
],
result: AssetDBOptions,
},
'create-asset-dialog': {
params: [
string,
] | [
string,
string,
],
result: string | null,
},
'init-asset': {
params: [
string,
string,
],
result: AssetInfo | null,
},
'query-all-importer': {
params: [],
result: string[],
},
'query-all-asset-types': {
params: [],
result: string[],
},
}

View File

@ -1,115 +0,0 @@
// Basic information about the resource
// 资源的基础信息
export interface AssetInfo {
// Asset name
// 资源名字
name: string;
// Asset display name
// 资源用于显示的名字
displayName: string;
// URL
source: string;
// loader 加载的层级地址
path: string;
// loader 加载地址会去掉扩展名,这个参数不去掉
url: string;
// 绝对路径
file: string;
// 资源的唯一 ID
uuid: string;
// 使用的导入器名字
importer: string;
// 类型
type: string;
// 是否是文件夹
isDirectory: boolean;
// 导入资源的 map
library: { [key: string]: string };
// 子资源 map
subAssets: { [key: string]: AssetInfo };
// 是否显示
visible: boolean;
// 是否只读
readonly: boolean;
// 虚拟资源可以实例化成实体的话,会带上这个扩展名
instantiation?: string;
// 跳转指向资源
redirect?: IRedirectInfo;
// 继承类型
extends?: string[];
// 是否导入完成
imported: boolean;
// 是否导入失败
invalid: boolean;
}
export interface IRedirectInfo {
// 跳转资源的类型
type: string;
// 跳转资源的 uuid
uuid: string;
}
export interface QueryAssetsOption {
type?: string;
pattern?: string;
ccType?: string;
extname?: string;
importer?: string;
isBundle?: boolean;
}
export interface AssetOperationOption {
// 是否强制覆盖已经存在的文件,默认 false
overwrite?: boolean;
// 是否自动重命名冲突文件,默认 false
rename?: boolean;
}
export interface AssetDBOptions {
name: string;
target: string;
library: string;
temp: string;
/**
* 0: 忽略错误
* 1: 仅仅打印错误
* 2: 打印错误
* 3: 打印错误
* 4: 打印错误
*/
level: number;
ignoreFiles: string[];
readonly: boolean;
}
export interface ContributionInfo {
mount?: {
path: string;
readonly?: boolean;
};
}
export interface ExecuteAssetDBScriptMethodOptions {
name: string;
method: string;
args: any[];
}
export interface IAssetMeta {
ver: string;
importer: string;
imported: boolean;
uuid: string;
files: string[];
subMetas: {
[index: string]: IAssetMeta;
};
userData: {
[index: string]: any;
};
displayName: string;
id: string;
name: string;
}

View File

@ -1,2 +0,0 @@
export * from './public';

View File

@ -1,119 +0,0 @@
import { AssetInfo } from "../../../asset-db/@types/public";
import { UUID } from "../public";
import { IInternalBuildOptions } from "./options";
export interface IBuildStatiscInfo {
packageName: string;
gameName: string;
platform: string;
scenesNum: number;
assetsNum: number;
scriptNum: number;
includeModules: string[];
orientation: string;
remoteServerAddress: string;
appid: string;
size: number;
time: number;
err: string;
// 2 为 3D 工程1 为 2D 工程
dimension?: 1 | 2;
}
// ********************************* asset-manager *********************************
export class BuilderAssetCache {
// 场景资源的 assets 信息缓存
public readonly sceneUuids: Array<string>;
// 脚本资源的 assets 信息缓存
public readonly scriptUuids: Array<string>;
// 除场景、脚本资源外的资源 assets uuid 缓存
public readonly assetUuids: Array<string>;
init: () => Promise<void>;
addAsset: (asset: IAssetInfo) => void;
addInstance: (instance: any) => void;
clearAsset: (uuid: string) => void;
getMeta: (uuid: string) => Promise<any>;
getAssetInfo: (uuid: string) => IAssetInfo;
getDependUuids: (uuid: string) => Promise<readonly string[]>;
getDependUuidsDeep: (uuid: string) => Promise<readonly string[]>;
/**
*
*/
getLibraryJSON: (uuid: string) => Promise<any>;
getSerializedJSON: (uuid: string, options: IInternalBuildOptions) => Promise<any>;
forEach: (type: string, handle: Function) => Promise<void>;
getInstance: (uuid: string) => Promise<any>;
__addStaticsInfo: (info: any) => void;
}
export interface IAssetInfo extends AssetInfo {
meta?: any;
temp?: string; // 资源的构建缓存目录
fatherInfo?: any;
// fatherUuid?: string | undefined;
userData?: any;
subAssets: Record<string, IAssetInfo>;
dirty?: boolean;
// 内置资源没有 mtime
mtime?: number;
}
export type IUrl = string; // 需要的是符合 url 标准的字符串,例如 asset/script/text.ts
export type IAssetInfoMap = Record<UUID, IAssetInfo>;
export type IUuidDependMap = Record<UUID, UUID[]>;
export type IJsonGroupMap = Record<UUID, IJSONGroupItem>;
export type IAssetGroupMap = Record<UUID, IAssetGroupItem>;
// TODO meta 的类型定义
export type IMetaMap = Record<UUID, any>;
export type IJsonMap = Record<UUID, any>;
export type IInstanceMap = Record<UUID, any>;
export type ICompressOptions = Record<string, number>;
export interface IAssetGroupItem {
// 分组名字
// name: string;
// 分组的根 url
baseUrls: string[];
// 脚本编译后的实际地址
scriptDest: string;
// 脚本 uuid 列表
scriptUuids: UUID[];
// raw 资源 uuid 列表
assetUuids: UUID[];
}
export interface IJSONGroupItem {
// 分组名字
name?: string;
// 分组名字
type: string;
// json 资源 uuid 列表
uuids: UUID[];
}
export interface IAssetGroupOptions {
// 脚本打包后的输出路径
scriptUrl: string;
baseUrl: string;
}
export type IGroupType = 'json' | 'script' | 'asset';
export interface PacInfo {
meta: any;
asset: IAssetInfo;
spriteFrames: any[];
relativePath: string;
relativeDir: string;
}
export type IUpdateType = 'asset-change' | 'asset-add' | 'asset-delete';
export interface IUpdateInfo {
type: IUpdateType;
uuid: string;
}

View File

@ -1,152 +0,0 @@
// ********************************* plugin ****************************************
import { BundleCompressionType, IBuildPluginConfig, IBuildTaskOption, IDisplayOptions, ISettings, IVerificationRuleMap } from '../public';
import { BuilderAssetCache } from './asset-manager';
import { InternalBuildResult } from './build-result';
import { IInternalBuildOptions } from './options';
import { ITextureCompressPlatform, ITextureCompressType } from '../public/texture-compress';
export interface IBuildWorkerPluginInfo {
assetHandlers?: string;
// 注册到各个平台的钩子函数
hooks?: Record<string, string>;
pkgName: string;
internal: boolean; // 是否为内置插件
priority: number; // 优先级
}
export type IPluginHookName =
| 'onBeforeBuild'
| 'onAfterInit'
| 'onBeforeInit'
| 'onAfterInit'
| 'onBeforeBuildAssets'
| 'onAfterBuildAssets'
| 'onBeforeCompressSettings'
| 'onAfterCompressSettings'
| 'onAfterBuild';
// | 'onBeforeCompile'
// | 'compile'
// | 'onAfterCompile'
// | 'run';
export type IPluginHook = Record<IPluginHookName, IInternalBaseHooks>;
export interface IInternalHook {
throwError?: boolean; // 插件注入的钩子函数,在执行失败时是否直接退出构建流程
title?: string; // 插件任务整体 title支持 i18n 写法
// ------------------ 钩子函数 --------------------------
onBeforeBuild?: IInternalBaseHooks;
onBeforeInit?: IInternalBaseHooks;
onAfterInit?: IInternalBaseHooks;
onBeforeBuildAssets?: IInternalBaseHooks;
onAfterBuildAssets?: IInternalBaseHooks;
onBeforeCompressSettings?: IInternalBaseHooks;
onAfterCompressSettings?: IInternalBaseHooks;
onAfterBuild?: IInternalBaseHooks;
// ------------------ 其他操作函数 ---------------------
// 内置插件才有可能触发这个函数
run?: (dest: string, options: IBuildTaskOption) => Promise<boolean>;
// 内置插件才有可能触发这个函数
compile?: (dest: string, options: IBuildTaskOption) => boolean;
}
export type IInternalBaseHooks = (options: IInternalBuildOptions, result: InternalBuildResult, cache: BuilderAssetCache, ...args: any[]) => void;
export interface IBuildTask {
handle: (options: IInternalBuildOptions, result: InternalBuildResult, cache: BuilderAssetCache, settings?: ISettings) => {};
title: string;
name: string;
}
export interface IBuildHooksInfo {
pkgNameOrder: string[];
infos: Record<string, { path: string; internal: boolean }>;
}
export interface IBuildAssetHandlerInfo {
pkgNameOrder: string[];
handles: {[pkgName: string]: Function};
}
export interface IInternalBuildPluginConfig extends IBuildPluginConfig {
platformName?: string; // 平台名,可以指定为 i18n 写法, 只有官方构建插件的该字段有效
hooks?: string; // 钩子函数的存储路径
panel?: string; // 存储导出 vue 组件、button 配置的脚本路径
textureCompressConfig?: {
// 仅对内部插件开放
platformType: ITextureCompressPlatform; // 注册的纹理压缩平台类型
support: {
rgba: ITextureCompressType[];
rgb: ITextureCompressType[];
}; // 该平台支持的纹理压缩格式,按照推荐优先级排列
};
assetBundleConfig?: {
// asset bundle 的配置
supportedCompressionTypes: BundleCompressionType[];
};
priority?: number;
wrapWithFold?: boolean; // 是否将选项显示在折叠框内(默认 true
options?: IDisplayOptions; // 需要注入的平台参数配置
verifyRuleMap?: IVerificationRuleMap; // 注入的需要更改原有参数校验规则的函数
commonOptions?: Record<string, boolean>;
// TODO 之前为 ios-app-clip HACK 出来的接口,之后需要重新设计
realInFileExplorer?: (options: IInternalBuildOptions | any) => void; // 根据构建配置计算输出地址(界面中的在文件夹中显示)
debugConfig?: IDebugConfig;
internal?: boolean;
}
export interface BuildCheckResult {
error: string;
newValue: any;
}
export type IBuildVerificationFunc = (value: any, options: IBuildTaskOption) => boolean | Promise<boolean>;
export interface IButtonConfigItem {
label: string; // 按钮名称
click?: (event: Event, options: IBuildTaskOption) => void; // 点击事件响应函数
hookHandle?: string; // 点击后执行的方法,与 click 二选一
tooltip?: string; // 鼠标上移到按钮上的文本提示
// 只有指定 hookHandle 配置的按钮,才可以影响进度条,构建将会自动为每个按钮创建一个构建内置任务,并提供对应的进度 log 更新等等。
// attributes?: any; // 想要添加在按钮上的一些属性值class, style, title)
}
export interface IDebugConfig {
options?: IDisplayOptions; // 显示在构建平台编译运行调试工具上的配置选项
custom?: string; // 显示在构建平台编译运行调试工具上的配置 vue 组件
}
// ui-panel 注册数据
export interface PanelInfo {
$?: { [name: string]: string | HTMLElement | null };
template?: string; // TODO 暂时设置为可选
style?: string;
methods?: { [name: string]: Function };
ready?: Function;
close?: Function;
update?: (options: IBuildTaskOption, path: string, value: any) => void | Promise<void>;
}
export interface IPanelThis {
$: Record<string, HTMLElement>;
dispatch: (name: string, ...args: any[]) => void;
}
export interface IPanelInfo extends PanelInfo {
component?: any; // 注入面板的 vue 组件,可与与 options 共存options 会优先显示
buttonConfig?: IButtonConfig; // 要注入的构建选项脚本
}
export interface IButtonConfig {
configs?: Record<string, IButtonConfigItem>;
custom?: any;
}
export interface ICompInfo {
custom?: any;
options?: IDisplayOptions;
panelInfo?: PanelInfo;
displayName?: string;
wrapWithFold: boolean;
// ..... 初始化时未存在的字段 .....
panel?: any; // 实例化后的 panel 对象
pkgName?: string; // 插件名称
}

View File

@ -1,142 +0,0 @@
import { BundleCompressionType, IAssetPathInfo, IBuildPaths, IBuildTaskOption, IBundleConfig, IJsonPathInfo, ISettings, UUID } from "../public";
import { IAssetInfo } from "./asset-manager";
import { ImportMapWithImports } from "./import-map";
export class InternalBuildResult {
settings: ISettings;
readonly bundles: IBundle[];
readonly bundleMap: Record<string, IBundle>;
// 构建实际使用到的插件脚本 uuid 列表
plugins: UUID[];
// 脚本资源包分组(子包/分包)
scriptPackages: string[];
// MD5 后缀 map
pluginVers: Record<UUID, string>;
// 纹理压缩任务
imageTaskMap: Record<UUID, IImageTask>;
compressImageResult: ICompressImageResult;
importMap: ImportMapWithImports;
// 传入构建的 options
rawOptions: IBuildTaskOption;
// 输出路径集合
paths: IBuildPaths;
// 允许自定义编译选项
compileOptions?: any;
addBundle: (bundle: IBundle) => void;
addPlugin: (plugin: IAssetInfo) => void;
}
export interface IImageTask {
src: string;
// TODO 这个名称已和意义有冲突需要整理调整
dest: string[];
presetId: string;
hasAlpha: boolean;
mtime?: any;
// 生成阶段将会重新获取 bundle 的输出地址
bundleNames: string[];
}
export interface IVersionMap {
import: Record<UUID, string>;
native: Record<UUID, string>;
}
export interface IMD5Map {
'raw-assets': Record<UUID, string>;
import: Record<UUID, string>;
plugin?: Record<UUID, string>;
}
export interface IAtlasResult {
assetsToImage: Record<string, string>;
imageToAtlas: Record<string, string>;
atlasToImages: Record<string, string[]>;
}
export class IBundle {
readonly scenes: UUID[]; // 该 bundle 中的所有场景,包含重定向的
readonly assets: UUID[]; // 该 bundle 中的所有资源,包含重定向的
readonly assetsWithoutRedirect: UUID[]; // 该 bundle 中的未重定向的资源
readonly scripts: UUID[]; // 该 bundle 中的所有脚本
readonly rootAssets: UUID[]; // 该 bundle 中的根资源,即直接放在 bundle 目录下的资源,包含重定向的资源
readonly isSubpackage: boolean; // 该 bundle 是否是子包
root: string; // bundle 的根目录, 开发者勾选的目录,如果是 main 包,这个字段为 ''
dest: string; // bundle 的输出目录
importBase: string;
nativeBase: string;
scriptDest: string; // 脚本的输出目录
name: string; // bundle 的名称
priority: number; // bundle 的优先级
compressionType: BundleCompressionType; // bundle 的压缩类型
assetVer: IVersionMap; // bundle 内的资源版本
version: string; // bundle 本身的版本信息
readonly isRemote: boolean; // bundle 是否是远程包
redirect: Record<UUID, string>; // bundle 中的重定向资源
deps: Set<string>; // bundle 的依赖 bundle
groups: IGroup[]; // 该 bundle 中的资源分组
cache: any;
configOutPutName: string;
config: IBundleConfig; // 该 bundle 的资源清单
readonly isZip: boolean; // 该 bundle 是否是 zip 模式
zipVer: string; // Zip 压缩模式,压缩包的版本
atlasRes: IAtlasResult;
compressRes: Record<string, string[]>;
_rootAssets: Set<UUID>; // 该 bundle 直接包含的资源
_scenes: Set<UUID>;
_scripts: Set<UUID>;
_assets: Set<UUID>;
addScene(scene: IAssetInfo): void;
addScript(script: IAssetInfo): void;
addRootAsset(asset: IAssetInfo): void;
addAsset(asset: IAssetInfo): void;
removeAsset(asset: UUID): void;
addRedirectWithUuid(asset: UUID, redirect: string): void;
addRedirect(asset: IAssetInfo, redirect: string): void;
addAssetWithUuid(asset: UUID): void;
getRedirect(uuid: UUID): string | undefined;
addGroup(type: IJSONGroupType, uuids: UUID[]): void;
addToGroup(type: IJSONGroupType, uuid: UUID): void;
removeFromGroups(uuid: UUID): void;
getJsonPath(uuid: string): string;
getAssetPathInfo(uuid: string): IAssetPathInfo | null;
containsAsset(uuid: string): boolean;
getRawAssetPaths(uuid: string): string[];
getJsonPathInfo(uuid: string): IJsonPathInfo | null;
_resolveImportPath: (name: string) => string;
_resolveNativePath: (libraryPath: string, extName: string) => string;
}
export type ICompressImageResult = Record<UUID, {
formats: string[],
files: string[],
}>;
export interface IGroup {
// 分组名字
name?: string;
// 分组类型
type: IJSONGroupType;
// 该组中的资源 uuid 列表
uuids: UUID[];
}
export type IJSONGroupType = 'NORMAL' | 'TEXTURE' | 'IMAGE';
export interface IDefaultGroup {
assetUuids: UUID[];
scriptUuids: UUID[];
jsonUuids: UUID[];
}
export interface IBundleOptions {
root: string, // bundle 的根目录, 开发者勾选的目录,如果是 main 包,这个字段为''
dest: string, // bundle 的输出目录
scriptDest: string, // 脚本的输出目录
name: string, // bundle 的名称
priority: number, // bundle 的优先级
compressionType: BundleCompressionType, // bundle 的压缩类型
isRemote: boolean // bundle 是否是远程包
// isEncrypted: boolean // bundle 中的代码是否加密,原生平台使用
}

View File

@ -1,18 +0,0 @@
import { IBuildPanel, IInternalBuild } from ".";
// 定义 builder 进程内的全局变量
declare global {
// 构建进程可用
// @ts-ignore
const Build: IInternalBuild;
const __manager: {
taskManager: any;
currentCompileTask: any;
currentBuildTask: any;
__taskId: string;
};
// 渲染进程可用
const BuildPanel: IBuildPanel;
}

View File

@ -1,7 +0,0 @@
export interface ImportMap {
imports?: Record<string, string>;
scopes?: Record<string, Record<string, string>>;
}
export type ImportMapWithImports = ImportMap & { imports: NonNullable<ImportMap['imports']> };

View File

@ -1,89 +0,0 @@
import { IBuild, IBuildUtils, ITaskState } from '../public';
import { InternalBuildResult } from './build-result';
export * from './asset-manager';
export * from './import-map';
export * from './options';
export * from './build-result';
export * from './build-plugin';
export * from '../public';
export type Physics = 'cannon' | 'ammo' | 'builtin';
export type Url = string; // 需要的是符合 url 标准的字符串
export type AssetInfoArr = Array<string | number>; // 固定两位或三位数组 [url,ccType,isSubAsset]
export type IProcessingFunc = (process: number, message: string, state?: ITaskState) => void;
export interface IBuildManager {
taskManager: any;
currentCompileTask: any;
currentBuildTask: any;
__taskId: string;
}
export interface IQuickSpawnOption {
cwd?: string;
env?: any;
downGradeWaring?: boolean; // 将会转为 log 打印,默认为 false
downGradeLog?: boolean; // 将会转为 debug 打印,默认为 true
downGradeError?: boolean; // 将会转为警告,默认为 false
ignoreLog?: boolean; // 忽略 log 信息
}
export interface IInternalBuildUtils extends IBuildUtils {
/**
*
*/
getModuleFiles(result: InternalBuildResult): Promise<string[]>;
/**
*
* @param command
* @param cmdParams
* @param options
*/
quickSpawn(command: string, cmdParams: string[], options?: IQuickSpawnOption): Promise<number | boolean>;
/**
* hash
* @param targetPath
* @param hash
* @returns
*/
patchMd5ToPath(targetPath: string, hash: string): string;
}
export interface IInternalBuild extends IBuild {
Utils: IInternalBuildUtils;
}
export interface IBuildProcessInfo {
state: ITaskState; // 任务状态
progress: number; // 任务进度
message: string; // 最后一次更新的进度消息
id: string; // 任务唯一标识符
options: any; // 构建参数
}
export interface fileMap {
src: string;
dest: string;
}
export interface IScriptInfo {
file: string;
uuid: string;
}
type ICheckRule = 'pathExist' | 'valid' | 'required' | 'normalName' | 'noChinese' | 'array' | 'string' | 'number' | 'http';
export interface IBuildPanel {
// 内部使用的 Vue
Vue: any;
// 内置 vue 组件
vueComps: {
buildProp: any;
templateComp: any;
},
validator: {
has: (ruleName: string) => boolean;
check: (ruleName: ICheckRule, val: any) => boolean;
},
}

View File

@ -1,145 +0,0 @@
import { IBuildTimeConstantValue } from "@cocos/build-engine/dist/build-time-constants";
import { IBuildDesignResolution, IBuildTaskOption } from "../public";
export interface ScriptAssetuserData {
isPlugin?: boolean;
isNative?: boolean;
loadPluginInNative?: boolean;
loadPluginInWeb?: boolean;
}
export interface IBuildScriptParam {
/**
* import map
*/
importMapFormat?: 'commonjs' | 'esm';
polyfills?: IPolyFills;
/**
* 使 `import.meta``import()`
* @experimental
*/
experimentalEraseModules?: boolean;
outputName: string; // 输出文件夹名称(带后缀)
targets?: ITransformTarget;
system?: {
preset?: 'web' | 'commonjs-like',
},
flags: Record<string, IBuildTimeConstantValue>,
}
export interface IPolyFills {
/**
* True if async functions polyfills(i.e. regeneratorRuntime) needs to be included.
* You need to turn on this field if you want to use async functions in language.
*/
asyncFunctions?: boolean;
/**
* If true, [core-js](https://github.com/zloirock/core-js) polyfills are included.
* The default options of [core-js-builder](https://github.com/zloirock/core-js/tree/master/packages/core-js-builder)
* will be used to build the core-js.
*/
coreJs?: boolean;
targets?: string;
}
/**
*
* - 'erase'
* - 'preserve'
* - 'facade' SystemJS IIFE bundle
* bundle
* 使import.meta.url使
*/
export type ModulePreservation = 'erase' | 'preserve' | 'facade';
export type INewConsoleType = 'log' | 'warn' | 'error' | 'debug';
export interface IInternalBuildOptions extends IBuildTaskOption {
dest: string;
// 编译 application.js 参数配置
appTemplateData: appTemplateData;
// 编译引擎参数配置
buildEngineParam: IBuildEngineParam;
// 编译脚本配置选项
buildScriptParam: IBuildScriptParam;
// 序列化打包资源时的特殊处理
assetSerializeOptions: {
'cc.EffectAsset': {
glsl1: boolean;
glsl3: boolean;
glsl4: boolean;
};
// 是否输出 ccon 格式
exportCCON?: boolean;
allowCCONExtension?: boolean;
};
updateOnly: boolean;
nextTasks?: string[];
generateCompileConfig?: boolean;
recompileConfig?: IRecompileConfig;
logDest?: string; // log 输出地址
// 项目设置,重复定义为必选参数
includeModules: string[];
renderPipeline: string;
designResolution: IBuildDesignResolution;
physicsConfig: any;
flags: Record<string, boolean>;
}
export interface appTemplateData {
debugMode: boolean;
renderMode: boolean; // !!options.renderMode,
// ImportMapSupported: boolean;
// NonconformingCommonJs: boolean;
showFPS: boolean;
importMapFile?: string;
resolution: {
policy: number;
width: number;
height: number;
};
// hasPhysicsAmmo: boolean;
md5Cache: boolean;
server: string; // 服务器地址
cocosTemplate?: string; // 注入的子模板路径
}
export interface IBuildEngineParam {
entry?: string; // 引擎入口文件
debug: boolean;
sourceMaps: boolean;
platform: string;
includeModules: string[];
engineVersion: string;
md5Map: string[];
engineName: string;
useCache: boolean;
split?: boolean;
targets?: ITransformTarget;
skip?: boolean;
ammoJsWasm?: boolean | 'fallback';
assetURLFormat?:
| 'relative-from-out'
| 'relative-from-chunk'
| 'runtime-resolved';
baseUrl?: string;
}
export type ITransformTarget = string | string[] | Record<string, string>;
export interface IRecompileConfig {
enable: boolean;
generateAssets: boolean;
generateScripts: boolean;
generateEngine: boolean; // 是否生成引擎
generateEngineByCache: boolean; // 是否使用缓存引擎
}

View File

@ -1,76 +0,0 @@
import { ITextureCompressType, IPVRQuality, IASTCQuality, IETCQuality } from './texture-compress';
import { IBuildTaskOption } from './options';
import { IBuildResult } from './build-result';
export interface IBuildPluginConfig {
hooks?: string; // relate url about IHook
options?: IDisplayOptions; // config of options
verifyRuleMap?: IVerificationRuleMap;
}
export type IVerificationFunc = (val: any, ...arg: any[]) => boolean | Promise<boolean>;
export type IVerificationRuleMap = Record<
string,
{
func?: IVerificationFunc;
message?: string;
}
>;
export type IDisplayOptions = Record<string, IConfigItem>;
export type ArrayItem = {
label: string;
value: string;
};
export interface IConfigItem {
// 配置显示的名字,如果需要翻译,则传入 i18n:${key}
label?: string;
// 设置的简单说明
description?: string;
// 默认值
default?: any;
// 配置的类型
type?: 'array' | 'object';
itemConfigs?: IConfigItem[] | Record<string, IConfigItem>;
verifyRules?: string[];
attributes?: any;
render?: {
ui: string;
attributes?: any;
items?: ArrayItem[];
};
}
export interface IBuildPlugin {
configs?: BuildPlugin.Configs;
assetHandlers?: BuildPlugin.AssetHandlers;
load?: BuildPlugin.load;
unload?: BuildPlugin.Unload;
}
export type IBaseHooks = (options: IBuildTaskOption, result: IBuildResult) => Promise<void> | void;
export namespace BuildPlugin {
export type Configs = Record<string, IBuildPluginConfig>;
export type AssetHandlers = string;
export type load = () => Promise<void> | void;
export type Unload = () => Promise<void> | void;
}
export namespace BuildHook {
export type throwError = boolean; // 插件注入的钩子函数,在执行失败时是否直接退出构建流程
export type title = string; // 插件任务整体 title支持 i18n 写法
export type onBeforeBuild = IBaseHooks;
export type onBeforeCompressSettings = IBaseHooks;
export type onAfterCompressSettings = IBaseHooks;
export type onAfterBuild = IBaseHooks;
export type load = () => Promise<void> | void;
export type unload = () => Promise<void> | void;
}
export namespace AssetHandlers {
export type compressTextures = (
tasks: { src: string; dest: string; quality: number | IPVRQuality | IASTCQuality | IETCQuality; format: ITextureCompressType }[],
) => Promise<void>;
}

View File

@ -1,189 +0,0 @@
/**
* settings.js
*/
import { ISplashSetting, ICustomJointTextureLayout, UUID } from "./options";
// ****************************** settings ************************************************
// debug: true
// designResolution: {width: "960", height: "640", policy: 4}
// jsList: ["assets/resources/b.js", "assets/resources/a.js"]
// launchScene: "db://assets/New Scene-001.scene"
// platform: "web-desktop"
// rawAssets: {
// assets: {
// "0e95a9f8-d4e7-4849-875a-7a11dd692b34": ["mesh/env/gltf/textures/birch_yellow_mat_baseColor.png", "cc.ImageAsset"]
// }
// internal: {
// "1baf0fc9-befa-459c-8bdd-af1a450a0319": ["effects/builtin-standard.effect", "cc.EffectAsset"]
// }
// }
// scenes: [{url: "db://assets/New Scene-001.scene", uuid: "69dc4a42-cc6c-49fb-9a57-7de0c212f83d"},…]
// startScene: "current_scene"
export interface ISettings {
CocosEngine: string;
debug: boolean;
designResolution: ISettingsDesignResolution;
jsList: string[];
launchScene: string;
moduleIds: string[];
platform: string;
renderPipeline: string;
physics?: IPhysicsConfig;
exactFitScreen: boolean;
bundleVers: Record<string, string>;
subpackages: string[];
remoteBundles: string[];
server: string;
hasResourcesBundle: boolean;
hasStartSceneBundle: boolean;
scriptPackages?: string[];
splashScreen?: ISplashSetting;
customJointTextureLayouts?: ICustomJointTextureLayout[];
importMaps?: Array<{
url: string;
map: any;
}>;
macros?: Record<string, any>;
collisionMatrix?: any;
groupList?: any;
// preview
engineModules: string[];
customLayers: {name: string, bit: number}[];
}
// 物理配置
export interface IVec3Like {
x: number;
y: number;
z: number;
}
export interface ICollisionMatrix {
[x: string]: number;
}
export interface IPhysicsMaterial {
friction: number; // 0.5
rollingFriction: number; // 0.1
spinningFriction: number; // 0.1
restitution: number; // 0.1
}
export interface IPhysicsConfig {
gravity: IVec3Like; // 0-10 0
allowSleep: boolean; // true
sleepThreshold: number; // 0.1,最小 0
autoSimulation: boolean; // true
fixedTimeStep: number; // 1 / 60 ,最小 0
maxSubSteps: number; // 1最小 0
defaultMaterial: IPhysicsMaterial;
useNodeChains: boolean; // true
collisionMatrix: ICollisionMatrix;
physicsEngine: string;
}
export interface IPackageInfo {
name: string;
path: string;
uuids: UUID[];
}
export interface ISettingsDesignResolution {
width: number;
height: number;
policy: number;
}
interface IAssetPathBase {
bundleName?: string;
redirect?: string; // 重定向的 bundle 包名
}
export interface IRawAssetPathInfo extends IAssetPathBase {
raw: string[];
}
export declare interface IAssetPathInfo extends IAssetPathBase {
raw?: string[];
json?: string;
groupIndex?: number;
}
export interface IJsonPathInfo extends IAssetPathBase {
json?: string;
groupIndex?: number;
}
export interface IBuildPaths {
dir: string; // 构建资源输出地址( assets 所在的目录,并不一定与构建目录对应)
settings: string; // settings.json 输出地址
systemJs?: string; // system.js 生成地址
engineDir?: string; // 引擎生成地址
polyfillsJs?: string; // polyfill.js 生成地址
assets: string; // assets 目录
subpackages: string; // subpackages 目录
remote: string; // remote 目录
bundleScripts: string // bundle 的脚本,某些平台无法下载脚本,则将远程包中的脚本移到本地
applicationJS: string; // application.js 的生成地址
compileConfig?: string; // cocos.compile.config.json
importMap: string; // import-map 文件地址
}
export declare class IBuildResult {
dest: string; // options 指定的构建目录
paths: IBuildPaths; // 构建后资源相关地址集合
settings?: ISettings;
/**
* uuid
*/
containsAsset: (uuid: string) => boolean;
/**
* uuid json
* uuid uuid
* uuid
*/
getRawAssetPaths: (uuid: string) => IRawAssetPathInfo[];
/**
* uuid json
*/
getJsonPathInfo: (uuid: string) => IJsonPathInfo[];
/**
* uuid
* @return {raw?: string[]; json?: string; groupIndex?: number;}
* @return.raw: 该资源源文件的实际存储位置
* @return.json: 该资源序列化 json
* @return.groupIndex: 若该资源的序列化 json json index
*/
getAssetPathInfo: (uuid: string) => IAssetPathInfo[];
}
export interface IBundleConfig {
importBase: string; // bundle 中 import 目录的名称,通常是 'import'
nativeBase: string; // native 中 native 目录的名称,通常是 'native'
name: string; // bundle 的名称,可以通过 bundle 名称加载 bundle
deps: string[]; // 该 bundle 依赖的其他 bundle 名称
uuids: UUID[]; // 该 bundle 中的所有资源的 uuid
paths: Record<string, any[]>; // 该 bundle 中可以通过路径加载的资源,参考以前 settings 中 rawAssets 的定义
scenes: Record<string, UUID|number>; // 该 bundle 中所有场景,场景名为 key, uuid 为 value
packs: Record<UUID, UUID[]>; // 该 bundle 中所有合并的 json, 参考以前 settings 中 packedAssets 的定义
versions: { import: Array<string|number>, native: Array<string|number> }; // 该 bundle 中所有资源的版本号,参考以前 settings 中 md5AssetsMap 的定义
redirect: Array<string|number>; // 该 bundle 中重定向到其他 bundle 的资源
debug: boolean; // 是否是 debug 模式debug 模式会对 config.json 的数据进行压缩,所以运行时得解压
types?: string[]; // paths 中的类型数组,参考以前 settings 中 assetTypes 的定义
encrypted?: boolean; // 原生上使用,标记该 bundle 中的脚本是否加密
isZip?: boolean; // 是否是 zip 模式
zipVersion?: string;
extensionMap: Record<string, UUID[]>
}

View File

@ -1,7 +0,0 @@
import { IBuild } from ".";
// 定义 builder 进程内的全局变量
declare global {
// @ts-ignore
const Build: IBuild;
}

View File

@ -1,100 +0,0 @@
import { ITransformOptions } from './options';
export * from './build-result';
export * from './build-plugin';
export * from './texture-compress';
export * from './options';
interface IAppendRes {
hash: string;
paths: string[];
}
interface ICreateBundleOptions {
excludes?: string[];
debug?: boolean;
sourceMap?: boolean;
}
export interface IBuildUtils {
/**
* uuid
* 'fc991dd7-0033-4b80-9d41-c8a86a702e59' -> 'fc9913XADNLgJ1ByKhqcC5Z'
*/
compressUuid: (uuid: string, min: boolean) => string;
/**
* uuid
* 'fc9913XADNLgJ1ByKhqcC5Z' -> 'fc991dd7-0033-4b80-9d41-c8a86a702e59'
*/
decompressUuid: (uuid: string) => string;
/**
* i18n i18n:test)()
* 'i18n:test' -> '测试'
*/
transI18nName: (name: string) => string;
/**
* db
* 'db://assets/test.jpg' -> 'assets/test.jpg'
*/
removeDbHeader: (url: string) => string;
/**
* db url url
* 'db://assets/test.jpg' -> 'c:/project/assets/test.jpg'
*/
dbUrlToRawPath: (url: string) => string;
/**
* uuid
* 'E:\test3d\library\oc\0c0c1f5742-89b0-4a1e-b5eb-914d84f48c1c.json' -> '0c0c1f5742-89b0-4a1e-b5eb-914d84f48c1c'
*/
getUuidFromPath: (path: string) => string;
/**
* nodejs
*/
isInstallNodeJs: () => Promise<boolean>;
/**
*
*/
copyDirSync: (src: string, dest: string) => void;
/**
*
* /
*/
relativeUrl: (from: string, to: string) => string;
transformCode: (code: string, options: ITransformOptions) => Promise<string>;
/**
* md5
*/
appendMd5ToPaths: (paths: string[]) => Promise<IAppendRes | null>;
calcMd5: (data: Buffer | string) => string;
copyPaths: (paths: { src: string; dest: string }[]) => Promise<void[]>;
createBundle: (src: string, dest: string, options?: ICreateBundleOptions) => Promise<unknown>;
}
export interface IBuild {
Utils: IBuildUtils;
LIBRARY_NAME: string;
IMPORT_HEADER: string;
NATIVE_HEADER: string;
ASSETS_HEADER: string;
SUBPACKAGES_HEADER: string;
REMOTE_HEADER: string;
BUNDLE_SCRIPTS_HEADER: string;
SCRIPT_NAME: string;
CONFIG_NAME: string;
BUNDLE_ZIP_NAME: string;
projectTempDir: string;
globalTempDir: string;
buildTemplateDir: string; // 构建模板地址 build-templates
}

View File

@ -1,40 +0,0 @@
import { IBundleConfig, ISettings } from "./build-result";
import { ITaskItemJSON } from "./options";
export interface message extends EditorMessageMap {
'open-devtools': {
params: [],
result: void,
},
open: {
params: [],
result: void,
},
'generate-preview-setting': {
params: any[],
result: Promise<{
settings: ISettings;
script2library: Record<string, string>;
bundleConfigs: IBundleConfig[];
}>,
},
'query-tasks-info': {
params: [],
result: {
queue: Record<string, ITaskItemJSON>,
free: Promise<boolean>,
},
},
'query-task': {
params: string[],
result: Promise<ITaskItemJSON>,
},
/**
*
* @param {object} pacUuid
*/
'preview-pac': {
params: string[],
result: Promise<ITaskItemJSON>,
},
}

View File

@ -1,132 +0,0 @@
/**
*
*/
export interface IBuildTaskOption {
// 构建后的游戏文件夹生成的路径
buildPath: string;
debug: boolean;
inlineSpriteFrames: boolean;
md5Cache: boolean;
// bundle 设置
mainBundleCompressionType: BundleCompressionType;
mainBundleIsRemote: boolean;
moveRemoteBundleScript: boolean;
mergeJson: boolean;
name: string;
packAutoAtlas: boolean;
platform: Platform;
scenes: IBuildSceneItem[];
compressTexture: boolean;
sourceMaps: boolean;
startScene: string;
outputName: string;
experimentalEraseModules: boolean;
/**
*
* @default false
*/
preview?: boolean;
// 项目设置
includeModules?: string[];
renderPipeline?: string;
designResolution?: IBuildDesignResolution;
physicsConfig?: any;
flags?: Record<string, boolean>;
// 是否使用自定义插屏选项
replaceSplashScreen?: boolean;
splashScreen: ISplashSetting;
packages?: Record<string, any>;
id?: string; // 手动配置构建任务 id
// recompileConfig?: IRecompileConfig;
customLayers: {name: string, value: number}[];
}
export type UUID = string;
export interface ISplashSetting {
base64src: string;
displayRatio: number;
totalTime: number;
effect: string;
clearColor: { x: number; y: number; z: number; w: number };
displayWatermark: boolean;
}
export interface ICustomJointTextureLayout {
textureLength: number;
contents: IChunkContent[];
}
export interface IChunkContent {
skeleton: null | string;
clips: string[];
}
/**
* 使
*/
export interface IBuildDesignResolution {
height: number;
width: number;
fitWidth?: boolean;
fitHeight?: boolean;
}
/**
* 使
*/
export interface IBuildSceneItem {
url: string;
uuid: string;
}
// **************************** options *******************************************
export type Platform =
| 'web-desktop'
| 'web-mobile'
| 'wechatgame'
| 'oppo-mini-game'
| 'vivo-mini-game'
| 'huawei-quick-game'
| 'alipay-mini-game'
| 'mac'
| 'ios'
// | 'ios-app-clip'
| 'android'
| 'ohos'
| 'windows'
| 'xiaomi-quick-game'
| 'baidu-mini-game'
| 'bytedance-mini-game'
| 'cocos-play'
| 'huawei-agc'
| 'link-sure'
| 'qtt'
| 'cocos-runtime'
;
export type BundleCompressionType = 'none' | 'merge_dep' | 'merge_all_json' | 'subpackage' | 'zip';
export type IModules = 'esm' | 'commonjs' | 'systemjs';
export interface ITransformOptions {
importMapFormat: IModules;
}
export type ITaskState = 'waiting' | 'success' | 'failure' | 'cancel' | 'processing';
export interface ITaskItemJSON {
id: string;
progress: number;
state: ITaskState;
message: string;
options: IBuildTaskOption;
time: string;
dirty: boolean;
rawOptions: IBuildTaskOption;
}

View File

@ -1,43 +0,0 @@
export type ITextureCompressType =
| 'jpg'
| 'png'
| 'webp'
| 'pvrtc_4bits_rgb'
| 'pvrtc_4bits_rgba'
| 'pvrtc_4bits_rgb_a'
| 'pvrtc_2bits_rgb'
| 'pvrtc_2bits_rgba'
| 'pvrtc_2bits_rgb_a'
| 'etc1_rgb'
| 'etc1_rgb_a'
| 'etc2_rgb'
| 'etc2_rgba'
| 'astc_4x4'
| 'astc_5x5'
| 'astc_6x6'
| 'astc_8x8'
| 'astc_10x5'
| 'astc_10x10'
| 'astc_12x12';
export type ITextureCompressPlatform = 'miniGame' | 'web' | 'ios' | 'android';
export type IQualityType = 'etc' | 'pvr' | 'number' | 'astc';
export interface ITextureFormatInfo {
displayName: string;
qualityType: IQualityType;
alpha?: boolean;
}
export interface ISupportFormat {
rgb: ITextureCompressType[];
rgba: ITextureCompressType[];
}
export interface IConfigGroupsInfo {
defaultSupport?: ISupportFormat,
support: ISupportFormat,
displayName: string;
icon: string;
}
export type IConfigGroups = Record<ITextureCompressPlatform, IConfigGroupsInfo>;
export type IPVRQuality = 'fastest' | 'fast' | 'normal' | 'high' | 'best';
export type IETCQuality = 'slow' | 'fast';
export type IASTCQuality = 'veryfast' | 'fast' | 'medium' | 'thorough' | 'exhaustive';

View File

@ -1,16 +0,0 @@
export interface IMessageItem {
rows: number;
translateY: number;
show: boolean;
title: string;
content: string[];
count: number;
fold: boolean;
type: string;
message: any;
texture: string;
date?: number;
time?: number;
process?: string;
stack: string[];
}

View File

@ -1,16 +0,0 @@
export interface message extends EditorMessageMap {
'query-info': {
params: [] | [
string,
],
result: {
type: string;
version: string;
path: string;
nativeVersion: string; // 原生引擎类型 'custom' 'builtin'
nativePath: string;
editor: string;
renderPipeline: string;
},
},
}

View File

@ -1 +0,0 @@
export * from './protect/';

View File

@ -1,33 +0,0 @@
import { Platform } from "../../../builder/@types/public";
export type IPreviewType = 'game-view' | 'simulator' | 'browser';
export type ISupportDataType = 'settings' | 'renderData';
export interface IHookConfig {
methods: string;
hook: string;
}
export interface IGenerateSettingsOptions {
type: IPreviewType;
startScene?: string;
platform?: Platform;
}
export interface IPreviewPluginConfig {
methods?: string;
hooks?: Record<string, string>;
}
// 界面渲染配置
export interface IRenderData {
title: string; // 预览页面 title
enableDebugger: boolean; // 是否开启 vConsole
config: { // 预览页面菜单栏配置
device: string; // 设备名称
// https://github.com/cocos-creator/engine/blob/3d/cocos/core/platform/debug.ts
debugMode: string; // cc.DebugMode 枚举名称
showFps: boolean;
fps: number;
}
}

View File

@ -1,19 +0,0 @@
export interface message extends EditorMessageMap {
'query-shared-settings': {
params: [],
result: {
useDefineForClassFields: boolean;
allowDeclareFields: boolean;
loose: boolean;
guessCommonJsExports: boolean;
exportsConditions: string[];
importMap?: {
json: {
imports?: Record<string, string>;
scopes?: Record<string, Record<string, string>>;
};
url: string;
};
}
};
}

View File

@ -1,68 +0,0 @@
import {
SetPropertyOptions,
} from './public';
export interface message extends EditorMessageMap {
'update-create-node-template': {
params: [],
result: any,
},
'open': {
params: [],
result: any,
},
'open-devtools': {
params: [],
result: any,
},
'graphical-tools': {
params: [
boolean,
],
result: void,
},
'open-scene': {
params: [
string,
],
result: boolean,
},
'save-scene': {
params: [] | [
boolean,
],
result: boolean,
},
'save-as-scene': {
params: [
boolean,
],
result: boolean,
},
'close-scene': {
params: [],
result: boolean,
},
'set-property': {
params: [
SetPropertyOptions,
],
result: boolean,
},
'query-node-tree': {
params: [] | [
string,
],
result: any,
},
'execute-scene-script': {
params: [] | [
{
name: string;
method: string;
args: any[];
}
],
result: any,
},
}

View File

@ -1,407 +0,0 @@
// ---- 一些 engine 基础数据 ---- start
interface Vec2 {
x: number;
y: number;
}
export interface Vec3 {
x: number;
y: number;
z: number;
}
interface Vec4 {
x: number;
y: number;
z: number;
w: number;
}
interface Quat {
x: number;
y: number;
z: number;
w: number;
}
interface Color3 {
r: number;
g: number;
b: number;
}
interface Color4 {
r: number;
g: number;
b: number;
a: number;
}
interface Mat3 {
m00: number;
m01: number;
m02: number;
m03: number;
m04: number;
m05: number;
m06: number;
m07: number;
m08: number;
}
interface Mat4 {
m00: number;
m01: number;
m02: number;
m03: number;
m04: number;
m05: number;
m06: number;
m07: number;
m08: number;
m09: number;
m10: number;
m11: number;
m12: number;
m13: number;
m14: number;
m15: number;
}
// ---- 一些 engine 基础数据 ---- end
// ---- 操作消息的参数定义 --- strart
// set-property 消息的 options 定义
export interface SetPropertyOptions {
uuid: string; // 修改属性的对象的 uuid
path: string; // 属性挂载对象的搜索路径
// key: string; // 属性的 key
dump: IProperty; // 属性 dump 出来的数据
}
// move-array-element 消息的 options 定义
export interface MoveArrayOptions {
uuid: string;
path: string;
target: number;
offset: number;
}
// remove-array-element 消息的 options 定义
export interface RemoveArrayOptions {
uuid: string;
path: string;
index: number;
}
export interface PasteNodeOptions {
target: string; // 目标节点
uuids: string | string[]; // 被复制的节点 uuids
keepWorldTransform?: boolean; // 是否保持新节点的世界坐标不变
}
export interface CutNodeOptions {
parent: string; // 父节点
uuids: string | string[]; // 被移入的节点 uuids
keepWorldTransform?: boolean; // 是否保持新节点的世界坐标不变
}
// create-node 消息的 options 定义
export interface CreateNodeOptions {
parent?: string;
components?: string[];
name?: string;
dump?: INode | IScene; // node 初始化应用的数据
keepWorldTransform?: boolean; // 是否保持新节点的世界坐标不变
type?: string; // 资源类型
assetUuid?: string; // asset uuid , type value 格式保持兼容拖动的数据格式,有资源 id则从资源内创建对应的节点
canvasRequired?: boolean; // 是否需要有 Canvas
unlinkPrefab?: boolean; // 创建后取消 prefab 状态
position?: Vec3; // 指定生成的位置
}
export interface ResetNodeOptions {
uuid: string | string[];
}
export interface RemoveNodeOptions {
uuid: string | string[];
keepWorldTransform?: boolean;
}
export interface CreateComponentOptions {
uuid: string;
component: string;
}
export interface ResetComponentOptions {
uuid: string;
}
export interface RemoveComponentOptions {
uuid: string;
component: string;
}
export interface ExecuteComponentMethodOptions {
uuid: string;
name: string;
args: any[];
}
export interface IAnimOperation {
funcName: string;
args: any[];
}
export interface ExecuteSceneScriptMethodOptions {
name: string;
method: string;
args: any[];
}
export type IPropertyValueType = IProperty | IProperty[] | null | undefined | number | boolean | string | Vec3 | Vec2;
export interface IPropertyGroupOptions {
id: string // 默认 'default'
name: string,
displayOrder: number, // 默认 Infinity, 排在最后面
style: string // 默认为 'tab'
}
export interface IProperty {
value: { [key: string]: IPropertyValueType } | IPropertyValueType;
default?: any; // 默认值
// 多选节点之后,这里存储多个数据,用于自行判断多选后的显示效果,无需更新该数据
values?: ({ [key: string]: IPropertyValueType } | IPropertyValueType)[];
cid?: string;
type?: string;
readonly?: boolean;
visible?: boolean;
name?: string;
elementTypeData?: IProperty; // 数组里的数据的默认值 dump
path?: string; // 数据的搜索路径,这个是由使用方填充的
isArray?: boolean;
invalid?: boolean;
extends?: string[]; // 继承链
displayName?: string; // 显示到界面上的名字
displayOrder?: number; // 显示排序
group?: IPropertyGroupOptions; // tab
tooltip?: string; // 提示文本
editor?: any; // 组件上定义的编辑器数据
animatable?: boolean; // 是否可以在动画中编辑
// Enum
enumList?: any[]; // enum 类型的 list 选项数组
bitmaskList?: any[];
// Number
min?: number; // 数值类型的最小值
max?: number; // 数值类型的最大值
step?: number; // 数值类型的步进值
slide?: boolean; // 数组是否显示为滑块
unit?: string; // 显示的单位
radian?: boolean; // 标识是否为角度
// Label
multiline?: boolean; // 字符串是否允许换行
// nullable?: boolean; 属性是否允许为空
}
export interface IRemovedComponentInfo {
name: string;
fileID: string;
}
export interface INode {
active: IProperty;
locked: IProperty;
name: IProperty;
position: IProperty;
/**
* dump node.rotation
* node.eulerAngles
* rotation
*/
rotation: IProperty;
scale: IProperty;
layer: IProperty;
uuid: IProperty;
children: any[];
parent: any;
__comps__: IProperty[];
__type__: string;
__prefab__?: any;
_prefabInstance?: any;
removedComponents?: IRemovedComponentInfo[];
mountedRoot?: string;
}
export interface IComponent extends IProperty {
value: {
enabled: IPropertyValueType;
uuid: IPropertyValueType;
name: IPropertyValueType;
} & Record<string, IPropertyValueType>;
mountedRoot?: string;
}
export interface IScene {
name: IProperty;
active: IProperty;
locked: IProperty;
_globals: any;
isScene: boolean;
autoReleaseAssets: IProperty;
uuid: IProperty;
children: any[];
parent: any;
__type__: string;
targetOverrides?: any;
}
export interface ITargetOverrideInfo {
source: string;
sourceInfo?: string[];
propertyPath: string[];
target: string;
targetInfo?: string[];
}
// ---- 操作消息的参数定义 --- end
// ---- 场景插件返回的 info 信息 ---- start
interface ScenePluginNodeInfo {
uuid: string;
components: ScenePluginComponentInfo[];
}
// 场景插件传回的场景信息
export interface ScenePluginInfo {
// 选中节点列表
nodes: ScenePluginNodeInfo[];
// gizmo 的一些信息
gizmo: {
is2D: boolean;
};
// 当前编辑模式数组
modes: string[];
}
// 场景插件传回的组件信息
export interface ScenePluginComponentInfo {
uuid: string;
enabled: boolean;
type: string;
}
export interface QueryClassesOptions {
extends?: string | string[];
excludeSelf?: boolean;
}
// ---- 场景插件返回的 info 信息 ---- end
// ---- 动画数据 ---- start
export interface IKeyDumpData {
frame: number;
dump: any; // value的dump数据
inTangent?: number;
inTangentWeight?: number;
outTangent?: number;
outTangentWeight?: number;
interpMode?: number;
broken?: boolean;
tangentWeightMode?: number;
imgUrl?: string;
easingMethod?: number;
}
export interface IDumpType {
value: string;
extends?: string[];
}
export interface IPropCurveDumpData {
nodePath: string;
keyframes: IKeyDumpData[];
displayName: string;
key: string;
type?: IDumpType;
preExtrap: number;
postExtrap: number;
isCurveSupport: boolean; // 是否支持贝塞尔曲线编辑
}
export interface IAnimCopyKeySrcInfo {
curvesDump: IPropCurveDumpData[];
}
export interface IAnimCopyNodeSrcInfo {
curvesDump: IPropCurveDumpData[];
}
export interface IAnimCopyNodeDstInfo {
nodePath: string;
}
interface IEventDump {
frame: number;
func: string;
params: string[];
}
export interface IAnimCopyEventSrcInfo {
eventsDump: IEventDump[];
}
export interface IAnimCopyPropSrcInfo {
curvesDump: IPropCurveDumpData[];
}
export interface IAnimCopyPropDstInfo {
nodePath: string;
propKeys?: string[];
}
export interface IAnimCopyKeyDstInfo {
nodePath: string;
propKeys?: string[];
startFrame: number;
}
export interface IAnimCopyEventDstInfo {
startFrame: number;
}
// ---- 动画数据 ---- end
// ---- Contributions ---- start
export interface ContributionDropItem {
type: string;
message: string;
}
// ---- Contributions ---- end
export interface UnitTestInfo {
name: string;
}

View File

@ -1,27 +0,0 @@
// 消息定义
interface MessageInterface {
params: any[],
result: any;
}
// host
export interface HostInfo {
host: string;
ip: string;
port: number;
}
// 消息定义
export interface main {
scene: {
[x: string]: MessageInterface;
'query-port': {
params: [],
result: number,
};
'scan-lan': {
params: [],
result: HostInfo[],
};
}
}

View File

@ -1,19 +0,0 @@
export interface ShortcutItem {
when: string;
message: string;
shortcut: string;
pkgName: string;
rawShortcut?: string;
key: string;
missing?: boolean;
}
export type IShortcutItemMap = Record<string, ShortcutItem>;
export interface IShortcutEditInfo {
key: string;
shortcut: string;
searches: ShortcutItem[];
conflict: boolean;
when: string;
}

View File

@ -10,15 +10,17 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path")); const path_1 = __importDefault(require("path"));
const os_1 = __importDefault(require("os")); const os_1 = __importDefault(require("os"));
const child_process_1 = __importDefault(require("child_process")); const child_process_1 = __importDefault(require("child_process"));
const updater_1 = require("./updater");
let exec = child_process_1.default.exec; let exec = child_process_1.default.exec;
const ENGINE_VER = "v342"; // const ENGINE_VER = "v342"; //
const packagePath = path_1.default.join(Editor.Project.path, "extensions", package_json_1.default.name); const pluginPath = path_1.default.join(Editor.Project.path, "extensions", package_json_1.default.name);
const projectAssets = path_1.default.join(Editor.Project.path, "assets"); const projectAssets = path_1.default.join(Editor.Project.path, "assets");
const cacheFile = path_1.default.join(Editor.Project.path, "local", "psd-to-prefab-cache.json"); const cacheFile = path_1.default.join(Editor.Project.path, "local", "psd-to-prefab-cache.json");
const configFile = path_1.default.join(`${packagePath}/config/psd.config.json`); const configFile = path_1.default.join(`${pluginPath}/config/psd.config.json`);
const nodejsFile = path_1.default.join(packagePath, "bin", `node${os_1.default.platform() == 'darwin' ? "" : ".exe"}`); const nodejsFile = path_1.default.join(pluginPath, "bin", `node${os_1.default.platform() == 'darwin' ? "" : ".exe"}`);
const commandFile = path_1.default.join(packagePath, "libs", "psd2ui", `command.${os_1.default.platform() == 'darwin' ? "sh" : "bat"}`); const commandFile = path_1.default.join(pluginPath, "libs", "psd2ui", `command.${os_1.default.platform() == 'darwin' ? "sh" : "bat"}`);
const psd = path_1.default.join(packagePath, "libs", "psd2ui", "index.js"); const psdCore = path_1.default.join(pluginPath, "libs", "psd2ui", "index.js");
const packagePath = path_1.default.join(pluginPath, "package.json");
let uuid2md5 = new Map(); let uuid2md5 = new Map();
let cacheFileJson = {}; let cacheFileJson = {};
/** /**
@ -86,8 +88,10 @@ exports.methods = {
_exec(args, tasks); _exec(args, tasks);
} }
Promise.all(tasks).then(() => { Promise.all(tasks).then(() => {
if (tasks.length) {
genUUID2MD5Mapping(); genUUID2MD5Mapping();
console.log("[ccc-tnt-psd2ui] psd 导出完成,输出位置为:", output ? output : "psd 同级目录"); console.log("[ccc-tnt-psd2ui] psd 导出完成,输出位置为:", output ? output : "psd 同级目录");
}
}).catch((reason) => { }).catch((reason) => {
console.log("[ccc-tnt-psd2ui] 导出失败", reason); console.log("[ccc-tnt-psd2ui] 导出失败", reason);
}).finally(() => { }).finally(() => {
@ -112,24 +116,15 @@ function _exec(options, tasks) {
} }
} }
console.log("[ccc-tnt-psd2ui] 命令参数:" + jsonContent); console.log("[ccc-tnt-psd2ui] 命令参数:" + jsonContent);
console.log("[ccc-tnt-psd2ui] 命令执行中"); console.log("[ccc-tnt-psd2ui] 命令执行中,执行完后请手动关闭终端窗口");
let base64 = Buffer.from(jsonContent).toString("base64"); let base64 = Buffer.from(jsonContent).toString("base64");
tasks.push(new Promise((rs) => { tasks.push(new Promise((rs) => {
// console.log(`[ccc-tnt-psd2ui] `, `${nodejsFile} ${psd}` + ' ' + `--json ${base64}`);
// exec(`${nodejsFile} ${psd}` + ' ' + `--json ${base64}`, { windowsHide: false }, (err, stdout, stderr) => {
// console.log("[ccc-tnt-psd2ui]:\n", stdout);
// if (stderr) {
// console.log(stderr);
// }
// rs();
// })
let shellScript = commandFile; // 你的脚本路径 let shellScript = commandFile; // 你的脚本路径
let scriptArgs = `--json ${base64}`; // 你的脚本参数 let scriptArgs = `--json ${base64}`; // 你的脚本参数
let command = os_1.default.platform() == 'darwin' ? `osascript -e 'tell app "Terminal" to do script "cd ${process.cwd()}; ${shellScript} ${scriptArgs}"'` let command = os_1.default.platform() == 'darwin' ? `osascript -e 'tell app "Terminal" to do script "cd ${process.cwd()}; ${shellScript} ${scriptArgs}"'`
: `start ${commandFile} ${scriptArgs}`; : `start ${commandFile} ${scriptArgs}`;
exec(command, (error, stdout, stderr) => { exec(command, (error, stdout, stderr) => {
console.log("[ccc-tnt-psd2ui]:\n", stdout); console.log("[ccc-tnt-psd2ui]:\n", stdout);
console.log("[ccc-tnt-psd2ui]: 程序执行完后请手动关闭终端窗口");
if (stderr) { if (stderr) {
console.log(stderr); console.log(stderr);
} }
@ -174,6 +169,7 @@ function genUUID2MD5Mapping() {
const load = function () { const load = function () {
genUUID2MD5Mapping(); genUUID2MD5Mapping();
Editor.Message.addBroadcastListener("asset-db:asset-delete", onAssetDeletedListener); Editor.Message.addBroadcastListener("asset-db:asset-delete", onAssetDeletedListener);
Editor.Message.addBroadcastListener("ccc-tnt-psd2ui:check-update", checkUpdate);
}; };
exports.load = load; exports.load = load;
/** /**
@ -182,5 +178,53 @@ exports.load = load;
*/ */
const unload = function () { const unload = function () {
Editor.Message.removeBroadcastListener("asset-db:asset-delete", onAssetDeletedListener); Editor.Message.removeBroadcastListener("asset-db:asset-delete", onAssetDeletedListener);
Editor.Message.removeBroadcastListener("ccc-tnt-psd2ui:check-update", checkUpdate);
}; };
exports.unload = unload; exports.unload = unload;
async function checkUpdate() {
const result = await updater_1.updater.checkUpdate();
const remoteVersion = await updater_1.updater.getRemoteVersion();
if (result === -10 || result === -100) {
console.info(`[ccc-tnt-psd2ui]:插件发现新版本:${remoteVersion}`);
console.info(`[ccc-tnt-psd2ui]:下载地址:${package_json_1.default.repository.url}/releases`);
}
else if (result === -1) {
console.log(`[ccc-tnt-psd2ui]:更新 psd2ui 运行库`);
updateCore(remoteVersion);
}
}
async function updateCore(remoteVersion) {
// 备份当前版本
console.log(`[ccc-tnt-psd2ui]:备份 ${psdCore}`);
let localVersion = updater_1.updater.getLocalVersion();
try {
let psdCoreFile = await fs_extra_1.default.readFile(psdCore);
await fs_extra_1.default.writeFile(`${psdCore}.${localVersion}`, psdCoreFile, "binary");
}
catch (error) {
console.log(`[ccc-tnt-psd2ui]:备份失败,停止更新`, error);
return;
}
console.log(`[ccc-tnt-psd2ui]:备份完成,开始下载新版本`);
try {
let fileBuffer = await updater_1.updater.downloadCoreAsBuffer("psd2ui-tools/dist/index.js");
await fs_extra_1.default.writeFile(psdCore, fileBuffer, "binary");
}
catch (error) {
console.log(`[ccc-tnt-psd2ui]:更新失败`, error);
return;
}
console.log(`[ccc-tnt-psd2ui]:更新版本号`);
try {
let packageJSON = await fs_extra_1.default.readJson(packagePath);
packageJSON.version = remoteVersion;
await fs_extra_1.default.writeJson(packagePath, packageJSON, {
spaces: 4,
encoding: 'utf-8'
});
}
catch (error) {
console.log(`[ccc-tnt-psd2ui]:更新版本号失败,下次启动会重新进行更新`);
}
console.log(`[ccc-tnt-psd2ui]:更新完成`);
}

View File

@ -41,6 +41,7 @@ module.exports = Editor.Panel.define({
if (str) { if (str) {
this.outputPath = str; this.outputPath = str;
} }
Editor.Message.broadcast("ccc-tnt-psd2ui:check-update");
}, },
beforeUnmount() { beforeUnmount() {
localStorage.setItem(`${Editor.Project.name}_psd2ui_output`, this.outputPath); localStorage.setItem(`${Editor.Project.name}_psd2ui_output`, this.outputPath);

72
ccc-tnt-psd2ui-v3.4.+/dist/updater.js vendored Normal file
View File

@ -0,0 +1,72 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.updater = void 0;
const node_fetch_1 = __importDefault(require("node-fetch"));
const package_json_1 = __importDefault(require("../package.json"));
class Updater {
constructor() {
this.branch = "master";
}
async getRemotePackageJson() {
const packageJsonUrl = `${package_json_1.default.repository.url}/raw/${this.branch}/package.json`;
let res = await (0, node_fetch_1.default)(packageJsonUrl, {
method: 'GET',
});
// 请求结果
if (res.status !== 200) {
return null;
}
const json = await res.json();
return json;
}
async getRemoteVersion() {
let json = await this.getRemotePackageJson();
return (json === null || json === void 0 ? void 0 : json.version) || null;
}
getLocalVersion() {
return package_json_1.default.version;
}
compareVersion(localVersion, remoteVersion) {
const parts1 = localVersion.split('.');
const parts2 = remoteVersion.split('.');
if (parts1.length != parts2.length) {
// 版本号格式不正确,返回 -100
return -100;
}
for (let i = 0; i < 2; i++) {
if (parts1[i] != parts2[i]) {
return parts1[i] < parts2[i] ? -10 : 10;
}
}
if (parts1[2] !== parts2[2]) {
// 最后一位不一致,返回 -1 或 1
return parts1[2] < parts2[2] ? -1 : 1;
}
return 0;
}
async checkUpdate() {
let remoteVersion = await this.getRemoteVersion();
let localVersion = this.getLocalVersion();
let compareResult = this.compareVersion(localVersion, remoteVersion);
return compareResult;
}
async downloadCoreAsBuffer(file) {
const targetUrl = `${package_json_1.default.repository.url}/raw/${this.branch}/${file}`;
let res = await (0, node_fetch_1.default)(targetUrl, {
method: 'GET',
});
let buffer = await res.buffer();
return buffer;
}
static getInstance() {
if (!this._instance) {
this._instance = new Updater();
}
return this._instance;
}
}
Updater._instance = null;
exports.updater = Updater.getInstance();

View File

@ -3,5 +3,5 @@ module.exports = {
menu: "psd2ui", menu: "psd2ui",
open_panel: "主面板", open_panel: "主面板",
send_to_panel: "发送消息给面板", send_to_panel: "发送消息给面板",
description: "含有一个基于Vue3.x开发的面板的扩展" description: "PSD转预制体工具"
}; };

View File

@ -1022,14 +1022,17 @@
// 镜像图像管理 // 镜像图像管理
this._imageIdKeyMap = new Map(); this._imageIdKeyMap = new Map();
// 当前 psd 所有的图片 // 当前 psd 所有的图片
this._imageArray = new Map(); this._imageMapMd5Key = new Map();
this._imageMapImgNameKey = new Map();
} }
// /** 相同名称不同 md5 图片的后缀id */
// private _sameImgNameId: Record<string, number> = {};
add(psdImage) { add(psdImage) {
var _a; var _a;
// 不忽略导出图片 // 不忽略导出图片
if (!psdImage.isIgnore() && !psdImage.isBind()) { if (!psdImage.isIgnore() && !psdImage.isBind()) {
if (!this._imageArray.has(psdImage.md5)) { if (!this._imageMapMd5Key.has(psdImage.md5)) {
this._imageArray.set(psdImage.md5, psdImage); this._imageMapMd5Key.set(psdImage.md5, psdImage);
} }
} }
if (typeof ((_a = psdImage.attr.comps.img) === null || _a === void 0 ? void 0 : _a.id) != "undefined") { if (typeof ((_a = psdImage.attr.comps.img) === null || _a === void 0 ? void 0 : _a.id) != "undefined") {
@ -1039,9 +1042,33 @@
} }
this._imageIdKeyMap.set(id, psdImage); this._imageIdKeyMap.set(id, psdImage);
} }
this.handleSameImgName(psdImage, psdImage.imgName, 0);
}
/**
* 处理相同名称的图片
*
* @param {PsdImage} psdImage
* @param {string} imgName
* @param {number} idx
* @memberof ImageMgr
*/
handleSameImgName(psdImage, imgName, idx) {
if (this._imageMapImgNameKey.has(imgName)) {
let _psdImage = this._imageMapImgNameKey.get(imgName);
if (_psdImage.md5 != psdImage.md5) {
this.handleSameImgName(psdImage, `${psdImage.imgName}_R${idx}`, idx + 1);
}
else {
psdImage.imgName = imgName;
}
}
else {
psdImage.imgName = imgName;
this._imageMapImgNameKey.set(imgName, psdImage);
}
} }
getAllImage() { getAllImage() {
return this._imageArray; return this._imageMapMd5Key;
} }
/** 尝试获取有编号的图像图层 */ /** 尝试获取有编号的图像图层 */
getSerialNumberImage(psdImage) { getSerialNumberImage(psdImage) {
@ -1059,7 +1086,7 @@
} }
clear() { clear() {
this._imageIdKeyMap.clear(); this._imageIdKeyMap.clear();
this._imageArray.clear(); this._imageMapMd5Key.clear();
} }
static getInstance() { static getInstance() {
if (!this._instance) { if (!this._instance) {

File diff suppressed because it is too large Load Diff

View File

@ -4,19 +4,19 @@
"name": "ccc-tnt-psd2ui", "name": "ccc-tnt-psd2ui",
"description": "i18n:ccc-tnt-psd2ui.description", "description": "i18n:ccc-tnt-psd2ui.description",
"main": "./dist/main.js", "main": "./dist/main.js",
"repository": {
"type": "git",
"url": "https://gitee.com/onvia/ccc-tnt-psd2ui"
},
"dependencies": { "dependencies": {
"ag-psd": "^15.0.0", "ag-psd": "^15.0.0",
"canvas": "^2.11.0", "canvas": "^2.11.0",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"minimist": "^1.2.7", "minimist": "^1.2.7",
"node-fetch": "^2.7.0",
"pinyin-pro": "^3.16.0", "pinyin-pro": "^3.16.0",
"vue": "^3.1.4" "vue": "^3.1.4"
}, },
"devDependencies": {
"@types/fs-extra": "^9.0.5",
"@types/node": "^16.0.1",
"typescript": "^4.3.4"
},
"panels": { "panels": {
"default": { "default": {
"title": "psd2ui Panel", "title": "psd2ui Panel",

View File

@ -1,207 +0,0 @@
//@ts-ignore
import packageJSON from '../package.json';
import fs from 'fs-extra';
import path from 'path';
import Os from 'os';
import child_process from "child_process";
let exec = child_process.exec;
const ENGINE_VER = "v342"; //
const packagePath = path.join(Editor.Project.path, "extensions", packageJSON.name);
const projectAssets = path.join(Editor.Project.path, "assets");
const cacheFile = path.join(Editor.Project.path, "local", "psd-to-prefab-cache.json");
const configFile = path.join(`${packagePath}/config/psd.config.json`);
const nodejsFile = path.join(packagePath, "bin", `node${Os.platform() == 'darwin' ? "" : ".exe"}`);
const commandFile = path.join(packagePath, "libs", "psd2ui", `command.${Os.platform() == 'darwin' ? "sh" : "bat"}`);
const psd = path.join(packagePath, "libs", "psd2ui", "index.js");
let uuid2md5: Map<string, string> = new Map();
let cacheFileJson: Record<string, any> = {};
/**
* @en
* @zh
*/
export const methods: { [key: string]: (...any: any) => any } = {
openPanel() {
Editor.Panel.open(packageJSON.name);
},
onClickPsd2UICache() {
console.log(`main-> onClickPsd2UICache111 `);
return new Promise<void>((resolve, reject) => {
console.log(`main-> onClickPsd2UICache`);
let options = {
"project-assets": projectAssets,
"cache": cacheFile,
"init": true,
"engine-version": ENGINE_VER
}
Promise.all(_exec(options, [])).then(() => {
console.log("[psd2prefab] 执行缓存结束");
resolve();
});
})
},
async onPsd2UIDropFiles(param) {
let files = param.files;
let isForceImg = param.isForceImg;
let isImgOnly = param.isImgOnly;
let output = param.output;
let isPinyin = param.isPinyin;
let options = {
"project-assets": projectAssets,
"cache": cacheFile,
"engine-version": ENGINE_VER,
"pinyin": isPinyin,
}
let tasks: Promise<void>[] = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
let stat = fs.statSync(file);
if (stat.isFile()) {
let ext = path.extname(file);
if (ext != '.psd') {
continue;
}
}
let args = JSON.parse(JSON.stringify(options));
args["input"] = file;
if (output) {
args["output"] = output;
}
if (isImgOnly) {
// 只导出图片
args["img-only"] = true;
} else {
// 强制导出图片
if (isForceImg) {
args["force-img"] = true;
}
args["config"] = configFile;
}
_exec(args, tasks)
}
Promise.all(tasks).then(() => {
genUUID2MD5Mapping();
console.log("[ccc-tnt-psd2ui] psd 导出完成,输出位置为:", output ? output : "psd 同级目录");
}).catch((reason) => {
console.log("[ccc-tnt-psd2ui] 导出失败", reason);
}).finally(() => {
});
},
};
function _exec(options: any, tasks: any) {
let jsonContent = JSON.stringify(options);
if (!fs.existsSync(nodejsFile)) {
console.log(`[ccc-tnt-psd2ui] 没有内置 nodejs`, nodejsFile);
return tasks;
}
// 处理权限问题
if (Os.platform() === 'darwin') {
if (fs.statSync(nodejsFile).mode != 33261) {
console.log(`[ccc-tnt-psd2ui] 设置权限`);
fs.chmodSync(nodejsFile, 33261);
}
if (fs.statSync(commandFile).mode != 33261) {
console.log(`[ccc-tnt-psd2ui] commandFile 设置权限`);
fs.chmodSync(commandFile, 33261);
}
}
console.log("[ccc-tnt-psd2ui] 命令参数:" + jsonContent);
console.log("[ccc-tnt-psd2ui] 命令执行中");
let base64 = Buffer.from(jsonContent).toString("base64");
tasks.push(new Promise<void>((rs) => {
// console.log(`[ccc-tnt-psd2ui] `, `${nodejsFile} ${psd}` + ' ' + `--json ${base64}`);
// exec(`${nodejsFile} ${psd}` + ' ' + `--json ${base64}`, { windowsHide: false }, (err, stdout, stderr) => {
// console.log("[ccc-tnt-psd2ui]:\n", stdout);
// if (stderr) {
// console.log(stderr);
// }
// rs();
// })
let shellScript = commandFile; // 你的脚本路径
let scriptArgs = `--json ${base64}`; // 你的脚本参数
let command =
Os.platform() == 'darwin' ? `osascript -e 'tell app "Terminal" to do script "cd ${process.cwd()}; ${shellScript} ${scriptArgs}"'`
: `start ${commandFile} ${scriptArgs}`;
exec(command, (error, stdout, stderr) => {
console.log("[ccc-tnt-psd2ui]:\n", stdout);
console.log("[ccc-tnt-psd2ui]: 程序执行完后请手动关闭终端窗口", );
if (stderr) {
console.log(stderr);
}
rs();
});
}));
return tasks;
}
/**
*
*
* @param {*} event
*/
function onAssetDeletedListener(event: any) {
if (uuid2md5.has(event)) {
let md5 = uuid2md5.get(event);
console.log(`[ccc-tnt-psd2ui] 删除资源 md5: ${md5}, uuid: ${event}`);
delete cacheFileJson[`${md5}`];
fs.writeFileSync(cacheFile, JSON.stringify(cacheFileJson, null, 2));
}
}
/**
* uuid MD5
*
*/
function genUUID2MD5Mapping() {
if (!fs.existsSync(cacheFile)) {
return;
}
let content = fs.readFileSync(cacheFile, 'utf-8');
let obj = JSON.parse(content);
cacheFileJson = obj;
for (const key in obj) {
const element = obj[key];
uuid2md5.set(element.textureUuid, key);
}
}
/**
* @en Hooks triggered after extension loading is complete
* @zh
*/
export const load = function () {
genUUID2MD5Mapping();
Editor.Message.addBroadcastListener("asset-db:asset-delete", onAssetDeletedListener);
};
/**
* @en Hooks triggered after extension uninstallation is complete
* @zh
*/
export const unload = function () {
Editor.Message.removeBroadcastListener("asset-db:asset-delete", onAssetDeletedListener);
};

View File

@ -1,135 +0,0 @@
import { readFileSync } from 'fs-extra';
import { extname, join, parse } from 'path';
import { createApp, App } from 'vue';
const weakMap = new WeakMap<any, App>();
const AssetDir = `${Editor.Project.path}/assets`;
/**
* @zh 3.3 使
* @en You can add the code below if you want compatibility with versions prior to 3.3
*/
// Editor.Panel.define = Editor.Panel.define || function(options: any) { return options }
module.exports = Editor.Panel.define({
listeners: {
show() { },
hide() { },
},
template: readFileSync(join(__dirname, '../../../static/template/default/index.html'), 'utf-8'),
style: readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8'),
$: {
app: '#app',
},
methods: {
},
ready() {
if (this.$.app) {
const app = createApp({});
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('ui-');
app.component('psd2ui', {
template: readFileSync(join(__dirname, '../../../static/template/vue/psd2ui.html'), 'utf-8'),
data() {
return {
isImgOnly: false,
isForceImg: false,
isProcessing: false,
isPinyin: true,
outputPath: "",
};
},
created() {
let str = localStorage.getItem(`${Editor.Project.name}_psd2ui_output`);
if (str) {
this.outputPath = str;
}
},
beforeUnmount() {
localStorage.setItem(`${Editor.Project.name}_psd2ui_output`, this.outputPath);
},
methods: {
async onClickCache() {
if (this.isProcessing) return;
this.isProcessing = true;
await Editor.Message.request("ccc-tnt-psd2ui", "on-click-cache");
this.isProcessing = false;
},
onForceChanged(e: any) {
this.isForceImg = !this.isForceImg;
},
onImgOnlyChanged() {
this.isImgOnly = !this.isImgOnly;
},
onPinyinChanged() {
this.isPinyin = !this.isPinyin;
},
async onClickDropArea(event: any) {
if (this.isProcessing) {
Editor.Dialog.warn("当前有正在处理的文件,请等待完成。\n如果已完成请关闭 DOS 窗口。")
return;
}
let result = await Editor.Dialog.select({
'multi': true,
'type': "file",
'filters': [
{
'extensions': ["psd"],
'name': "请选择 PSD"
}
]
});
let files = result.filePaths;
this.processPsd(files);
},
onDragEnter(event: any) {
event.stopPropagation()
event.preventDefault()
// event.target.add("drag-hovering")
},
onDragLeave(event: any) {
event.stopPropagation()
event.preventDefault()
// event.target.remove("drag-hovering")
},
async onDropFiles(event: any) {
let files: any[] = [];
[].forEach.call(event.dataTransfer.files, function (file: any) {
files.push(file.path);
}, false);
this.processPsd(files);
},
async processPsd(files: any[]) {
if (!files.length) {
return;
}
if (this.isProcessing) {
Editor.Dialog.warn("当前有正在处理的文件,请等待完成。\n如果已完成请关闭 DOS 窗口。")
return;
}
this.isProcessing = true;
await Editor.Message.request("ccc-tnt-psd2ui", "on-drop-file", { output: this.outputPath, files, isForceImg: this.isForceImg, isImgOnly: this.isImgOnly,isPinyin: this.isPinyin });
this.isProcessing = false;
}
},
});
app.mount(this.$.app);
weakMap.set(this, app);
}
},
beforeClose() { },
close() {
const app = weakMap.get(this);
if (app) {
app.unmount();
}
},
});

Binary file not shown.

View File

@ -388,8 +388,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.6.7", "version": "2.7.0",
"license": "MIT", "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": { "dependencies": {
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
}, },
@ -455,8 +456,7 @@
}, },
"node_modules/pinyin-pro": { "node_modules/pinyin-pro": {
"version": "3.16.0", "version": "3.16.0",
"resolved": "https://registry.npmmirror.com/pinyin-pro/-/pinyin-pro-3.16.0.tgz", "license": "MIT"
"integrity": "sha512-U4pMQ/KSMM5JmSb+ZcReCIbgzGl/JaglaHqWjCli0hpA0rDdjRbAO67e6fOa3ZFcJzbqfe6bJkaMMmpiWmkXgQ=="
}, },
"node_modules/readable-stream": { "node_modules/readable-stream": {
"version": "3.6.0", "version": "3.6.0",

View File

@ -188,6 +188,49 @@ fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png')
}); });
``` ```
In Node.js 14 you can also use async iterators to read `body`; however, be careful to catch
errors -- the longer a response runs, the more likely it is to encounter an error.
```js
const fetch = require('node-fetch');
const response = await fetch('https://httpbin.org/stream/3');
try {
for await (const chunk of response.body) {
console.dir(JSON.parse(chunk.toString()));
}
} catch (err) {
console.error(err.stack);
}
```
In Node.js 12 you can also use async iterators to read `body`; however, async iterators with streams
did not mature until Node.js 14, so you need to do some extra work to ensure you handle errors
directly from the stream and wait on it response to fully close.
```js
const fetch = require('node-fetch');
const read = async body => {
let error;
body.on('error', err => {
error = err;
});
for await (const chunk of body) {
console.dir(JSON.parse(chunk.toString()));
}
return new Promise((resolve, reject) => {
body.on('close', () => {
error ? reject(error) : resolve();
});
});
};
try {
const response = await fetch('https://httpbin.org/stream/3');
await read(response.body);
} catch (err) {
console.error(err.stack);
}
```
#### Buffer #### Buffer
If you prefer to cache binary data in full, use buffer(). (NOTE: `buffer()` is a `node-fetch`-only API) If you prefer to cache binary data in full, use buffer(). (NOTE: `buffer()` is a `node-fetch`-only API)
@ -344,7 +387,6 @@ Header | Value
------------------- | -------------------------------------------------------- ------------------- | --------------------------------------------------------
`Accept-Encoding` | `gzip,deflate` _(when `options.compress === true`)_ `Accept-Encoding` | `gzip,deflate` _(when `options.compress === true`)_
`Accept` | `*/*` `Accept` | `*/*`
`Connection` | `close` _(when no `options.agent` is present)_
`Content-Length` | _(automatically calculated, if possible)_ `Content-Length` | _(automatically calculated, if possible)_
`Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_ `Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_
`User-Agent` | `node-fetch/1.0 (+https://github.com/bitinn/node-fetch)` `User-Agent` | `node-fetch/1.0 (+https://github.com/bitinn/node-fetch)`
@ -361,6 +403,8 @@ The `agent` option allows you to specify networking related options which are ou
See [`http.Agent`](https://nodejs.org/api/http.html#http_new_agent_options) for more information. See [`http.Agent`](https://nodejs.org/api/http.html#http_new_agent_options) for more information.
If no agent is specified, the default agent provided by Node.js is used. Note that [this changed in Node.js 19](https://github.com/nodejs/node/blob/4267b92604ad78584244488e7f7508a690cb80d0/lib/_http_agent.js#L564) to have `keepalive` true by default. If you wish to enable `keepalive` in an earlier version of Node.js, you can override the agent as per the following code sample.
In addition, the `agent` option accepts a function that returns `http`(s)`.Agent` instance given current [URL](https://nodejs.org/api/url.html), this is useful during a redirection chain across HTTP and HTTPS protocol. In addition, the `agent` option accepts a function that returns `http`(s)`.Agent` instance given current [URL](https://nodejs.org/api/url.html), this is useful during a redirection chain across HTTP and HTTPS protocol.
```js ```js

View File

@ -11,15 +11,15 @@ var getGlobal = function () {
throw new Error('unable to locate global object'); throw new Error('unable to locate global object');
} }
var global = getGlobal(); var globalObject = getGlobal();
module.exports = exports = global.fetch; module.exports = exports = globalObject.fetch;
// Needed for TypeScript and Webpack. // Needed for TypeScript and Webpack.
if (global.fetch) { if (globalObject.fetch) {
exports.default = global.fetch.bind(global); exports.default = globalObject.fetch.bind(globalObject);
} }
exports.Headers = global.Headers; exports.Headers = globalObject.Headers;
exports.Request = global.Request; exports.Request = globalObject.Request;
exports.Response = global.Response; exports.Response = globalObject.Response;

View File

@ -1361,10 +1361,6 @@ function getNodeRequestOptions(request) {
agent = agent(parsedURL); agent = agent(parsedURL);
} }
if (!headers.has('Connection') && !agent) {
headers.set('Connection', 'close');
}
// HTTP-network fetch step 4.2 // HTTP-network fetch step 4.2
// chunked encoding is handled by Node.js // chunked encoding is handled by Node.js
@ -1413,6 +1409,20 @@ const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original)
return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest); return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest);
}; };
/**
* isSameProtocol reports whether the two provided URLs use the same protocol.
*
* Both domains must already be in canonical form.
* @param {string|URL} original
* @param {string|URL} destination
*/
const isSameProtocol = function isSameProtocol(destination, original) {
const orig = new URL$1(original).protocol;
const dest = new URL$1(destination).protocol;
return orig === dest;
};
/** /**
* Fetch function * Fetch function
* *
@ -1444,7 +1454,7 @@ function fetch(url, opts) {
let error = new AbortError('The user aborted a request.'); let error = new AbortError('The user aborted a request.');
reject(error); reject(error);
if (request.body && request.body instanceof Stream.Readable) { if (request.body && request.body instanceof Stream.Readable) {
request.body.destroy(error); destroyStream(request.body, error);
} }
if (!response || !response.body) return; if (!response || !response.body) return;
response.body.emit('error', error); response.body.emit('error', error);
@ -1485,9 +1495,43 @@ function fetch(url, opts) {
req.on('error', function (err) { req.on('error', function (err) {
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
if (response && response.body) {
destroyStream(response.body, err);
}
finalize(); finalize();
}); });
fixResponseChunkedTransferBadEnding(req, function (err) {
if (signal && signal.aborted) {
return;
}
if (response && response.body) {
destroyStream(response.body, err);
}
});
/* c8 ignore next 18 */
if (parseInt(process.version.substring(1)) < 14) {
// Before Node.js 14, pipeline() does not fully support async iterators and does not always
// properly handle when the socket close/end events are out of order.
req.on('socket', function (s) {
s.addListener('close', function (hadError) {
// if a data listener is still present we didn't end cleanly
const hasDataListener = s.listenerCount('data') > 0;
// if end happened before close but the socket didn't emit an error, do it now
if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
response.body.emit('error', err);
}
});
});
}
req.on('response', function (res) { req.on('response', function (res) {
clearTimeout(reqTimeout); clearTimeout(reqTimeout);
@ -1559,7 +1603,7 @@ function fetch(url, opts) {
size: request.size size: request.size
}; };
if (!isDomainOrSubdomain(request.url, locationURL)) { if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) { for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
requestOpts.headers.delete(name); requestOpts.headers.delete(name);
} }
@ -1652,6 +1696,13 @@ function fetch(url, opts) {
response = new Response(body, response_options); response = new Response(body, response_options);
resolve(response); resolve(response);
}); });
raw.on('end', function () {
// some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
if (!response) {
response = new Response(body, response_options);
resolve(response);
}
});
return; return;
} }
@ -1671,6 +1722,44 @@ function fetch(url, opts) {
writeToStream(req, request); writeToStream(req, request);
}); });
} }
function fixResponseChunkedTransferBadEnding(request, errorCallback) {
let socket;
request.on('socket', function (s) {
socket = s;
});
request.on('response', function (response) {
const headers = response.headers;
if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) {
response.once('close', function (hadError) {
// tests for socket presence, as in some situations the
// the 'socket' event is not triggered for the request
// (happens in deno), avoids `TypeError`
// if a data listener is still present we didn't end cleanly
const hasDataListener = socket && socket.listenerCount('data') > 0;
if (hasDataListener && !hadError) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
errorCallback(err);
}
});
}
});
}
function destroyStream(stream, err) {
if (stream.destroy) {
stream.destroy(err);
} else {
// node < 8
stream.emit('error', err);
stream.end();
}
}
/** /**
* Redirect code matching * Redirect code matching
* *
@ -1685,4 +1774,4 @@ fetch.isRedirect = function (code) {
fetch.Promise = global.Promise; fetch.Promise = global.Promise;
export default fetch; export default fetch;
export { Headers, Request, Response, FetchError }; export { Headers, Request, Response, FetchError, AbortError };

View File

@ -1365,10 +1365,6 @@ function getNodeRequestOptions(request) {
agent = agent(parsedURL); agent = agent(parsedURL);
} }
if (!headers.has('Connection') && !agent) {
headers.set('Connection', 'close');
}
// HTTP-network fetch step 4.2 // HTTP-network fetch step 4.2
// chunked encoding is handled by Node.js // chunked encoding is handled by Node.js
@ -1417,6 +1413,20 @@ const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original)
return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest); return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest);
}; };
/**
* isSameProtocol reports whether the two provided URLs use the same protocol.
*
* Both domains must already be in canonical form.
* @param {string|URL} original
* @param {string|URL} destination
*/
const isSameProtocol = function isSameProtocol(destination, original) {
const orig = new URL$1(original).protocol;
const dest = new URL$1(destination).protocol;
return orig === dest;
};
/** /**
* Fetch function * Fetch function
* *
@ -1448,7 +1458,7 @@ function fetch(url, opts) {
let error = new AbortError('The user aborted a request.'); let error = new AbortError('The user aborted a request.');
reject(error); reject(error);
if (request.body && request.body instanceof Stream.Readable) { if (request.body && request.body instanceof Stream.Readable) {
request.body.destroy(error); destroyStream(request.body, error);
} }
if (!response || !response.body) return; if (!response || !response.body) return;
response.body.emit('error', error); response.body.emit('error', error);
@ -1489,9 +1499,43 @@ function fetch(url, opts) {
req.on('error', function (err) { req.on('error', function (err) {
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
if (response && response.body) {
destroyStream(response.body, err);
}
finalize(); finalize();
}); });
fixResponseChunkedTransferBadEnding(req, function (err) {
if (signal && signal.aborted) {
return;
}
if (response && response.body) {
destroyStream(response.body, err);
}
});
/* c8 ignore next 18 */
if (parseInt(process.version.substring(1)) < 14) {
// Before Node.js 14, pipeline() does not fully support async iterators and does not always
// properly handle when the socket close/end events are out of order.
req.on('socket', function (s) {
s.addListener('close', function (hadError) {
// if a data listener is still present we didn't end cleanly
const hasDataListener = s.listenerCount('data') > 0;
// if end happened before close but the socket didn't emit an error, do it now
if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
response.body.emit('error', err);
}
});
});
}
req.on('response', function (res) { req.on('response', function (res) {
clearTimeout(reqTimeout); clearTimeout(reqTimeout);
@ -1563,7 +1607,7 @@ function fetch(url, opts) {
size: request.size size: request.size
}; };
if (!isDomainOrSubdomain(request.url, locationURL)) { if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) { for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
requestOpts.headers.delete(name); requestOpts.headers.delete(name);
} }
@ -1656,6 +1700,13 @@ function fetch(url, opts) {
response = new Response(body, response_options); response = new Response(body, response_options);
resolve(response); resolve(response);
}); });
raw.on('end', function () {
// some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
if (!response) {
response = new Response(body, response_options);
resolve(response);
}
});
return; return;
} }
@ -1675,6 +1726,44 @@ function fetch(url, opts) {
writeToStream(req, request); writeToStream(req, request);
}); });
} }
function fixResponseChunkedTransferBadEnding(request, errorCallback) {
let socket;
request.on('socket', function (s) {
socket = s;
});
request.on('response', function (response) {
const headers = response.headers;
if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) {
response.once('close', function (hadError) {
// tests for socket presence, as in some situations the
// the 'socket' event is not triggered for the request
// (happens in deno), avoids `TypeError`
// if a data listener is still present we didn't end cleanly
const hasDataListener = socket && socket.listenerCount('data') > 0;
if (hasDataListener && !hadError) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
errorCallback(err);
}
});
}
});
}
function destroyStream(stream, err) {
if (stream.destroy) {
stream.destroy(err);
} else {
// node < 8
stream.emit('error', err);
stream.end();
}
}
/** /**
* Redirect code matching * Redirect code matching
* *
@ -1695,3 +1784,4 @@ exports.Headers = Headers;
exports.Request = Request; exports.Request = Request;
exports.Response = Response; exports.Response = Response;
exports.FetchError = FetchError; exports.FetchError = FetchError;
exports.AbortError = AbortError;

View File

@ -1359,10 +1359,6 @@ function getNodeRequestOptions(request) {
agent = agent(parsedURL); agent = agent(parsedURL);
} }
if (!headers.has('Connection') && !agent) {
headers.set('Connection', 'close');
}
// HTTP-network fetch step 4.2 // HTTP-network fetch step 4.2
// chunked encoding is handled by Node.js // chunked encoding is handled by Node.js
@ -1411,6 +1407,20 @@ const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original)
return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest); return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest);
}; };
/**
* isSameProtocol reports whether the two provided URLs use the same protocol.
*
* Both domains must already be in canonical form.
* @param {string|URL} original
* @param {string|URL} destination
*/
const isSameProtocol = function isSameProtocol(destination, original) {
const orig = new URL$1(original).protocol;
const dest = new URL$1(destination).protocol;
return orig === dest;
};
/** /**
* Fetch function * Fetch function
* *
@ -1442,7 +1452,7 @@ function fetch(url, opts) {
let error = new AbortError('The user aborted a request.'); let error = new AbortError('The user aborted a request.');
reject(error); reject(error);
if (request.body && request.body instanceof Stream.Readable) { if (request.body && request.body instanceof Stream.Readable) {
request.body.destroy(error); destroyStream(request.body, error);
} }
if (!response || !response.body) return; if (!response || !response.body) return;
response.body.emit('error', error); response.body.emit('error', error);
@ -1483,9 +1493,43 @@ function fetch(url, opts) {
req.on('error', function (err) { req.on('error', function (err) {
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
if (response && response.body) {
destroyStream(response.body, err);
}
finalize(); finalize();
}); });
fixResponseChunkedTransferBadEnding(req, function (err) {
if (signal && signal.aborted) {
return;
}
if (response && response.body) {
destroyStream(response.body, err);
}
});
/* c8 ignore next 18 */
if (parseInt(process.version.substring(1)) < 14) {
// Before Node.js 14, pipeline() does not fully support async iterators and does not always
// properly handle when the socket close/end events are out of order.
req.on('socket', function (s) {
s.addListener('close', function (hadError) {
// if a data listener is still present we didn't end cleanly
const hasDataListener = s.listenerCount('data') > 0;
// if end happened before close but the socket didn't emit an error, do it now
if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
response.body.emit('error', err);
}
});
});
}
req.on('response', function (res) { req.on('response', function (res) {
clearTimeout(reqTimeout); clearTimeout(reqTimeout);
@ -1557,7 +1601,7 @@ function fetch(url, opts) {
size: request.size size: request.size
}; };
if (!isDomainOrSubdomain(request.url, locationURL)) { if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) { for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
requestOpts.headers.delete(name); requestOpts.headers.delete(name);
} }
@ -1650,6 +1694,13 @@ function fetch(url, opts) {
response = new Response(body, response_options); response = new Response(body, response_options);
resolve(response); resolve(response);
}); });
raw.on('end', function () {
// some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
if (!response) {
response = new Response(body, response_options);
resolve(response);
}
});
return; return;
} }
@ -1669,6 +1720,44 @@ function fetch(url, opts) {
writeToStream(req, request); writeToStream(req, request);
}); });
} }
function fixResponseChunkedTransferBadEnding(request, errorCallback) {
let socket;
request.on('socket', function (s) {
socket = s;
});
request.on('response', function (response) {
const headers = response.headers;
if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) {
response.once('close', function (hadError) {
// tests for socket presence, as in some situations the
// the 'socket' event is not triggered for the request
// (happens in deno), avoids `TypeError`
// if a data listener is still present we didn't end cleanly
const hasDataListener = socket && socket.listenerCount('data') > 0;
if (hasDataListener && !hadError) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
errorCallback(err);
}
});
}
});
}
function destroyStream(stream, err) {
if (stream.destroy) {
stream.destroy(err);
} else {
// node < 8
stream.emit('error', err);
stream.end();
}
}
/** /**
* Redirect code matching * Redirect code matching
* *
@ -1683,4 +1772,4 @@ fetch.isRedirect = function (code) {
fetch.Promise = global.Promise; fetch.Promise = global.Promise;
export default fetch; export default fetch;
export { Headers, Request, Response, FetchError }; export { Headers, Request, Response, FetchError, AbortError };

View File

@ -1,6 +1,6 @@
{ {
"name": "node-fetch", "name": "node-fetch",
"version": "2.6.7", "version": "2.7.0",
"description": "A light-weight module that brings window.fetch to node.js", "description": "A light-weight module that brings window.fetch to node.js",
"main": "lib/index.js", "main": "lib/index.js",
"browser": "./browser.js", "browser": "./browser.js",
@ -53,7 +53,9 @@
"abortcontroller-polyfill": "^1.3.0", "abortcontroller-polyfill": "^1.3.0",
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"babel-plugin-istanbul": "^4.1.6", "babel-plugin-istanbul": "^4.1.6",
"babel-preset-env": "^1.6.1", "babel-plugin-transform-async-generator-functions": "^6.24.1",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "1.4.0",
"babel-register": "^6.16.3", "babel-register": "^6.16.3",
"chai": "^3.5.0", "chai": "^3.5.0",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
@ -72,5 +74,16 @@
"rollup-plugin-babel": "^3.0.7", "rollup-plugin-babel": "^3.0.7",
"string-to-arraybuffer": "^1.0.2", "string-to-arraybuffer": "^1.0.2",
"teeny-request": "3.7.0" "teeny-request": "3.7.0"
},
"release": {
"branches": [
"+([0-9]).x",
"main",
"next",
{
"name": "beta",
"prerelease": true
}
]
} }
} }

View File

@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
else
exec node "$basedir/../typescript/bin/tsc" "$@"
fi

View File

@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsc" %*

View File

@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
} else {
& "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../typescript/bin/tsc" $args
} else {
& "node$exe" "$basedir/../typescript/bin/tsc" $args
}
$ret=$LASTEXITCODE
}
exit $ret

View File

@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
else
exec node "$basedir/../typescript/bin/tsserver" "$@"
fi

View File

@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsserver" %*

View File

@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
} else {
& "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../typescript/bin/tsserver" $args
} else {
& "node$exe" "$basedir/../typescript/bin/tsserver" $args
}
$ret=$LASTEXITCODE
}
exit $ret

View File

@ -0,0 +1,88 @@
data-uri-to-buffer
==================
### Generate a Buffer instance from a [Data URI][rfc] string
[![Build Status](https://travis-ci.org/TooTallNate/node-data-uri-to-buffer.svg?branch=master)](https://travis-ci.org/TooTallNate/node-data-uri-to-buffer)
This module accepts a ["data" URI][rfc] String of data, and returns a
node.js `Buffer` instance with the decoded data.
Installation
------------
Install with `npm`:
``` bash
$ npm install data-uri-to-buffer
```
Example
-------
``` js
import dataUriToBuffer from 'data-uri-to-buffer';
// plain-text data is supported
let uri = 'data:,Hello%2C%20World!';
let decoded = dataUriToBuffer(uri);
console.log(decoded.toString());
// 'Hello, World!'
// base64-encoded data is supported
uri = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D';
decoded = dataUriToBuffer(uri);
console.log(decoded.toString());
// 'Hello, World!'
```
API
---
### dataUriToBuffer(String uri) → Buffer
The `type` property on the Buffer instance gets set to the main type portion of
the "mediatype" portion of the "data" URI, or defaults to `"text/plain"` if not
specified.
The `typeFull` property on the Buffer instance gets set to the entire
"mediatype" portion of the "data" URI (including all parameters), or defaults
to `"text/plain;charset=US-ASCII"` if not specified.
The `charset` property on the Buffer instance gets set to the Charset portion of
the "mediatype" portion of the "data" URI, or defaults to `"US-ASCII"` if the
entire type is not specified, or defaults to `""` otherwise.
*Note*: If the only the main type is specified but not the charset, e.g.
`"data:text/plain,abc"`, the charset is set to the empty string. The spec only
defaults to US-ASCII as charset if the entire type is not specified.
License
-------
(The MIT License)
Copyright (c) 2014 Nathan Rajlich &lt;nathan@tootallnate.net&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[rfc]: http://tools.ietf.org/html/rfc2397

View File

@ -0,0 +1,15 @@
/// <reference types="node" />
export interface MimeBuffer extends Buffer {
type: string;
typeFull: string;
charset: string;
}
/**
* Returns a `Buffer` instance from the given data URI `uri`.
*
* @param {String} uri Data URI to turn into a Buffer instance
* @returns {Buffer} Buffer instance from Data URI
* @api public
*/
export declare function dataUriToBuffer(uri: string): MimeBuffer;
export default dataUriToBuffer;

View File

@ -0,0 +1,53 @@
/**
* Returns a `Buffer` instance from the given data URI `uri`.
*
* @param {String} uri Data URI to turn into a Buffer instance
* @returns {Buffer} Buffer instance from Data URI
* @api public
*/
export function dataUriToBuffer(uri) {
if (!/^data:/i.test(uri)) {
throw new TypeError('`uri` does not appear to be a Data URI (must begin with "data:")');
}
// strip newlines
uri = uri.replace(/\r?\n/g, '');
// split the URI up into the "metadata" and the "data" portions
const firstComma = uri.indexOf(',');
if (firstComma === -1 || firstComma <= 4) {
throw new TypeError('malformed data: URI');
}
// remove the "data:" scheme and parse the metadata
const meta = uri.substring(5, firstComma).split(';');
let charset = '';
let base64 = false;
const type = meta[0] || 'text/plain';
let typeFull = type;
for (let i = 1; i < meta.length; i++) {
if (meta[i] === 'base64') {
base64 = true;
}
else if (meta[i]) {
typeFull += `;${meta[i]}`;
if (meta[i].indexOf('charset=') === 0) {
charset = meta[i].substring(8);
}
}
}
// defaults to US-ASCII only if type is not provided
if (!meta[0] && !charset.length) {
typeFull += ';charset=US-ASCII';
charset = 'US-ASCII';
}
// get the encoded data portion and decode URI-encoded chars
const encoding = base64 ? 'base64' : 'ascii';
const data = unescape(uri.substring(firstComma + 1));
const buffer = Buffer.from(data, encoding);
// set `.type` and `.typeFull` properties to MIME type
buffer.type = type;
buffer.typeFull = typeFull;
// set the `.charset` property
buffer.charset = charset;
return buffer;
}
export default dataUriToBuffer;
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IAC1C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACzB,MAAM,IAAI,SAAS,CAClB,kEAAkE,CAClE,CAAC;KACF;IAED,iBAAiB;IACjB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEhC,+DAA+D;IAC/D,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE;QACzC,MAAM,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAC;KAC3C;IAED,mDAAmD;IACnD,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAErD,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC;IACrC,IAAI,QAAQ,GAAG,IAAI,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACrC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE;YACzB,MAAM,GAAG,IAAI,CAAC;SACd;aAAM,IAAG,IAAI,CAAC,CAAC,CAAC,EAAE;YAClB,QAAQ,IAAI,IAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE;gBACtC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;aAC/B;SACD;KACD;IACD,oDAAoD;IACpD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;QAChC,QAAQ,IAAI,mBAAmB,CAAC;QAChC,OAAO,GAAG,UAAU,CAAC;KACrB;IAED,4DAA4D;IAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAe,CAAC;IAEzD,sDAAsD;IACtD,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAE3B,8BAA8B;IAC9B,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;IAEzB,OAAO,MAAM,CAAC;AACf,CAAC;AAED,eAAe,eAAe,CAAC"}

View File

@ -0,0 +1,62 @@
{
"name": "data-uri-to-buffer",
"version": "4.0.1",
"description": "Generate a Buffer instance from a Data URI string",
"type": "module",
"exports": "./dist/index.js",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"src"
],
"scripts": {
"build": "tsc",
"test": "jest",
"prepublishOnly": "npm run build"
},
"repository": {
"type": "git",
"url": "git://github.com/TooTallNate/node-data-uri-to-buffer.git"
},
"engines": {
"node": ">= 12"
},
"keywords": [
"data",
"uri",
"datauri",
"data-uri",
"buffer",
"convert",
"rfc2397",
"2397"
],
"author": "Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)",
"license": "MIT",
"bugs": {
"url": "https://github.com/TooTallNate/node-data-uri-to-buffer/issues"
},
"homepage": "https://github.com/TooTallNate/node-data-uri-to-buffer",
"devDependencies": {
"@types/jest": "^27.0.2",
"@types/node": "^12.20.36",
"jest": "^27.3.1",
"ts-jest": "^27.0.7",
"typescript": "^4.4.4"
},
"jest": {
"preset": "ts-jest",
"globals": {
"ts-jest": {
"diagnostics": false,
"isolatedModules": true
}
},
"verbose": false,
"testEnvironment": "node",
"testMatch": [
"<rootDir>/test/**/*.test.ts"
]
}
}

View File

@ -0,0 +1,68 @@
export interface MimeBuffer extends Buffer {
type: string;
typeFull: string;
charset: string;
}
/**
* Returns a `Buffer` instance from the given data URI `uri`.
*
* @param {String} uri Data URI to turn into a Buffer instance
* @returns {Buffer} Buffer instance from Data URI
* @api public
*/
export function dataUriToBuffer(uri: string): MimeBuffer {
if (!/^data:/i.test(uri)) {
throw new TypeError(
'`uri` does not appear to be a Data URI (must begin with "data:")'
);
}
// strip newlines
uri = uri.replace(/\r?\n/g, '');
// split the URI up into the "metadata" and the "data" portions
const firstComma = uri.indexOf(',');
if (firstComma === -1 || firstComma <= 4) {
throw new TypeError('malformed data: URI');
}
// remove the "data:" scheme and parse the metadata
const meta = uri.substring(5, firstComma).split(';');
let charset = '';
let base64 = false;
const type = meta[0] || 'text/plain';
let typeFull = type;
for (let i = 1; i < meta.length; i++) {
if (meta[i] === 'base64') {
base64 = true;
} else if(meta[i]) {
typeFull += `;${ meta[i]}`;
if (meta[i].indexOf('charset=') === 0) {
charset = meta[i].substring(8);
}
}
}
// defaults to US-ASCII only if type is not provided
if (!meta[0] && !charset.length) {
typeFull += ';charset=US-ASCII';
charset = 'US-ASCII';
}
// get the encoded data portion and decode URI-encoded chars
const encoding = base64 ? 'base64' : 'ascii';
const data = unescape(uri.substring(firstComma + 1));
const buffer = Buffer.from(data, encoding) as MimeBuffer;
// set `.type` and `.typeFull` properties to MIME type
buffer.type = type;
buffer.typeFull = typeFull;
// set the `.charset` property
buffer.charset = charset;
return buffer;
}
export default dataUriToBuffer;

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 David Frank
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,106 @@
# fetch-blob
[![npm version][npm-image]][npm-url]
[![build status][ci-image]][ci-url]
[![coverage status][codecov-image]][codecov-url]
[![install size][install-size-image]][install-size-url]
A Blob implementation in Node.js, originally from [node-fetch](https://github.com/node-fetch/node-fetch).
## Installation
```sh
npm install fetch-blob
```
<details>
<summary>Upgrading from 2x to 3x</summary>
Updating from 2 to 3 should be a breeze since there is not many changes to the blob specification.
The major cause of a major release is coding standards.
- internal WeakMaps was replaced with private fields
- internal Buffer.from was replaced with TextEncoder/Decoder
- internal buffers was replaced with Uint8Arrays
- CommonJS was replaced with ESM
- The node stream returned by calling `blob.stream()` was replaced with whatwg streams
- (Read "Differences from other blobs" for more info.)
</details>
<details>
<summary>Differences from other Blobs</summary>
- Unlike NodeJS `buffer.Blob` (Added in: v15.7.0) and browser native Blob this polyfilled version can't be sent via PostMessage
- This blob version is more arbitrary, it can be constructed with blob parts that isn't a instance of itself
it has to look and behave as a blob to be accepted as a blob part.
- The benefit of this is that you can create other types of blobs that don't contain any internal data that has to be read in other ways, such as the `BlobDataItem` created in `from.js` that wraps a file path into a blob-like item and read lazily (nodejs plans to [implement this][fs-blobs] as well)
- The `blob.stream()` is the most noticeable differences. It returns a WHATWG stream now. to keep it as a node stream you would have to do:
```js
import {Readable} from 'stream'
const stream = Readable.from(blob.stream())
```
</details>
## Usage
```js
// Ways to import
// (PS it's dependency free ESM package so regular http-import from CDN works too)
import Blob from 'fetch-blob'
import File from 'fetch-blob/file.js'
import {Blob} from 'fetch-blob'
import {File} from 'fetch-blob/file.js'
const {Blob} = await import('fetch-blob')
// Ways to read the blob:
const blob = new Blob(['hello, world'])
await blob.text()
await blob.arrayBuffer()
for await (let chunk of blob.stream()) { ... }
blob.stream().getReader().read()
blob.stream().getReader({mode: 'byob'}).read(view)
```
### Blob part backed up by filesystem
`fetch-blob/from.js` comes packed with tools to convert any filepath into either a Blob or a File
It will not read the content into memory. It will only stat the file for last modified date and file size.
```js
// The default export is sync and use fs.stat to retrieve size & last modified as a blob
import blobFromSync from 'fetch-blob/from.js'
import {File, Blob, blobFrom, blobFromSync, fileFrom, fileFromSync} from 'fetch-blob/from.js'
const fsFile = fileFromSync('./2-GiB-file.bin', 'application/octet-stream')
const fsBlob = await blobFrom('./2-GiB-file.mp4')
// Not a 4 GiB memory snapshot, just holds references
// points to where data is located on the disk
const blob = new Blob([fsFile, fsBlob, 'memory', new Uint8Array(10)])
console.log(blob.size) // ~4 GiB
```
`blobFrom|blobFromSync|fileFrom|fileFromSync(path, [mimetype])`
### Creating Blobs backed up by other async sources
Our Blob & File class are more generic then any other polyfills in the way that it can accept any blob look-a-like item
An example of this is that our blob implementation can be constructed with parts coming from [BlobDataItem](https://github.com/node-fetch/fetch-blob/blob/8ef89adad40d255a3bbd55cf38b88597c1cd5480/from.js#L32) (aka a filepath) or from [buffer.Blob](https://nodejs.org/api/buffer.html#buffer_new_buffer_blob_sources_options), It dose not have to implement all the methods - just enough that it can be read/understood by our Blob implementation. The minium requirements is that it has `Symbol.toStringTag`, `size`, `slice()` and either a `stream()` or a `arrayBuffer()` method. If you then wrap it in our Blob or File `new Blob([blobDataItem])` then you get all of the other methods that should be implemented in a blob or file
An example of this could be to create a file or blob like item coming from a remote HTTP request. Or from a DataBase
See the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [tests](https://github.com/node-fetch/fetch-blob/blob/master/test.js) for more details of how to use the Blob.
[npm-image]: https://flat.badgen.net/npm/v/fetch-blob
[npm-url]: https://www.npmjs.com/package/fetch-blob
[ci-image]: https://github.com/node-fetch/fetch-blob/workflows/CI/badge.svg
[ci-url]: https://github.com/node-fetch/fetch-blob/actions
[codecov-image]: https://flat.badgen.net/codecov/c/github/node-fetch/fetch-blob/master
[codecov-url]: https://codecov.io/gh/node-fetch/fetch-blob
[install-size-image]: https://flat.badgen.net/packagephobia/install/fetch-blob
[install-size-url]: https://packagephobia.now.sh/result?p=fetch-blob
[fs-blobs]: https://github.com/nodejs/node/issues/37340

View File

@ -0,0 +1,2 @@
/** @type {typeof globalThis.File} */ export const File: typeof globalThis.File;
export default File;

View File

@ -0,0 +1,49 @@
import Blob from './index.js'
const _File = class File extends Blob {
#lastModified = 0
#name = ''
/**
* @param {*[]} fileBits
* @param {string} fileName
* @param {{lastModified?: number, type?: string}} options
*/// @ts-ignore
constructor (fileBits, fileName, options = {}) {
if (arguments.length < 2) {
throw new TypeError(`Failed to construct 'File': 2 arguments required, but only ${arguments.length} present.`)
}
super(fileBits, options)
if (options === null) options = {}
// Simulate WebIDL type casting for NaN value in lastModified option.
const lastModified = options.lastModified === undefined ? Date.now() : Number(options.lastModified)
if (!Number.isNaN(lastModified)) {
this.#lastModified = lastModified
}
this.#name = String(fileName)
}
get name () {
return this.#name
}
get lastModified () {
return this.#lastModified
}
get [Symbol.toStringTag] () {
return 'File'
}
static [Symbol.hasInstance] (object) {
return !!object && object instanceof Blob &&
/^(File)$/.test(object[Symbol.toStringTag])
}
}
/** @type {typeof globalThis.File} */// @ts-ignore
export const File = _File
export default File

View File

@ -0,0 +1,26 @@
export default blobFromSync;
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
*/
export function blobFromSync(path: string, type?: string): Blob;
import File from "./file.js";
import Blob from "./index.js";
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
* @returns {Promise<Blob>}
*/
export function blobFrom(path: string, type?: string): Promise<Blob>;
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
* @returns {Promise<File>}
*/
export function fileFrom(path: string, type?: string): Promise<File>;
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
*/
export function fileFromSync(path: string, type?: string): File;
export { File, Blob };

View File

@ -0,0 +1,100 @@
import { statSync, createReadStream, promises as fs } from 'node:fs'
import { basename } from 'node:path'
import DOMException from 'node-domexception'
import File from './file.js'
import Blob from './index.js'
const { stat } = fs
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
*/
const blobFromSync = (path, type) => fromBlob(statSync(path), path, type)
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
* @returns {Promise<Blob>}
*/
const blobFrom = (path, type) => stat(path).then(stat => fromBlob(stat, path, type))
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
* @returns {Promise<File>}
*/
const fileFrom = (path, type) => stat(path).then(stat => fromFile(stat, path, type))
/**
* @param {string} path filepath on the disk
* @param {string} [type] mimetype to use
*/
const fileFromSync = (path, type) => fromFile(statSync(path), path, type)
// @ts-ignore
const fromBlob = (stat, path, type = '') => new Blob([new BlobDataItem({
path,
size: stat.size,
lastModified: stat.mtimeMs,
start: 0
})], { type })
// @ts-ignore
const fromFile = (stat, path, type = '') => new File([new BlobDataItem({
path,
size: stat.size,
lastModified: stat.mtimeMs,
start: 0
})], basename(path), { type, lastModified: stat.mtimeMs })
/**
* This is a blob backed up by a file on the disk
* with minium requirement. Its wrapped around a Blob as a blobPart
* so you have no direct access to this.
*
* @private
*/
class BlobDataItem {
#path
#start
constructor (options) {
this.#path = options.path
this.#start = options.start
this.size = options.size
this.lastModified = options.lastModified
}
/**
* Slicing arguments is first validated and formatted
* to not be out of range by Blob.prototype.slice
*/
slice (start, end) {
return new BlobDataItem({
path: this.#path,
lastModified: this.lastModified,
size: end - start,
start: this.#start + start
})
}
async * stream () {
const { mtimeMs } = await stat(this.#path)
if (mtimeMs > this.lastModified) {
throw new DOMException('The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.', 'NotReadableError')
}
yield * createReadStream(this.#path, {
start: this.#start,
end: this.#start + this.size - 1
})
}
get [Symbol.toStringTag] () {
return 'Blob'
}
}
export default blobFromSync
export { File, Blob, blobFrom, blobFromSync, fileFrom, fileFromSync }

View File

@ -0,0 +1,3 @@
/** @type {typeof globalThis.Blob} */
export const Blob: typeof globalThis.Blob;
export default Blob;

View File

@ -0,0 +1,250 @@
/*! fetch-blob. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
// TODO (jimmywarting): in the feature use conditional loading with top level await (requires 14.x)
// Node has recently added whatwg stream into core
import './streams.cjs'
// 64 KiB (same size chrome slice theirs blob into Uint8array's)
const POOL_SIZE = 65536
/** @param {(Blob | Uint8Array)[]} parts */
async function * toIterator (parts, clone = true) {
for (const part of parts) {
if ('stream' in part) {
yield * (/** @type {AsyncIterableIterator<Uint8Array>} */ (part.stream()))
} else if (ArrayBuffer.isView(part)) {
if (clone) {
let position = part.byteOffset
const end = part.byteOffset + part.byteLength
while (position !== end) {
const size = Math.min(end - position, POOL_SIZE)
const chunk = part.buffer.slice(position, position + size)
position += chunk.byteLength
yield new Uint8Array(chunk)
}
} else {
yield part
}
/* c8 ignore next 10 */
} else {
// For blobs that have arrayBuffer but no stream method (nodes buffer.Blob)
let position = 0, b = (/** @type {Blob} */ (part))
while (position !== b.size) {
const chunk = b.slice(position, Math.min(b.size, position + POOL_SIZE))
const buffer = await chunk.arrayBuffer()
position += buffer.byteLength
yield new Uint8Array(buffer)
}
}
}
}
const _Blob = class Blob {
/** @type {Array.<(Blob|Uint8Array)>} */
#parts = []
#type = ''
#size = 0
#endings = 'transparent'
/**
* The Blob() constructor returns a new Blob object. The content
* of the blob consists of the concatenation of the values given
* in the parameter array.
*
* @param {*} blobParts
* @param {{ type?: string, endings?: string }} [options]
*/
constructor (blobParts = [], options = {}) {
if (typeof blobParts !== 'object' || blobParts === null) {
throw new TypeError('Failed to construct \'Blob\': The provided value cannot be converted to a sequence.')
}
if (typeof blobParts[Symbol.iterator] !== 'function') {
throw new TypeError('Failed to construct \'Blob\': The object must have a callable @@iterator property.')
}
if (typeof options !== 'object' && typeof options !== 'function') {
throw new TypeError('Failed to construct \'Blob\': parameter 2 cannot convert to dictionary.')
}
if (options === null) options = {}
const encoder = new TextEncoder()
for (const element of blobParts) {
let part
if (ArrayBuffer.isView(element)) {
part = new Uint8Array(element.buffer.slice(element.byteOffset, element.byteOffset + element.byteLength))
} else if (element instanceof ArrayBuffer) {
part = new Uint8Array(element.slice(0))
} else if (element instanceof Blob) {
part = element
} else {
part = encoder.encode(`${element}`)
}
this.#size += ArrayBuffer.isView(part) ? part.byteLength : part.size
this.#parts.push(part)
}
this.#endings = `${options.endings === undefined ? 'transparent' : options.endings}`
const type = options.type === undefined ? '' : String(options.type)
this.#type = /^[\x20-\x7E]*$/.test(type) ? type : ''
}
/**
* The Blob interface's size property returns the
* size of the Blob in bytes.
*/
get size () {
return this.#size
}
/**
* The type property of a Blob object returns the MIME type of the file.
*/
get type () {
return this.#type
}
/**
* The text() method in the Blob interface returns a Promise
* that resolves with a string containing the contents of
* the blob, interpreted as UTF-8.
*
* @return {Promise<string>}
*/
async text () {
// More optimized than using this.arrayBuffer()
// that requires twice as much ram
const decoder = new TextDecoder()
let str = ''
for await (const part of toIterator(this.#parts, false)) {
str += decoder.decode(part, { stream: true })
}
// Remaining
str += decoder.decode()
return str
}
/**
* The arrayBuffer() method in the Blob interface returns a
* Promise that resolves with the contents of the blob as
* binary data contained in an ArrayBuffer.
*
* @return {Promise<ArrayBuffer>}
*/
async arrayBuffer () {
// Easier way... Just a unnecessary overhead
// const view = new Uint8Array(this.size);
// await this.stream().getReader({mode: 'byob'}).read(view);
// return view.buffer;
const data = new Uint8Array(this.size)
let offset = 0
for await (const chunk of toIterator(this.#parts, false)) {
data.set(chunk, offset)
offset += chunk.length
}
return data.buffer
}
stream () {
const it = toIterator(this.#parts, true)
return new globalThis.ReadableStream({
// @ts-ignore
type: 'bytes',
async pull (ctrl) {
const chunk = await it.next()
chunk.done ? ctrl.close() : ctrl.enqueue(chunk.value)
},
async cancel () {
await it.return()
}
})
}
/**
* The Blob interface's slice() method creates and returns a
* new Blob object which contains data from a subset of the
* blob on which it's called.
*
* @param {number} [start]
* @param {number} [end]
* @param {string} [type]
*/
slice (start = 0, end = this.size, type = '') {
const { size } = this
let relativeStart = start < 0 ? Math.max(size + start, 0) : Math.min(start, size)
let relativeEnd = end < 0 ? Math.max(size + end, 0) : Math.min(end, size)
const span = Math.max(relativeEnd - relativeStart, 0)
const parts = this.#parts
const blobParts = []
let added = 0
for (const part of parts) {
// don't add the overflow to new blobParts
if (added >= span) {
break
}
const size = ArrayBuffer.isView(part) ? part.byteLength : part.size
if (relativeStart && size <= relativeStart) {
// Skip the beginning and change the relative
// start & end position as we skip the unwanted parts
relativeStart -= size
relativeEnd -= size
} else {
let chunk
if (ArrayBuffer.isView(part)) {
chunk = part.subarray(relativeStart, Math.min(size, relativeEnd))
added += chunk.byteLength
} else {
chunk = part.slice(relativeStart, Math.min(size, relativeEnd))
added += chunk.size
}
relativeEnd -= size
blobParts.push(chunk)
relativeStart = 0 // All next sequential parts should start at 0
}
}
const blob = new Blob([], { type: String(type).toLowerCase() })
blob.#size = span
blob.#parts = blobParts
return blob
}
get [Symbol.toStringTag] () {
return 'Blob'
}
static [Symbol.hasInstance] (object) {
return (
object &&
typeof object === 'object' &&
typeof object.constructor === 'function' &&
(
typeof object.stream === 'function' ||
typeof object.arrayBuffer === 'function'
) &&
/^(Blob|File)$/.test(object[Symbol.toStringTag])
)
}
}
Object.defineProperties(_Blob.prototype, {
size: { enumerable: true },
type: { enumerable: true },
slice: { enumerable: true }
})
/** @type {typeof globalThis.Blob} */
export const Blob = _Blob
export default Blob

View File

@ -0,0 +1,56 @@
{
"name": "fetch-blob",
"version": "3.2.0",
"description": "Blob & File implementation in Node.js, originally from node-fetch.",
"main": "index.js",
"type": "module",
"files": [
"from.js",
"file.js",
"file.d.ts",
"index.js",
"index.d.ts",
"from.d.ts",
"streams.cjs"
],
"scripts": {
"test": "node --experimental-loader ./test/http-loader.js ./test/test-wpt-in-node.js",
"report": "c8 --reporter json --reporter text npm run test",
"coverage": "npm run report && codecov -f coverage/coverage-final.json",
"prepublishOnly": "tsc --declaration --emitDeclarationOnly --allowJs index.js from.js"
},
"repository": "https://github.com/node-fetch/fetch-blob.git",
"keywords": [
"blob",
"file",
"node-fetch"
],
"engines": {
"node": "^12.20 || >= 14.13"
},
"author": "Jimmy Wärting <jimmy@warting.se> (https://jimmy.warting.se)",
"license": "MIT",
"bugs": {
"url": "https://github.com/node-fetch/fetch-blob/issues"
},
"homepage": "https://github.com/node-fetch/fetch-blob#readme",
"devDependencies": {
"@types/node": "^17.0.9",
"c8": "^7.11.0",
"typescript": "^4.5.4"
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
}
}

View File

@ -0,0 +1,51 @@
/* c8 ignore start */
// 64 KiB (same size chrome slice theirs blob into Uint8array's)
const POOL_SIZE = 65536
if (!globalThis.ReadableStream) {
// `node:stream/web` got introduced in v16.5.0 as experimental
// and it's preferred over the polyfilled version. So we also
// suppress the warning that gets emitted by NodeJS for using it.
try {
const process = require('node:process')
const { emitWarning } = process
try {
process.emitWarning = () => {}
Object.assign(globalThis, require('node:stream/web'))
process.emitWarning = emitWarning
} catch (error) {
process.emitWarning = emitWarning
throw error
}
} catch (error) {
// fallback to polyfill implementation
Object.assign(globalThis, require('web-streams-polyfill/dist/ponyfill.es2018.js'))
}
}
try {
// Don't use node: prefix for this, require+node: is not supported until node v14.14
// Only `import()` can use prefix in 12.20 and later
const { Blob } = require('buffer')
if (Blob && !Blob.prototype.stream) {
Blob.prototype.stream = function name (params) {
let position = 0
const blob = this
return new ReadableStream({
type: 'bytes',
async pull (ctrl) {
const chunk = blob.slice(position, Math.min(blob.size, position + POOL_SIZE))
const buffer = await chunk.arrayBuffer()
position += buffer.byteLength
ctrl.enqueue(new Uint8Array(buffer))
if (position === blob.size) {
ctrl.close()
}
}
})
}
}
} catch (error) {}
/* c8 ignore end */

View File

@ -0,0 +1,441 @@
/* formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
/* global FormData self Blob File */
/* eslint-disable no-inner-declarations */
if (typeof Blob !== 'undefined' && (typeof FormData === 'undefined' || !FormData.prototype.keys)) {
const global = typeof globalThis === 'object'
? globalThis
: typeof window === 'object'
? window
: typeof self === 'object' ? self : this
// keep a reference to native implementation
const _FormData = global.FormData
// To be monkey patched
const _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send
const _fetch = global.Request && global.fetch
const _sendBeacon = global.navigator && global.navigator.sendBeacon
// Might be a worker thread...
const _match = global.Element && global.Element.prototype
// Unable to patch Request/Response constructor correctly #109
// only way is to use ES6 class extend
// https://github.com/babel/babel/issues/1966
const stringTag = global.Symbol && Symbol.toStringTag
// Add missing stringTags to blob and files
if (stringTag) {
if (!Blob.prototype[stringTag]) {
Blob.prototype[stringTag] = 'Blob'
}
if ('File' in global && !File.prototype[stringTag]) {
File.prototype[stringTag] = 'File'
}
}
// Fix so you can construct your own File
try {
new File([], '') // eslint-disable-line
} catch (a) {
global.File = function File (b, d, c) {
const blob = new Blob(b, c || {})
const t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date()
Object.defineProperties(blob, {
name: {
value: d
},
lastModified: {
value: +t
},
toString: {
value () {
return '[object File]'
}
}
})
if (stringTag) {
Object.defineProperty(blob, stringTag, {
value: 'File'
})
}
return blob
}
}
function ensureArgs (args, expected) {
if (args.length < expected) {
throw new TypeError(`${expected} argument required, but only ${args.length} present.`)
}
}
/**
* @param {string} name
* @param {string | undefined} filename
* @returns {[string, File|string]}
*/
function normalizeArgs (name, value, filename) {
if (value instanceof Blob) {
filename = filename !== undefined
? String(filename + '')
: typeof value.name === 'string'
? value.name
: 'blob'
if (value.name !== filename || Object.prototype.toString.call(value) === '[object Blob]') {
value = new File([value], filename)
}
return [String(name), value]
}
return [String(name), String(value)]
}
// normalize line feeds for textarea
// https://html.spec.whatwg.org/multipage/form-elements.html#textarea-line-break-normalisation-transformation
function normalizeLinefeeds (value) {
return value.replace(/\r?\n|\r/g, '\r\n')
}
/**
* @template T
* @param {ArrayLike<T>} arr
* @param {{ (elm: T): void; }} cb
*/
function each (arr, cb) {
for (let i = 0; i < arr.length; i++) {
cb(arr[i])
}
}
const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')
/**
* @implements {Iterable}
*/
class FormDataPolyfill {
/**
* FormData class
*
* @param {HTMLFormElement=} form
*/
constructor (form) {
/** @type {[string, string|File][]} */
this._data = []
const self = this
form && each(form.elements, (/** @type {HTMLInputElement} */ elm) => {
if (
!elm.name ||
elm.disabled ||
elm.type === 'submit' ||
elm.type === 'button' ||
elm.matches('form fieldset[disabled] *')
) return
if (elm.type === 'file') {
const files = elm.files && elm.files.length
? elm.files
: [new File([], '', { type: 'application/octet-stream' })] // #78
each(files, file => {
self.append(elm.name, file)
})
} else if (elm.type === 'select-multiple' || elm.type === 'select-one') {
each(elm.options, opt => {
!opt.disabled && opt.selected && self.append(elm.name, opt.value)
})
} else if (elm.type === 'checkbox' || elm.type === 'radio') {
if (elm.checked) self.append(elm.name, elm.value)
} else {
const value = elm.type === 'textarea' ? normalizeLinefeeds(elm.value) : elm.value
self.append(elm.name, value)
}
})
}
/**
* Append a field
*
* @param {string} name field name
* @param {string|Blob|File} value string / blob / file
* @param {string=} filename filename to use with blob
* @return {undefined}
*/
append (name, value, filename) {
ensureArgs(arguments, 2)
this._data.push(normalizeArgs(name, value, filename))
}
/**
* Delete all fields values given name
*
* @param {string} name Field name
* @return {undefined}
*/
delete (name) {
ensureArgs(arguments, 1)
const result = []
name = String(name)
each(this._data, entry => {
entry[0] !== name && result.push(entry)
})
this._data = result
}
/**
* Iterate over all fields as [name, value]
*
* @return {Iterator}
*/
* entries () {
for (var i = 0; i < this._data.length; i++) {
yield this._data[i]
}
}
/**
* Iterate over all fields
*
* @param {Function} callback Executed for each item with parameters (value, name, thisArg)
* @param {Object=} thisArg `this` context for callback function
*/
forEach (callback, thisArg) {
ensureArgs(arguments, 1)
for (const [name, value] of this) {
callback.call(thisArg, value, name, this)
}
}
/**
* Return first field value given name
* or null if non existent
*
* @param {string} name Field name
* @return {string|File|null} value Fields value
*/
get (name) {
ensureArgs(arguments, 1)
const entries = this._data
name = String(name)
for (let i = 0; i < entries.length; i++) {
if (entries[i][0] === name) {
return entries[i][1]
}
}
return null
}
/**
* Return all fields values given name
*
* @param {string} name Fields name
* @return {Array} [{String|File}]
*/
getAll (name) {
ensureArgs(arguments, 1)
const result = []
name = String(name)
each(this._data, data => {
data[0] === name && result.push(data[1])
})
return result
}
/**
* Check for field name existence
*
* @param {string} name Field name
* @return {boolean}
*/
has (name) {
ensureArgs(arguments, 1)
name = String(name)
for (let i = 0; i < this._data.length; i++) {
if (this._data[i][0] === name) {
return true
}
}
return false
}
/**
* Iterate over all fields name
*
* @return {Iterator}
*/
* keys () {
for (const [name] of this) {
yield name
}
}
/**
* Overwrite all values given name
*
* @param {string} name Filed name
* @param {string} value Field value
* @param {string=} filename Filename (optional)
*/
set (name, value, filename) {
ensureArgs(arguments, 2)
name = String(name)
/** @type {[string, string|File][]} */
const result = []
const args = normalizeArgs(name, value, filename)
let replace = true
// - replace the first occurrence with same name
// - discards the remaining with same name
// - while keeping the same order items where added
each(this._data, data => {
data[0] === name
? replace && (replace = !result.push(args))
: result.push(data)
})
replace && result.push(args)
this._data = result
}
/**
* Iterate over all fields
*
* @return {Iterator}
*/
* values () {
for (const [, value] of this) {
yield value
}
}
/**
* Return a native (perhaps degraded) FormData with only a `append` method
* Can throw if it's not supported
*
* @return {FormData}
*/
['_asNative'] () {
const fd = new _FormData()
for (const [name, value] of this) {
fd.append(name, value)
}
return fd
}
/**
* [_blob description]
*
* @return {Blob} [description]
*/
['_blob'] () {
const boundary = '----formdata-polyfill-' + Math.random(),
chunks = [],
p = `--${boundary}\r\nContent-Disposition: form-data; name="`
this.forEach((value, name) => typeof value == 'string'
? chunks.push(p + escape(normalizeLinefeeds(name)) + `"\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
: chunks.push(p + escape(normalizeLinefeeds(name)) + `"; filename="${escape(value.name)}"\r\nContent-Type: ${value.type||"application/octet-stream"}\r\n\r\n`, value, `\r\n`))
chunks.push(`--${boundary}--`)
return new Blob(chunks, {
type: "multipart/form-data; boundary=" + boundary
})
}
/**
* The class itself is iterable
* alias for formdata.entries()
*
* @return {Iterator}
*/
[Symbol.iterator] () {
return this.entries()
}
/**
* Create the default string description.
*
* @return {string} [object FormData]
*/
toString () {
return '[object FormData]'
}
}
if (_match && !_match.matches) {
_match.matches =
_match.matchesSelector ||
_match.mozMatchesSelector ||
_match.msMatchesSelector ||
_match.oMatchesSelector ||
_match.webkitMatchesSelector ||
function (s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s)
var i = matches.length
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1
}
}
if (stringTag) {
/**
* Create the default string description.
* It is accessed internally by the Object.prototype.toString().
*/
FormDataPolyfill.prototype[stringTag] = 'FormData'
}
// Patch xhr's send method to call _blob transparently
if (_send) {
const setRequestHeader = global.XMLHttpRequest.prototype.setRequestHeader
global.XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
setRequestHeader.call(this, name, value)
if (name.toLowerCase() === 'content-type') this._hasContentType = true
}
global.XMLHttpRequest.prototype.send = function (data) {
// need to patch send b/c old IE don't send blob's type (#44)
if (data instanceof FormDataPolyfill) {
const blob = data['_blob']()
if (!this._hasContentType) this.setRequestHeader('Content-Type', blob.type)
_send.call(this, blob)
} else {
_send.call(this, data)
}
}
}
// Patch fetch's function to call _blob transparently
if (_fetch) {
global.fetch = function (input, init) {
if (init && init.body && init.body instanceof FormDataPolyfill) {
init.body = init.body['_blob']()
}
return _fetch.call(this, input, init)
}
}
// Patch navigator.sendBeacon to use native FormData
if (_sendBeacon) {
global.navigator.sendBeacon = function (url, data) {
if (data instanceof FormDataPolyfill) {
data = data['_asNative']()
}
return _sendBeacon.call(this, url, data)
}
}
global['FormData'] = FormDataPolyfill
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Jimmy Karl Roland Wärting
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,145 @@
### A `FormData` polyfill for the browser ...and a module for NodeJS (`New!`)
```bash
npm install formdata-polyfill
```
The browser polyfill will likely have done its part already, and i hope you stop supporting old browsers c",)<br>
But NodeJS still laks a proper FormData<br>The good old form-data package is a very old and isn't spec compatible and dose some abnormal stuff to construct and read FormData instances that other http libraries are not happy about when it comes to follow the spec.
### The NodeJS / ESM version
- The modular (~2.3 KiB minified uncompressed) version of this package is independent of any browser stuff and don't patch anything
- It's as pure/spec compatible as it possible gets the test are run by WPT.
- It's compatible with [node-fetch](https://github.com/node-fetch/node-fetch).
- It have higher platform dependencies as it uses classes, symbols, ESM & private fields
- Only dependency it has is [fetch-blob](https://github.com/node-fetch/fetch-blob)
```js
// Node example
import fetch from 'node-fetch'
import File from 'fetch-blob/file.js'
import { fileFromSync } from 'fetch-blob/from.js'
import { FormData } from 'formdata-polyfill/esm.min.js'
const file = fileFromSync('./README.md')
const fd = new FormData()
fd.append('file-upload', new File(['abc'], 'hello-world.txt'))
fd.append('file-upload', file)
// it's also possible to append file/blob look-a-like items
// if you have streams coming from other destinations
fd.append('file-upload', {
size: 123,
type: '',
name: 'cat-video.mp4',
stream() { return stream },
[Symbol.toStringTag]: 'File'
})
fetch('https://httpbin.org/post', { method: 'POST', body: fd })
```
----
It also comes with way to convert FormData into Blobs - it's not something that every developer should have to deal with.
It's mainly for [node-fetch](https://github.com/node-fetch/node-fetch) and other http library to ease the process of serializing a FormData into a blob and just wish to deal with Blobs instead (Both Deno and Undici adapted a version of this [formDataToBlob](https://github.com/jimmywarting/FormData/blob/5ddea9e0de2fc5e246ab1b2f9d404dee0c319c02/formdata-to-blob.js) to the core and passes all WPT tests run by the browser itself)
```js
import { Readable } from 'node:stream'
import { FormData, formDataToBlob } from 'formdata-polyfill/esm.min.js'
const blob = formDataToBlob(new FormData())
fetch('https://httpbin.org/post', { method: 'POST', body: blob })
// node built in http and other similar http library have to do:
const stream = Readable.from(blob.stream())
const req = http.request('http://httpbin.org/post', {
method: 'post',
headers: {
'Content-Length': blob.size,
'Content-Type': blob.type
}
})
stream.pipe(req)
```
PS: blob & file that are appended to the FormData will not be read until any of the serialized blob read-methods gets called
...so uploading very large files is no biggie
### Browser polyfill
usage:
```js
import 'formdata-polyfill' // that's it
```
The browser polyfill conditionally replaces the native implementation rather than fixing the missing functions,
since otherwise there is no way to get or delete existing values in the FormData object.
Therefore this also patches `XMLHttpRequest.prototype.send` and `fetch` to send the `FormData` as a blob,
and `navigator.sendBeacon` to send native `FormData`.
I was unable to patch the Response/Request constructor
so if you are constructing them with FormData then you need to call `fd._blob()` manually.
```js
new Request(url, {
method: 'post',
body: fd._blob ? fd._blob() : fd
})
```
Dependencies
---
If you need to support IE <= 9 then I recommend you to include eligrey's [blob.js]
(which i hope you don't - since IE is now dead)
<details>
<summary>Updating from 2.x to 3.x</summary>
Previously you had to import the polyfill and use that,
since it didn't replace the global (existing) FormData implementation.
But now it transparently calls `_blob()` for you when you are sending something with fetch or XHR,
by way of monkey-patching the `XMLHttpRequest.prototype.send` and `fetch` functions.
So you maybe had something like this:
```javascript
var FormData = require('formdata-polyfill')
var fd = new FormData(form)
xhr.send(fd._blob())
```
There is no longer anything exported from the module
(though you of course still need to import it to install the polyfill),
so you can now use the FormData object as normal:
```javascript
require('formdata-polyfill')
var fd = new FormData(form)
xhr.send(fd)
```
</details>
Native Browser compatibility (as of 2021-05-08)
---
Based on this you can decide for yourself if you need this polyfill.
[![screenshot](https://user-images.githubusercontent.com/1148376/117550329-0993aa80-b040-11eb-976c-14e31f1a3ba4.png)](https://developer.mozilla.org/en-US/docs/Web/API/FormData#Browser_compatibility)
This normalizes support for the FormData API:
- `append` with filename
- `delete()`, `get()`, `getAll()`, `has()`, `set()`
- `entries()`, `keys()`, `values()`, and support for `for...of`
- Available in web workers (just include the polyfill)
[npm-image]: https://img.shields.io/npm/v/formdata-polyfill.svg
[npm-url]: https://www.npmjs.com/package/formdata-polyfill
[blob.js]: https://github.com/eligrey/Blob.js

View File

@ -0,0 +1,5 @@
export declare const FormData: {
new (): FormData;
prototype: FormData;
};
export declare function formDataToBlob(formData: FormData): Blob;

View File

@ -0,0 +1,40 @@
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
import C from 'fetch-blob'
import F from 'fetch-blob/file.js'
var {toStringTag:t,iterator:i,hasInstance:h}=Symbol,
r=Math.random,
m='append,set,get,getAll,delete,keys,values,entries,forEach,constructor'.split(','),
f=(a,b,c)=>(a+='',/^(Blob|File)$/.test(b && b[t])?[(c=c!==void 0?c+'':b[t]=='File'?b.name:'blob',a),b.name!==c||b[t]=='blob'?new F([b],c,b):b]:[a,b+'']),
e=(c,f)=>(f?c:c.replace(/\r?\n|\r/g,'\r\n')).replace(/\n/g,'%0A').replace(/\r/g,'%0D').replace(/"/g,'%22'),
x=(n, a, e)=>{if(a.length<e){throw new TypeError(`Failed to execute '${n}' on 'FormData': ${e} arguments required, but only ${a.length} present.`)}}
export const File = F
/** @type {typeof globalThis.FormData} */
export const FormData = class FormData {
#d=[];
constructor(...a){if(a.length)throw new TypeError(`Failed to construct 'FormData': parameter 1 is not of type 'HTMLFormElement'.`)}
get [t]() {return 'FormData'}
[i](){return this.entries()}
static [h](o) {return o&&typeof o==='object'&&o[t]==='FormData'&&!m.some(m=>typeof o[m]!='function')}
append(...a){x('append',arguments,2);this.#d.push(f(...a))}
delete(a){x('delete',arguments,1);a+='';this.#d=this.#d.filter(([b])=>b!==a)}
get(a){x('get',arguments,1);a+='';for(var b=this.#d,l=b.length,c=0;c<l;c++)if(b[c][0]===a)return b[c][1];return null}
getAll(a,b){x('getAll',arguments,1);b=[];a+='';this.#d.forEach(c=>c[0]===a&&b.push(c[1]));return b}
has(a){x('has',arguments,1);a+='';return this.#d.some(b=>b[0]===a)}
forEach(a,b){x('forEach',arguments,1);for(var [c,d]of this)a.call(b,d,c,this)}
set(...a){x('set',arguments,2);var b=[],c=!0;a=f(...a);this.#d.forEach(d=>{d[0]===a[0]?c&&(c=!b.push(a)):b.push(d)});c&&b.push(a);this.#d=b}
*entries(){yield*this.#d}
*keys(){for(var[a]of this)yield a}
*values(){for(var[,a]of this)yield a}}
/** @param {FormData} F */
export function formDataToBlob (F,B=C){
var b=`${r()}${r()}`.replace(/\./g, '').slice(-28).padStart(32, '-'),c=[],p=`--${b}\r\nContent-Disposition: form-data; name="`
F.forEach((v,n)=>typeof v=='string'
?c.push(p+e(n)+`"\r\n\r\n${v.replace(/\r(?!\n)|(?<!\r)\n/g, '\r\n')}\r\n`)
:c.push(p+e(n)+`"; filename="${e(v.name, 1)}"\r\nContent-Type: ${v.type||"application/octet-stream"}\r\n\r\n`, v, '\r\n'))
c.push(`--${b}--`)
return new B(c,{type:"multipart/form-data; boundary="+b})}

View File

@ -0,0 +1,39 @@
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
const escape = (str, filename) =>
(filename ? str : str.replace(/\r?\n|\r/g, '\r\n'))
.replace(/\n/g, '%0A')
.replace(/\r/g, '%0D')
.replace(/"/g, '%22')
/**
* pure function to convert any formData instance to a Blob
* instances synchronous without reading all of the files
*
* @param {FormData|*} formData an instance of a formData Class
* @param {Blob|*} [BlobClass=Blob] the Blob class to use when constructing it
*/
export function formDataToBlob (formData, BlobClass = Blob) {
const boundary = ('----formdata-polyfill-' + Math.random())
const chunks = []
const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`
for (let [name, value] of formData) {
if (typeof value === 'string') {
chunks.push(prefix + escape(name) + `"\r\n\r\n${value.replace(/\r(?!\n)|(?<!\r)\n/g, '\r\n')}\r\n`)
} else {
chunks.push(
prefix + escape(name) + `"; filename="${escape(value.name, 1)}"\r\n` +
`Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`,
value,
'\r\n'
)
}
}
chunks.push(`--${boundary}--`)
return new BlobClass(chunks, {
type: 'multipart/form-data; boundary=' + boundary
})
}

View File

@ -0,0 +1,21 @@
/*! formdata-polyfill. MIT License. Jimmy W?rting <https://jimmy.warting.se/opensource> */
;(function(){var h;function l(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}}var m="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};
function n(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");}var q=n(this);function r(a,b){if(b)a:{var c=q;a=a.split(".");for(var d=0;d<a.length-1;d++){var e=a[d];if(!(e in c))break a;c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&m(c,a,{configurable:!0,writable:!0,value:b})}}
r("Symbol",function(a){function b(f){if(this instanceof b)throw new TypeError("Symbol is not a constructor");return new c(d+(f||"")+"_"+e++,f)}function c(f,g){this.A=f;m(this,"description",{configurable:!0,writable:!0,value:g})}if(a)return a;c.prototype.toString=function(){return this.A};var d="jscomp_symbol_"+(1E9*Math.random()>>>0)+"_",e=0;return b});
r("Symbol.iterator",function(a){if(a)return a;a=Symbol("Symbol.iterator");for(var b="Array Int8Array Uint8Array Uint8ClampedArray Int16Array Uint16Array Int32Array Uint32Array Float32Array Float64Array".split(" "),c=0;c<b.length;c++){var d=q[b[c]];"function"===typeof d&&"function"!=typeof d.prototype[a]&&m(d.prototype,a,{configurable:!0,writable:!0,value:function(){return u(l(this))}})}return a});function u(a){a={next:a};a[Symbol.iterator]=function(){return this};return a}
function v(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):{next:l(a)}}var w;if("function"==typeof Object.setPrototypeOf)w=Object.setPrototypeOf;else{var y;a:{var z={a:!0},A={};try{A.__proto__=z;y=A.a;break a}catch(a){}y=!1}w=y?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null}var B=w;function C(){this.m=!1;this.j=null;this.v=void 0;this.h=1;this.u=this.C=0;this.l=null}
function D(a){if(a.m)throw new TypeError("Generator is already running");a.m=!0}C.prototype.o=function(a){this.v=a};C.prototype.s=function(a){this.l={D:a,F:!0};this.h=this.C||this.u};C.prototype.return=function(a){this.l={return:a};this.h=this.u};function E(a,b){a.h=3;return{value:b}}function F(a){this.g=new C;this.G=a}F.prototype.o=function(a){D(this.g);if(this.g.j)return G(this,this.g.j.next,a,this.g.o);this.g.o(a);return H(this)};
function I(a,b){D(a.g);var c=a.g.j;if(c)return G(a,"return"in c?c["return"]:function(d){return{value:d,done:!0}},b,a.g.return);a.g.return(b);return H(a)}F.prototype.s=function(a){D(this.g);if(this.g.j)return G(this,this.g.j["throw"],a,this.g.o);this.g.s(a);return H(this)};
function G(a,b,c,d){try{var e=b.call(a.g.j,c);if(!(e instanceof Object))throw new TypeError("Iterator result "+e+" is not an object");if(!e.done)return a.g.m=!1,e;var f=e.value}catch(g){return a.g.j=null,a.g.s(g),H(a)}a.g.j=null;d.call(a.g,f);return H(a)}function H(a){for(;a.g.h;)try{var b=a.G(a.g);if(b)return a.g.m=!1,{value:b.value,done:!1}}catch(c){a.g.v=void 0,a.g.s(c)}a.g.m=!1;if(a.g.l){b=a.g.l;a.g.l=null;if(b.F)throw b.D;return{value:b.return,done:!0}}return{value:void 0,done:!0}}
function J(a){this.next=function(b){return a.o(b)};this.throw=function(b){return a.s(b)};this.return=function(b){return I(a,b)};this[Symbol.iterator]=function(){return this}}function K(a,b){b=new J(new F(b));B&&a.prototype&&B(b,a.prototype);return b}function L(a,b){a instanceof String&&(a+="");var c=0,d=!1,e={next:function(){if(!d&&c<a.length){var f=c++;return{value:b(f,a[f]),done:!1}}d=!0;return{done:!0,value:void 0}}};e[Symbol.iterator]=function(){return e};return e}
r("Array.prototype.entries",function(a){return a?a:function(){return L(this,function(b,c){return[b,c]})}});
if("undefined"!==typeof Blob&&("undefined"===typeof FormData||!FormData.prototype.keys)){var M=function(a,b){for(var c=0;c<a.length;c++)b(a[c])},N=function(a){return a.replace(/\r?\n|\r/g,"\r\n")},O=function(a,b,c){if(b instanceof Blob){c=void 0!==c?String(c+""):"string"===typeof b.name?b.name:"blob";if(b.name!==c||"[object Blob]"===Object.prototype.toString.call(b))b=new File([b],c);return[String(a),b]}return[String(a),String(b)]},P=function(a,b){if(a.length<b)throw new TypeError(b+" argument required, but only "+
a.length+" present.");},Q="object"===typeof globalThis?globalThis:"object"===typeof window?window:"object"===typeof self?self:this,R=Q.FormData,S=Q.XMLHttpRequest&&Q.XMLHttpRequest.prototype.send,T=Q.Request&&Q.fetch,U=Q.navigator&&Q.navigator.sendBeacon,V=Q.Element&&Q.Element.prototype,W=Q.Symbol&&Symbol.toStringTag;W&&(Blob.prototype[W]||(Blob.prototype[W]="Blob"),"File"in Q&&!File.prototype[W]&&(File.prototype[W]="File"));try{new File([],"")}catch(a){Q.File=function(b,c,d){b=new Blob(b,d||{});
Object.defineProperties(b,{name:{value:c},lastModified:{value:+(d&&void 0!==d.lastModified?new Date(d.lastModified):new Date)},toString:{value:function(){return"[object File]"}}});W&&Object.defineProperty(b,W,{value:"File"});return b}}var escape=function(a){return a.replace(/\n/g,"%0A").replace(/\r/g,"%0D").replace(/"/g,"%22")},X=function(a){this.i=[];var b=this;a&&M(a.elements,function(c){if(c.name&&!c.disabled&&"submit"!==c.type&&"button"!==c.type&&!c.matches("form fieldset[disabled] *"))if("file"===
c.type){var d=c.files&&c.files.length?c.files:[new File([],"",{type:"application/octet-stream"})];M(d,function(e){b.append(c.name,e)})}else"select-multiple"===c.type||"select-one"===c.type?M(c.options,function(e){!e.disabled&&e.selected&&b.append(c.name,e.value)}):"checkbox"===c.type||"radio"===c.type?c.checked&&b.append(c.name,c.value):(d="textarea"===c.type?N(c.value):c.value,b.append(c.name,d))})};h=X.prototype;h.append=function(a,b,c){P(arguments,2);this.i.push(O(a,b,c))};h.delete=function(a){P(arguments,
1);var b=[];a=String(a);M(this.i,function(c){c[0]!==a&&b.push(c)});this.i=b};h.entries=function b(){var c,d=this;return K(b,function(e){1==e.h&&(c=0);if(3!=e.h)return c<d.i.length?e=E(e,d.i[c]):(e.h=0,e=void 0),e;c++;e.h=2})};h.forEach=function(b,c){P(arguments,1);for(var d=v(this),e=d.next();!e.done;e=d.next()){var f=v(e.value);e=f.next().value;f=f.next().value;b.call(c,f,e,this)}};h.get=function(b){P(arguments,1);var c=this.i;b=String(b);for(var d=0;d<c.length;d++)if(c[d][0]===b)return c[d][1];
return null};h.getAll=function(b){P(arguments,1);var c=[];b=String(b);M(this.i,function(d){d[0]===b&&c.push(d[1])});return c};h.has=function(b){P(arguments,1);b=String(b);for(var c=0;c<this.i.length;c++)if(this.i[c][0]===b)return!0;return!1};h.keys=function c(){var d=this,e,f,g,k,p;return K(c,function(t){1==t.h&&(e=v(d),f=e.next());if(3!=t.h){if(f.done){t.h=0;return}g=f.value;k=v(g);p=k.next().value;return E(t,p)}f=e.next();t.h=2})};h.set=function(c,d,e){P(arguments,2);c=String(c);var f=[],g=O(c,
d,e),k=!0;M(this.i,function(p){p[0]===c?k&&(k=!f.push(g)):f.push(p)});k&&f.push(g);this.i=f};h.values=function d(){var e=this,f,g,k,p,t;return K(d,function(x){1==x.h&&(f=v(e),g=f.next());if(3!=x.h){if(g.done){x.h=0;return}k=g.value;p=v(k);p.next();t=p.next().value;return E(x,t)}g=f.next();x.h=2})};X.prototype._asNative=function(){for(var d=new R,e=v(this),f=e.next();!f.done;f=e.next()){var g=v(f.value);f=g.next().value;g=g.next().value;d.append(f,g)}return d};X.prototype._blob=function(){var d="----formdata-polyfill-"+
Math.random(),e=[],f="--"+d+'\r\nContent-Disposition: form-data; name="';this.forEach(function(g,k){return"string"==typeof g?e.push(f+escape(N(k))+('"\r\n\r\n'+N(g)+"\r\n")):e.push(f+escape(N(k))+('"; filename="'+escape(g.name)+'"\r\nContent-Type: '+(g.type||"application/octet-stream")+"\r\n\r\n"),g,"\r\n")});e.push("--"+d+"--");return new Blob(e,{type:"multipart/form-data; boundary="+d})};X.prototype[Symbol.iterator]=function(){return this.entries()};X.prototype.toString=function(){return"[object FormData]"};
V&&!V.matches&&(V.matches=V.matchesSelector||V.mozMatchesSelector||V.msMatchesSelector||V.oMatchesSelector||V.webkitMatchesSelector||function(d){d=(this.document||this.ownerDocument).querySelectorAll(d);for(var e=d.length;0<=--e&&d.item(e)!==this;);return-1<e});W&&(X.prototype[W]="FormData");if(S){var Y=Q.XMLHttpRequest.prototype.setRequestHeader;Q.XMLHttpRequest.prototype.setRequestHeader=function(d,e){Y.call(this,d,e);"content-type"===d.toLowerCase()&&(this.B=!0)};Q.XMLHttpRequest.prototype.send=
function(d){d instanceof X?(d=d._blob(),this.B||this.setRequestHeader("Content-Type",d.type),S.call(this,d)):S.call(this,d)}}T&&(Q.fetch=function(d,e){e&&e.body&&e.body instanceof X&&(e.body=e.body._blob());return T.call(this,d,e)});U&&(Q.navigator.sendBeacon=function(d,e){e instanceof X&&(e=e._asNative());return U.call(this,d,e)});Q.FormData=X};})();

View File

@ -0,0 +1,50 @@
{
"name": "formdata-polyfill",
"version": "4.0.10",
"description": "HTML5 `FormData` for Browsers and Node.",
"type": "module",
"main": "formdata.min.js",
"scripts": {
"build": "node build.js",
"test": "node test/test-esm.js",
"test-wpt": "node --experimental-loader ./test/http-loader.js ./test/test-wpt-in-node.js",
"test-polyfill": "php -S localhost:4445 & open http://localhost:4445/test/test-polyfill.html"
},
"repository": {
"type": "git",
"url": "git+https://jimmywarting@github.com/jimmywarting/FormData.git"
},
"files": [
"esm.min.js",
"esm.min.d.ts",
"FormData.js",
"formdata-to-blob.js",
"formdata.min.js",
"README.md"
],
"engines": {
"node": ">=12.20.0"
},
"keywords": [
"formdata",
"fetch",
"node-fetch",
"html5",
"browser",
"polyfill"
],
"author": "Jimmy Wärting",
"license": "MIT",
"bugs": {
"url": "https://github.com/jimmywarting/FormData/issues"
},
"homepage": "https://github.com/jimmywarting/FormData#readme",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"devDependencies": {
"@types/google-closure-compiler": "^0.0.19",
"@types/node": "^16.7.10",
"google-closure-compiler": "^20210808.0.0"
}
}

View File

@ -0,0 +1,2 @@
# node-domexception
An implementation of the DOMException class from NodeJS

View File

@ -0,0 +1,41 @@
# DOMException
An implementation of the DOMException class from NodeJS
This package implements the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class, from NodeJS itself.
NodeJS has DOMException built in, but it's not globally available, and you can't require/import it from somewhere.
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
The instanceof check would not have worked with a custom class such as the DOMexception provided by domenic which also is much larger in size.
```js
import DOMException from 'node-domexception'
hello().catch(err => {
if (err instanceof DOMException) {
...
}
})
const e1 = new DOMException("Something went wrong", "BadThingsError");
console.assert(e1.name === "BadThingsError");
console.assert(e1.code === 0);
const e2 = new DOMException("Another exciting error message", "NoModificationAllowedError");
console.assert(e2.name === "NoModificationAllowedError");
console.assert(e2.code === 7);
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10);
```
## APIs
This package exposes two flavors of the `DOMException` interface depending on the imported module.
### `domexception` module
This module default-exports the `DOMException` interface constructor.
### `domexception/webidl2js-wrapper` module
This module exports the `DOMException` [interface wrapper API](https://github.com/jsdom/webidl2js#for-interfaces) generated by [webidl2js](https://github.com/jsdom/webidl2js).

View File

@ -0,0 +1,36 @@
# DOMException
An implementation of the DOMException class from NodeJS
This package implements the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class, from NodeJS itself. (including the legacy codes)
NodeJS has DOMException built in, but it's not globally available, and you can't require/import it from somewhere.
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size.
```js
import DOMException from 'node-domexception'
import { MessageChannel } from 'worker_threads'
async function hello() {
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
port.postMessage(ab, [ab, ab])
}
hello().catch(err => {
console.assert(err.name === 'DataCloneError')
console.assert(err.code === 25)
console.assert(err instanceof DOMException)
})
const e1 = new DOMException('Something went wrong', 'BadThingsError')
console.assert(e1.name === 'BadThingsError')
console.assert(e1.code === 0)
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
console.assert(e2.name === 'NoModificationAllowedError')
console.assert(e2.code === 7)
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
```

View File

@ -0,0 +1,36 @@
# DOMException
An implementation of the DOMException class from NodeJS
This package implements the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class that comes from NodeJS itself. (including the legacy codes)
NodeJS has DOMException built in, but it's not globally available, and you can't require/import it from somewhere.
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size.
```js
import DOMException from 'node-domexception'
import { MessageChannel } from 'worker_threads'
async function hello() {
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
port.postMessage(ab, [ab, ab])
}
hello().catch(err => {
console.assert(err.name === 'DataCloneError')
console.assert(err.code === 25)
console.assert(err instanceof DOMException)
})
const e1 = new DOMException('Something went wrong', 'BadThingsError')
console.assert(e1.name === 'BadThingsError')
console.assert(e1.code === 0)
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
console.assert(e2.name === 'NoModificationAllowedError')
console.assert(e2.code === 7)
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
```

View File

@ -0,0 +1,36 @@
# DOMException
An implementation of the DOMException class from NodeJS
This package exposes the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class that comes from NodeJS itself. (including all of the deprecated legacy codes)
NodeJS has it built in, but it's not globally available, and you can't require/import it from somewhere.
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size since it has to re-construct the hole class from the ground up.
```js
import DOMException from 'node-domexception'
import { MessageChannel } from 'worker_threads'
async function hello() {
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
port.postMessage(ab, [ab, ab])
}
hello().catch(err => {
console.assert(err.name === 'DataCloneError')
console.assert(err.code === 25)
console.assert(err instanceof DOMException)
})
const e1 = new DOMException('Something went wrong', 'BadThingsError')
console.assert(e1.name === 'BadThingsError')
console.assert(e1.code === 0)
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
console.assert(e2.name === 'NoModificationAllowedError')
console.assert(e2.code === 7)
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
```

View File

@ -0,0 +1,38 @@
# DOMException
An implementation of the DOMException class from NodeJS
This package exposes the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class that comes from NodeJS itself. (including all of the deprecated legacy codes)
NodeJS has it built in, but it's not globally available, and you can't require/import it from somewhere.
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size since it has to re-construct the hole class from the ground up.
(plz don't depend on this package in any other environment other than node >=10.5)
```js
import DOMException from 'node-domexception'
import { MessageChannel } from 'worker_threads'
async function hello() {
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
port.postMessage(ab, [ab, ab])
}
hello().catch(err => {
console.assert(err.name === 'DataCloneError')
console.assert(err.code === 25)
console.assert(err instanceof DOMException)
})
const e1 = new DOMException('Something went wrong', 'BadThingsError')
console.assert(e1.name === 'BadThingsError')
console.assert(e1.code === 0)
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
console.assert(e2.name === 'NoModificationAllowedError')
console.assert(e2.code === 7)
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
```

View File

@ -0,0 +1,38 @@
# DOMException
An implementation of the DOMException class from NodeJS
This package exposes the [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) class that comes from NodeJS itself. (including all of the deprecated legacy codes)
NodeJS has it built in, but it's not globally available, and you can't require/import it from somewhere.
The only possible way is to use some web-ish tools that have been introduced into NodeJS that throws an error and catch the constructor.
This way you will have the same class that NodeJS has and you can check if the error is a instance of DOMException.
The instanceof check would not have worked with a custom class such as the DOMException provided by domenic which also is much larger in size since it has to re-construct the hole class from the ground up.
(plz don't depend on this package in any other environment other than node >=10.5)
```js
import DOMException from 'node-domexception'
import { MessageChannel } from 'worker_threads'
async function hello() {
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
port.postMessage(ab, [ab, ab])
}
hello().catch(err => {
console.assert(err.name === 'DataCloneError')
console.assert(err.code === 25)
console.assert(err instanceof DOMException)
})
const e1 = new DOMException('Something went wrong', 'BadThingsError')
console.assert(e1.name === 'BadThingsError')
console.assert(e1.code === 0)
const e2 = new DOMException('Another exciting error message', 'NoModificationAllowedError')
console.assert(e2.name === 'NoModificationAllowedError')
console.assert(e2.code === 7)
console.assert(DOMException.INUSE_ATTRIBUTE_ERR === 10)
```

View File

@ -0,0 +1,8 @@
const { MessageChannel } = require('worker_threads')
if (!globalThis.DOMException) {
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
try { port.postMessage(ab, [ab, ab]) }
catch (err) { globalThis.DOMException = err.constructor }
}

View File

@ -0,0 +1,9 @@
if (!globalThis.DOMException) {
const { MessageChannel } = require('worker_threads')
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
try { port.postMessage(ab, [ab, ab]) }
catch (err) { globalThis.DOMException = err.constructor }
}
module.exports

View File

@ -0,0 +1,9 @@
if (!globalThis.DOMException) {
const { MessageChannel } = require('worker_threads')
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
try { port.postMessage(ab, [ab, ab]) }
catch (err) { globalThis.DOMException = err.constructor }
}
module.exports = globalThis.DOMException

View File

@ -0,0 +1,11 @@
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se> */
if (!globalThis.DOMException) {
const { MessageChannel } = require('worker_threads')
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
try { port.postMessage(ab, [ab, ab]) }
catch (err) { globalThis.DOMException = err.constructor }
}
module.exports = globalThis.DOMException

View File

@ -0,0 +1,11 @@
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
if (!globalThis.DOMException) {
const { MessageChannel } = require('worker_threads')
const port = new MessageChannel().port1
const ab = new ArrayBuffer()
try { port.postMessage(ab, [ab, ab]) }
catch (err) { globalThis.DOMException = err.constructor }
}
module.exports = globalThis.DOMException

View File

@ -0,0 +1,15 @@
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
if (!globalThis.DOMException) {
var { MessageChannel } = require('worker_threads'),
port = new MessageChannel().port1,
ab = new ArrayBuffer()
try { port.postMessage(ab, [ab, ab]) }
catch (err) {
err.constructor.name === 'DOMException' && (
globalThis.DOMException = err.constructor
)
}
}
module.exports = globalThis.DOMException

View File

@ -0,0 +1,15 @@
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
if (!globalThis.DOMException) {
const { MessageChannel } = require('worker_threads'),
port = new MessageChannel().port1,
ab = new ArrayBuffer()
try { port.postMessage(ab, [ab, ab]) }
catch (err) {
err.constructor.name === 'DOMException' && (
globalThis.DOMException = err.constructor
)
}
}
module.exports = globalThis.DOMException

View File

@ -0,0 +1,23 @@
/*! blob-to-buffer. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
if (!globalThis.DOMException) {
const { MessageChannel } = require('worker_threads'),
port = new MessageChannel().port1,
ab = new ArrayBuffer()
try { port.postMessage(ab, [ab, ab]) }
catch (err) {
err.constructor.name === 'DOMException' && (
globalThis.DOMException = err.constructor
)
}
}
module.exports = globalThis.DOMException
const e1 = new DOMException("Something went wrong", "BadThingsError");
console.assert(e1.name === "BadThingsError");
console.assert(e1.code === 0);
const e2 = new DOMException("Another exciting error message", "NoModificationAllowedError");
console.assert(e2.name === "NoModificationAllowedError");
console.assert(e2.code === 7);

Some files were not shown because too many files have changed in this diff Show More