mirror of
https://github.com/scratchfoundation/scratch-storage.git
synced 2025-06-03 00:45:24 -04:00
feat!: upgrade webpack to 5 and add TS support
Breaking flag is because it may have some differences in the way the library is exported - `module.exports = ` vs `module.exports.default = `. That would depend on the Webpack config, so it should continue working, but just to be safe.
This commit is contained in:
parent
f4e7e908f5
commit
3d0b429526
30 changed files with 4507 additions and 5190 deletions
.browserslistrcjest.config.jspackage-lock.jsonpackage.jsontsconfig.jsontsconfig.test.jsonwebpack.config.js
src
Asset.jsAsset.tsAssetType.tsBuiltinHelper.tsDataFormat.tsFetchTool.tsFetchWorkerTool.tsHelper.tsProxyTool.tsScratchStorage.tsWebHelper.tsindex.jsindex.tslog.jslog.tsmemoizedToString.jsscratchFetch.jstypes.d.ts
test
integration
unit
7
.browserslistrc
Normal file
7
.browserslistrc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# See https://scratch.mit.edu/faq
|
||||||
|
Chrome >= 63
|
||||||
|
Edge >= 15
|
||||||
|
Firefox >= 57
|
||||||
|
Safari >= 11
|
||||||
|
Android >= 63
|
||||||
|
iOS >= 11
|
|
@ -1,5 +1,37 @@
|
||||||
|
const {createDefaultEsmPreset} = require('ts-jest');
|
||||||
|
|
||||||
|
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
moduleNameMapper: {
|
||||||
|
// Allows jest to find the asset files, otherwise it looks for them with the
|
||||||
|
// `?arrayBuffer` as part of the name and doesn't end up transforming them.
|
||||||
|
'^(.+)\\?arrayBuffer$': '$1'
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'js'],
|
||||||
transform: {
|
transform: {
|
||||||
|
...createDefaultEsmPreset({
|
||||||
|
tsconfig: 'tsconfig.test.json',
|
||||||
|
|
||||||
|
// The webpack 5 way to include web workers is to use
|
||||||
|
// `new Worker(new URL('./worker.js', import.meta.url));`.
|
||||||
|
// See https://webpack.js.org/guides/web-workers/
|
||||||
|
// However, the `import.meta.url` is ESM-only and Jest's support for ESM is
|
||||||
|
// still experimental. So, we need to mock it instead (or use experimental
|
||||||
|
// jest & node features).
|
||||||
|
//
|
||||||
|
// Also see https://www.npmjs.com/package/ts-jest-mock-import-meta
|
||||||
|
diagnostics: {
|
||||||
|
ignoreCodes: [1343]
|
||||||
|
},
|
||||||
|
astTransformers: {
|
||||||
|
before: [
|
||||||
|
{
|
||||||
|
path: 'ts-jest-mock-import-meta',
|
||||||
|
options: {metaObjectReplacement: {url: 'https://example.com'}}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}).transform,
|
||||||
'\\.(png|svg|wav)$': '<rootDir>/test/transformers/arraybuffer-loader.js'
|
'\\.(png|svg|wav)$': '<rootDir>/test/transformers/arraybuffer-loader.js'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
8920
package-lock.json
generated
8920
package-lock.json
generated
File diff suppressed because it is too large
Load diff
27
package.json
27
package.json
|
@ -11,19 +11,20 @@
|
||||||
},
|
},
|
||||||
"main": "./dist/node/scratch-storage.js",
|
"main": "./dist/node/scratch-storage.js",
|
||||||
"browser": "./dist/web/scratch-storage.js",
|
"browser": "./dist/web/scratch-storage.js",
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --progress --colors --bail",
|
"build": "webpack",
|
||||||
"commitmsg": "commitlint -e $GIT_PARAMS",
|
"commitmsg": "commitlint -e $GIT_PARAMS",
|
||||||
"coverage": "tap ./test/{unit,integration}/*.js --coverage --coverage-report=lcov",
|
"coverage": "tap ./test/{unit,integration}/*.js --coverage --coverage-report=lcov",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"semantic-release": "semantic-release",
|
"semantic-release": "semantic-release",
|
||||||
"test": "npm run test:lint && jest \"test[\\\\/](unit|integration)\"",
|
"test": "npm run test:lint && jest \"test[\\\\/](unit|integration)\"",
|
||||||
"test:clearCache": "jest --clearCache",
|
"test:clearCache": "jest --clearCache",
|
||||||
"test:integration": "jest \"test[\\\\/]integration\"",
|
"test:integration": "jest \"test[\\\\/]integration\" --no-cache",
|
||||||
"test:lint": "eslint .",
|
"test:lint": "eslint .",
|
||||||
"test:unit": "jest \"test[\\\\/]unit\"",
|
"test:unit": "jest \"test[\\\\/]unit\"",
|
||||||
"version": "json -f package.json -I -e \"this.repository.sha = '$(git log -n1 --pretty=format:%H)'\"",
|
"version": "json -f package.json -I -e \"this.repository.sha = '$(git log -n1 --pretty=format:%H)'\"",
|
||||||
"watch": "webpack --progress --colors --watch"
|
"watch": "webpack --watch"
|
||||||
},
|
},
|
||||||
"tap": {
|
"tap": {
|
||||||
"check-coverage": false
|
"check-coverage": false
|
||||||
|
@ -35,8 +36,7 @@
|
||||||
"cross-fetch": "^3.1.5",
|
"cross-fetch": "^3.1.5",
|
||||||
"fastestsmallesttextencoderdecoder": "^1.0.7",
|
"fastestsmallesttextencoderdecoder": "^1.0.7",
|
||||||
"js-md5": "^0.7.3",
|
"js-md5": "^0.7.3",
|
||||||
"minilog": "^3.1.0",
|
"minilog": "^3.1.0"
|
||||||
"worker-loader": "^2.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.24.4",
|
"@babel/core": "7.24.4",
|
||||||
|
@ -46,22 +46,25 @@
|
||||||
"@commitlint/cli": "18.6.1",
|
"@commitlint/cli": "18.6.1",
|
||||||
"@commitlint/config-conventional": "18.6.3",
|
"@commitlint/config-conventional": "18.6.3",
|
||||||
"@commitlint/travis-cli": "8.3.6",
|
"@commitlint/travis-cli": "8.3.6",
|
||||||
"@types/jest": "29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"babel-eslint": "10.1.0",
|
"babel-loader": "9.1.3",
|
||||||
"babel-loader": "8.3.0",
|
"buffer": "6.0.3",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-config-scratch": "9.0.8",
|
"eslint-config-scratch": "9.0.8",
|
||||||
"eslint-plugin-jest": "27.9.0",
|
"eslint-plugin-jest": "27.9.0",
|
||||||
"eslint-plugin-react": "7.34.1",
|
"eslint-plugin-react": "7.34.1",
|
||||||
"file-loader": "4.3.0",
|
"file-loader": "6.2.0",
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"json": "^9.0.4",
|
"json": "^9.0.4",
|
||||||
"scratch-semantic-release-config": "1.0.14",
|
"scratch-semantic-release-config": "1.0.14",
|
||||||
|
"scratch-webpack-configuration": "1.6.0",
|
||||||
"semantic-release": "19.0.5",
|
"semantic-release": "19.0.5",
|
||||||
"uglifyjs-webpack-plugin": "2.2.0",
|
"ts-jest": "29.2.5",
|
||||||
"webpack": "4.47.0",
|
"ts-jest-mock-import-meta": "^1.2.0",
|
||||||
"webpack-cli": "3.3.12"
|
"ts-loader": "9.5.1",
|
||||||
|
"webpack": "5.94.0",
|
||||||
|
"webpack-cli": "5.1.4"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"commitizen": {
|
"commitizen": {
|
||||||
|
|
146
src/Asset.js
146
src/Asset.js
|
@ -1,146 +0,0 @@
|
||||||
// Use JS implemented TextDecoder and TextEncoder if it is not provided by the
|
|
||||||
// browser.
|
|
||||||
let _TextDecoder;
|
|
||||||
let _TextEncoder;
|
|
||||||
if (typeof TextDecoder === 'undefined' || typeof TextEncoder === 'undefined') {
|
|
||||||
// Wait to require the text encoding polyfill until we know it's needed.
|
|
||||||
// eslint-disable-next-line global-require
|
|
||||||
const encoding = require('fastestsmallesttextencoderdecoder');
|
|
||||||
_TextDecoder = encoding.TextDecoder;
|
|
||||||
_TextEncoder = encoding.TextEncoder;
|
|
||||||
} else {
|
|
||||||
_TextDecoder = TextDecoder;
|
|
||||||
_TextEncoder = TextEncoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
const md5 = require('js-md5');
|
|
||||||
|
|
||||||
const memoizedToString = (function () {
|
|
||||||
/**
|
|
||||||
* The maximum length of a chunk before encoding it into base64.
|
|
||||||
*
|
|
||||||
* 32766 is a multiple of 3 so btoa does not need to use padding characters
|
|
||||||
* except for the final chunk where that is fine. 32766 is also close to
|
|
||||||
* 32768 so it is close to a size an memory allocator would prefer.
|
|
||||||
* @const {number}
|
|
||||||
*/
|
|
||||||
const BTOA_CHUNK_MAX_LENGTH = 32766;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array cache of bytes to characters.
|
|
||||||
* @const {?Array.<string>}
|
|
||||||
*/
|
|
||||||
let fromCharCode = null;
|
|
||||||
|
|
||||||
const strings = {};
|
|
||||||
return (assetId, data) => {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(strings, assetId)) {
|
|
||||||
if (typeof btoa === 'undefined') {
|
|
||||||
// Use a library that does not need btoa to run.
|
|
||||||
/* eslint-disable-next-line global-require */
|
|
||||||
const base64js = require('base64-js');
|
|
||||||
strings[assetId] = base64js.fromByteArray(data);
|
|
||||||
} else {
|
|
||||||
// Native btoa is faster than javascript translation. Use js to
|
|
||||||
// create a "binary" string and btoa to encode it.
|
|
||||||
if (fromCharCode === null) {
|
|
||||||
// Cache the first 256 characters for input byte values.
|
|
||||||
fromCharCode = new Array(256);
|
|
||||||
for (let i = 0; i < 256; i++) {
|
|
||||||
fromCharCode[i] = String.fromCharCode(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const {length} = data;
|
|
||||||
let s = '';
|
|
||||||
// Iterate over chunks of the binary data.
|
|
||||||
for (let i = 0, e = 0; i < length; i = e) {
|
|
||||||
// Create small chunks to cause more small allocations and
|
|
||||||
// less large allocations.
|
|
||||||
e = Math.min(e + BTOA_CHUNK_MAX_LENGTH, length);
|
|
||||||
let s_ = '';
|
|
||||||
for (let j = i; j < e; j += 1) {
|
|
||||||
s_ += fromCharCode[data[j]];
|
|
||||||
}
|
|
||||||
// Encode the latest chunk so the we create one big output
|
|
||||||
// string instead of creating a big input string and then
|
|
||||||
// one big output string.
|
|
||||||
s += btoa(s_);
|
|
||||||
}
|
|
||||||
strings[assetId] = s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings[assetId];
|
|
||||||
};
|
|
||||||
}());
|
|
||||||
|
|
||||||
class Asset {
|
|
||||||
/**
|
|
||||||
* Construct an Asset.
|
|
||||||
* @param {AssetType} assetType - The type of this asset (sound, image, etc.)
|
|
||||||
* @param {string} assetId - The ID of this asset.
|
|
||||||
* @param {DataFormat} [dataFormat] - The format of the data (WAV, PNG, etc.); required iff `data` is present.
|
|
||||||
* @param {Buffer} [data] - The in-memory data for this asset; optional.
|
|
||||||
* @param {bool} [generateId] - Whether to create id from an md5 hash of data
|
|
||||||
*/
|
|
||||||
constructor (assetType, assetId, dataFormat, data, generateId) {
|
|
||||||
/** @type {AssetType} */
|
|
||||||
this.assetType = assetType;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
this.assetId = assetId;
|
|
||||||
|
|
||||||
this.setData(data, dataFormat || assetType.runtimeFormat, generateId);
|
|
||||||
|
|
||||||
/** @type {Asset[]} */
|
|
||||||
this.dependencies = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
setData (data, dataFormat, generateId) {
|
|
||||||
if (data && !dataFormat) {
|
|
||||||
throw new Error('Data provided without specifying its format');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {DataFormat} */
|
|
||||||
this.dataFormat = dataFormat;
|
|
||||||
|
|
||||||
/** @type {Buffer} */
|
|
||||||
this.data = data;
|
|
||||||
|
|
||||||
if (generateId) this.assetId = md5(data);
|
|
||||||
|
|
||||||
// Mark as clean only if set is being called without generateId
|
|
||||||
// If a new id is being generated, mark this asset as not clean
|
|
||||||
this.clean = !generateId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} - This asset's data, decoded as text.
|
|
||||||
*/
|
|
||||||
decodeText () {
|
|
||||||
const decoder = new _TextDecoder();
|
|
||||||
return decoder.decode(this.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as `setData` but encodes text first.
|
|
||||||
* @param {string} data - the text data to encode and store.
|
|
||||||
* @param {DataFormat} dataFormat - the format of the data (DataFormat.SVG for example).
|
|
||||||
* @param {bool} generateId - after setting data, set the id to an md5 of the data?
|
|
||||||
*/
|
|
||||||
encodeTextData (data, dataFormat, generateId) {
|
|
||||||
const encoder = new _TextEncoder();
|
|
||||||
this.setData(encoder.encode(data), dataFormat, generateId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} [contentType] - Optionally override the content type to be included in the data URI.
|
|
||||||
* @returns {string} - A data URI representing the asset's data.
|
|
||||||
*/
|
|
||||||
encodeDataURI (contentType) {
|
|
||||||
contentType = contentType || this.assetType.contentType;
|
|
||||||
return `data:${contentType};base64,${memoizedToString(this.assetId, this.data)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Asset;
|
|
79
src/Asset.ts
Normal file
79
src/Asset.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import md5 from 'js-md5';
|
||||||
|
import {memoizedToString, _TextEncoder, _TextDecoder} from './memoizedToString';
|
||||||
|
|
||||||
|
export default class Asset {
|
||||||
|
// TODO: Typing
|
||||||
|
public assetType: any;
|
||||||
|
public assetId: any;
|
||||||
|
public data: any;
|
||||||
|
public dataFormat: any;
|
||||||
|
public dependencies: any;
|
||||||
|
public clean?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an Asset.
|
||||||
|
* @param {AssetType} assetType - The type of this asset (sound, image, etc.)
|
||||||
|
* @param {string} assetId - The ID of this asset.
|
||||||
|
* @param {DataFormat} [dataFormat] - The format of the data (WAV, PNG, etc.); required iff `data` is present.
|
||||||
|
* @param {Buffer} [data] - The in-memory data for this asset; optional.
|
||||||
|
* @param {bool} [generateId] - Whether to create id from an md5 hash of data
|
||||||
|
*/
|
||||||
|
constructor (assetType, assetId, dataFormat, data?, generateId?) {
|
||||||
|
/** @type {AssetType} */
|
||||||
|
this.assetType = assetType;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
this.assetId = assetId;
|
||||||
|
|
||||||
|
this.setData(data, dataFormat || assetType.runtimeFormat, generateId);
|
||||||
|
|
||||||
|
/** @type {Asset[]} */
|
||||||
|
this.dependencies = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
setData (data, dataFormat, generateId?) {
|
||||||
|
if (data && !dataFormat) {
|
||||||
|
throw new Error('Data provided without specifying its format');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {DataFormat} */
|
||||||
|
this.dataFormat = dataFormat;
|
||||||
|
|
||||||
|
/** @type {Buffer} */
|
||||||
|
this.data = data;
|
||||||
|
|
||||||
|
if (generateId) this.assetId = md5(data);
|
||||||
|
|
||||||
|
// Mark as clean only if set is being called without generateId
|
||||||
|
// If a new id is being generated, mark this asset as not clean
|
||||||
|
this.clean = !generateId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} - This asset's data, decoded as text.
|
||||||
|
*/
|
||||||
|
decodeText () {
|
||||||
|
const decoder = new _TextDecoder();
|
||||||
|
return decoder.decode(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as `setData` but encodes text first.
|
||||||
|
* @param {string} data - the text data to encode and store.
|
||||||
|
* @param {DataFormat} dataFormat - the format of the data (DataFormat.SVG for example).
|
||||||
|
* @param {bool} generateId - after setting data, set the id to an md5 of the data?
|
||||||
|
*/
|
||||||
|
encodeTextData (data, dataFormat, generateId) {
|
||||||
|
const encoder = new _TextEncoder();
|
||||||
|
this.setData(encoder.encode(data), dataFormat, generateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} [contentType] - Optionally override the content type to be included in the data URI.
|
||||||
|
* @returns {string} - A data URI representing the asset's data.
|
||||||
|
*/
|
||||||
|
encodeDataURI (contentType) {
|
||||||
|
contentType = contentType || this.assetType.contentType;
|
||||||
|
return `data:${contentType};base64,${memoizedToString(this.assetId, this.data)}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
const DataFormat = require('./DataFormat');
|
import DataFormat from './DataFormat';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumeration of the supported asset types.
|
* Enumeration of the supported asset types.
|
||||||
|
@ -41,6 +41,6 @@ const AssetType = {
|
||||||
runtimeFormat: DataFormat.JSON,
|
runtimeFormat: DataFormat.JSON,
|
||||||
immutable: true
|
immutable: true
|
||||||
}
|
}
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
module.exports = AssetType;
|
export default AssetType;
|
|
@ -1,11 +1,17 @@
|
||||||
const md5 = require('js-md5');
|
import md5 from 'js-md5';
|
||||||
|
|
||||||
const log = require('./log');
|
import log from './log';
|
||||||
|
|
||||||
const Asset = require('./Asset');
|
import Asset from './Asset';
|
||||||
const AssetType = require('./AssetType');
|
import AssetType from './AssetType';
|
||||||
const DataFormat = require('./DataFormat');
|
import DataFormat from './DataFormat';
|
||||||
const Helper = require('./Helper');
|
import Helper from './Helper';
|
||||||
|
|
||||||
|
import defaultImageBitmap from './builtins/defaultBitmap.png?arrayBuffer';
|
||||||
|
import defaultSound from './builtins/defaultSound.wav?arrayBuffer';
|
||||||
|
import defaultImageVector from './builtins/defaultVector.svg?arrayBuffer';
|
||||||
|
|
||||||
|
import {Buffer} from 'buffer/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} BuiltinAssetRecord
|
* @typedef {object} BuiltinAssetRecord
|
||||||
|
@ -23,25 +29,19 @@ const DefaultAssets = [
|
||||||
type: AssetType.ImageBitmap,
|
type: AssetType.ImageBitmap,
|
||||||
format: DataFormat.PNG,
|
format: DataFormat.PNG,
|
||||||
id: null,
|
id: null,
|
||||||
data: Buffer.from(
|
data: Buffer.from(defaultImageBitmap)
|
||||||
require('./builtins/defaultBitmap.png') // eslint-disable-line global-require
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: AssetType.Sound,
|
type: AssetType.Sound,
|
||||||
format: DataFormat.WAV,
|
format: DataFormat.WAV,
|
||||||
id: null,
|
id: null,
|
||||||
data: Buffer.from(
|
data: Buffer.from(defaultSound)
|
||||||
require('./builtins/defaultSound.wav') // eslint-disable-line global-require
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: AssetType.ImageVector,
|
type: AssetType.ImageVector,
|
||||||
format: DataFormat.SVG,
|
format: DataFormat.SVG,
|
||||||
id: null,
|
id: null,
|
||||||
data: Buffer.from(
|
data: Buffer.from(defaultImageVector)
|
||||||
require('./builtins/defaultVector.svg') // eslint-disable-line global-require
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -51,7 +51,10 @@ const DefaultAssets = [
|
||||||
const BuiltinAssets = DefaultAssets.concat([
|
const BuiltinAssets = DefaultAssets.concat([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
class BuiltinHelper extends Helper {
|
export default class BuiltinHelper extends Helper {
|
||||||
|
// TODO: Typing
|
||||||
|
public assets: any;
|
||||||
|
|
||||||
constructor (parent) {
|
constructor (parent) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
|
||||||
|
@ -85,7 +88,7 @@ class BuiltinHelper extends Helper {
|
||||||
* @returns {?Asset} The asset for assetId, if it exists.
|
* @returns {?Asset} The asset for assetId, if it exists.
|
||||||
*/
|
*/
|
||||||
get (assetId) {
|
get (assetId) {
|
||||||
let asset = null;
|
let asset: Asset | null = null;
|
||||||
if (Object.prototype.hasOwnProperty.call(this.assets, assetId)) {
|
if (Object.prototype.hasOwnProperty.call(this.assets, assetId)) {
|
||||||
/** @type{BuiltinAssetRecord} */
|
/** @type{BuiltinAssetRecord} */
|
||||||
const assetRecord = this.assets[assetId];
|
const assetRecord = this.assets[assetId];
|
||||||
|
@ -163,5 +166,3 @@ class BuiltinHelper extends Helper {
|
||||||
return Promise.resolve(this.get(assetId));
|
return Promise.resolve(this.get(assetId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BuiltinHelper;
|
|
|
@ -11,6 +11,6 @@ const DataFormat = {
|
||||||
SB3: 'sb3',
|
SB3: 'sb3',
|
||||||
SVG: 'svg',
|
SVG: 'svg',
|
||||||
WAV: 'wav'
|
WAV: 'wav'
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
module.exports = DataFormat;
|
export default DataFormat;
|
|
@ -1,4 +1,4 @@
|
||||||
const {scratchFetch} = require('./scratchFetch');
|
import {scratchFetch} from './scratchFetch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Request & {withCredentials: boolean}} ScratchSendRequest
|
* @typedef {Request & {withCredentials: boolean}} ScratchSendRequest
|
||||||
|
@ -7,7 +7,7 @@ const {scratchFetch} = require('./scratchFetch');
|
||||||
/**
|
/**
|
||||||
* Get and send assets with the fetch standard web api.
|
* Get and send assets with the fetch standard web api.
|
||||||
*/
|
*/
|
||||||
class FetchTool {
|
export class FetchTool {
|
||||||
/**
|
/**
|
||||||
* Is get supported?
|
* Is get supported?
|
||||||
* Always true for `FetchTool` because `scratchFetch` ponyfills `fetch` if necessary.
|
* Always true for `FetchTool` because `scratchFetch` ponyfills `fetch` if necessary.
|
||||||
|
@ -55,5 +55,3 @@ class FetchTool {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = FetchTool;
|
|
|
@ -1,9 +1,15 @@
|
||||||
const {Headers, applyMetadata} = require('./scratchFetch');
|
import {Headers, applyMetadata} from './scratchFetch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get and send assets with a worker that uses fetch.
|
* Get and send assets with a worker that uses fetch.
|
||||||
*/
|
*/
|
||||||
class PrivateFetchWorkerTool {
|
class PrivateFetchWorkerTool {
|
||||||
|
// TODO: Typing
|
||||||
|
private _workerSupport: any;
|
||||||
|
private _supportError: any;
|
||||||
|
private worker: any;
|
||||||
|
private jobs: any;
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
/**
|
/**
|
||||||
* What does the worker support of the APIs we need?
|
* What does the worker support of the APIs we need?
|
||||||
|
@ -33,10 +39,11 @@ class PrivateFetchWorkerTool {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.isGetSupported) {
|
if (this.isGetSupported) {
|
||||||
// eslint-disable-next-line global-require
|
// Yes, this is a browser API and we've specified `browser: false` in the eslint env,
|
||||||
const FetchWorker = require('worker-loader?{"inline":true,"fallback":true}!./FetchWorkerTool.worker');
|
// but `isGetSupported` checks for the presence of Worker and uses it only if present.
|
||||||
|
// Also see https://webpack.js.org/guides/web-workers/
|
||||||
const worker = new FetchWorker();
|
// eslint-disable-next-line no-undef
|
||||||
|
const worker = new Worker(new URL('./FetchWorkerTool.worker', import.meta.url));
|
||||||
|
|
||||||
worker.addEventListener('message', ({data}) => {
|
worker.addEventListener('message', ({data}) => {
|
||||||
if (data.support) {
|
if (data.support) {
|
||||||
|
@ -110,8 +117,9 @@ class PrivateFetchWorkerTool {
|
||||||
reject
|
reject
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
// TODO: Typing
|
||||||
/* eslint no-confusing-arrow: ["error", {"allowParens": true}] */
|
/* eslint no-confusing-arrow: ["error", {"allowParens": true}] */
|
||||||
.then(body => (body ? new Uint8Array(body) : null));
|
.then((body: any) => (body ? new Uint8Array(body) : null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,6 +138,8 @@ class PrivateFetchWorkerTool {
|
||||||
throw new Error('Not implemented.');
|
throw new Error('Not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static _instance?: PrivateFetchWorkerTool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a static PrivateFetchWorkerTool instance on demand.
|
* Return a static PrivateFetchWorkerTool instance on demand.
|
||||||
* @returns {PrivateFetchWorkerTool} A static PrivateFetchWorkerTool
|
* @returns {PrivateFetchWorkerTool} A static PrivateFetchWorkerTool
|
||||||
|
@ -146,7 +156,10 @@ class PrivateFetchWorkerTool {
|
||||||
/**
|
/**
|
||||||
* Get and send assets with a worker that uses fetch.
|
* Get and send assets with a worker that uses fetch.
|
||||||
*/
|
*/
|
||||||
class PublicFetchWorkerTool {
|
export default class PublicFetchWorkerTool {
|
||||||
|
// TODO: Typing
|
||||||
|
private inner: any;
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
/**
|
/**
|
||||||
* Shared instance of an internal worker. PublicFetchWorkerTool proxies
|
* Shared instance of an internal worker. PublicFetchWorkerTool proxies
|
||||||
|
@ -189,5 +202,3 @@ class PublicFetchWorkerTool {
|
||||||
throw new Error('Not implemented.');
|
throw new Error('Not implemented.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PublicFetchWorkerTool;
|
|
|
@ -1,8 +1,13 @@
|
||||||
|
import Asset from "./Asset";
|
||||||
|
import {ScratchStorage} from "./ScratchStorage";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for asset load/save helpers.
|
* Base class for asset load/save helpers.
|
||||||
* @abstract
|
* @abstract
|
||||||
*/
|
*/
|
||||||
class Helper {
|
export default class Helper {
|
||||||
|
public parent!: ScratchStorage;
|
||||||
|
|
||||||
constructor (parent) {
|
constructor (parent) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
|
@ -14,9 +19,7 @@ class Helper {
|
||||||
* @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc.
|
* @param {DataFormat} dataFormat - The file format / file extension of the asset to fetch: PNG, JPG, etc.
|
||||||
* @return {Promise.<Asset>} A promise for the contents of the asset.
|
* @return {Promise.<Asset>} A promise for the contents of the asset.
|
||||||
*/
|
*/
|
||||||
load (assetType, assetId, dataFormat) {
|
load (assetType, assetId, dataFormat): null | Asset | Promise<Asset | null> {
|
||||||
return Promise.reject(new Error(`No asset of type ${assetType} for ID ${assetId} with format ${dataFormat}`));
|
return Promise.reject(new Error(`No asset of type ${assetType} for ID ${assetId} with format ${dataFormat}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Helper;
|
|
|
@ -1,5 +1,5 @@
|
||||||
const FetchWorkerTool = require('./FetchWorkerTool');
|
import FetchWorkerTool from './FetchWorkerTool';
|
||||||
const FetchTool = require('./FetchTool');
|
import {FetchTool} from './FetchTool';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} Request
|
* @typedef {object} Request
|
||||||
|
@ -12,7 +12,26 @@ const FetchTool = require('./FetchTool');
|
||||||
/**
|
/**
|
||||||
* Get and send assets with other tools in sequence.
|
* Get and send assets with other tools in sequence.
|
||||||
*/
|
*/
|
||||||
class ProxyTool {
|
export default class ProxyTool {
|
||||||
|
// TODO: Typing
|
||||||
|
public tools: any[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant values that filter the set of tools in a ProxyTool instance.
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
public static TOOL_FILTER = {
|
||||||
|
/**
|
||||||
|
* Use all tools.
|
||||||
|
*/
|
||||||
|
ALL: 'all',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use tools that are ready right now.
|
||||||
|
*/
|
||||||
|
READY: 'ready'
|
||||||
|
};
|
||||||
|
|
||||||
constructor (filter = ProxyTool.TOOL_FILTER.ALL) {
|
constructor (filter = ProxyTool.TOOL_FILTER.ALL) {
|
||||||
let tools;
|
let tools;
|
||||||
if (filter === ProxyTool.TOOL_FILTER.READY) {
|
if (filter === ProxyTool.TOOL_FILTER.READY) {
|
||||||
|
@ -43,7 +62,7 @@ class ProxyTool {
|
||||||
*/
|
*/
|
||||||
get (reqConfig) {
|
get (reqConfig) {
|
||||||
let toolIndex = 0;
|
let toolIndex = 0;
|
||||||
const nextTool = err => {
|
const nextTool = (err?) => {
|
||||||
const tool = this.tools[toolIndex++];
|
const tool = this.tools[toolIndex++];
|
||||||
if (!tool) {
|
if (!tool) {
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -71,7 +90,7 @@ class ProxyTool {
|
||||||
*/
|
*/
|
||||||
send (reqConfig) {
|
send (reqConfig) {
|
||||||
let toolIndex = 0;
|
let toolIndex = 0;
|
||||||
const nextTool = err => {
|
const nextTool = (err?) => {
|
||||||
const tool = this.tools[toolIndex++];
|
const tool = this.tools[toolIndex++];
|
||||||
if (!tool) {
|
if (!tool) {
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -84,21 +103,3 @@ class ProxyTool {
|
||||||
return nextTool();
|
return nextTool();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constant values that filter the set of tools in a ProxyTool instance.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
ProxyTool.TOOL_FILTER = {
|
|
||||||
/**
|
|
||||||
* Use all tools.
|
|
||||||
*/
|
|
||||||
ALL: 'all',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use tools that are ready right now.
|
|
||||||
*/
|
|
||||||
READY: 'ready'
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = ProxyTool;
|
|
|
@ -1,14 +1,21 @@
|
||||||
const log = require('./log');
|
import log from './log';
|
||||||
|
|
||||||
const BuiltinHelper = require('./BuiltinHelper');
|
import BuiltinHelper from './BuiltinHelper';
|
||||||
const WebHelper = require('./WebHelper');
|
import WebHelper from './WebHelper';
|
||||||
|
|
||||||
const _Asset = require('./Asset');
|
import _Asset from './Asset';
|
||||||
const _AssetType = require('./AssetType');
|
import _AssetType from './AssetType';
|
||||||
const _DataFormat = require('./DataFormat');
|
import _DataFormat from './DataFormat';
|
||||||
const _scratchFetch = require('./scratchFetch');
|
import _scratchFetch from './scratchFetch';
|
||||||
|
|
||||||
|
export class ScratchStorage {
|
||||||
|
// TODO: Typing
|
||||||
|
public defaultAssetId: any;
|
||||||
|
public builtinHelper: any;
|
||||||
|
public webHelper: any;
|
||||||
|
|
||||||
|
private _helpers: any;
|
||||||
|
|
||||||
class ScratchStorage {
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.defaultAssetId = {};
|
this.defaultAssetId = {};
|
||||||
|
|
||||||
|
@ -133,7 +140,7 @@ class ScratchStorage {
|
||||||
* @param {UrlFunction} createFunction - A function which computes a POST URL for asset data.
|
* @param {UrlFunction} createFunction - A function which computes a POST URL for asset data.
|
||||||
* @param {UrlFunction} updateFunction - A function which computes a PUT URL for asset data.
|
* @param {UrlFunction} updateFunction - A function which computes a PUT URL for asset data.
|
||||||
*/
|
*/
|
||||||
addWebStore (types, getFunction, createFunction, updateFunction) {
|
addWebStore (types, getFunction, createFunction?, updateFunction?) {
|
||||||
this.webHelper.addStore(types, getFunction, createFunction, updateFunction);
|
this.webHelper.addStore(types, getFunction, createFunction, updateFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,12 +192,12 @@ class ScratchStorage {
|
||||||
load (assetType, assetId, dataFormat) {
|
load (assetType, assetId, dataFormat) {
|
||||||
/** @type {Helper[]} */
|
/** @type {Helper[]} */
|
||||||
const helpers = this._helpers.map(x => x.helper);
|
const helpers = this._helpers.map(x => x.helper);
|
||||||
const errors = [];
|
const errors: any[] = [];
|
||||||
dataFormat = dataFormat || assetType.runtimeFormat;
|
dataFormat = dataFormat || assetType.runtimeFormat;
|
||||||
|
|
||||||
let helperIndex = 0;
|
let helperIndex = 0;
|
||||||
let helper;
|
let helper;
|
||||||
const tryNextHelper = err => {
|
const tryNextHelper = (err?) => {
|
||||||
if (err) { // Track the error, but continue looking
|
if (err) { // Track the error, but continue looking
|
||||||
errors.push(err);
|
errors.push(err);
|
||||||
}
|
}
|
||||||
|
@ -231,6 +238,7 @@ class ScratchStorage {
|
||||||
dataFormat = dataFormat || assetType.runtimeFormat;
|
dataFormat = dataFormat || assetType.runtimeFormat;
|
||||||
return new Promise(
|
return new Promise(
|
||||||
(resolve, reject) =>
|
(resolve, reject) =>
|
||||||
|
// TODO: Iterate this.helpers
|
||||||
this.webHelper.store(assetType, dataFormat, data, assetId)
|
this.webHelper.store(assetType, dataFormat, data, assetId)
|
||||||
.then(body => {
|
.then(body => {
|
||||||
this.builtinHelper._store(assetType, dataFormat, data, body.id);
|
this.builtinHelper._store(assetType, dataFormat, data, body.id);
|
||||||
|
@ -240,5 +248,3 @@ class ScratchStorage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ScratchStorage;
|
|
|
@ -1,8 +1,8 @@
|
||||||
const log = require('./log');
|
import log from './log';
|
||||||
|
|
||||||
const Asset = require('./Asset');
|
import Asset from './Asset';
|
||||||
const Helper = require('./Helper');
|
import Helper from './Helper';
|
||||||
const ProxyTool = require('./ProxyTool');
|
import ProxyTool from './ProxyTool';
|
||||||
|
|
||||||
const ensureRequestConfig = reqConfig => {
|
const ensureRequestConfig = reqConfig => {
|
||||||
if (typeof reqConfig === 'string') {
|
if (typeof reqConfig === 'string') {
|
||||||
|
@ -20,7 +20,12 @@ const ensureRequestConfig = reqConfig => {
|
||||||
* the underlying fetch call (necessary for configuring e.g. authentication)
|
* the underlying fetch call (necessary for configuring e.g. authentication)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class WebHelper extends Helper {
|
export default class WebHelper extends Helper {
|
||||||
|
// TODO: Typing
|
||||||
|
public stores: any[];
|
||||||
|
public assetTool: any;
|
||||||
|
public projectTool: any;
|
||||||
|
|
||||||
constructor (parent) {
|
constructor (parent) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
|
||||||
|
@ -68,7 +73,7 @@ class WebHelper extends Helper {
|
||||||
* @param {UrlFunction} createFunction - A function which computes a POST URL for an Asset
|
* @param {UrlFunction} createFunction - A function which computes a POST URL for an Asset
|
||||||
* @param {UrlFunction} updateFunction - A function which computes a PUT URL for an Asset
|
* @param {UrlFunction} updateFunction - A function which computes a PUT URL for an Asset
|
||||||
*/
|
*/
|
||||||
addStore (types, getFunction, createFunction, updateFunction) {
|
addStore (types, getFunction, createFunction?, updateFunction?) {
|
||||||
this.stores.push({
|
this.stores.push({
|
||||||
types: types.map(assetType => assetType.name),
|
types: types.map(assetType => assetType.name),
|
||||||
get: getFunction,
|
get: getFunction,
|
||||||
|
@ -86,11 +91,12 @@ class WebHelper extends Helper {
|
||||||
*/
|
*/
|
||||||
load (assetType, assetId, dataFormat) {
|
load (assetType, assetId, dataFormat) {
|
||||||
|
|
||||||
|
// TODO: Typing
|
||||||
/** @type {Array.<{url:string, result:*}>} List of URLs attempted & errors encountered. */
|
/** @type {Array.<{url:string, result:*}>} List of URLs attempted & errors encountered. */
|
||||||
const errors = [];
|
const errors: any = [];
|
||||||
const stores = this.stores.slice()
|
const stores = this.stores.slice()
|
||||||
.filter(store => store.types.indexOf(assetType.name) >= 0);
|
.filter(store => store.types.indexOf(assetType.name) >= 0);
|
||||||
|
|
||||||
// New empty asset but it doesn't have data yet
|
// New empty asset but it doesn't have data yet
|
||||||
const asset = new Asset(assetType, assetId, dataFormat);
|
const asset = new Asset(assetType, assetId, dataFormat);
|
||||||
|
|
||||||
|
@ -100,7 +106,7 @@ class WebHelper extends Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
let storeIndex = 0;
|
let storeIndex = 0;
|
||||||
const tryNextSource = err => {
|
const tryNextSource = (err?) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
errors.push(err);
|
errors.push(err);
|
||||||
}
|
}
|
||||||
|
@ -191,5 +197,3 @@ class WebHelper extends Helper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = WebHelper;
|
|
|
@ -1,7 +0,0 @@
|
||||||
const ScratchStorage = require('./ScratchStorage');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export for use with NPM & Node.js.
|
|
||||||
* @type {ScratchStorage}
|
|
||||||
*/
|
|
||||||
module.exports = ScratchStorage;
|
|
7
src/index.ts
Normal file
7
src/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import {ScratchStorage} from './ScratchStorage';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export for use with NPM & Node.js.
|
||||||
|
* @type {ScratchStorage}
|
||||||
|
*/
|
||||||
|
export default ScratchStorage;
|
|
@ -1,4 +0,0 @@
|
||||||
const minilog = require('minilog');
|
|
||||||
minilog.enable();
|
|
||||||
|
|
||||||
module.exports = minilog('storage');
|
|
4
src/log.ts
Normal file
4
src/log.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import minilog from 'minilog';
|
||||||
|
minilog.enable();
|
||||||
|
|
||||||
|
export default minilog('storage');
|
75
src/memoizedToString.js
Normal file
75
src/memoizedToString.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// Use JS implemented TextDecoder and TextEncoder if it is not provided by the
|
||||||
|
// browser.
|
||||||
|
let _TextDecoder;
|
||||||
|
let _TextEncoder;
|
||||||
|
if (typeof TextDecoder === 'undefined' || typeof TextEncoder === 'undefined') {
|
||||||
|
// Wait to require the text encoding polyfill until we know it's needed.
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
const encoding = require('fastestsmallesttextencoderdecoder');
|
||||||
|
_TextDecoder = encoding.TextDecoder;
|
||||||
|
_TextEncoder = encoding.TextEncoder;
|
||||||
|
} else {
|
||||||
|
_TextDecoder = TextDecoder;
|
||||||
|
_TextEncoder = TextEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
const memoizedToString = (function () {
|
||||||
|
/**
|
||||||
|
* The maximum length of a chunk before encoding it into base64.
|
||||||
|
*
|
||||||
|
* 32766 is a multiple of 3 so btoa does not need to use padding characters
|
||||||
|
* except for the final chunk where that is fine. 32766 is also close to
|
||||||
|
* 32768 so it is close to a size an memory allocator would prefer.
|
||||||
|
* @const {number}
|
||||||
|
*/
|
||||||
|
const BTOA_CHUNK_MAX_LENGTH = 32766;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array cache of bytes to characters.
|
||||||
|
* @const {?Array.<string>}
|
||||||
|
*/
|
||||||
|
let fromCharCode = null;
|
||||||
|
|
||||||
|
const strings = {};
|
||||||
|
return (assetId, data) => {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(strings, assetId)) {
|
||||||
|
if (typeof btoa === 'undefined') {
|
||||||
|
// Use a library that does not need btoa to run.
|
||||||
|
/* eslint-disable-next-line global-require */
|
||||||
|
const base64js = require('base64-js');
|
||||||
|
strings[assetId] = base64js.fromByteArray(data);
|
||||||
|
} else {
|
||||||
|
// Native btoa is faster than javascript translation. Use js to
|
||||||
|
// create a "binary" string and btoa to encode it.
|
||||||
|
if (fromCharCode === null) {
|
||||||
|
// Cache the first 256 characters for input byte values.
|
||||||
|
fromCharCode = new Array(256);
|
||||||
|
for (let i = 0; i < 256; i++) {
|
||||||
|
fromCharCode[i] = String.fromCharCode(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {length} = data;
|
||||||
|
let s = '';
|
||||||
|
// Iterate over chunks of the binary data.
|
||||||
|
for (let i = 0, e = 0; i < length; i = e) {
|
||||||
|
// Create small chunks to cause more small allocations and
|
||||||
|
// less large allocations.
|
||||||
|
e = Math.min(e + BTOA_CHUNK_MAX_LENGTH, length);
|
||||||
|
let s_ = '';
|
||||||
|
for (let j = i; j < e; j += 1) {
|
||||||
|
s_ += fromCharCode[data[j]];
|
||||||
|
}
|
||||||
|
// Encode the latest chunk so the we create one big output
|
||||||
|
// string instead of creating a big input string and then
|
||||||
|
// one big output string.
|
||||||
|
s += btoa(s_);
|
||||||
|
}
|
||||||
|
strings[assetId] = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings[assetId];
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
|
||||||
|
module.exports = {memoizedToString, _TextEncoder, _TextDecoder};
|
|
@ -103,8 +103,6 @@ const unsetMetadata = name => {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
default: scratchFetch,
|
|
||||||
|
|
||||||
Headers: crossFetch.Headers,
|
Headers: crossFetch.Headers,
|
||||||
RequestMetadata,
|
RequestMetadata,
|
||||||
applyMetadata,
|
applyMetadata,
|
||||||
|
|
3
src/types.d.ts
vendored
Normal file
3
src/types.d.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
declare module '*.png?arrayBuffer';
|
||||||
|
declare module '*.wav?arrayBuffer';
|
||||||
|
declare module '*.svg?arrayBuffer';
|
|
@ -1,6 +1,6 @@
|
||||||
const md5 = require('js-md5');
|
import md5 from 'js-md5';
|
||||||
|
|
||||||
const ScratchStorage = require('../../src/index.js');
|
import ScratchStorage from '../../src/index';
|
||||||
|
|
||||||
test('constructor', () => {
|
test('constructor', () => {
|
||||||
const storage = new ScratchStorage();
|
const storage = new ScratchStorage();
|
|
@ -1,4 +1,4 @@
|
||||||
const ScratchStorage = require('../../src');
|
const {ScratchStorage} = require('../../src/ScratchStorage');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulate a storage helper, adding log messages when "load" is called rather than actually loading anything.
|
* Simulate a storage helper, adding log messages when "load" is called rather than actually loading anything.
|
||||||
|
|
|
@ -2,7 +2,7 @@ const TextDecoder = require('util').TextDecoder;
|
||||||
|
|
||||||
jest.mock('cross-fetch');
|
jest.mock('cross-fetch');
|
||||||
const mockFetch = require('cross-fetch');
|
const mockFetch = require('cross-fetch');
|
||||||
const FetchTool = require('../../src/FetchTool.js');
|
const {FetchTool} = require('../../src/FetchTool');
|
||||||
|
|
||||||
test('send success returns response.text()', async () => {
|
test('send success returns response.text()', async () => {
|
||||||
const tool = new FetchTool();
|
const tool = new FetchTool();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const md5 = require('js-md5');
|
const md5 = require('js-md5');
|
||||||
|
|
||||||
const ScratchStorage = require('../../dist/node/scratch-storage');
|
const {ScratchStorage} = require('../../src/ScratchStorage');
|
||||||
|
|
||||||
// Hash and file size of each default asset
|
// Hash and file size of each default asset
|
||||||
const knownSizes = {
|
const knownSizes = {
|
||||||
|
|
|
@ -14,7 +14,7 @@ beforeEach(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('get without metadata', async () => {
|
test('get without metadata', async () => {
|
||||||
const FetchTool = require('../../src/FetchTool.js');
|
const {FetchTool} = require('../../src/FetchTool');
|
||||||
const tool = new FetchTool();
|
const tool = new FetchTool();
|
||||||
|
|
||||||
const mockFetchTestData = {};
|
const mockFetchTestData = {};
|
||||||
|
@ -26,7 +26,7 @@ test('get without metadata', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('get with metadata', async () => {
|
test('get with metadata', async () => {
|
||||||
const FetchTool = require('../../src/FetchTool.js');
|
const {FetchTool} = require('../../src/FetchTool');
|
||||||
const ScratchFetch = require('../../src/scratchFetch');
|
const ScratchFetch = require('../../src/scratchFetch');
|
||||||
const {RequestMetadata, setMetadata} = ScratchFetch;
|
const {RequestMetadata, setMetadata} = ScratchFetch;
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ test('get with metadata', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('send without metadata', async () => {
|
test('send without metadata', async () => {
|
||||||
const FetchTool = require('../../src/FetchTool.js');
|
const {FetchTool} = require('../../src/FetchTool');
|
||||||
const tool = new FetchTool();
|
const tool = new FetchTool();
|
||||||
|
|
||||||
const mockFetchTestData = {};
|
const mockFetchTestData = {};
|
||||||
|
@ -58,7 +58,7 @@ test('send without metadata', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('send with metadata', async () => {
|
test('send with metadata', async () => {
|
||||||
const FetchTool = require('../../src/FetchTool.js');
|
const {FetchTool} = require('../../src/FetchTool');
|
||||||
const ScratchFetch = require('../../src/scratchFetch');
|
const ScratchFetch = require('../../src/scratchFetch');
|
||||||
const {RequestMetadata, setMetadata} = ScratchFetch;
|
const {RequestMetadata, setMetadata} = ScratchFetch;
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ test('send with metadata', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('selectively delete metadata', async () => {
|
test('selectively delete metadata', async () => {
|
||||||
const FetchTool = require('../../src/FetchTool.js');
|
const {FetchTool} = require('../../src/FetchTool');
|
||||||
const ScratchFetch = require('../../src/scratchFetch');
|
const ScratchFetch = require('../../src/scratchFetch');
|
||||||
const {RequestMetadata, setMetadata, unsetMetadata} = ScratchFetch;
|
const {RequestMetadata, setMetadata, unsetMetadata} = ScratchFetch;
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ test('selectively delete metadata', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('metadata has case-insensitive keys', async () => {
|
test('metadata has case-insensitive keys', async () => {
|
||||||
const FetchTool = require('../../src/FetchTool.js');
|
const {FetchTool} = require('../../src/FetchTool');
|
||||||
const ScratchFetch = require('../../src/scratchFetch');
|
const ScratchFetch = require('../../src/scratchFetch');
|
||||||
const {setMetadata} = ScratchFetch;
|
const {setMetadata} = ScratchFetch;
|
||||||
|
|
||||||
|
|
29
tsconfig.json
Normal file
29
tsconfig.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"include": ["src"],
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "ESNext",
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "Preserve",
|
||||||
|
"types": ["./src/types.d.ts"],
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist/types/",
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
11
tsconfig.test.json
Normal file
11
tsconfig.test.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"include": ["test"],
|
||||||
|
"extends": ["./tsconfig.json"],
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "CommonJS",
|
||||||
|
"types": ["jest", "./src/types.d.ts"],
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,80 +1,74 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
const webpack = require('webpack');
|
||||||
|
|
||||||
const base = {
|
const ScratchWebpackConfigBuilder = require('scratch-webpack-configuration');
|
||||||
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
|
|
||||||
devtool: 'cheap-module-source-map',
|
const baseConfig = new ScratchWebpackConfigBuilder(
|
||||||
module: {
|
{
|
||||||
rules: [
|
rootPath: path.resolve(__dirname),
|
||||||
{
|
enableReact: false,
|
||||||
include: [
|
enableTs: true,
|
||||||
path.resolve('src')
|
shouldSplitChunks: false
|
||||||
],
|
})
|
||||||
test: /\.js$/,
|
.setTarget('browserslist')
|
||||||
loader: 'babel-loader',
|
.merge({
|
||||||
options: {
|
resolve: {
|
||||||
plugins: [
|
fallback: {
|
||||||
'@babel/plugin-transform-runtime'
|
Buffer: require.resolve('buffer/')
|
||||||
],
|
|
||||||
presets: [
|
|
||||||
['@babel/preset-env', {targets: {browsers: ['last 3 versions', 'Safari >= 8', 'iOS >= 8']}}]
|
|
||||||
],
|
|
||||||
// Consider a file a "module" if import/export statements are present, or else consider it a
|
|
||||||
// "script". Fixes "Cannot assign to read only property 'exports'" when using
|
|
||||||
// @babel/plugin-transform-runtime with CommonJS files.
|
|
||||||
sourceType: 'unambiguous'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|svg|wav)$/,
|
|
||||||
loader: 'arraybuffer-loader'
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
});
|
||||||
optimization: {
|
|
||||||
minimizer: [
|
|
||||||
new UglifyJsPlugin({
|
|
||||||
include: /\.min\.js$/,
|
|
||||||
sourceMap: true
|
|
||||||
})
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: []
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = [
|
if (!process.env.CI) {
|
||||||
// Web-compatible
|
baseConfig.addPlugin(new webpack.ProgressPlugin());
|
||||||
Object.assign({}, base, {
|
}
|
||||||
target: 'web',
|
|
||||||
entry: {
|
// Web-compatible
|
||||||
'scratch-storage': './src/index.js',
|
const webConfig = baseConfig.clone()
|
||||||
'scratch-storage.min': './src/index.js'
|
.merge({
|
||||||
},
|
|
||||||
output: {
|
output: {
|
||||||
library: 'ScratchStorage',
|
library: 'ScratchStorage',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
path: path.resolve('dist', 'web'),
|
path: path.resolve(__dirname, 'dist', 'web'),
|
||||||
filename: '[name].js'
|
filename: '[name].js',
|
||||||
|
clean: false
|
||||||
}
|
}
|
||||||
}),
|
});
|
||||||
|
|
||||||
// Node-compatible
|
const webNonMinConfig = webConfig.clone()
|
||||||
Object.assign({}, base, {
|
.merge({
|
||||||
target: 'node',
|
|
||||||
entry: {
|
entry: {
|
||||||
'scratch-storage': './src/index.js'
|
'scratch-storage': path.join(__dirname, './src/index.ts')
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const webMinConfig = webConfig.clone()
|
||||||
|
.merge({
|
||||||
|
entry: {
|
||||||
|
'scratch-storage.min': path.join(__dirname, './src/index.ts')
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Node-compatible
|
||||||
|
const nodeConfig = baseConfig.clone()
|
||||||
|
.merge({
|
||||||
|
entry: {
|
||||||
|
'scratch-storage': path.join(__dirname, './src/index.ts')
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
library: 'ScratchStorage',
|
library: 'ScratchStorage',
|
||||||
libraryTarget: 'commonjs2',
|
libraryTarget: 'commonjs2',
|
||||||
path: path.resolve('dist', 'node'),
|
path: path.resolve(__dirname, 'dist', 'node'),
|
||||||
filename: '[name].js'
|
filename: '[name].js',
|
||||||
},
|
clean: false
|
||||||
externals: {
|
|
||||||
'base64-js': true,
|
|
||||||
'js-md5': true,
|
|
||||||
'localforage': true,
|
|
||||||
'text-encoding': true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
];
|
.addExternals(['base64-js', 'js-md5', 'localforage', 'text-encoding']);
|
||||||
|
|
||||||
|
module.exports = [webNonMinConfig.get(), webMinConfig.get(), nodeConfig.get()];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue