refactor(struct): tweak type plugin system

This commit is contained in:
Simon Chan 2021-01-07 14:16:30 +08:00
parent b601f7a7f2
commit 9f3d480523
22 changed files with 862 additions and 530 deletions

View file

@ -24,6 +24,7 @@
"hhmm",
"hisi",
"jmuxer",
"keyof",
"killforward",
"laggy",
"lapo",
@ -46,8 +47,10 @@
"tcpip",
"tinyh",
"tsbuildinfo",
"typeof",
"uifabric",
"webadb",
"webpackbar",
"websockify",
"webusb",
"wifi",

View file

@ -1,5 +1,6 @@
{
"name": "@yume-chan/adb-backend-web",
"private": true,
"version": "0.0.1",
"description": "Backend for `@yume-chan/adb` using Web technologies.",
"keywords": [

View file

@ -1,5 +1,6 @@
{
"name": "@yume-chan/adb",
"private": true,
"version": "0.0.1",
"description": "TypeScript implementation of Android Debug Bridge (ADB) protocol.",
"keywords": [
@ -34,7 +35,7 @@
"dependencies": {
"@yume-chan/async-operation-manager": "2.1.3",
"@yume-chan/event": "^0.0.1",
"@yume-chan/struct": "^0.0.1",
"@yume-chan/struct": "^0.0.2",
"tslib": "2.0.3"
},
"devDependencies": {

View file

@ -861,6 +861,15 @@
"integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==",
"dev": true
},
"ansi-escapes": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
"integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
"dev": true,
"requires": {
"type-fest": "^0.11.0"
}
},
"ansi-html": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
@ -1232,6 +1241,12 @@
}
}
},
"ci-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
"dev": true
},
"class-utils": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
@ -1407,6 +1422,12 @@
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
"dev": true
},
"consola": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
"integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ==",
"dev": true
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
@ -2249,6 +2270,15 @@
"websocket-driver": ">=0.5.1"
}
},
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"file-loader": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
@ -4348,6 +4378,12 @@
}
}
},
"pretty-time": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz",
"integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==",
"dev": true
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -5232,6 +5268,15 @@
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
"dev": true
},
"std-env": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-2.2.1.tgz",
"integrity": "sha512-IjYQUinA3lg5re/YMlwlfhqNRTzMZMqE+pezevdcTaHceqx8ngEi1alX9nNCk9Sc81fy1fLDeQoaCzeiW1yBOQ==",
"dev": true,
"requires": {
"ci-info": "^1.6.0"
}
},
"streamsaver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/streamsaver/-/streamsaver-2.0.5.tgz",
@ -5402,6 +5447,12 @@
}
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@ -5516,6 +5567,12 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
},
"type-fest": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
"integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
"dev": true
},
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@ -6454,6 +6511,122 @@
"source-map": "~0.6.1"
}
},
"webpackbar": {
"version": "5.0.0-3",
"resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.0-3.tgz",
"integrity": "sha512-viW6KCYjMb0NPoDrw2jAmLXU2dEOhRrtku28KmOfeE1vxbfwCYuTbTaMhnkrCZLFAFyY9Q49Z/jzYO80Dw5b8g==",
"dev": true,
"requires": {
"ansi-escapes": "^4.3.1",
"chalk": "^4.1.0",
"consola": "^2.15.0",
"figures": "^3.2.0",
"pretty-time": "^1.1.0",
"std-env": "^2.2.1",
"text-table": "^0.2.0",
"wrap-ansi": "^7.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
}
}
},
"websocket-driver": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",

View file

@ -1,7 +1,7 @@
{
"name": "demo",
"version": "0.0.1",
"private": true,
"version": "0.0.1",
"description": "Demo of `@yume-chan/adb` and `@yume-chan/adb-backend-web`.",
"author": "Simon Chan <cnsimonchan@live.com>",
"homepage": "https://github.com/yume-chan/ya-webadb#readme",
@ -37,6 +37,7 @@
"webpack-bundle-analyzer": "4.3.0",
"webpack-cli": "4.2.0",
"webpack-dev-server": "3.11.0",
"webpackbar": "5.0.0-3",
"worker-loader": "3.0.7"
},
"dependencies": {
@ -47,7 +48,7 @@
"@yume-chan/adb-backend-web": "^0.0.1",
"@yume-chan/async-operation-manager": "2.1.3",
"@yume-chan/event": "^0.0.1",
"@yume-chan/struct": "^0.0.1",
"@yume-chan/struct": "^0.0.2",
"jmuxer": "2.0.0",
"path-browserify": "1.0.1",
"react": "17.0.1",

View file

@ -12,6 +12,18 @@ import { createTinyH264Decoder, TinyH264Decoder } from './tinyh264';
const DeviceServerPath = '/data/local/tmp/scrcpy-server.jar';
function clamp(value: number, min: number, max: number): number {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
export const Scrcpy = withDisplayName('Scrcpy')(({
device
}: RouteProps): JSX.Element | null => {
@ -283,14 +295,11 @@ export const Scrcpy = withDisplayName('Scrcpy')(({
action: AndroidMotionEventAction,
e: React.PointerEvent<HTMLCanvasElement>
) => {
e.preventDefault();
e.stopPropagation();
const view = e.currentTarget.getBoundingClientRect();
const view = canvasRef.current!.getBoundingClientRect();
const pointerViewX = e.clientX - view.x;
const pointerViewY = e.clientY - view.y;
const pointerScreenX = pointerViewX / view.width * width;
const pointerScreenY = pointerViewY / view.height * height;
const pointerScreenX = clamp(pointerViewX / view.width, 0, 1) * width;
const pointerScreenY = clamp(pointerViewY / view.height, 0, 1) * height;
scrcpyClientRef.current?.injectTouch({
action,
@ -307,6 +316,7 @@ export const Scrcpy = withDisplayName('Scrcpy')(({
return;
}
canvasRef.current!.focus();
e.currentTarget.setPointerCapture(e.pointerId);
injectTouch(AndroidMotionEventAction.Down, e);
}, [injectTouch]);
@ -321,6 +331,7 @@ export const Scrcpy = withDisplayName('Scrcpy')(({
if (e.button !== 0) {
return;
}
e.currentTarget.releasePointerCapture(e.pointerId);
injectTouch(AndroidMotionEventAction.Up, e);
}, [injectTouch]);
@ -406,6 +417,7 @@ export const Scrcpy = withDisplayName('Scrcpy')(({
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
onPointerCancel={handlePointerUp}
onKeyDown={handleKeyDown}
/>
</DeviceView>

View file

@ -6,9 +6,10 @@ var html_webpack_plugin_1 = tslib_1.__importDefault(require("html-webpack-plugin
var mini_css_extract_plugin_1 = tslib_1.__importDefault(require("mini-css-extract-plugin"));
var path_1 = tslib_1.__importDefault(require("path"));
var webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer");
var webpackbar_1 = tslib_1.__importDefault(require("webpackbar"));
var context = path_1.default.resolve(process.cwd());
var plugins = [
new clean_webpack_plugin_1.CleanWebpackPlugin(),
new webpackbar_1.default({}),
new mini_css_extract_plugin_1.default({
filename: '[name].[contenthash].css',
}),
@ -29,7 +30,11 @@ var plugins = [
if (process.env.ANALYZE) {
plugins.push(new webpack_bundle_analyzer_1.BundleAnalyzerPlugin());
}
var config = function (env, argv) { return ({
var config = function (env, argv) {
if (argv.mode !== 'production') {
plugins.unshift(new clean_webpack_plugin_1.CleanWebpackPlugin());
}
return {
mode: 'development',
devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map',
context: context,
@ -43,6 +48,7 @@ var config = function (env, argv) { return ({
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
// @ts-expect-error typing is not up to date
fallback: { "path": require.resolve("path-browserify") },
},
plugins: plugins,
@ -58,5 +64,6 @@ var config = function (env, argv) { return ({
contentBase: path_1.default.resolve(context, 'lib'),
port: 9000,
},
}); };
};
};
module.exports = config;

View file

@ -1,5 +1,6 @@
{
"name": "@yume-chan/event",
"private": true,
"version": "0.0.1",
"description": "Event/EventEmitter",
"keywords": [

View file

@ -4,30 +4,42 @@ C-style structure serializer and deserializer.
Fully compatible with TypeScript.
- [Compatibility](#compatibility)
- [Quick Start](#quick-start)
- [Compatibility](#compatibility)
- [API](#api)
- [`placeholder` method](#placeholder-method)
- [`Struct` constructor](#struct-constructor)
- [`Struct#uint8`/`uint16`/`uint32`/`int32` methods](#structuint8uint16uint32int32-methods)
- [`Struct#uint64`/`int64` method](#structuint64int64-method)
- [`Struct#fields` method](#structfields-method)
- [`Struct#uint8`/`uint16`/`int32`/`uint32` methods](#structuint8uint16int32uint32-methods)
- [`Struct#int64`/`uint64` methods](#structint64uint64-methods)
- [`extra` function](#extra-function)
- [`afterParsed` method](#afterparsed-method)
- [`postDeserialize` method](#postdeserialize-method)
- [`deserialize` method](#deserialize-method)
- [`serialize` method](#serialize-method)
- [Extend types](#extend-types)
- [Backing Field](#backing-field)
- [`FieldDescriptorBase` interface](#fielddescriptorbase-interface)
- [`field` method](#field-method)
- [`FieldTypeDefinition` interface](#fieldtypedefinition-interface)
- [`deserialize` method](#deserialize-method-1)
- [`getSize` method](#getsize-method)
- [`getDynamicSize` method](#getdynamicsize-method)
- [`initialize` method](#initialize-method)
- [`serialize` method](#serialize-method-1)
- [Array type](#array-type)
- [`registerFieldTypeDefinition` method](#registerfieldtypedefinition-method)
- [Data flow](#data-flow)
- [Custom types](#custom-types)
- [`Struct#field` method](#structfield-method)
- [FieldDefinition](#fielddefinition)
- [`FieldDefinition#getSize` method](#fielddefinitiongetsize-method)
- [`FieldDefinition#deserialize` method](#fielddefinitiondeserialize-method)
- [`FieldDefinition#createValue` method](#fielddefinitioncreatevalue-method)
- [FieldRuntimeValue](#fieldruntimevalue)
## Quick Start
```ts
import Struct from '@yume-chan/struct';
const MyStruct =
new Struct({ littleEndian: true })
.int32('foo')
.int32('bar');
const value = await MyStruct.deserialize(someStream);
// TypeScript can infer type of the result object.
const { foo, bar } = value;
const buffer = MyStruct.serialize({ foo, bar });
```
## Compatibility
@ -47,7 +59,7 @@ Basic usage requires [`Promise`][MDN_Promise], [`ArrayBuffer`][MDN_ArrayBuffer],
| **Safari** | 8 | |
| **Node.js** | 0.12 | |
Usage of `uint64` requires [`BigInt`][MDN_BigInt] (**can't** be polyfilled), [`DataView#getBigUint64`][MDN_DataView_getBigUint64] and [`DataView#setBigUint64`][MDN_DataView_setBigUint64] (can be polyfilled).
Usage of `int64`/`uint64` requires [`BigInt`][MDN_BigInt] (**can't** be polyfilled), [`DataView#getBigUint64`][MDN_DataView_getBigUint64] and [`DataView#setBigUint64`][MDN_DataView_setBigUint64] (can be polyfilled).
[MDN_BigInt]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
[MDN_DataView_getBigUint64]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64
@ -62,21 +74,6 @@ Usage of `uint64` requires [`BigInt`][MDN_BigInt] (**can't** be polyfilled), [`D
| **Safari** | 14 | Requires polyfills for `DataView#getBigUint64`/`DataView#setBigUint64` |
| **Node.js** | 10.4.0 | |
## Quick Start
```ts
import Struct from '@yume-chan/struct';
const MyStruct =
new Struct({ littleEndian: true })
.int32('foo')
.int32('bar');
const value = MyStruct.deserialize(someStream);
// TypeScript can infer type of the result object.
const { foo, bar } = value;
```
## API
### `placeholder` method
@ -89,20 +86,18 @@ export function placeholder<T>(): T {
Return a (fake) value of the given type.
Because TypeScript only supports supply all or none type arguments, this library allows all type parameters to be inferred from arguments.
This method can be used where an argument is only used to infer a type parameter.
Because TypeScript only supports supply all or none type arguments, this method allows all type parameters to be inferred from arguments.
**While all following APIs heavily rely on generic, DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
### `Struct` constructor
```ts
export default class Struct<
TResult extends object = {},
class Struct<
TValue extends object = {},
TInit extends object = {},
TExtra extends object = {},
TAfterParsed = undefined,
TPostDeserialized = undefined
> {
public constructor(options: Partial<StructOptions> = StructDefaultOptions);
}
@ -112,10 +107,10 @@ Creates a new structure definition.
**Generic Parameters**
1. `TResult`: Type of the result object.
2. `TInit`: Type requirement to create such a structure. (Because some fields may implies other fields)
1. `TValue`: Type of the Struct instance.
2. `TInit`: Type requirement to create such a structure. (May not be same as `TValue` because some fields can implies others)
3. `TExtra`: Type of extra fields.
4. `TAfterParsed`: State of the `afterParsed` function.
4. `TPostDeserialized`: State of the `postDeserialize` function.
**DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
@ -124,52 +119,90 @@ These are considered "internal state" of the `Struct` and will be taken care of
**Parameters**
1. `options`:
* `littleEndian:boolean = false`: Whether all multi-byte fields are [little-endian encoded][Wikipeida_Endianess].
* `littleEndian:boolean = false`: Whether all multi-byte fields in this struct are [little-endian encoded][Wikipeida_Endianess].
[Wikipeida_Endianess]: https://en.wikipedia.org/wiki/Endianness
### `Struct#uint8`/`uint16`/`uint32`/`int32` methods
### `Struct#fields` method
```ts
public int32<
TName extends string,
TTypeScriptType = number
fields<
TOther extends Struct<any, any, any, any>
>(
name: TName,
options: FieldDescriptorBaseOptions = {},
_typescriptType?: TTypeScriptType,
struct: TOther
): Struct<
TResult & Record<TName, TTypeScriptType>,
TInit & Record<TName, TTypeScriptType>,
TExtra,
TAfterParsed,
>;
public uint32<
TName extends string,
TTypeScriptType = number
>(
name: TName,
options: {} = {},
_typescriptType?: TTypeScriptType,
): Struct<
TResult & Record<TName, TTypeScriptType>,
TInit & Record<TName, TTypeScriptType>,
TExtra,
TAfterParsed,
TValue & TOther['valueType'],
TInit & TOther['initType'],
TExtra & TOther['extraType'],
TPostDeserialized
>;
```
Return a new `Struct` instance with an `int32`/`uint32` field appended to the end.
Merges (flats) another `Struct`'s fields and extra fields into this one.
The original `Struct` instance will not be changed.
**Examples**
TypeScript will also append a `name: TTypeScriptType` field into the result object and the init object.
1. Extending another `Struct`
```ts
const MyStructV1 =
new Struct()
.int32('field1');
const MyStructV2 =
new Struct()
.fields(MyStructV1)
.int32('field2');
const structV2 = await MyStructV2.deserialize(context);
structV2.field1; // number
structV2.field2; // number
// Same result, but serialize/deserialize order is reversed
```
2. Also possible in any order
```ts
const MyStructV1 =
new Struct()
.int32('field1');
const MyStructV2 =
new Struct()
.int32('field2')
.fields(MyStructV1);
const structV2 = await MyStructV2.deserialize(context);
structV2.field1; // number
structV2.field2; // number
// Fields are flatten
```
### `Struct#uint8`/`uint16`/`int32`/`uint32` methods
```ts
int32<
TName extends PropertyKey,
TTypeScriptType = number
>(
name: TName,
_typescriptType?: TTypeScriptType
): Struct<
Evaluate<TValue & Record<TName, TTypeScriptType>>,
Evaluate<TInit & Record<TName, TTypeScriptType>>,
TExtra,
TPostDeserialized
>;
```
(All method signatures are same)
Appends an `uint8`/`uint16`/`int32`/`uint32` field to the `Struct`
**Generic Parameters**
1. `TName`: Literal type of the field's name.
2. `TTypeScriptType = number`: Type of the field in the result object.
2. `TTypeScriptType = number`: Type of the field in the result object. For example you can declare it as a number literal type, or some enum type.
**DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
@ -178,7 +211,6 @@ TypeScript will infer them from arguments. See examples below.
**Parameters**
1. `name`: (Required) Field name. Should be a string literal to make types work.
1. `options`: currently unused.
2. `_typescriptType`: Set field's type. See examples below.
**Note**
@ -226,67 +258,62 @@ But obviously, it's a bad idea.
struct.create({ foo: MyEnum.a, bar: MyEnum.b }); // ok
```
3. Create a new struct by extending existing one
### `Struct#int64`/`uint64` methods
```ts
const struct1 = new Struct()
.int32('foo');
const struct2 = struct1
.int32('bar');
assert(struct2 !== struct1);
// `struct1` will not be changed
```
### `Struct#uint64`/`int64` method
```ts
public uint64<
TName extends string,
int64<
TName extends PropertyKey,
TTypeScriptType = bigint
>(
name: TName,
options: FieldDescriptorBaseOptions = {},
_typescriptType?: TTypeScriptType,
_typescriptType?: TTypeScriptType
): Struct<
TResult & Record<TName, TTypeScriptType>,
TInit & Record<TName, TTypeScriptType>,
Evaluate<TValue & Record<TName, TTypeScriptType>>,
Evaluate<TInit & Record<TName, TTypeScriptType>>,
TExtra,
TAfterParsed,
TPostDeserialized
>;
```
Return a new `Struct` instance with an `uint64` field appended to the end.
Appends an `int64`/`uint64` field to the `Struct`.
The original `Struct` instance will not be changed.
TypeScript will also append a `name: TTypeScriptType` field into the result object and the init object.
Require native `BigInt` support in runtime. See [compatibility](#compatibility).
Requires native `BigInt` support of runtime. See [compatibility](#compatibility).
### `extra` function
```ts
public extra<TValue extends object>(
value: TValue & ThisType<WithBackingField<Overwrite<Overwrite<TExtra, TValue>, TResult>>>
extra<
T extends Record<
Exclude<
keyof T,
Exclude<
keyof T,
keyof TValue
>
>,
never
>
>(
value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TValue>>
): Struct<
TResult,
TValue,
TInit,
Overwrite<TExtra, TValue>,
TAfterParsed
Overwrite<TExtra, T>,
TPostDeserialized
>;
```
Return a new `Struct` instance adding some extra fields.
Adds some extra fields into every Struct instance.
The original `Struct` instance will not be changed.
Extra fields will not affect serialize or deserialize process.
TypeScript will also append all extra fields into the result object (if not already exited).
Multiple calls to `extra` will merge all values together.
See examples below.
**Generic Parameters**
1. `TValue`: Type of the extra fields.
1. `T`: Type of the extra fields. The scary looking generic constraint is used to forbid overwriting any already existed fields.
**DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
@ -299,8 +326,6 @@ TypeScript will infer them from arguments. See examples below.
**Note**
1. If the current `Struct` already has some extra fields, it will be merged with `value`, with `value` taking precedence.
2. Extra fields will not be serialized.
3. Extra fields will be ignored if it has the same name with some defined fields.
**Examples**
@ -343,31 +368,30 @@ TypeScript will infer them from arguments. See examples below.
value.logBar();
```
### `afterParsed` method
### `postDeserialize` method
```ts
public afterParsed(
callback?: StructAfterParsed<TResult, void>
): Struct<TResult, TInit, TExtra, undefined>;
postDeserialize(callback: StructPostDeserialized<TValue, never>): Struct<TValue, TInit, TExtra, never>;
postDeserialize(callback?: StructPostDeserialized<TValue, void>): Struct<TValue, TInit, TExtra, undefined>;
```
Return a new `Struct` instance, registering (or replacing) a custom callback to be run after deserialized.
Registers (or replaces) a custom callback to be run after deserialized.
The original `Struct` instance will not be changed.
A callback returning `never` (always throw an error) will also change the return type of `deserialize` to `never`.
A callback returning `void` means it modify the result object in-place (or doesn't modify it at all), so `deserialize` will still return the result object.
```ts
public afterParsed<TAfterParsed>(
callback?: StructAfterParsed<TResult, TAfterParsed>
): Struct<TResult, TInit, TExtra, TAfterParsed>;
postDeserialize<TPostSerialize>(callback?: StructPostDeserialized<TValue, TPostSerialize>): Struct<TValue, TInit, TExtra, TPostSerialize>;
```
Return a new `Struct` instance, registering (or replacing) a custom callback to be run after deserialized, and replacing the result object with the returned value.
Registers (or replaces) a custom callback to be run after deserialized.
The original `Struct` instance will not be changed.
A callback returning anything other than `undefined` will `deserialize` to return that object instead.
**Generic Parameters**
1. `TAfterParsed`: Type of the new result object.
1. `TPostSerialize`: Type of the new result object.
**DO NOT PASS ANY GENERIC ARGUMENTS MANUALLY!**
@ -389,7 +413,7 @@ TypeScript will infer them from arguments. See examples below.
const struct = new Struct()
.int32('messageLength')
.string('message', { lengthField: 'messageLength' })
.afterParsed(value => {
.postDeserialize(value => {
throw new Error(value.message);
});
```
@ -400,28 +424,12 @@ TypeScript will infer them from arguments. See examples below.
// I think this one doesn't need any code example
```
3. Clear a previously set `afterParsed` callback
```ts
// Most used with extending structures
const struct1 = new Struct()
.int32('foo')
.afterParsed(value => {
// do something
});
const struct2 = struct1
.afterParsed() // don't inherit `struct1`'s `afterParsed`
.int32('bar');
```
4. Replace result object
3. Replace result object
```ts
const struct1 = new Struct()
.int32('foo')
.afterParsed(value => {
.postDeserialize(value => {
return {
bar: value.foo,
};
@ -434,14 +442,12 @@ TypeScript will infer them from arguments. See examples below.
### `deserialize` method
```ts
public async deserialize(
context: StructDeserializationContext
): Promise<TAfterParsed extends undefined ? Overwrite<TExtra, TResult> : TAfterParsed>;
deserialize(context: StructDeserializationContext): Promise<TPostDeserialized extends undefined ? Overwrite<TExtra, TValue> : TPostDeserialized>;
```
Deserialize one structure from the `context`.
Deserialize a Struct instance from `context`.
As you can see, if your `afterParsed` callback returns a value, that value will be returned by `deserialize`. Or the result object will be returned.
As you can see, if your `postDeserialize` callback returns something, that value will be returned by `deserialize`.
### `serialize` method
@ -449,214 +455,97 @@ As you can see, if your `afterParsed` callback returns a value, that value will
public serialize(init: TInit, context: StructSerializationContext): ArrayBuffer;
```
Serialize a value as the structure.
Serialize a Struct instance into an `ArrayBuffer`.
## Extend types
## Custom types
The library also supports adding custom types.
It also supports adding custom types.
There are two concepts around the type plugin system.
### Backing Field
The result object has a hidden backing field, containing implementation details of each field.
### `Struct#field` method
```ts
import { getBackingField, setBackingField } from '@yume-chan/struct';
const value = getBackingField<number>(resultObject, 'foo');
setBackingField(resultObject, 'foo', value);
field<
TName extends PropertyKey,
TDefinition extends FieldDefinition<any, any, any>
>(
name: TName,
definition: TDefinition
): Struct<
Evaluate<TValue & Record<TFieldName, TDefinition['valueType']>>,
Evaluate<Omit<TInit, TDefinition['removeFields']> & Record<TFieldName, TDefinition['valueType']>>,
TExtra,
TPostDeserialized
>;
```
It's possible to access other fields' data if you know the type. But it's not recommended to modify them.
Appends a `FieldDefinition` to the `Struct.
### `FieldDescriptorBase` interface
All above built-in methods are all alias to this method.
This interface describes one field, and will be stored in `Struct` class.
**Generic Parameters**
* `TName extends string = string`: Name of the field. Although `FieldDescriptorBase` doesn't need it to be generic, derived types will need it. So marking this way helps TypeScript infer the type.
* `TResultObject = {}`: Type that will be merged into the result object (`TResult`). Any key that has `never` type will be removed.
* `TInitObject = {}`: Type that will be merged into the init object (`TInit`). Any key that has `never` type will be removed. Normally you only need to add the current field into `TInit`, but sometimes one field will imply other fields, so you may want to also remove those implied fields from `TInit`.
* `TOptions extends FieldDescriptorBaseOptions = FieldDescriptorBaseOptions`: Type of the `options`. currently `FieldDescriptorBaseOptions` is empty but maybe something will happen later.
When declaring your own field descriptor, you need to extend `FieldDescriptorBase`, and correctly pass all generic arguments.
**Fields**
* `type: string`: The unique identifier of the type.
* `name: TName`: Field name in the result object.
* `options: TOptions`: You can store your options here.
* `resultObject?: TResultObject`: Make it possible for TypeScript to infer `TResultObject`. DO NOT TOUCH.
* `initObject?: TInitObject`: Make it possible for TypeScript to infer `TInitObject`. DO NOT TOUCH.
When declaring your own field descriptor, you can also add more fields to hold your data.
### `field` method
`Struct` class also has a `field` method to add a custom field descriptor.
Due to the limitation of TypeScript, you can't extend `Struct` class while keeping the fluent style API working.
So for type safety you should provide a function to generate your own field descriptor, then let the user call the `field` method.
### `FieldTypeDefinition` interface
This interface defines how to serialize and deserialize a type. You need to implement this interface for your type and register it.
**Generic Parameters**
* `TDescriptor extends FieldDescriptorBase = FieldDescriptorBase`: Type of the field descriptor. Just pass in your own field descriptor type.
**Fields**
* `type: string`: The unique identifier of the type. Make sure it's same as in `FieldDescriptor`.
#### `deserialize` method
### FieldDefinition
```ts
deserialize(options: {
context: StructDeserializationContext;
field: TDescriptor;
object: any;
options: StructOptions;
}): Promise<void>;
abstract class FieldDefinition<TOptions = void, TValueType = unknown, TRemoveFields = never> {
readonly options: TOptions;
constructor(options: TOptions);
}
```
Defines how to deserialize the field.
A `FieldDefinition` defines its type, size, etc.
You should `read` data from the `context` according to your `field` descriptor, and set appropriate values onto `object` ("appropriate" means "same as `TDescriptor`'s `TResultObject`").
It's an `abstract` class, means it lacks some method implementations, so it shouldn't be constructed.
If you also defined `initialize` method, the result data shape of `object` should be same as the result of `initialize`.
To create a custom type, one should create its own derived classes of `FieldDefinition` and `FieldRuntimeValue`.
#### `getSize` method
The custom `FieldDefinition` then can be passed to `Struct#field` method to append such a custom type field.
### `FieldDefinition#getSize` method
```ts
getSize(options: {
field: TDescriptor;
options: StructOptions;
}): number;
abstract getSize(): number;
```
Get the size (in bytes) of the field.
Returns the size (or minimal size if it's dynamic) of this field.
If the size is (partially or fully) dynamic, returns the minimal size.
Actual size should been returned from `FieldRuntimeValue#getSize`
It's just a hint for how much data should be ready before parsing, not that important.
#### `getDynamicSize` method
### `FieldDefinition#deserialize` method
```ts
getDynamicSize?(options: {
context: StructSerializationContext,
field: TDescriptor,
abstract deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
options: StructOptions,
}): number;
): ValueOrPromise<FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>>;
```
Similar to `getSize`, but also has access to `object` and `context` so the actual size can be calculated.
Defines how to deserialize a value from `context`. Can also return a `Promise`.
This method will be called just before `serialize`, so you can also prepare your field to be serialized in it.
Usually implementations should be:
You can also modify `object` to store your lazily evaluated values so next serialization can reuse them. But make sure you have also defined a setter in `initialize` method to invalidate the cache.
1. Some how parse the value from `context`
2. Pass the value into `FieldDefinition#createValue`
#### `initialize` method
Sometimes, some metadata is present when deserializing, but need to be calculated when serializing, for example a UTF-8 encoded string may have different length between itself (character count) and serialized form (byte length). So `deserialize` and save those metadata on the `FieldRuntimeValue` instance.
### `FieldDefinition#createValue` method
```ts
initialize?(options: {
context: StructSerializationContext;
field: TDescriptor;
init: any;
object: any;
options: StructOptions;
}): void;
abstract createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TValueType,
): FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>;
```
When creating or serializing an object, you can fine tune how to map fields from `init` object onto the result `object`.
Similar to `deserialize`, creates a `FieldRuntimeValue` for this instance.
You can modify the `object` as your wish, but a common practice is storing actual data on the backing field and define getter/setter on `object` to access them. Because fields may be overwritten by `extra` fields, where data on the backing field is still useful.
The difference is `createValue` will be called when a init value was provided to create a Struct instance.
```ts
initialize({ field, init, object }) {
object[BackingField][field.name] = {
value: init[field.name],
...extraData,
};
### FieldRuntimeValue
Object.defineProperty(object, field.name, {
configurable: true,
enumerable: true,
get() { return object[BackingField][field.name].value; }
set(value) {
object[BackingField][field.name].value = value;
// set some other data
}
});
}
```
One `FieldDefinition` instance represents one field declaration, and one `FieldRuntimeValue` instance represents one value.
If omitted, value from `init` will be set into the backing field and a pair of simple getter/setter will be defined on `object`.
Some possible usages:
1. Do some calculations and then set it onto `object`.
2. Define getter/setter onto `object` to intercept read/write.
3. Maybe one field implies others, so you can define multiple fields onto `object` for a single `field`.
#### `serialize` method
```ts
serialize(options: {
context: StructSerializationContext;
dataView: DataView;
field: TDescriptor;
object: any;
offset: number;
options: StructOptions;
}): void;
```
Defines how to serialize the field.
You should serialize your `field`'s value on `object`, and write it to `dataView` at `offset`.
You must not write more data than `getSize`/`getDynamicSize` returned. Or an Error will be thrown.
### Array type
Instead of true "Array", the current array types (`arraybuffer` and `string`) are more like buffers.
### `registerFieldTypeDefinition` method
This library exports the `registerFieldTypeDefinition` method to register your custom type definitions.
Pass the `undefined as unknown as YourTypeDescriptor` as the first argument to make TypeScript infers the type.
### Data flow
Here are lists of methods calling order to help you understand how this library works.
| Method | Description |
| ----------------------------- | -------------------------- |
| `Struct#field` | Add a field descriptor |
| `FieldTypeDefinition#getSize` | Add up struct's total size |
| Method | Description |
| --------------------------------- | ------------------------------------ |
| `Struct#deserialize` | Start deserializing from a `context` |
| `FieldTypeDefinition#deserialize` | Deserialize each field |
| Method | Description |
| -------------------------------- | ---------------------------------------------------- |
| `Struct#create` | Validate and create a value of the current structure |
| `FieldTypeDefinition#initialize` | Initialize each field |
| Method | Description |
| ------------------------------------ | ---------------------------------------------------- |
| `Struct#serialize` | Serialize a value into a buffer |
| `Struct#create` | Validate and create a value of the current structure |
| `FieldTypeDefinition#initialize` | Initialize each field |
| `FieldTypeDefinition#getDynamicSize` | Get actual sizes of each field |
| `FieldTypeDefinition#serialize` | Write each field into the allocated buffer |
It defines how to get, set, and serialize a value.

View file

@ -28,7 +28,7 @@ export interface StructDeserializationContext extends StructSerializationContext
export interface StructOptions {
/**
* Whether multi-byte fields in this struct are in little endian
* Whether all multi-byte fields in this struct are little-endian encoded.
*
* Default to `false`
*/

View file

@ -1,6 +1,8 @@
import type { StructOptions, StructSerializationContext } from './context';
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
import type { FieldRuntimeValue } from './runtime-value';
type ValueOrPromise<T> = T | Promise<T>;
/**
* A field definition is a bridge between its type and its runtime value.
*
@ -14,44 +16,54 @@ import type { FieldRuntimeValue } from './runtime-value';
*
* @template TOptions TypeScript type of this definition's `options`.
* @template TValueType TypeScript type of this field.
* @template TRemoveFields Optional remove keys from current `Struct`. Should be a union of string literal types.
* @template TRemoveInitFields Optional remove some field from the `TInit` type. Should be a union of string literal types.
*/
export abstract class FieldDefinition<
TOptions = void,
TValueType = unknown,
TRemoveFields = never,
TRemoveInitFields = never,
> {
public readonly options: TOptions;
/**
* When `T` is a type initiated `FieldDefinition`,
* use `T['valueType']` to retrieve its `TValueType` type parameter
* use `T['valueType']` to retrieve its `TValueType` type parameter.
*/
public readonly valueType!: TValueType;
/**
* When `T` is a type initiated `FieldDefinition`,
* use `T['removeFields']` to retrieve its `TRemoveFields` type parameter .
* use `T['removeInitFields']` to retrieve its `TRemoveInitFields` type parameter.
*/
public readonly removeFields!: TRemoveFields;
public readonly removeInitFields!: TRemoveInitFields;
public constructor(options: TOptions) {
this.options = options;
}
/**
* When implemented in derived classes, returns the static size (or smallest size) of this field.
* When implemented in derived classes, returns the size (or minimal size if it's dynamic) of this field.
*
* Actual size can be retrieved from `FieldRuntimeValue#getSize`
*/
public abstract getSize(): number;
/**
* When implemented in derived classes, creates a `FieldRuntimeValue` for the current field definition.
* When implemented in derived classes, creates a `FieldRuntimeValue` by parsing the `context`.
*/
public abstract deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): ValueOrPromise<FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>>;
/**
* When implemented in derived classes, creates a `FieldRuntimeValue` from a given `value`.
*/
public abstract createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any
): FieldRuntimeValue;
object: any,
value: TValueType,
): FieldRuntimeValue<FieldDefinition<TOptions, TValueType, TRemoveInitFields>>;
}

View file

@ -1,4 +1,4 @@
import type { StructDeserializationContext, StructOptions, StructSerializationContext } from './context';
import type { StructOptions, StructSerializationContext } from './context';
import type { FieldDefinition } from './definition';
/**
@ -22,23 +22,22 @@ export abstract class FieldRuntimeValue<
/** Gets the associated `Struct` instance */
public readonly object: any;
protected value: TDefinition['valueType'];
public constructor(
definition: TDefinition,
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TDefinition['valueType'],
) {
this.definition = definition;
this.options = options;
this.context = context;
this.object = object;
this.value = value;
}
/** When implemented in derived classes, deserialize this field from the specified `context` */
public abstract deserialize(
context: StructDeserializationContext
): void | Promise<void>;
/**
* Gets the actual size of this field. By default, the return value of its `definition.getSize()`
*
@ -51,12 +50,16 @@ export abstract class FieldRuntimeValue<
/**
* When implemented in derived classes, returns the current value of this field
*/
public abstract get(): unknown;
public get(): TDefinition['valueType'] {
return this.value;
}
/**
* When implemented in derived classes, update the current value of this field
*/
public abstract set(value: unknown): void;
public set(value: TDefinition['valueType']): void {
this.value = value;
}
/**
* When implemented in derived classes, serializes this field into `dataView` at `offset`

View file

@ -31,7 +31,7 @@ type AddFieldDescriptor<
Evaluate<TValue & Record<TFieldName, TDefinition['valueType']>>,
// There is no `Evaluate` here, because otherwise the type of a `Struct` with many fields
// can become too complex for TypeScript to compute
Evaluate<Omit<TInit, TDefinition['removeFields']> & Record<TFieldName, TDefinition['valueType']>>,
Evaluate<Omit<TInit, TDefinition['removeInitFields']> & Record<TFieldName, TDefinition['valueType']>>,
TExtra,
TPostDeserialized
>>;
@ -171,7 +171,7 @@ export default class Struct<
private _size = 0;
/**
* Get the static size (exclude fields that can change size at runtime)
* Gets the static size (exclude fields that can change size at runtime)
*/
public get size() { return this._size; }
@ -185,6 +185,9 @@ export default class Struct<
this.options = { ...StructDefaultOptions, ...options };
}
/**
* Appends a `FieldDefinition` to the `Struct
*/
public field<
TName extends PropertyKey,
TDefinition extends FieldDefinition<any, any, any>
@ -208,6 +211,9 @@ export default class Struct<
return this as any;
}
/**
* Merges (flats) another `Struct`'s fields and extra fields into this one.
*/
public fields<TOther extends Struct<any, any, any, any>>(
struct: TOther
): Struct<
@ -239,6 +245,9 @@ export default class Struct<
);
}
/**
* Appends an `uint8` field to the `Struct`
*/
public uint8<
TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Uint8']['valueType']
@ -253,6 +262,9 @@ export default class Struct<
);
}
/**
* Appends an `uint16` field to the `Struct`
*/
public uint16<
TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Uint16']['valueType']
@ -267,6 +279,9 @@ export default class Struct<
);
}
/**
* Appends an `int32` field to the `Struct`
*/
public int32<
TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Int32']['valueType']
@ -281,6 +296,9 @@ export default class Struct<
);
}
/**
* Appends an `uint32` field to the `Struct`
*/
public uint32<
TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Uint32']['valueType']
@ -295,6 +313,11 @@ export default class Struct<
);
}
/**
* Appends an `int64` field to the `Struct`
*
* Requires native `BigInt` support
*/
public int64<
TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Int64']['valueType']
@ -309,6 +332,11 @@ export default class Struct<
);
}
/**
* Appends an `uint64` field to the `Struct`
*
* Requires native `BigInt` support
*/
public uint64<
TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Uint64']['valueType']
@ -380,6 +408,13 @@ export default class Struct<
return this.arrayBufferLike(name, StringFieldType.instance, options);
};
/**
* Adds some extra fields into every Struct instance.
*
* Extra fields will not affect serialize or deserialize process.
*
* Multiple calls to `extra` will merge all values together.
*/
public extra<T extends Record<
// This trick disallows any keys that are already in `TValue`
Exclude<
@ -400,14 +435,29 @@ export default class Struct<
}
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A callback returning `never` (always throw an error)
* will also change the return type of `deserialize` to `never`.
*/
public postDeserialize(
callback: StructPostDeserialized<TValue, never>
): Struct<TValue, TInit, TExtra, never>;
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A callback returning `void` means it modify the result object in-place
* (or doesn't modify it at all), so `deserialize` will still return the result object.
*/
public postDeserialize(
callback?: StructPostDeserialized<TValue, void>
): Struct<TValue, TInit, TExtra, undefined>;
/**
* Registers (or replaces) a custom callback to be run after deserialized.
*
* A callback returning anything other than `undefined`
* will `deserialize` to return that object instead.
*/
public postDeserialize<TPostSerialize>(
callback?: StructPostDeserialized<TValue, TPostSerialize>
): Struct<TValue, TInit, TExtra, TPostSerialize>;
@ -418,24 +468,18 @@ export default class Struct<
return this as any;
}
private initializeObject(context: StructSerializationContext) {
private initializeObject() {
const object = createRuntimeObject();
Object.defineProperties(object, this._extra);
for (const [name, definition] of this._fields) {
const runtimeValue = definition.createValue(this.options, context, object);
setRuntimeValue(object, name, runtimeValue);
}
return object;
}
public create(init: TInit, context: StructSerializationContext): Overwrite<TExtra, TValue> {
const object = this.initializeObject(context);
const object = this.initializeObject();
for (const [name] of this._fields) {
const runtimeValue = getRuntimeValue(object, name);
runtimeValue.set((init as any)[name]);
for (const [name, definition] of this._fields) {
const runtimeValue = definition.createValue(this.options, context, object, (init as any)[name]);
setRuntimeValue(object, name, runtimeValue);
}
return object as any;
@ -444,11 +488,11 @@ export default class Struct<
public async deserialize(
context: StructDeserializationContext
): Promise<TPostDeserialized extends undefined ? Overwrite<TExtra, TValue> : TPostDeserialized> {
const object = this.initializeObject(context);
const object = this.initializeObject();
for (const [name] of this._fields) {
const runtimeValue = getRuntimeValue(object, name);
await runtimeValue.deserialize(context);
for (const [name, definition] of this._fields) {
const runtimeValue = await definition.deserialize(this.options, context, object);
setRuntimeValue(object, name, runtimeValue);
}
if (this._postDeserialized) {

View file

@ -1,4 +1,4 @@
import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructSerializationContext } from '../basic';
import { FieldDefinition, FieldRuntimeValue, StructDeserializationContext, StructOptions, StructSerializationContext } from '../basic';
/**
* Base class for all types that
@ -97,14 +97,16 @@ export class StringFieldType<TTypeScriptType = string>
}
}
const EmptyArrayBuffer = new ArrayBuffer(0);
export abstract class ArrayBufferLikeFieldDefinition<
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
TOptions = void,
TRemoveFields = never,
TRemoveInitFields = never,
> extends FieldDefinition<
TOptions,
TType['valueType'],
TRemoveFields
TRemoveInitFields
>{
public readonly type: TType;
@ -112,48 +114,55 @@ export abstract class ArrayBufferLikeFieldDefinition<
super(options);
this.type = type;
}
}
const EmptyArrayBuffer = new ArrayBuffer(0);
export abstract class ArrayBufferLikeFieldRuntimeValue<
TDefinition extends ArrayBufferLikeFieldDefinition<any, any, any>,
> extends FieldRuntimeValue<TDefinition> {
protected arrayBuffer: ArrayBuffer | undefined;
protected typedValue: unknown;
protected getDeserializeSize(): number {
protected getDeserializeSize(object: any): number {
return this.getSize();
}
public async deserialize(context: StructDeserializationContext): Promise<void> {
const size = this.getDeserializeSize();
this.arrayBuffer = undefined;
this.typedValue = undefined;
public async deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): Promise<ArrayBufferLikeFieldRuntimeValue<ArrayBufferLikeFieldDefinition<TType, TOptions, TRemoveInitFields>>> {
const size = this.getDeserializeSize(object);
let arrayBuffer: ArrayBuffer;
if (size === 0) {
this.arrayBuffer = EmptyArrayBuffer;
arrayBuffer = EmptyArrayBuffer;
} else {
this.arrayBuffer = await context.read(size);
arrayBuffer = await context.read(size);
}
this.typedValue = this.definition.type.fromArrayBuffer(this.arrayBuffer, context);
const value = this.type.fromArrayBuffer(arrayBuffer, context);
const runtimeValue = this.createValue(options, context, object, value);
runtimeValue.arrayBuffer = arrayBuffer;
return runtimeValue;
}
public get(): unknown {
return this.typedValue;
/**
* When implemented in derived classes, creates a `FieldRuntimeValue` for the current field definition.
*/
public abstract createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
value: TType['valueType'],
): ArrayBufferLikeFieldRuntimeValue<ArrayBufferLikeFieldDefinition<TType, TOptions, TRemoveInitFields>>;
}
public set(value: unknown): void {
this.typedValue = value;
export class ArrayBufferLikeFieldRuntimeValue<
TDefinition extends ArrayBufferLikeFieldDefinition<any, any, any>,
> extends FieldRuntimeValue<TDefinition> {
public arrayBuffer: ArrayBuffer | undefined;
public set(value: TDefinition['valueType']): void {
super.set(value);
this.arrayBuffer = undefined;
}
public serialize(dataView: DataView, offset: number, context: StructSerializationContext): void {
if (!this.arrayBuffer) {
this.arrayBuffer = this.definition.type.toArrayBuffer(this.typedValue, context);
this.arrayBuffer = this.definition.type.toArrayBuffer(this.value, context);
}
new Uint8Array(dataView.buffer)

View file

@ -19,15 +19,9 @@ export class FixedLengthArrayBufferLikeFieldDefinition<
public createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any
): FixedLengthArrayBufferFieldRuntimeValue {
return new FixedLengthArrayBufferFieldRuntimeValue(this, options, context, object);
object: any,
value: TType['valueType']
): ArrayBufferLikeFieldRuntimeValue<FixedLengthArrayBufferLikeFieldDefinition<TType, TOptions>> {
return new ArrayBufferLikeFieldRuntimeValue(this, options, context, object, value);
}
};
class FixedLengthArrayBufferFieldRuntimeValue
extends ArrayBufferLikeFieldRuntimeValue<FixedLengthArrayBufferLikeFieldDefinition>{
public static getSize(descriptor: FixedLengthArrayBufferLikeFieldDefinition) {
return descriptor.options.length;
}
}

View file

@ -57,12 +57,27 @@ export class NumberFieldDefinition<
return this.type.size;
}
public async deserialize(
options: Readonly<StructOptions>,
context: StructDeserializationContext,
object: any,
): Promise<NumberFieldRuntimeValue<TType, TTypeScriptType>> {
const buffer = await context.read(this.getSize());
const view = new DataView(buffer);
const value = view[this.type.dataViewGetter](
0,
options.littleEndian
) as any;
return this.createValue(options, context, object, value);
}
public createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any
object: any,
value: TTypeScriptType,
): NumberFieldRuntimeValue<TType, TTypeScriptType> {
return new NumberFieldRuntimeValue(this, options, context, object);
return new NumberFieldRuntimeValue(this, options, context, object, value);
}
}
@ -70,25 +85,6 @@ export class NumberFieldRuntimeValue<
TType extends NumberFieldType = NumberFieldType,
TTypeScriptType = TType['valueType'],
> extends FieldRuntimeValue<NumberFieldDefinition<TType, TTypeScriptType>> {
protected value: number | bigint | undefined;
public async deserialize(context: StructDeserializationContext): Promise<void> {
const buffer = await context.read(this.getSize());
const view = new DataView(buffer);
this.value = view[this.definition.type.dataViewGetter](
0,
this.options.littleEndian
);
}
public get(): unknown {
return this.value;
}
public set(value: unknown): void {
this.value = value as number | bigint;
}
public serialize(dataView: DataView, offset: number): void {
// `setBigInt64` requires a `bigint` while others require `number`
// So `dataView[DataViewSetters]` requires `bigint & number`

View file

@ -1,6 +1,5 @@
import { getRuntimeValue, setRuntimeValue, StructOptions, StructSerializationContext } from '../basic';
import { FieldRuntimeValue, getRuntimeValue, setRuntimeValue, StructOptions, StructSerializationContext } from '../basic';
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldRuntimeValue, ArrayBufferLikeFieldType } from './array-buffer';
import { NumberFieldDefinition, NumberFieldRuntimeValue } from './number';
import { KeysOfType } from './utils';
export interface VariableLengthArrayBufferLikeFieldOptions<
@ -22,47 +21,59 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
return 0;
}
protected getDeserializeSize(object: any) {
let value = object[this.options.lengthField] as number | string;
if (typeof value === 'string') {
value = Number.parseInt(value, 10);
}
return value;
}
public createValue(
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any
): VariableLengthArrayBufferLikeFieldRuntimeValue {
return new VariableLengthArrayBufferLikeFieldRuntimeValue(this, options, context, object);
object: any,
value: TType['valueType'],
): VariableLengthArrayBufferLikeFieldRuntimeValue<TType, TOptions> {
return new VariableLengthArrayBufferLikeFieldRuntimeValue(this, options, context, object, value);
}
}
class VariableLengthArrayBufferLikeLengthFieldRuntimeValue extends NumberFieldRuntimeValue {
class VariableLengthArrayBufferLikeLengthFieldRuntimeValue extends FieldRuntimeValue {
protected originalValue: FieldRuntimeValue;
protected arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue;
public constructor(
definition: NumberFieldDefinition,
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any,
originalValue: FieldRuntimeValue,
arrayBufferValue: VariableLengthArrayBufferLikeFieldRuntimeValue,
) {
super(definition, options, context, object);
super(originalValue.definition, originalValue.options, originalValue.context, originalValue.object, 0);
this.originalValue = originalValue;
this.arrayBufferValue = arrayBufferValue;
}
getDeserializeSize() {
return this.value;
public getSize() {
return this.originalValue.getSize();
}
get() {
// TODO: originalValue might be a `string` type, now it always returns `number`.
return this.arrayBufferValue.getSize();
}
set() { }
serialize(dataView: DataView, offset: number) {
this.value = this.get();
super.serialize(dataView, offset);
serialize(dataView: DataView, offset: number, context: StructSerializationContext) {
this.originalValue.set(this.get());
this.originalValue.serialize(dataView, offset, context);
}
}
class VariableLengthArrayBufferLikeFieldRuntimeValue
extends ArrayBufferLikeFieldRuntimeValue<VariableLengthArrayBufferLikeFieldDefinition> {
class VariableLengthArrayBufferLikeFieldRuntimeValue<
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
TOptions extends VariableLengthArrayBufferLikeFieldOptions = VariableLengthArrayBufferLikeFieldOptions
> extends ArrayBufferLikeFieldRuntimeValue<VariableLengthArrayBufferLikeFieldDefinition<TType, TOptions>> {
public static getSize() {
return 0;
}
@ -72,38 +83,29 @@ class VariableLengthArrayBufferLikeFieldRuntimeValue
protected lengthFieldValue: VariableLengthArrayBufferLikeLengthFieldRuntimeValue;
public constructor(
descriptor: VariableLengthArrayBufferLikeFieldDefinition,
definition: VariableLengthArrayBufferLikeFieldDefinition<TType, TOptions>,
options: Readonly<StructOptions>,
context: StructSerializationContext,
object: any
object: any,
value: TType['valueType'],
) {
super(descriptor, options, context, object);
super(definition, options, context, object, value);
// Patch the associated length field.
const lengthField = this.definition.options.lengthField;
const oldValue = getRuntimeValue(object, lengthField) as NumberFieldRuntimeValue;
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthFieldRuntimeValue(
oldValue.definition,
this.options,
this.context,
object,
this
);
const originalValue = getRuntimeValue(object, lengthField);
this.lengthFieldValue = new VariableLengthArrayBufferLikeLengthFieldRuntimeValue(originalValue, this);
setRuntimeValue(object, lengthField, this.lengthFieldValue);
}
protected getDeserializeSize() {
const value = this.lengthFieldValue.getDeserializeSize() as number;
return value;
}
public getSize() {
if (this.length === undefined) {
if (this.arrayBuffer !== undefined) {
this.length = this.arrayBuffer.byteLength;
} else {
this.length = this.definition.type.getSize(this.typedValue);
this.length = this.definition.type.getSize(this.value);
if (this.length === -1) {
this.arrayBuffer = this.definition.type.toArrayBuffer(this.typedValue, this.context);
this.arrayBuffer = this.definition.type.toArrayBuffer(this.value, this.context);
this.length = this.arrayBuffer.byteLength;
}
}

View file

@ -263,6 +263,30 @@
}
}
},
"ansi-escapes": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
"integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
"dev": true,
"requires": {
"type-fest": "^0.11.0"
}
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@ -294,6 +318,22 @@
"concat-map": "0.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"ci-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
"dev": true
},
"clean-webpack-plugin": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz",
@ -304,12 +344,33 @@
"del": "^4.1.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"consola": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
"integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ==",
"dev": true
},
"del": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
@ -325,6 +386,27 @@
"rimraf": "^2.6.3"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -366,6 +448,12 @@
}
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -382,6 +470,12 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"is-path-cwd": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
@ -469,6 +563,12 @@
"pinkie": "^2.0.0"
}
},
"pretty-time": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz",
"integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==",
"dev": true
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@ -484,12 +584,89 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"std-env": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-2.2.1.tgz",
"integrity": "sha512-IjYQUinA3lg5re/YMlwlfhqNRTzMZMqE+pezevdcTaHceqx8ngEi1alX9nNCk9Sc81fy1fLDeQoaCzeiW1yBOQ==",
"dev": true,
"requires": {
"ci-info": "^1.6.0"
}
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"type-fest": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
"integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
"dev": true
},
"typescript": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
"integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==",
"dev": true
},
"webpackbar": {
"version": "5.0.0-3",
"resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.0-3.tgz",
"integrity": "sha512-viW6KCYjMb0NPoDrw2jAmLXU2dEOhRrtku28KmOfeE1vxbfwCYuTbTaMhnkrCZLFAFyY9Q49Z/jzYO80Dw5b8g==",
"dev": true,
"requires": {
"ansi-escapes": "^4.3.1",
"chalk": "^4.1.0",
"consola": "^2.15.0",
"figures": "^3.2.0",
"pretty-time": "^1.1.0",
"std-env": "^2.2.1",
"text-table": "^0.2.0",
"wrap-ansi": "^7.0.0"
}
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View file

@ -1,7 +1,7 @@
{
"name": "webpack-config",
"version": "0.0.1",
"private": true,
"version": "0.0.1",
"description": "Webpack config for `demo` project in TypeScript",
"author": "Simon Chan <cnsimonchan@live.com>",
"homepage": "https://github.com/yume-chan/ya-webadb#readme",
@ -25,6 +25,7 @@
"@types/webpack-bundle-analyzer": "3.9.0",
"@types/webpack-dev-server": "3.11.1",
"clean-webpack-plugin": "3.0.0",
"typescript": "4.1.3"
"typescript": "4.1.3",
"webpackbar": "5.0.0-3"
}
}

View file

@ -5,11 +5,12 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import path from 'path';
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import WebpackBar from 'webpackbar';
const context = path.resolve(process.cwd());
const plugins: webpack.Plugin[] = [
new CleanWebpackPlugin(),
new WebpackBar({}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
@ -35,7 +36,12 @@ if (process.env.ANALYZE) {
const config: webpack.ConfigurationFactory = (
env: unknown,
argv: webpack.CliConfigOptions
): webpack.Configuration => ({
): webpack.Configuration => {
if (argv.mode !== 'production') {
plugins.unshift(new CleanWebpackPlugin());
}
return {
mode: 'development',
devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map',
context,
@ -65,6 +71,7 @@ const config: webpack.ConfigurationFactory = (
contentBase: path.resolve(context, 'lib'),
port: 9000,
},
});
};
};
export = config;

View file

@ -6,12 +6,12 @@
"ESNext"
],
"declaration": false, // /* Generates corresponding '.d.ts' file. */
"declarationDir": null,
"declarationMap": false, // /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": false, // /* Generates corresponding '.map' file. */
"outDir": "../demo", // /* Redirect output structure to the directory. */
"rootDir": "./src", // /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"composite": false, // /* Enable project compilation */
"tsBuildInfoFile": "./tsconfig.tsbuildinfo", /* Specify file to store incremental compilation information */
},
"include": [
"src"

View file

@ -10,7 +10,6 @@
"rootDir": "src",
"outDir": "esm",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"declarationDir": "dts",
"declarationMap": true,