From 9f3d4805230bb5e47ce4785329508b96df1ea4ab Mon Sep 17 00:00:00 2001 From: Simon Chan Date: Thu, 7 Jan 2021 14:16:30 +0800 Subject: [PATCH] refactor(struct): tweak type plugin system --- .vscode/settings.json | 3 + packages/adb-backend-web/package.json | 1 + packages/adb/package.json | 3 +- packages/demo/package-lock.json | 173 ++++++ packages/demo/package.json | 5 +- packages/demo/src/routes/scrcpy/index.tsx | 24 +- packages/demo/webpack.config.js | 69 ++- packages/event/package.json | 1 + packages/struct/README.md | 535 +++++++----------- packages/struct/src/basic/context.ts | 2 +- packages/struct/src/basic/definition.ts | 32 +- packages/struct/src/basic/runtime-value.ts | 19 +- packages/struct/src/struct.ts | 78 ++- packages/struct/src/types/array-buffer.ts | 65 ++- .../src/types/fixed-length-array-buffer.ts | 14 +- packages/struct/src/types/number.ts | 38 +- .../src/types/variable-length-array-buffer.ts | 74 +-- packages/webpack-config/package-lock.json | 177 ++++++ packages/webpack-config/package.json | 5 +- packages/webpack-config/src/webpack.config.ts | 71 +-- packages/webpack-config/tsconfig.json | 2 +- tsconfig.base.json | 1 - 22 files changed, 862 insertions(+), 530 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 13fa7273..f04179fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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", diff --git a/packages/adb-backend-web/package.json b/packages/adb-backend-web/package.json index d5a2e88a..5c1c7a69 100644 --- a/packages/adb-backend-web/package.json +++ b/packages/adb-backend-web/package.json @@ -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": [ diff --git a/packages/adb/package.json b/packages/adb/package.json index f41ee5fc..52d69eee 100644 --- a/packages/adb/package.json +++ b/packages/adb/package.json @@ -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": { diff --git a/packages/demo/package-lock.json b/packages/demo/package-lock.json index 866ba098..6265f2ef 100644 --- a/packages/demo/package-lock.json +++ b/packages/demo/package-lock.json @@ -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", diff --git a/packages/demo/package.json b/packages/demo/package.json index ea389a11..5325283b 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -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 ", "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", diff --git a/packages/demo/src/routes/scrcpy/index.tsx b/packages/demo/src/routes/scrcpy/index.tsx index 59b3651c..09501969 100644 --- a/packages/demo/src/routes/scrcpy/index.tsx +++ b/packages/demo/src/routes/scrcpy/index.tsx @@ -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 ) => { - 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} /> diff --git a/packages/demo/webpack.config.js b/packages/demo/webpack.config.js index 47ea548a..9cd2f637 100644 --- a/packages/demo/webpack.config.js +++ b/packages/demo/webpack.config.js @@ -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,34 +30,40 @@ var plugins = [ if (process.env.ANALYZE) { plugins.push(new webpack_bundle_analyzer_1.BundleAnalyzerPlugin()); } -var config = function (env, argv) { return ({ - mode: 'development', - devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map', - context: context, - target: 'web', - entry: { - index: './src/index.tsx', - }, - output: { - path: path_1.default.resolve(context, 'lib'), - filename: '[name].[contenthash].js', - }, - resolve: { - extensions: ['.ts', '.tsx', '.js'], - fallback: { "path": require.resolve("path-browserify") }, - }, - plugins: plugins, - module: { - rules: [ - { test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], }, - { test: /\.css$/i, use: [mini_css_extract_plugin_1.default.loader, 'css-loader'] }, - { test: /\.asset$/, use: { loader: "file-loader" } }, - { test: /\.tsx?$/i, loader: 'ts-loader', options: { projectReferences: true } }, - ], - }, - devServer: { - contentBase: path_1.default.resolve(context, 'lib'), - port: 9000, - }, -}); }; +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, + target: 'web', + entry: { + index: './src/index.tsx', + }, + output: { + path: path_1.default.resolve(context, 'lib'), + filename: '[name].[contenthash].js', + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'], + // @ts-expect-error typing is not up to date + fallback: { "path": require.resolve("path-browserify") }, + }, + plugins: plugins, + module: { + rules: [ + { test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], }, + { test: /\.css$/i, use: [mini_css_extract_plugin_1.default.loader, 'css-loader'] }, + { test: /\.asset$/, use: { loader: "file-loader" } }, + { test: /\.tsx?$/i, loader: 'ts-loader', options: { projectReferences: true } }, + ], + }, + devServer: { + contentBase: path_1.default.resolve(context, 'lib'), + port: 9000, + }, + }; +}; module.exports = config; diff --git a/packages/event/package.json b/packages/event/package.json index 8fb766e9..4777bd59 100644 --- a/packages/event/package.json +++ b/packages/event/package.json @@ -1,5 +1,6 @@ { "name": "@yume-chan/event", + "private": true, "version": "0.0.1", "description": "Event/EventEmitter", "keywords": [ diff --git a/packages/struct/README.md b/packages/struct/README.md index b5493a42..b3ab2135 100644 --- a/packages/struct/README.md +++ b/packages/struct/README.md @@ -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 { 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 = 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 >( - name: TName, - options: FieldDescriptorBaseOptions = {}, - _typescriptType?: TTypeScriptType, + struct: TOther ): Struct< - TResult & Record, - TInit & Record, - TExtra, - TAfterParsed, ->; - -public uint32< - TName extends string, - TTypeScriptType = number ->( - name: TName, - options: {} = {}, - _typescriptType?: TTypeScriptType, -): Struct< - TResult & Record, - TInit & Record, - 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>, + Evaluate>, + 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 - - ```ts - const struct1 = new Struct() - .int32('foo'); - - const struct2 = struct1 - .int32('bar'); - - assert(struct2 !== struct1); - // `struct1` will not be changed - ``` - -### `Struct#uint64`/`int64` method +### `Struct#int64`/`uint64` methods ```ts -public uint64< - TName extends string, +int64< + TName extends PropertyKey, TTypeScriptType = bigint >( name: TName, - options: FieldDescriptorBaseOptions = {}, - _typescriptType?: TTypeScriptType, + _typescriptType?: TTypeScriptType ): Struct< - TResult & Record, - TInit & Record, + Evaluate>, + Evaluate>, 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( - value: TValue & ThisType, TResult>>> +extra< + T extends Record< + Exclude< + keyof T, + Exclude< + keyof T, + keyof TValue + > + >, + never + > +>( + value: T & ThisType, TValue>> ): Struct< - TResult, + TValue, TInit, - Overwrite, - TAfterParsed + Overwrite, + 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 -): Struct; +postDeserialize(callback: StructPostDeserialized): Struct; +postDeserialize(callback?: StructPostDeserialized): Struct; ``` -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( - callback?: StructAfterParsed -): Struct; +postDeserialize(callback?: StructPostDeserialized): Struct; ``` -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>; +deserialize(context: StructDeserializationContext): Promise : 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(resultObject, 'foo'); -setBackingField(resultObject, 'foo', value); +field< + TName extends PropertyKey, + TDefinition extends FieldDefinition +>( + name: TName, + definition: TDefinition +): Struct< + Evaluate>, + Evaluate & Record>, + 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; -``` +abstract class FieldDefinition { + readonly options: TOptions; -Defines how to deserialize the field. - -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`"). - -If you also defined `initialize` method, the result data shape of `object` should be same as the result of `initialize`. - -#### `getSize` method - -```ts -getSize(options: { - field: TDescriptor; - options: StructOptions; -}): number; -``` - -Get the size (in bytes) of the field. - -If the size is (partially or fully) dynamic, returns the minimal size. - -It's just a hint for how much data should be ready before parsing, not that important. - -#### `getDynamicSize` method - -```ts -getDynamicSize?(options: { - context: StructSerializationContext, - field: TDescriptor, - object: any, - options: StructOptions, -}): number; -``` - -Similar to `getSize`, but also has access to `object` and `context` so the actual size can be calculated. - -This method will be called just before `serialize`, so you can also prepare your field to be serialized in it. - -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. - -#### `initialize` method - -```ts -initialize?(options: { - context: StructSerializationContext; - field: TDescriptor; - init: any; - object: any; - options: StructOptions; -}): void; -``` - -When creating or serializing an object, you can fine tune how to map fields from `init` object onto the result `object`. - -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. - -```ts -initialize({ field, init, object }) { - object[BackingField][field.name] = { - value: init[field.name], - ...extraData, - }; - - 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 - } - }); + constructor(options: TOptions); } ``` -If omitted, value from `init` will be set into the backing field and a pair of simple getter/setter will be defined on `object`. +A `FieldDefinition` defines its type, size, etc. -Some possible usages: +It's an `abstract` class, means it lacks some method implementations, so it shouldn't be constructed. -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`. +To create a custom type, one should create its own derived classes of `FieldDefinition` and `FieldRuntimeValue`. -#### `serialize` method +The custom `FieldDefinition` then can be passed to `Struct#field` method to append such a custom type field. + +### `FieldDefinition#getSize` method ```ts -serialize(options: { - context: StructSerializationContext; - dataView: DataView; - field: TDescriptor; - object: any; - offset: number; - options: StructOptions; -}): void; +abstract getSize(): number; ``` -Defines how to serialize the field. +Returns the size (or minimal size if it's dynamic) of this field. -You should serialize your `field`'s value on `object`, and write it to `dataView` at `offset`. +Actual size should been returned from `FieldRuntimeValue#getSize` -You must not write more data than `getSize`/`getDynamicSize` returned. Or an Error will be thrown. +### `FieldDefinition#deserialize` method -### Array type +```ts +abstract deserialize( + options: Readonly, + context: StructDeserializationContext, + object: any, +): ValueOrPromise>>; +``` -Instead of true "Array", the current array types (`arraybuffer` and `string`) are more like buffers. +Defines how to deserialize a value from `context`. Can also return a `Promise`. -### `registerFieldTypeDefinition` method +Usually implementations should be: -This library exports the `registerFieldTypeDefinition` method to register your custom type definitions. +1. Some how parse the value from `context` +2. Pass the value into `FieldDefinition#createValue` -Pass the `undefined as unknown as YourTypeDescriptor` as the first argument to make TypeScript infers the type. +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. -### Data flow +### `FieldDefinition#createValue` method -Here are lists of methods calling order to help you understand how this library works. +```ts +abstract createValue( + options: Readonly, + context: StructSerializationContext, + object: any, + value: TValueType, +): FieldRuntimeValue>; +``` -| Method | Description | -| ----------------------------- | -------------------------- | -| `Struct#field` | Add a field descriptor | -| `FieldTypeDefinition#getSize` | Add up struct's total size | +Similar to `deserialize`, creates a `FieldRuntimeValue` for this instance. -| Method | Description | -| --------------------------------- | ------------------------------------ | -| `Struct#deserialize` | Start deserializing from a `context` | -| `FieldTypeDefinition#deserialize` | Deserialize each field | +The difference is `createValue` will be called when a init value was provided to create a Struct instance. -| Method | Description | -| -------------------------------- | ---------------------------------------------------- | -| `Struct#create` | Validate and create a value of the current structure | -| `FieldTypeDefinition#initialize` | Initialize each field | +### FieldRuntimeValue -| 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 | +One `FieldDefinition` instance represents one field declaration, and one `FieldRuntimeValue` instance represents one value. + +It defines how to get, set, and serialize a value. diff --git a/packages/struct/src/basic/context.ts b/packages/struct/src/basic/context.ts index 074b966b..7efe09a9 100644 --- a/packages/struct/src/basic/context.ts +++ b/packages/struct/src/basic/context.ts @@ -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` */ diff --git a/packages/struct/src/basic/definition.ts b/packages/struct/src/basic/definition.ts index 678dea1b..df9d30de 100644 --- a/packages/struct/src/basic/definition.ts +++ b/packages/struct/src/basic/definition.ts @@ -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 | Promise; + /** * 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, + context: StructDeserializationContext, + object: any, + ): ValueOrPromise>>; + + /** + * When implemented in derived classes, creates a `FieldRuntimeValue` from a given `value`. */ public abstract createValue( options: Readonly, context: StructSerializationContext, - object: any - ): FieldRuntimeValue; + object: any, + value: TValueType, + ): FieldRuntimeValue>; } diff --git a/packages/struct/src/basic/runtime-value.ts b/packages/struct/src/basic/runtime-value.ts index c056f5d6..23e3fbe4 100644 --- a/packages/struct/src/basic/runtime-value.ts +++ b/packages/struct/src/basic/runtime-value.ts @@ -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, 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; - /** * 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` diff --git a/packages/struct/src/struct.ts b/packages/struct/src/struct.ts index d5e333d3..c93dd136 100644 --- a/packages/struct/src/struct.ts +++ b/packages/struct/src/struct.ts @@ -31,7 +31,7 @@ type AddFieldDescriptor< Evaluate>, // There is no `Evaluate` here, because otherwise the type of a `Struct` with many fields // can become too complex for TypeScript to compute - Evaluate & Record>, + Evaluate & Record>, 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 @@ -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>( 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 ): Struct; + /** + * 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 ): Struct; + /** + * 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( callback?: StructPostDeserialized ): Struct; @@ -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 { - 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> { - 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) { diff --git a/packages/struct/src/types/array-buffer.ts b/packages/struct/src/types/array-buffer.ts index 3aa99615..fb731251 100644 --- a/packages/struct/src/types/array-buffer.ts +++ b/packages/struct/src/types/array-buffer.ts @@ -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 } } +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, - > extends FieldRuntimeValue { - protected arrayBuffer: ArrayBuffer | undefined; - - protected typedValue: unknown; - - protected getDeserializeSize(): number { + protected getDeserializeSize(object: any): number { return this.getSize(); } - public async deserialize(context: StructDeserializationContext): Promise { - const size = this.getDeserializeSize(); - - this.arrayBuffer = undefined; - this.typedValue = undefined; + public async deserialize( + options: Readonly, + context: StructDeserializationContext, + object: any, + ): Promise>> { + 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, + context: StructSerializationContext, + object: any, + value: TType['valueType'], + ): ArrayBufferLikeFieldRuntimeValue>; +} - public set(value: unknown): void { - this.typedValue = value; +export class ArrayBufferLikeFieldRuntimeValue< + TDefinition extends ArrayBufferLikeFieldDefinition, + > extends FieldRuntimeValue { + 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) diff --git a/packages/struct/src/types/fixed-length-array-buffer.ts b/packages/struct/src/types/fixed-length-array-buffer.ts index 64f46235..0a8e6d48 100644 --- a/packages/struct/src/types/fixed-length-array-buffer.ts +++ b/packages/struct/src/types/fixed-length-array-buffer.ts @@ -19,15 +19,9 @@ export class FixedLengthArrayBufferLikeFieldDefinition< public createValue( options: Readonly, context: StructSerializationContext, - object: any - ): FixedLengthArrayBufferFieldRuntimeValue { - return new FixedLengthArrayBufferFieldRuntimeValue(this, options, context, object); + object: any, + value: TType['valueType'] + ): ArrayBufferLikeFieldRuntimeValue> { + return new ArrayBufferLikeFieldRuntimeValue(this, options, context, object, value); } }; - -class FixedLengthArrayBufferFieldRuntimeValue - extends ArrayBufferLikeFieldRuntimeValue{ - public static getSize(descriptor: FixedLengthArrayBufferLikeFieldDefinition) { - return descriptor.options.length; - } -} diff --git a/packages/struct/src/types/number.ts b/packages/struct/src/types/number.ts index 3b1a10cb..3cfd0d2e 100644 --- a/packages/struct/src/types/number.ts +++ b/packages/struct/src/types/number.ts @@ -57,12 +57,27 @@ export class NumberFieldDefinition< return this.type.size; } + public async deserialize( + options: Readonly, + context: StructDeserializationContext, + object: any, + ): Promise> { + 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, context: StructSerializationContext, - object: any + object: any, + value: TTypeScriptType, ): NumberFieldRuntimeValue { - 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> { - protected value: number | bigint | undefined; - - public async deserialize(context: StructDeserializationContext): Promise { - 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` diff --git a/packages/struct/src/types/variable-length-array-buffer.ts b/packages/struct/src/types/variable-length-array-buffer.ts index ca4d7823..112394aa 100644 --- a/packages/struct/src/types/variable-length-array-buffer.ts +++ b/packages/struct/src/types/variable-length-array-buffer.ts @@ -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, context: StructSerializationContext, - object: any - ): VariableLengthArrayBufferLikeFieldRuntimeValue { - return new VariableLengthArrayBufferLikeFieldRuntimeValue(this, options, context, object); + object: any, + value: TType['valueType'], + ): VariableLengthArrayBufferLikeFieldRuntimeValue { + 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, - 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 { +class VariableLengthArrayBufferLikeFieldRuntimeValue< + TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType, + TOptions extends VariableLengthArrayBufferLikeFieldOptions = VariableLengthArrayBufferLikeFieldOptions + > extends ArrayBufferLikeFieldRuntimeValue> { public static getSize() { return 0; } @@ -72,38 +83,29 @@ class VariableLengthArrayBufferLikeFieldRuntimeValue protected lengthFieldValue: VariableLengthArrayBufferLikeLengthFieldRuntimeValue; public constructor( - descriptor: VariableLengthArrayBufferLikeFieldDefinition, + definition: VariableLengthArrayBufferLikeFieldDefinition, options: Readonly, 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; } } diff --git a/packages/webpack-config/package-lock.json b/packages/webpack-config/package-lock.json index 800b9b8d..25e2eba3 100644 --- a/packages/webpack-config/package-lock.json +++ b/packages/webpack-config/package-lock.json @@ -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", diff --git a/packages/webpack-config/package.json b/packages/webpack-config/package.json index 3f8d821a..57de1158 100644 --- a/packages/webpack-config/package.json +++ b/packages/webpack-config/package.json @@ -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 ", "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" } } diff --git a/packages/webpack-config/src/webpack.config.ts b/packages/webpack-config/src/webpack.config.ts index 449511a0..94728c5a 100644 --- a/packages/webpack-config/src/webpack.config.ts +++ b/packages/webpack-config/src/webpack.config.ts @@ -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,36 +36,42 @@ if (process.env.ANALYZE) { const config: webpack.ConfigurationFactory = ( env: unknown, argv: webpack.CliConfigOptions -): webpack.Configuration => ({ - mode: 'development', - devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map', - context, - target: 'web', - entry: { - index: './src/index.tsx', - }, - output: { - path: path.resolve(context, 'lib'), - filename: '[name].[contenthash].js', - }, - resolve: { - extensions: ['.ts', '.tsx', '.js'], - // @ts-expect-error typing is not up to date - fallback: { "path": require.resolve("path-browserify") }, - }, - plugins, - module: { - rules: [ - { test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], }, - { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader'] }, - { test: /\.asset$/, use: { loader: "file-loader" } }, - { test: /\.tsx?$/i, loader: 'ts-loader', options: { projectReferences: true } }, - ], - }, - devServer: { - contentBase: path.resolve(context, 'lib'), - port: 9000, - }, -}); +): webpack.Configuration => { + if (argv.mode !== 'production') { + plugins.unshift(new CleanWebpackPlugin()); + } + + return { + mode: 'development', + devtool: argv.mode === 'production' ? 'source-map' : 'eval-source-map', + context, + target: 'web', + entry: { + index: './src/index.tsx', + }, + output: { + path: path.resolve(context, 'lib'), + filename: '[name].[contenthash].js', + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'], + // @ts-expect-error typing is not up to date + fallback: { "path": require.resolve("path-browserify") }, + }, + plugins, + module: { + rules: [ + { test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], }, + { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader'] }, + { test: /\.asset$/, use: { loader: "file-loader" } }, + { test: /\.tsx?$/i, loader: 'ts-loader', options: { projectReferences: true } }, + ], + }, + devServer: { + contentBase: path.resolve(context, 'lib'), + port: 9000, + }, + }; +}; export = config; diff --git a/packages/webpack-config/tsconfig.json b/packages/webpack-config/tsconfig.json index 623e6c47..2e2da644 100644 --- a/packages/webpack-config/tsconfig.json +++ b/packages/webpack-config/tsconfig.json @@ -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" diff --git a/tsconfig.base.json b/tsconfig.base.json index d83de332..c052f96e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -10,7 +10,6 @@ "rootDir": "src", "outDir": "esm", "sourceMap": true, - "removeComments": true, "declaration": true, "declarationDir": "dts", "declarationMap": true,