diff --git a/Cargo.lock b/Cargo.lock
index facc99b..d2051f0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,39 +1,48 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
-name = "adler"
-version = "1.0.2"
+name = "adler2"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.20"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "bumpalo"
-version = "3.12.0"
+version = "3.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
 
 [[package]]
 name = "byteorder"
-version = "1.4.3"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2"
+dependencies = [
+ "shlex",
+]
 
 [[package]]
 name = "cfg-if"
-version = "0.1.7"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 
 [[package]]
 name = "cfg-if"
@@ -53,27 +62,24 @@ dependencies = [
 
 [[package]]
 name = "crc32fast"
-version = "1.3.2"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
 dependencies = [
  "cfg-if 1.0.0",
 ]
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.15"
+version = "0.8.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
-dependencies = [
- "cfg-if 1.0.0",
-]
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
 
 [[package]]
 name = "flate2"
-version = "1.0.25"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
+checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
 dependencies = [
  "crc32fast",
  "miniz_oxide",
@@ -81,9 +87,9 @@ dependencies = [
 
 [[package]]
 name = "gloo-utils"
-version = "0.1.6"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5"
+checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e"
 dependencies = [
  "js-sys",
  "serde",
@@ -94,16 +100,17 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "0.4.3"
+version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
 
 [[package]]
 name = "js-sys"
-version = "0.3.61"
+version = "0.3.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
 dependencies = [
+ "once_cell",
  "wasm-bindgen",
 ]
 
@@ -125,24 +132,21 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.141"
+version = "0.2.169"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
 
 [[package]]
 name = "log"
-version = "0.4.6"
+version = "0.4.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
-dependencies = [
- "cfg-if 0.1.7",
-]
+checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
 
 [[package]]
 name = "memchr"
-version = "2.5.0"
+version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "memory_units"
@@ -150,44 +154,66 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
 
+[[package]]
+name = "minicov"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
+dependencies = [
+ "cc",
+ "walkdir",
+]
+
 [[package]]
 name = "miniz_oxide"
-version = "0.6.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
 dependencies = [
- "adler",
+ "adler2",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.17.1"
+version = "1.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.56"
+version = "1.0.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.26"
+version = "1.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "regex"
-version = "1.7.3"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -196,27 +222,36 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.29"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "rustversion"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
 
 [[package]]
 name = "ryu"
-version = "0.2.7"
+version = "1.0.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
+checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
 
 [[package]]
-name = "scoped-tls"
-version = "1.0.1"
+name = "same-file"
+version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
 
 [[package]]
 name = "serde"
-version = "1.0.160"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
 dependencies = [
  "serde_derive",
 ]
@@ -234,42 +269,38 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.160"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.15",
+ "syn",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.39"
+version = "1.0.138"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
+checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
 dependencies = [
  "itoa",
+ "memchr",
  "ryu",
  "serde",
 ]
 
 [[package]]
-name = "syn"
-version = "1.0.109"
+name = "shlex"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "syn"
-version = "2.0.15"
+version = "2.0.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -278,52 +309,64 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.8"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.84"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
 dependencies = [
  "cfg-if 1.0.0",
+ "once_cell",
+ "rustversion",
  "wasm-bindgen-macro",
 ]
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.84"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
 dependencies = [
  "bumpalo",
  "log",
- "once_cell",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.34"
+version = "0.4.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
 dependencies = [
  "cfg-if 1.0.0",
  "js-sys",
+ "once_cell",
  "wasm-bindgen",
  "web-sys",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.84"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -331,32 +374,34 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.84"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.84"
+version = "0.2.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
 
 [[package]]
 name = "wasm-bindgen-test"
-version = "0.3.34"
+version = "0.3.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b"
+checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3"
 dependencies = [
- "console_error_panic_hook",
  "js-sys",
- "scoped-tls",
+ "minicov",
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "wasm-bindgen-test-macro",
@@ -364,19 +409,20 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-test-macro"
-version = "0.3.34"
+version = "0.3.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9"
+checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
 dependencies = [
  "proc-macro2",
  "quote",
+ "syn",
 ]
 
 [[package]]
 name = "web-sys"
-version = "0.3.61"
+version = "0.3.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -388,7 +434,7 @@ version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
 dependencies = [
- "cfg-if 0.1.7",
+ "cfg-if 0.1.10",
  "libc",
  "memory_units",
  "winapi",
@@ -410,17 +456,99 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys",
+]
+
 [[package]]
 name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
 [[package]]
 name = "zip"
-version = "0.6.4"
+version = "0.6.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef"
+checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
 dependencies = [
  "byteorder",
  "crc32fast",
diff --git a/README.md b/README.md
index fb4d74b..fe69f2f 100644
--- a/README.md
+++ b/README.md
@@ -24,8 +24,8 @@ yarn add json2excel-wasm
 
 ```js
 // worker.js
-import {convert} "json2excel-wasm";
-const blob = convert(json_data_to_export);
+import { convert } "json2excel-wasm";
+const blob = await convert(json_data_to_export);
 ```
 
 you can use code like next to force download of the result blob
@@ -43,15 +43,14 @@ document.body.remove(a);
 
 CDN links are the following:
 
-- https://cdn.dhtmlx.com/libs/json2excel/1.2/worker.js 
-- https://cdn.dhtmlx.com/libs/json2excel/1.2/module.js 
-- https://cdn.dhtmlx.com/libs/json2excel/1.2/json2excel_wasm_bg.wasm
+- https://cdn.dhtmlx.com/libs/json2excel/1.3/worker.js 
+- https://cdn.dhtmlx.com/libs/json2excel/1.3/module.js 
 
 You can import and use lib dynamically like 
 
 ```js
-const convert = import("https://cdn.dhtmlx.com/libs/json2excel/1.2/module.js");
-const blob = convert(json_data_to_export);
+const convert = import("https://cdn.dhtmlx.com/libs/json2excel/1.3/module.js");
+const blob = await convert(json_data_to_export);
 ```
 
 or use it as web worker
@@ -75,7 +74,7 @@ if you want to load worker script from CDN and not from your domain it requires
 
 ```js
 var url = window.URL.createObjectURL(new Blob([
-    "importScripts('https://cdn.dhtmlx.com/libs/json2excel/1.2/worker.js');"
+    "importScripts('https://cdn.dhtmlx.com/libs/json2excel/1.3/worker.js');"
 ], { type: "text/javascript" }));
 
 var worker = new Promise((res) => {
diff --git a/example/cdn.html b/example/cdn.html
new file mode 100644
index 0000000..a1df432
--- /dev/null
+++ b/example/cdn.html
@@ -0,0 +1,43 @@
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>JSON2Excel - api</title>
+    <style>
+        .spreadsheet-data {
+            width: 80%;
+            height: 400px;
+        }
+        button {
+            font-size: 14px; padding: 8px; margin:4px;
+        }
+    </style>
+</head>
+<body>
+    <div><textarea class="spreadsheet-data"></textarea></div>
+    <button id="convert-button">download</button>
+
+    <script src="./datasets.js"></script>
+    <script type="module">
+    import { convert } from "https://cdn.dhtmlx.com/libs/json2excel/1.3/module.js";
+    let $ = document.querySelector.bind(document);
+
+    const dataArea = $(".spreadsheet-data");
+    dataArea.value = JSON.stringify(example, undefined, 2);
+    
+    $("#convert-button").addEventListener("click", async function(e){
+        const blob = await convert(dataArea.value);
+        const a = document.createElement("a");
+        a.innerHTML = "test";
+        a.href = URL.createObjectURL(blob);
+        a.setAttribute("target", "_blank");
+        a.download = "data.xlsx";
+        document.body.append(a);
+        a.click();
+        document.body.remove(a);
+    });
+    </script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/example/datasets.js b/example/datasets.js
index a2ec5d6..757188a 100644
--- a/example/datasets.js
+++ b/example/datasets.js
@@ -1,368 +1,850 @@
 var example = {
-  "data": [
-  {
-    "name": "Sheet1",
-    "cells": [
-      [
+  data: [
+    {
+      name: "data",
+      cols: [
         {
-          "v": "Report - July 2016",
-          "s": 14
+          width: 59.5,
         },
         {
-          "v": null,
-          "s": 15
+          width: 93.5,
         },
         {
-          "v": null,
-          "s": 15
+          width: 160.31,
+          hidden: true,
         },
         {
-          "v": null,
-          "s": 15
+          width: 139.655,
         },
         {
-          "v": null,
-          "s": 15
-        }
-      ],
-      [
-        {
-          "v": "Region",
-          "s": 2
+          width: 150.53499,
         },
         {
-          "v": "Country",
-          "s": 2
+          width: 129.965,
         },
         {
-          "v": "Sales - Group A",
-          "s": 3
+          width: 139.655,
         },
         {
-          "v": "Sales - Group B",
-          "s": 3
+          width: 160.31,
         },
         {
-          "v": "Total",
-          "s": 4
-        }
-      ],
-      [
-        {
-          "v": "Europe",
-          "s": 5
+          width: 129.965,
         },
         {
-          "v": "Germany",
-          "s": 6
+          width: 129.965,
         },
         {
-          "v": "188400",
-          "s": 7
+          width: 129.965,
         },
         {
-          "v": "52000",
-          "s": 7
+          width: 129.965,
         },
         {
-          "v": "240400",
-          "s": 7
-        }
-      ],
-      [
-        {
-          "v": "Europe",
-          "s": 5
+          width: 129.965,
         },
+      ],
+      rows: [
         {
-          "v": "France",
-          "s": 5
+          height: 40,
         },
         {
-          "v": "192200",
-          "s": 8
+          height: 40,
         },
         {
-          "v": "12000",
-          "s": 9
+          height: 40,
         },
         {
-          "v": "204200",
-          "s": 7
-        }
-      ],
-      [
-        {
-          "v": "Europe",
-          "s": 5
+          height: 40,
+          hidden: true,
         },
         {
-          "v": "Poland",
-          "s": 6
+          height: 40,
         },
         {
-          "v": "68900",
-          "s": 7
+          height: 40,
         },
         {
-          "v": "8000",
-          "s": 7
+          height: 40,
         },
         {
-          "v": "76900",
-          "s": 7
-        }
-      ],
-      [
-        {
-          "v": "Asia",
-          "s": 5
+          height: 40,
         },
         {
-          "v": "Japan",
-          "s": 5
+          height: 40,
         },
         {
-          "v": "140000",
-          "s": 10
+          height: 40,
         },
         {
-          "v": "14000",
-          "s": 11
+          height: 120,
         },
-        {
-          "v": "154000",
-          "s": 7
-        }
       ],
-      [
-        {
-          "v": "Asia",
-          "s": 5
-        },
-        {
-          "v": "China",
-          "s": 5
-        },
-        {
-          "v": "50000",
-          "s": 7
+      cells: [
+        [
+          {
+            v: null,
+            s: 1,
+          },
+          {
+            v: "Project",
+            s: 1,
+          },
+          {
+            v: "Access",
+            s: 1,
+          },
+          {
+            v: "Status",
+            s: 1,
+          },
+          {
+            v: "Owner",
+            s: 1,
+          },
+          {
+            v: "Balance",
+            s: 2,
+          },
+          {
+            v: "Number of hours",
+            s: 2,
+          },
+          {
+            v: "Number of renewals",
+            s: 1,
+          },
+          {
+            v: "Calendar",
+            s: 3,
+          },
+          null,
+          {
+            v: "Cost",
+            s: 2,
+          },
+          {
+            v: "Budget",
+            s: 2,
+          },
+          {
+            v: "Project ID",
+            s: 4,
+          },
+        ],
+        [
+          {
+            v: null,
+            s: 1,
+          },
+          {
+            v: null,
+            s: 1,
+          },
+          {
+            v: null,
+            s: 1,
+          },
+          {
+            v: null,
+            s: 1,
+          },
+          {
+            v: null,
+            s: 1,
+          },
+          {
+            v: null,
+            s: 2,
+          },
+          {
+            v: null,
+            s: 2,
+          },
+          {
+            v: null,
+            s: 1,
+          },
+          {
+            v: "Start date",
+            s: 3,
+          },
+          {
+            v: "End date",
+            s: 3,
+          },
+          {
+            v: null,
+            s: 2,
+          },
+          {
+            v: null,
+            s: 2,
+          },
+          {
+            v: null,
+            s: 1,
+          },
+        ],
+        [
+          {
+            v: "true",
+            s: 5,
+          },
+          {
+            v: "Real Estate",
+            s: 6,
+          },
+          {
+            v: "Gary Ortiz, Albert Williamson, Russell Robinson",
+            s: 6,
+          },
+          {
+            v: "Done",
+            s: 7,
+            hyperlink:
+              "https://git.webix.io/iizobov/salesforce/src/branch/prod",
+          },
+          {
+            v: "Louise Fisher",
+            s: 8,
+          },
+          {
+            v: "8180.0",
+            s: 9,
+          },
+          {
+            v: "92.0",
+            s: 10,
+          },
+          {
+            v: "1-2 times",
+            s: 6,
+          },
+          {
+            v: "43134.0",
+            s: 11,
+          },
+          {
+            v: "43256.0",
+            s: 11,
+          },
+          {
+            v: "3588.0",
+            s: 9,
+          },
+          {
+            v: "11768.0",
+            s: 9,
+          },
+          {
+            v: "ISS-996.9",
+            s: 6,
+          },
+        ],
+        [
+          {
+            v: "false",
+            s: 5,
+          },
+          {
+            v: null,
+            s: 6,
+          },
+          {
+            v: "Mildred Fuller, Gary Ortiz",
+            s: 6,
+          },
+          {
+            v: "Done",
+            s: 6,
+          },
+          {
+            v: "Fred Duncan",
+            s: 8,
+          },
+          {
+            v: "-6389.0",
+            s: 9,
+          },
+          {
+            v: "484.0",
+            s: 12,
+          },
+          {
+            v: "1 time",
+            s: 6,
+          },
+          {
+            v: "43106.0",
+            s: 11,
+          },
+          {
+            v: "43109.0",
+            s: 11,
+          },
+          {
+            v: "21296.0",
+            s: 9,
+          },
+          {
+            v: "14907.0",
+            s: 9,
+          },
+          {
+            v: "ISS-111.0",
+            s: 6,
+          },
+        ],
+        [
+          {
+            v: "false",
+            s: 5,
+          },
+          {
+            v: null,
+            s: 6,
+          },
+          {
+            v: "Albert Williamson, Russell Robinson, Phyllis Webb",
+            s: 6,
+          },
+          {
+            v: "Louise Fisher",
+            s: 13,
+          },
+          {
+            v: "Michael Rice",
+            s: 8,
+          },
+          {
+            v: "56076.0",
+            s: 9,
+          },
+          {
+            v: "345.0",
+            s: 10,
+          },
+          {
+            v: "1-2 times",
+            s: 6,
+          },
+          {
+            v: "43108.0",
+            s: 11,
+          },
+          {
+            v: "43262.0",
+            s: 11,
+          },
+          {
+            v: "14835.0",
+            s: 9,
+          },
+          {
+            v: "70911.0",
+            s: 9,
+          },
+          {
+            v: "ISS-678.9",
+            s: 6,
+          },
+        ],
+        [
+          {
+            v: "true",
+            s: 5,
+          },
+          {
+            v: "HR System",
+            s: 6,
+          },
+          {
+            v: "Russell Robinson, Phyllis Webb",
+            s: 6,
+          },
+          {
+            v: "Done",
+            s: 6,
+          },
+          {
+            v: "Daniel Peterson",
+            s: 8,
+          },
+          {
+            v: "2876.0",
+            s: 9,
+          },
+          {
+            v: "340.0",
+            s: 10,
+          },
+          {
+            v: "1 time",
+            s: 6,
+          },
+          {
+            v: "43163.0",
+            s: 11,
+          },
+          {
+            v: "43138.0",
+            s: 11,
+          },
+          {
+            v: "15980.0",
+            s: 9,
+          },
+          {
+            v: "18856.0",
+            s: 9,
+          },
+          {
+            v: "ISS-134.2",
+            s: 6,
+          },
+        ],
+        [
+          {
+            v: "true",
+            s: 5,
+          },
+          {
+            v: null,
+            s: 6,
+          },
+          {
+            v: "Gary Ortiz, Albert Williamson, Russell Robinson, Phyllis Webb",
+            s: 6,
+          },
+          {
+            v: "Done",
+            s: 6,
+          },
+          {
+            v: "Andrew Stewart",
+            s: 8,
+          },
+          {
+            v: "3016.0",
+            s: 9,
+          },
+          {
+            v: "57.0",
+            s: 10,
+          },
+          {
+            v: "1-2 times",
+            s: 6,
+          },
+          {
+            v: "43106.0",
+            s: 11,
+          },
+          {
+            v: "43140.0",
+            s: 11,
+          },
+          {
+            v: "2052.0",
+            s: 9,
+          },
+          {
+            v: "5068.0",
+            s: 9,
+          },
+          {
+            v: "ISS-433.2",
+            s: 6,
+          },
+        ],
+        [
+          {
+            v: "false",
+            s: 5,
+          },
+          {
+            v: null,
+            s: 6,
+          },
+          {
+            v: "Mildred Fuller, Phyllis Webb",
+            s: 6,
+          },
+          {
+            v: "Done",
+            s: 6,
+          },
+          {
+            v: "Martin Thompson",
+            s: 8,
+          },
+          {
+            v: "8311.0",
+            s: 9,
+          },
+          {
+            v: "211.0",
+            s: 10,
+          },
+          {
+            v: "more than 5 times",
+            s: 6,
+          },
+          {
+            v: "43253.0",
+            s: 11,
+          },
+          {
+            v: "43106.0",
+            s: 11,
+          },
+          {
+            v: "8229.0",
+            s: 9,
+          },
+          {
+            v: "16540.0",
+            s: 9,
+          },
+          {
+            v: "ISS-356.2",
+            s: 6,
+          },
+        ],
+        [
+          {
+            v: "true",
+            s: 5,
+          },
+          {
+            v: "Ticket System",
+            s: 6,
+          },
+          {
+            v: "Mildred Fuller, Albert Williamson",
+            s: 6,
+          },
+          {
+            v: "In Progress",
+            s: 6,
+          },
+          {
+            v: "Martin Thompson",
+            s: 8,
+          },
+          {
+            v: "-22.0",
+            s: 9,
+          },
+          {
+            v: "3.0",
+            s: 14,
+          },
+          {
+            v: "1 time",
+            s: 6,
+          },
+          {
+            v: "43621.0",
+            s: 11,
+          },
+          {
+            v: "43531.0",
+            s: 11,
+          },
+          {
+            v: "144.0",
+            s: 9,
+          },
+          {
+            v: "122.0",
+            s: 9,
+          },
+          {
+            v: "ISS-342.1",
+            s: 6,
+          },
+        ],
+        [
+          {
+            v: "true",
+            s: 5,
+          },
+          {
+            v: null,
+            s: 6,
+          },
+          {
+            v: "Gary Ortiz, Phyllis Webb",
+            s: 6,
+          },
+          {
+            v: "In Progress",
+            s: 6,
+          },
+          {
+            v: "Mark Harper",
+            s: 8,
+          },
+          {
+            v: "9019.0",
+            s: 9,
+          },
+          {
+            v: "76.0",
+            s: 10,
+          },
+          {
+            v: "more than 5 times",
+            s: 6,
+          },
+          {
+            v: "43500.0",
+            s: 11,
+          },
+          {
+            v: "43532.0",
+            s: 11,
+          },
+          {
+            v: "3496.0",
+            s: 9,
+          },
+          {
+            v: "12515.0",
+            s: 9,
+          },
+          {
+            v: "ISS-256.7",
+            s: 6,
+          },
+        ],
+        [
+          {
+            v: null,
+            s: 15,
+          },
+          {
+            v: "Total",
+            s: 15,
+          },
+          {
+            v: null,
+            s: 15,
+          },
+          {
+            v: null,
+            s: 15,
+          },
+          {
+            v: "s",
+            s: 16,
+          },
+          {
+            v: "56076",
+            s: 15,
+          },
+          {
+            v: "Avg:201.0\n            Med:151.5\n            Max:484.0\n            Min:3.0\n            Sum:1608.0",
+            s: 15,
+          },
+          {
+            v: null,
+            s: 15,
+          },
+          {
+            v: null,
+            s: 15,
+          },
+          {
+            v: null,
+            s: 15,
+          },
+          {
+            v: "69620",
+            s: 15,
+          },
+          {
+            v: "150687",
+            s: 15,
+          },
+          {
+            v: null,
+            s: 15,
+          },
+        ],
+      ],
+      merged: [
+        {
+          from: {
+            column: 8,
+            row: 0,
+          },
+          to: {
+            column: 9,
+            row: 0,
+          },
         },
+      ],
+      validations: [
         {
-          "v": "4800",
-          "s": 7
+          range: "H3 D6:D9",
+          source: {
+            type: "RangeReference",
+            value: "data!$E$3:$E$10",
+          },
         },
         {
-          "v": "54800",
-          "s": 7
-        }
-      ]
-    ],
-    "merged": [
-      {
-        "from": {
-          "column": 0,
-          "row": 0
+          range: "E11",
+          source: {
+            type: "RangeReference",
+            value: 'NOT(ISERROR(SEARCH(("Some"),(E11))))',
+          },
         },
-        "to": {
-          "column": 4,
-          "row": 0
-        }
-      }
-    ]
-  }
-],
-"styles": [
-  {
-    "fontFamily": "Calibri",
-    "fontSize": "16px",
-    "color": "rgba(0,0,0,1)",
-    "borderRight": "1px solid #34e343",
-    "borderLeft": "1px solid #34e343",
-    "format": "General",
-    "borderTop": "1px solid #34e343",
-    "borderBottom": "1px solid #34e343"
-  },
-  {
-    "fontSize": "16px",
-    "fontFamily": "Calibri",
-    "borderLeft": "0.5px solid #000",
-    "borderTop": "0.5px solid #000",
-    "borderRight": "0.5px solid #000",
-    "format": "General",
-    "borderBottom": "0.5px solid #000"
-  },
-  {
-    "fontWeight": "bold",
-    "borderBottom": "1px dashed #701243",
-    "verticalAlign": "top",
-    "background": "rgba(234,234,234,1)",
-    "fontSize": "15px",
-    "fontFamily": "PT Sans",
-    "borderTop": "1px dashed #701243",
-    "format": "General",
-    "color": "rgba(129,129,129,1)",
-    "borderLeft": "1px dashed #701243",
-    "borderRight": "1px dashed #701243",
-    "textAlign": "center"
-  },
-  {
-    "color": "rgba(129,129,129,1)",
-    "borderBottom": "1px dotted #000",
-    "fontSize": "15px",
-    "fontFamily": "PT Sans",
-    "verticalAlign": "center",
-    "format": "General",
-    "textAlign": "center",
-    "borderTop": "1px dotted #000",
-    "fontWeight": "bold",
-    "borderRight": "1px dotted #000",
-    "borderLeft": "1px dotted #000"
-  },
-  {
-    "fontFamily": "PT Sans",
-    "borderBottom": "1px double #000",
-    "borderTop": "1px double #000",
-    "fontSize": "15px",
-    "verticalAlign": "center",
-    "color": "rgba(129,129,129,1)",
-    "fontWeight": "bold",
-    "textAlign": "right",
-    "format": "General",
-    "borderLeft": "1px double #000",
-    "borderRight": "1px double #000"
-  },
-  {
-    "format": "General",
-    "borderBottom": "1px solid #94ff4d",
-    "background": "rgba(234,234,234,1)",
-    "verticalAlign": "top",
-    "textAlign": "center",
-    "borderTop": "1px solid #94ff4d",
-    "borderLeft": "1px solid #94ff4d",
-    "borderRight": "1px solid #94ff4d",
-    "color": "rgba(129,129,129,1)",
-    "fontSize": "15px",
-    "fontFamily": "PT Sans"
-  },
-  {
-    "textAlign": "center",
-    "borderLeft": "1px solid #000",
-    "fontWeight": "bold",
-    "background": "rgba(234,234,234,1)",
-    "fontStyle": "italic",
-    "borderRight": "1px solid #000",
-    "textDecoration": "underline",
-    "color": "rgba(129,129,129,1)",
-    "format": "General",
-    "fontFamily": "PT Sans",
-    "fontSize": "15px",
-    "verticalAlign": "bottom",
-    "borderBottom": "1px solid #000",
-    "borderTop": "1px solid #000"
-  },
-  {
-    "fontFamily": "PT Sans",
-    "fontSize": "15px",
-    "borderRight": "2px solid #000",
-    "borderBottom": "2px solid #000",
-    "format": "General",
-    "color": "rgba(0,0,0,1)",
-    "verticalAlign": "top",
-    "borderLeft": "2px solid #000",
-    "borderTop": "2px solid #000",
-    "textAlign": "right"
-  },
-  {
-    "fontFamily": "PT Sans",
-    "borderTop": "1px solid #000",
-    "verticalAlign": "top",
-    "background": "rgba(152,0,0,1)",
-    "color": "rgba(0,0,0,1)",
-    "textAlign": "right",
-    "borderBottom": "1px solid #000",
-    "format": "General",
-    "borderLeft": "1px solid #000",
-    "fontSize": "15px",
-    "borderRight": "1px solid #000"
-  },
-  {
-    "fontSize": "32px",
-    "borderLeft": "1px solid #000",
-    "borderBottom": "1px solid #000",
-    "verticalAlign": "top",
-    "format": "General",
-    "color": "rgba(0,0,0,1)",
-    "fontFamily": "PT Sans",
-    "borderTop": "1px solid #000",
-    "borderRight": "1px solid #000",
-    "textAlign": "right"
-  },
-  {
-    "borderTop": "1px solid #000",
-    "textAlign": "right",
-    "format": "General",
-    "fontFamily": "PT Sans",
-    "borderBottom": "1px solid #000",
-    "fontSize": "15px",
-    "borderRight": "1px solid #000",
-    "borderLeft": "1px solid #000",
-    "verticalAlign": "top",
-    "color": "rgba(60,120,216,1)"
-  },
-  {
-    "borderLeft": "1px solid #000",
-    "format": "General",
-    "borderTop": "1px solid #000",
-    "color": "rgba(0,0,0,1)",
-    "fontSize": "15px",
-    "textAlign": "right",
-    "borderBottom": "1px solid #000",
-    "borderRight": "1px solid #000",
-    "fontFamily": "Impact",
-    "verticalAlign": "top"
-  },
-  {
-    "background": "rgba(255,255,0,1)",
-    "borderLeft": "1px solid #000000",
-    "borderTop": "1px solid #000000",
-    "borderRight": "1px solid #000000",
-    "fontSize": "16px",
-    "borderBottom": "1px solid #000000",
-    "color": "rgba(255,0,0,1)",
-    "format": "General",
-    "fontFamily": "Calibri"
-  },
-  {
-    "borderBottom": "1px solid #000",
-    "borderTop": "1px solid #000",
-    "color": "rgba(0,0,0,1)",
-    "fontSize": "16px",
-    "fontFamily": "Calibri",
-    "borderRight": "1px solid #000",
-    "borderLeft": "1px solid #000",
-    "format": "General"
-  },
-  {
-    "color": "rgba(255,239,239,1)",
-    "borderRight": "1px solid #000",
-    "background": "rgba(110,110,255,1)",
-    "textAlign": "center",
-    "borderLeft": "1px solid #000",
-    "borderTop": "1px solid #000",
-    "fontSize": "16px",
-    "borderBottom": "1px solid #000",
-    "format": "General",
-    "fontFamily": "PT Sans"
-  },
-  {
-    "borderBottom": "1px solid #000",
-    "format": "General",
-    "borderRight": "1px solid #000",
-    "fontFamily": "Calibri",
-    "fontSize": "16px",
-    "borderTop": "1px solid #000",
-    "borderLeft": "1px solid #000"
-  }
-]
-}
\ No newline at end of file
+      ],
+      frozen_cols: 2,
+      frozen_rows: 1,
+    },
+  ],
+  styles: [
+    {
+      format: "General",
+      fontSize: "13.333333px",
+      verticalAlign: "bottom",
+      fontFamily: "Calibri",
+      color: "rgba(0,0,0,1)",
+    },
+    {
+      color: "rgba(0,0,0,1)",
+      verticalAlign: "center",
+      fontWeight: "bold",
+      format: "General",
+      background: "rgba(247,247,247,1)",
+      fontFamily: "Calibri",
+    },
+    {
+      color: "rgba(0,0,0,1)",
+      background: "rgba(247,247,247,1)",
+      fontWeight: "bold",
+      fontFamily: "Calibri",
+      align: "right",
+      format: "General",
+      verticalAlign: "center",
+    },
+    {
+      background: "rgba(247,247,247,1)",
+      color: "rgba(0,0,0,1)",
+      align: "center",
+      fontFamily: "Calibri",
+      fontWeight: "bold",
+      verticalAlign: "center",
+      format: "dd/mm/yy",
+    },
+    {
+      fontWeight: "bold",
+      format: "@",
+      verticalAlign: "center",
+      fontFamily: "Calibri",
+      background: "rgba(247,247,247,1)",
+      color: "rgba(0,0,0,1)",
+      align: "center",
+    },
+    {
+      color: "rgba(0,0,0,1)",
+      fontSize: "13.333333px",
+      format: "General",
+      fontFamily: "Calibri",
+      verticalAlign: "center",
+    },
+    {
+      color: "rgba(0,0,0,1)",
+      fontFamily: "Calibri",
+      fontSize: "13.333333px",
+      format: "@",
+      verticalAlign: "center",
+    },
+    {
+      color: "rgba(0,0,255,1)",
+      format: "@",
+      textDecoration: "underline",
+      verticalAlign: "center",
+      fontSize: "13.333333px",
+    },
+    {
+      format: "@",
+      fontSize: "13.333333px",
+      fontFamily: "Calibri",
+      verticalAlign: "center",
+      background: "rgba(173,216,230,1)",
+      color: "rgba(0,0,0,1)",
+    },
+    {
+      fontFamily: "Calibri",
+      align: "right",
+      format: "General",
+      verticalAlign: "center",
+      color: "rgba(0,0,0,1)",
+      fontSize: "13.333333px",
+    },
+    {
+      fontFamily: "Calibri",
+      fontSize: "13.333333px",
+      format: "#.##",
+      align: "right",
+      verticalAlign: "center",
+      color: "rgba(0,0,0,1)",
+    },
+    {
+      fontFamily: "Calibri",
+      format: "dd/mm/yy",
+      verticalAlign: "center",
+      fontSize: "13.333333px",
+      align: "center",
+      color: "rgba(0,0,0,1)",
+    },
+    {
+      color: "rgba(255,255,255,1)",
+      format: "#.##",
+      fontFamily: "Calibri",
+      verticalAlign: "center",
+      background: "rgba(244,67,54,1)",
+      align: "right",
+      fontSize: "13.333333px",
+    },
+    {
+      fontFamily: "Calibri",
+      format: "@",
+      color: "rgba(0,0,0,1)",
+      fontSize: "13.333333px",
+      verticalAlign: "center",
+    },
+    {
+      format: "#.##",
+      fontSize: "13.333333px",
+      fontFamily: "Calibri",
+      color: "rgba(255,255,255,1)",
+      verticalAlign: "center",
+      align: "right",
+      background: "rgba(76,175,80,1)",
+    },
+    {
+      fontFamily: "Calibri",
+      background: "rgba(247,247,247,1)",
+      fontWeight: "bold",
+      fontSize: "13.333333px",
+      format: "General",
+      verticalAlign: "center",
+      color: "rgba(0,0,0,1)",
+    },
+    {
+      verticalAlign: "center",
+      color: "rgba(0,0,0,1)",
+      fontWeight: "bold",
+      fontFamily: "Calibri",
+      background: "rgba(247,247,247,1)",
+      fontSize: "13.333333px",
+      format: "General",
+    },
+  ],
+};
diff --git a/index.html b/example/lib.html
similarity index 84%
rename from index.html
rename to example/lib.html
index 4d220f0..a6c00a6 100644
--- a/index.html
+++ b/example/lib.html
@@ -3,7 +3,7 @@
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
-    <title>JSON 2 Excel</title>
+    <title>JSON2Excel - api</title>
     <style>
         .spreadsheet-data {
             width: 80%;
@@ -18,7 +18,7 @@
     <div><textarea class="spreadsheet-data"></textarea></div>
     <button id="convert-button">download</button>
 
-    <script src="./example/datasets.js"></script>
+    <script src="./datasets.js"></script>
     <script type="module">
     let $ = document.querySelector.bind(document);
 
@@ -26,8 +26,8 @@
     dataArea.value = JSON.stringify(example, undefined, 2);
     
     $("#convert-button").addEventListener("click", function(e){
-        import("./js/module").then(({ convert }) => {
-            const blob = convert(dataArea.value);
+        import("../dist/module.js").then(async ({ convert }) => {
+            const blob = await convert(dataArea.value);
             const a = document.createElement("a");
             a.innerHTML = "test";
             a.href = URL.createObjectURL(blob);
diff --git a/example/worker.html b/example/worker.html
index 0054f3d..407b479 100644
--- a/example/worker.html
+++ b/example/worker.html
@@ -3,7 +3,7 @@
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <meta http-equiv="X-UA-Compatible" content="ie=edge">
-    <title>Excel 2 JSON</title>
+    <title>JSON2Excel - worker</title>
     <style>
         .spreadsheet-data {
             width: 80%;
@@ -29,10 +29,13 @@
     $("#convert-button").addEventListener("click", function(e){
         if (!worker) {
             worker = new Promise((res) => {
-                const path = "http://127.0.0.1:8200/dist/assets/worker.js";
+                 const path = `${window.location.origin}/dist/worker.js`;
+               // const path = "https://cdn.dhtmlx.com/libs/json2excel/1.3/worker.js";
                 const url = window.URL.createObjectURL(
-			    	new Blob([`importScripts('${path}');`], { type: "text/javascript" })
-			    );
+                    new Blob([`importScripts('${path}');`], {
+                        type: "text/javascript",
+                    })
+                );
 
                 const x = new Worker(url);
                 x.addEventListener("message", e => {
diff --git a/js/module.js b/js/module.js
index 3342e5d..0a32a77 100644
--- a/js/module.js
+++ b/js/module.js
@@ -1,7 +1,9 @@
-import { import_to_xlsx } from "../pkg/json2excel_wasm.js";
+import init, { import_to_xlsx } from "../pkg/json2excel_wasm.js";
 
 export { import_to_xlsx as toExcel };
-export function convert(data){
+export async function convert(data){
+    await init();
+    
     if (typeof data === "string")
         data = JSON.parse(data);
 
diff --git a/js/worker.js b/js/worker.js
index 8bc0fab..61016d5 100644
--- a/js/worker.js
+++ b/js/worker.js
@@ -1,4 +1,4 @@
-import { import_to_xlsx } from '../pkg/json2excel_wasm';
+import init, { import_to_xlsx } from '../pkg/json2excel_wasm.js';
 
 
 onmessage = function(e) {
@@ -10,7 +10,9 @@ onmessage = function(e) {
     }
 }
 
-function doConvert(data, config = {}){
+async function doConvert(data, config = {}){
+    await init();
+
     const result = import_to_xlsx(data);
     const blob = new Blob([result], {
         type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,"
diff --git a/package.json b/package.json
index 3be2f99..063c603 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "json2excel-wasm",
   "description": "WASM based web worker for converting excel data to raw json",
   "keywords": ["excel", "convert", "wasm"],
-  "version": "1.2.1",
+  "version": "1.3.1",
   "license": "MIT",
   "collaborators": [
     "Aleksei Kolosov <akolosov@xbsoftware.com>",
@@ -13,19 +13,16 @@
     "url": "https://github.com/DHTMLX/json2excel"
   },
   "files": [
-    "pkg/json2excel_wasm_bg.wasm",
-    "pkg/json2excel_wasm.js",
-    "pkg/json2excel_wasm_bg.js",
-    "pkg/json2excel_wasm.d.ts",
-    "js/worker.js",
-    "js/module.js"
+    "dist/worker.js",
+    "dist/module.js"
   ],
-  "module": "js/module.js",
+  "module": "dist/module.js",
   "scripts": {
-    "rdev": "cargo build && wasm-pack build && yarn dev",
-    "dev": "vite",
-    "build": "vite build",
-    "preview": "vite preview"
+    "dev": "yarn build && vite --mode worker",
+    "build:wasm": "cargo build && wasm-pack build --target web",
+    "build:module": "vite build --mode module",
+    "build:worker": "vite build --mode worker",
+    "build": "yarn build:wasm && yarn build:module && yarn build:worker"
   },
   "devDependencies": {
     "vite": "^4.2.0",
diff --git a/src/lib.rs b/src/lib.rs
index fb30182..1dc67b7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -33,11 +33,13 @@ const ROOT_RELS: &'static [u8] = br#"<?xml version="1.0" encoding="UTF-8" standa
 #[derive(Deserialize)]
 pub struct ColumnData {
     pub width: f32,
+    pub hidden: Option<bool>,
 }
 
 #[derive(Deserialize)]
 pub struct RowData {
     pub height: f32,
+    pub hidden: Option<bool>,
 }
 
 #[derive(Deserialize)]
@@ -56,6 +58,19 @@ pub struct MergedCell {
 pub struct Cell {
     pub v: Option<String>,
     pub s: Option<u32>,
+    pub hyperlink: Option<String>,
+}
+
+#[derive(Deserialize)]
+pub struct ValidationSource {
+    pub r#type: String,
+    pub value: String,
+}
+
+#[derive(Deserialize)]
+pub struct DataValidation {
+    pub range: String,
+    pub source: ValidationSource,
 }
 
 #[derive(Deserialize)]
@@ -66,6 +81,9 @@ pub struct SheetData {
     cols: Option<Vec<Option<ColumnData>>>,
     rows: Option<Vec<Option<RowData>>>,
     merged: Option<Vec<MergedCell>>,
+    frozen_rows: Option<u32>,
+    frozen_cols: Option<u32>,
+    validations: Option<Vec<DataValidation>>,
 }
 
 #[derive(Deserialize)]
@@ -80,6 +98,11 @@ struct InnerCell {
     style: Option<u32>
 }
 
+#[derive(Clone)]
+struct Hyperlink {
+    cell: String,
+    url: String,
+}
 impl InnerCell {
     pub fn new(cell: String, style: &Option<u32>) -> InnerCell {
         InnerCell {
@@ -114,9 +137,9 @@ pub fn import_to_xlsx(raw_data: &JsValue) -> Vec<u8> {
     let w = Cursor::new(buf);
     let mut zip = zip::ZipWriter::new(w);
     let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored).unix_permissions(0o755);
-
     for (sheet_index, sheet) in data.data.iter().enumerate() {
         let mut rows: Vec<Vec<InnerCell>> = vec!();
+        let mut hyperlinks: Vec<Hyperlink> = vec![];
         match &sheet.cells {
             Some(cells) => {
                 for (row_index, row) in cells.iter().enumerate() {
@@ -126,15 +149,20 @@ pub fn import_to_xlsx(raw_data: &JsValue) -> Vec<u8> {
                             Some(cell) => {
                                 let cell_name = cell_offsets_to_index(row_index, col_index);
                                 let mut inner_cell = InnerCell::new(cell_name, &cell.s);
-
+                                if let Some(link) = &cell.hyperlink {
+                                    hyperlinks.push(Hyperlink {
+                                        cell: inner_cell.cell.clone(),
+                                        url: link.clone(),
+                                    });
+                                }
                                 match &cell.v {
                                     Some(value) => {
                                         if !value.is_empty() {
                                             match value.parse::<f64>() {
-                                                Ok(_) => {
+                                                Ok(_) if !value.starts_with("0") || value.len() == 1 => {
                                                     inner_cell.value = CellValue::Value(value.to_owned());
                                                 },
-                                                Err(_) => {
+                                                Err(_) | _ => {
                                                     if value.starts_with("=") {
                                                         // [FIXME] formula can be corrupted
                                                         inner_cell.value = CellValue::Formula(formulas::fix_formula(&value[1..], &futures));
@@ -177,10 +205,10 @@ pub fn import_to_xlsx(raw_data: &JsValue) -> Vec<u8> {
                                         let cell_name = cell_offsets_to_index(row_index, col_index);
                                         let mut inner_cell = InnerCell::new(cell_name, &None);
                                         match value.parse::<f64>() {
-                                            Ok(_) => {
+                                            Ok(_) if !value.starts_with("0") || value.len() == 1 => {
                                                 inner_cell.value = CellValue::Value(value.to_owned());
                                             },
-                                            Err(_) => {
+                                            Err(_) | _ => {
                                                 if value.starts_with("=") {
                                                     // [FIXME] formula can be corrupted
                                                     inner_cell.value = CellValue::Formula(formulas::fix_formula(&value[1..], &futures));
@@ -204,9 +232,24 @@ pub fn import_to_xlsx(raw_data: &JsValue) -> Vec<u8> {
         }
 
         let sheet_info = get_sheet_info(sheet.name.clone(), sheet_index);
+        let rels_path = format!("xl/worksheets/_rels/sheet{}.xml.rels", sheet_index + 1);
+        zip.start_file(rels_path.clone(), options).unwrap();
+        zip.write_all(get_sheet_rels(&hyperlinks).as_bytes()).unwrap();
         zip.start_file(sheet_info.0.clone(), options).unwrap();
-        zip.write_all(get_sheet_data(rows, &sheet.cols, &sheet.rows, &sheet.merged).as_bytes()).unwrap();
-        sheets_info.push(sheet_info);
+        zip.write_all(
+            get_sheet_data(
+                rows,
+                &sheet.cols,
+                &sheet.rows,
+                &sheet.merged,
+                sheet.frozen_rows,
+                sheet.frozen_cols,
+                &sheet.validations,
+                &hyperlinks,
+            )
+            .as_bytes(),
+        ).unwrap();
+       sheets_info.push(sheet_info);
     }
 
     zip.start_file("_rels/.rels", options).unwrap();
@@ -251,7 +294,7 @@ fn get_styles_data(style_table: StyleTable) -> String {
     let fonts_children: Vec<Element> = style_table.fonts.iter().map(|ref font| {
         let mut font_element = Element::new("font");
         let mut font_children = vec!();
-        match font.family {
+        match font.name {
             Some(ref font) => {
                 let mut name_el = Element::new("name");
                 name_el.add_attr("val", font);
@@ -304,16 +347,16 @@ fn get_styles_data(style_table: StyleTable) -> String {
         let mut border_element = Element::new("border");
         let mut children: Vec<Element> = vec![];
 
-        if let Some(b) = &border.top {
+        if let Some(b) = &border.left {
             children.push(b.to_xml_el())
         }
         if let Some(b) = &border.right {
             children.push(b.to_xml_el())
         }
-        if let Some(b) = &border.bottom {
+        if let Some(b) = &border.top {
             children.push(b.to_xml_el())
         }
-        if let Some(b) = &border.left {
+        if let Some(b) = &border.bottom {
             children.push(b.to_xml_el())
         }
 
@@ -346,38 +389,35 @@ fn get_styles_data(style_table: StyleTable) -> String {
         .add_children(fills_children);
 
     let mut cell_xfs = Element::new("cellXfs");
-    let xfs_children: Vec<Element> = style_table.xfs.iter().map(|props| {
+    let xfs_children: Vec<Element> = style_table.xfs.iter().map(|p| {
         let mut xf = Element::new("xf");
 
-        props.as_ref().map(|p| {
-            p.font_id.map(|id|{
-                xf
-                    .add_attr("applyFont", "1")
-                    .add_attr("fontId", id.to_string());
-            });
-            p.fill_id.map(|id| {
-                xf
-                    .add_attr("applyFill", "1")
-                    .add_attr("fillId", id.to_string());
-            });
-            p.format_id.map(|id| {
-                xf
-                    .add_attr("applyNumberFormat", "1")
-                    .add_attr("numFmtId", id.to_string());
-            });            
-            p.border_id.map(|id| {
-                xf
-                    .add_attr("applyBorder", "1")
-                    .add_attr("borderId", id.to_string());
-            });
-
-            if p.align_h.is_some() || p.align_v.is_some() {
-                let mut alignment = Element::new("alignment");
-                p.align_h.as_ref().map(|v| alignment.add_attr("horizontal", v));
-                p.align_v.as_ref().map(|v| alignment.add_attr("vertical", v));
-                xf.add_attr("applyAlignment", "1").add_children(vec![alignment]);
-            }
+        p.font_id.map(|id|{
+            xf
+                .add_attr("applyFont", "1")
+                .add_attr("fontId", id.to_string());
         });
+        p.fill_id.map(|id| {
+            xf
+                .add_attr("applyFill", "1")
+                .add_attr("fillId", id.to_string());
+        });
+        p.format_id.map(|id| {
+            xf
+                .add_attr("applyNumberFormat", "1")
+                .add_attr("numFmtId", id.to_string());
+        });            
+        p.border_id.map(|id| {
+            xf
+                .add_attr("applyBorder", "1")
+                .add_attr("borderId", id.to_string());
+        });
+        if p.align_h.is_some() || p.align_v.is_some() {
+            let mut alignment = Element::new("alignment");
+            p.align_h.as_ref().map(|v| alignment.add_attr("horizontal", v));
+            p.align_v.as_ref().map(|v| alignment.add_attr("vertical", v));
+            xf.add_attr("applyAlignment", "1").add_children(vec![alignment]);
+        }
 
         xf
     }).collect();
@@ -414,9 +454,50 @@ fn cell_offsets_to_index(row: usize, col: usize) -> String {
     format!("{}{}", String::from_utf8(chars).unwrap(), row + 1)
 }
 
-fn get_sheet_data(cells: Vec<Vec<InnerCell>>, columns: &Option<Vec<Option<ColumnData>>>, rows: &Option<Vec<Option<RowData>>>, merged: &Option<Vec<MergedCell>>) -> String {
+fn get_sheet_data(
+    cells: Vec<Vec<InnerCell>>, 
+    columns: &Option<Vec<Option<ColumnData>>>, 
+    rows: &Option<Vec<Option<RowData>>>, 
+    merged: &Option<Vec<MergedCell>>,
+    frozen_rows: Option<u32>,
+    frozen_cols: Option<u32>,
+    validations: &Option<Vec<DataValidation>>,
+    hyperlinks: &[Hyperlink],
+) -> String {
     let mut worksheet = Element::new("worksheet");
     let mut sheet_view = Element::new("sheetView");
+
+    let has_frozen = frozen_cols.unwrap_or(0) > 0 || frozen_rows.unwrap_or(0) > 0;
+    if has_frozen {
+        let x_split = frozen_cols.unwrap_or(0);
+        let y_split = frozen_rows.unwrap_or(0);
+        let top_left_cell = cell_offsets_to_index(
+            y_split as usize,
+            x_split as usize,
+        );
+
+        let mut pane = Element::new("pane");
+        if x_split > 0 {
+            pane.add_attr("xSplit", x_split.to_string());
+        }
+        if y_split > 0 {
+            pane.add_attr("ySplit", y_split.to_string());
+        }
+
+        pane.add_attr("topLeftCell", top_left_cell.clone());
+        pane.add_attr("state", "frozen");
+
+        let active_pane = match (x_split > 0, y_split > 0) {
+            (true, true) => "bottomRight",
+            (true, false) => "topRight",
+            (false, true) => "bottomLeft",
+            _ => "topLeft",
+        };
+        pane.add_attr("activePane", active_pane);
+
+        sheet_view.add_children(vec![pane]);
+    }
+
     sheet_view.add_attr("workbookViewId", "0");
     let mut sheet_views = Element::new("sheetViews");
     sheet_views.add_children(vec![sheet_view]);
@@ -440,8 +521,12 @@ fn get_sheet_data(cells: Vec<Vec<InnerCell>>, columns: &Option<Vec<Option<Column
                             .add_attr("max", (index + 1).to_string())
                             .add_attr("customWidth", "1")
                             .add_attr("width", (col.width / WIDTH_COEF).to_string());
+                        if let Some(true) = col.hidden {
+                            column_element.add_attr("hidden", "1");
+                        }
                         cols_children.push(column_element)
-                    },
+                    
+                    }
                     None => ()
                 }
             }
@@ -473,6 +558,9 @@ fn get_sheet_data(cells: Vec<Vec<InnerCell>>, columns: &Option<Vec<Option<Column
                 row_el
                     .add_attr("ht", (row_data.height * HEIGHT_COEF).to_string())
                     .add_attr("customHeight", "1");
+                if let Some(true) = row_data.hidden {
+                    row_el.add_attr("hidden", "1");
+                }
             },
             None => ()
         }
@@ -503,7 +591,8 @@ fn get_sheet_data(cells: Vec<Vec<InnerCell>>, columns: &Option<Vec<Option<Column
             }
             match &cell.style {
                 Some(ref v) => {
-                    cell_el.add_attr("s", v.to_string());
+                    // style index should be incremented as zero style was prepended
+                    cell_el.add_attr("s", (v+1).to_string());
                 },
                 None => ()
             }
@@ -541,7 +630,58 @@ fn get_sheet_data(cells: Vec<Vec<InnerCell>>, columns: &Option<Vec<Option<Column
         },
         None => ()
     }
+    if let Some(validations) = validations {
+        if !validations.is_empty() {
+            let mut dv_el = Element::new("dataValidations");
+            dv_el.add_attr("count", validations.len().to_string());
+
+            let dv_children: Vec<Element> = validations.iter().map(|v| {
+                let mut el = Element::new("dataValidation");
+
+                el.add_attr("type", "list");
+                el.add_attr("sqref", &v.range);
+                el.add_attr("allowBlank", "1");
+                el.add_attr("showDropDown", "0");
+
+                let mut f1 = Element::new("formula1");
+
+                match v.source.r#type.as_str() {
+                    "RangeReference" => {
+                        f1.add_value(&v.source.value);
+                    },
+                    "StringList" => {
+                       let value = format!("\"{}\"", v.source.value);
+                        f1.add_value(value);
+                    },
+                    _ => {
+                        f1.add_value(&v.source.value);
+                    }
+                }
 
+                el.add_children(vec![f1]);
+                el
+            }).collect();
+
+            dv_el.add_children(dv_children);
+            worksheet_children.push(dv_el);
+        }
+    }
+    if !hyperlinks.is_empty() {
+        let mut hyperlinks_el = Element::new("hyperlinks");
+        let mut hyperlink_children = vec![];
+
+        for (i, h) in hyperlinks.iter().enumerate() {
+            let mut hyperlink_el = Element::new("hyperlink");
+            hyperlink_el
+                .add_attr("r:id", format!("rId{}", 1 + i))
+                .add_attr("ref", &h.cell);
+            hyperlink_children.push(hyperlink_el);
+        }
+
+        hyperlinks_el.add_children(hyperlink_children);
+        worksheet_children.push(hyperlinks_el);
+    }
+        
     worksheet
         .add_attr("xmlns:xm", "http://schemas.microsoft.com/office/excel/2006/main")
         .add_attr("xmlns:x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac")
@@ -580,6 +720,25 @@ fn get_sheet_info(name: Option<String>, index: usize) -> (String, String) {
     (format!("xl/worksheets/sheet{}.xml", index + 1), sheet_name)
 }
 
+fn get_sheet_rels(hyperlinks: &[Hyperlink]) -> String {
+    let mut rels = Element::new("Relationships");
+    rels.add_attr("xmlns", "http://schemas.openxmlformats.org/package/2006/relationships");
+
+    let mut children = vec![];
+
+    for (i, link) in hyperlinks.iter().enumerate() {
+        let mut rel = Element::new("Relationship");
+        rel.add_attr("Id", format!("rId{}", 1 + i));
+        rel.add_attr("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink");
+        rel.add_attr("Target", &link.url);
+        rel.add_attr("TargetMode", "External");
+        children.push(rel);
+    }
+
+    rels.add_children(children);
+    rels.to_xml()
+}
+
 fn get_nav(sheets: Vec<(String, String)>) -> (String, String, String) {
     let mut content_types = Element::new("Types");
     content_types.add_attr("xmlns", "http://schemas.openxmlformats.org/package/2006/content-types");
diff --git a/src/style.rs b/src/style.rs
index 6378a7a..988c81d 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -4,7 +4,7 @@ use crate::xml::Element;
 
 #[derive(PartialEq)]
 pub struct Font {
-    pub family: Option<String>,
+    pub name: Option<String>,
     pub size: Option<String>,
     pub color: Option<String>,
     pub bold: bool,
@@ -65,12 +65,12 @@ pub struct StyleTable {
     pub fonts: Vec<Font>,
     pub fills: Vec<Fill>,
     pub borders: Vec<Border>,
-    pub xfs: Vec<Option<XSFProps>>,
+    pub xfs: Vec<XFSProps>,
     pub custom_formats: HashMap<String, u32>,
     next_custom_format: u32,
 }
 
-pub struct XSFProps {
+pub struct XFSProps {
     pub font_id: Option<usize>,
     pub border_id: Option<usize>,
     pub fill_id: Option<usize>,
@@ -85,23 +85,22 @@ impl StyleTable {
             fonts: vec![Font::new()],
             fills: vec![Fill::new(None, "none"), Fill::new(None, "gray125")],
             borders: vec![Border::new()],
-            xfs: vec![],
+            xfs: vec![XFSProps::new()],
             custom_formats: HashMap::new(),
-            next_custom_format: 164,
+            next_custom_format: 165,
         };
-        match css {
-            Some(map) => {
-                for style in map {
-                    table.add(style);
-                }
+        table.custom_formats.insert(String::from("General"), 164);
+
+        css.map(|map| {
+            for style in map {
+                table.add(style);
             }
-            None => table.xfs.push(Some(XSFProps::new())),
-        }
+        });
 
         table
     }
     pub fn add(&mut self, style: HashMap<String, String>) {
-        let mut xsf_props: XSFProps = XSFProps::new();
+        let mut xsf_props: XFSProps = XFSProps::new();
         let st = style_to_props(&style);
 
         xsf_props.font_id = st
@@ -151,7 +150,7 @@ impl StyleTable {
             i as usize
         });
 
-        self.xfs.push(Some(xsf_props));
+        self.xfs.push(xsf_props);
     }
 }
 
@@ -167,7 +166,7 @@ impl Fill {
 impl Font {
     pub fn new() -> Font {
         Font {
-            family: None,
+            name: None,
             size: None,
             color: None,
             bold: false,
@@ -233,9 +232,9 @@ impl StyleProps {
     }
 }
 
-impl XSFProps {
-    pub fn new() -> XSFProps {
-        XSFProps {
+impl XFSProps {
+    pub fn new() -> XFSProps {
+        XFSProps {
             font_id: None,
             fill_id: None,
             border_id: None,
@@ -260,13 +259,13 @@ fn style_to_props(styles: &HashMap<String, String>) -> StyleProps {
             "color" => font.color = color_to_argb(value),
             "fontWeight" => font.bold = value == "bold",
             "fontStyle" => font.italic = value == "italic",
-            "fontFamily" => font.family = Some(value.to_string()),
+            "fontFamily" => font.name = Some(value.to_string()),
             "textDecoration" => {
                 font.underline = value.contains("underline");
                 font.strike = value.contains("line-through");
             }
             "fontSize" => font.size = px_to_pt(&value),
-            "textAlign" => st.align_h = Some(value.to_owned()),
+            "align" => st.align_h = Some(value.to_owned()),
             "verticalAlign" => st.align_v = Some(value.to_owned()),
             "borderTop" => border.top = str_to_border(&value, BorderPosition::Top),
             "borderRight" => border.right = str_to_border(&value, BorderPosition::Right),
@@ -435,7 +434,7 @@ fn str_to_border(v: &str, pos: BorderPosition) -> Option<BorderProps> {
         "dashed" => BorderStyle::Dashed,
         "solid" => {
             let mut st = BorderStyle::Thin;
-            
+
             if size == 0.5 {
                 st = BorderStyle::Thin
             } else if size == 1.0 {
@@ -472,7 +471,7 @@ fn style_to_props_test() {
     styles.insert(String::from("fontSize"), String::from("24px"));
     styles.insert(String::from("fontFamily"), String::from("Calibri"));
     styles.insert(String::from("textDecoration"), String::from("underline"));
-    styles.insert(String::from("textAlign"), String::from("left"));
+    styles.insert(String::from("align"), String::from("left"));
     styles.insert(String::from("verticalAlign"), String::from("bottom"));
     styles.insert(String::from("borderTop"), String::from("1px solid #9AFF02"));
     styles.insert(
@@ -486,7 +485,7 @@ fn style_to_props_test() {
     assert_eq!(font.size, Some(String::from("18")));
     assert_eq!(font.color, Some(String::from("FFFFFF00")));
     assert_eq!(font.bold, true);
-    assert_eq!(font.family, Some(String::from("Calibri")));
+    assert_eq!(font.name, Some(String::from("Calibri")));
     assert_eq!(font.italic, true);
     assert_eq!(font.underline, true);
     assert_eq!(st.fill.unwrap().color, Some(String::from("FFFF0000")));
diff --git a/vite.config.js b/vite.config.js
index 1e75467..5d63adf 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,41 +1,63 @@
 import { resolve } from "path";
 import { defineConfig } from 'vite'
-
 import wasm from "vite-plugin-wasm";
 import topLevelAwait from "vite-plugin-top-level-await";
 
-export default defineConfig({
-  base:"./",
+const baseConfig = {
+  base: "./",
   plugins: [
-    wasm()
+    wasm(),
+    topLevelAwait()
   ],
-  format: "esm",
-  build:{
+  build: {
     target: "esnext",
-    rollupOptions: {
-      input:{
-        main: resolve(__dirname, "index.html"),
-        worker: resolve(__dirname, "worker.html"),
-      },
-      output: {
-        entryFileNames: `assets/[name].js`,
-        chunkFileNames: `assets/[name].js`,
-        assetFileNames: `assets/[name].[ext]`,
+    emptyOutDir: false,
+  }
+};
+
+// Use mode to determine which config to use
+export default defineConfig(({ mode }) => {
+  if (mode === 'module') {
+    return {
+      ...baseConfig,
+      build: {
+        ...baseConfig.build,
+        lib: {
+          entry: resolve(__dirname, "./js/module.js"),
+          formats: ['es'],
+          fileName: () => 'module.js',
+        },
+        rollupOptions: {
+          output: {
+            entryFileNames: `[name].js`,
+            chunkFileNames: `[name].js`,
+            assetFileNames: `[name].[ext]`
+          }
+        }
       }
     }
-  },
-  worker:{
-    plugins: [
-      wasm(),
-      topLevelAwait()
-    ],
-    format: "esm",
-    rollupOptions: {
-      output: {
-        entryFileNames: `assets/[name].js`,
-        chunkFileNames: `assets/[name].js`,
-        assetFileNames: `assets/[name].[ext]`,
+  }
+
+  if (mode === 'worker') {
+    return {
+      ...baseConfig,
+      build: {
+        ...baseConfig.build,
+        lib: {
+          entry: resolve(__dirname, "./js/worker.js"),
+          formats: ['cjs'],
+          fileName: () => 'worker.js',
+        },
+        rollupOptions: {
+          output: {
+            entryFileNames: `[name].js`,
+            chunkFileNames: `[name].js`,
+            assetFileNames: `[name].[ext]`
+          }
+        }
       }
     }
-  },
+  }
+
+  throw new Error('Please specify mode: module or worker');
 });
\ No newline at end of file
diff --git a/worker.html b/worker.html
deleted file mode 100644
index ab0bf52..0000000
--- a/worker.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <meta http-equiv="X-UA-Compatible" content="ie=edge">
-    <title>JSON 2 Excel</title>
-    <style>
-        .spreadsheet-data {
-            width: 80%;
-            height: 400px;
-        }
-        button {
-            font-size: 14px; padding: 8px; margin:4px;
-        }
-    </style>
-</head>
-<body>
-    <div><textarea class="spreadsheet-data"></textarea></div>
-    <button id="convert-button">download</button>
-
-    <script src="./example/datasets.js"></script>
-    <script type="module">
-        let $ = document.querySelector.bind(document);
-
-        const dataArea = $(".spreadsheet-data");
-        dataArea.value = JSON.stringify(example, undefined, 2);
-
-        import MyWorker from "./js/worker.js?worker";
-
-        let worker;
-        $("#convert-button").addEventListener("click", function(e){
-            if (!worker) {
-                worker = new MyWorker();
-                worker.addEventListener("message", e => {
-                    if (e.data.type === "ready"){
-                        const blob = e.data.blob;
-                        
-                        const a = document.createElement("a");
-                        a.href = URL.createObjectURL(blob);
-                        a.download = "data.xlsx";
-                        document.body.append(a);
-                        a.click();
-                        document.body.remove(a);
-                    }
-                });
-            }
-
-            worker.postMessage({
-                type: "convert",
-                data: dataArea.value
-            });
-        });
-    </script>
-
-</body>
-</html>
\ No newline at end of file
