@divriots/jampack 0.30.3 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,6 +59,12 @@ const default_options = {
59
59
  add_width_and_height: false,
60
60
  },
61
61
  },
62
+ iframe: {
63
+ lazyload: {
64
+ when: 'below-the-fold',
65
+ how: 'native',
66
+ },
67
+ },
62
68
  misc: {
63
69
  prefetch_links: 'off',
64
70
  },
@@ -59,6 +59,12 @@ export type Options = {
59
59
  add_width_and_height: boolean;
60
60
  };
61
61
  };
62
+ iframe: {
63
+ lazyload: {
64
+ when: 'never' | 'below-the-fold' | 'always';
65
+ how: 'native' | 'js';
66
+ };
67
+ };
62
68
  misc: {
63
69
  prefetch_links: 'in-viewport' | 'off';
64
70
  };
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @ts-ignore
2
- import load from '@proload/core';
2
+ import load from './proload/esm/index.mjs';
3
3
  import deepmerge from 'deepmerge';
4
4
  import default_options from './config-default.js';
5
5
  import fast_options_override from './config-fast.js';
package/dist/optimize.js CHANGED
@@ -12,6 +12,7 @@ import { isLocal, Resource, translateSrc } from './utils/resource.js';
12
12
  import { downloadExternalImage } from './optimizers/img-external.js';
13
13
  import { inlineCriticalCss } from './optimizers/inline-critical-css.js';
14
14
  import { prefetch_links_in_viewport } from './optimizers/prefetch-links.js';
15
+ import { processIframe } from './optimizers/process-iframe.js';
15
16
  const UNPIC_DEFAULT_HOST_REGEX = /^https:\/\/n\//g;
16
17
  const ABOVE_FOLD_DATA_ATTR = 'data-abovethefold';
17
18
  function getIntAttr(img, attr) {
@@ -25,6 +26,8 @@ function getIntAttr(img, attr) {
25
26
  }
26
27
  async function analyse(state, file) {
27
28
  console.log('▶ ' + file);
29
+ // For processes to add into
30
+ const appendToBody = {};
28
31
  const html = (await fs.readFile(path.join(state.dir, file))).toString();
29
32
  if (!html.includes('<html'))
30
33
  return;
@@ -79,7 +82,7 @@ async function analyse(state, file) {
79
82
  const isAboveTheFold = isElementAboveTheFold(ifr, ifElement, theFold);
80
83
  ifr.removeAttr(ABOVE_FOLD_DATA_ATTR);
81
84
  try {
82
- await processIframe(file, $, ifElement, isAboveTheFold);
85
+ await processIframe(state, file, $, ifElement, isAboveTheFold, appendToBody);
83
86
  }
84
87
  catch (e) {
85
88
  state.reportIssue(file, {
@@ -142,14 +145,15 @@ async function analyse(state, file) {
142
145
  }
143
146
  // Add to <body>
144
147
  //
145
- let appendToBody = '';
146
148
  if (options.misc.prefetch_links === 'in-viewport') {
147
- appendToBody += await prefetch_links_in_viewport(state, file);
149
+ await prefetch_links_in_viewport(state, file, appendToBody);
148
150
  }
149
- if (appendToBody) {
151
+ // Custom code
152
+ const keys_to_install = Object.keys(appendToBody);
153
+ if (keys_to_install.length > 0) {
150
154
  const body = $('body');
151
155
  if (body.length > 0) {
152
- body.append(appendToBody);
156
+ keys_to_install.forEach((key) => body.append(appendToBody[key]));
153
157
  }
154
158
  }
155
159
  // Render HTML
@@ -760,12 +764,6 @@ async function setImageSize(state, htmlfile, img, image) {
760
764
  // Something when wrong
761
765
  throw new Error(`Unexpected issue when resolving image size "${image.src}"`);
762
766
  }
763
- async function processIframe(htmlfile, $, imgElement, isAboveTheFold) {
764
- const iframe = $(imgElement);
765
- if (!isAboveTheFold) {
766
- iframe.attr('loading', 'lazy');
767
- }
768
- }
769
767
  export async function optimize(state, include, exclude) {
770
768
  const glob = include ? [include] : ['**/*.{htm,html}'];
771
769
  if (exclude)
@@ -1,2 +1,2 @@
1
1
  import { GlobalState } from '../state.js';
2
- export declare function prefetch_links_in_viewport(state: GlobalState, html_file: string): Promise<string>;
2
+ export declare function prefetch_links_in_viewport(state: GlobalState, html_file: string, appendToBody: Record<string, string>): Promise<void>;
@@ -1,26 +1,17 @@
1
- import * as fs from 'fs/promises';
2
- import * as path from 'path';
3
1
  import { createRequire } from 'node:module';
2
+ import { install_dependency } from '../utils/install-dep.js';
4
3
  const require = createRequire(import.meta.url);
5
- const src_filename = 'quicklink.mjs';
6
- const folder = `/_jampack/quicklink-2.3.0`;
7
- const url_loader = `${folder}/loader.js`;
8
- export async function prefetch_links_in_viewport(state, html_file) {
9
- const path_html = path.dirname('/' + html_file);
10
- const quickLinkDestination = path.join(state.dir, folder, src_filename);
11
- const payloadStored = await fs
12
- .access(quickLinkDestination)
13
- .catch(() => false);
14
- if (!payloadStored) {
15
- const path_loader = path.join(state.dir, url_loader);
16
- await fs.mkdir(path.join(state.dir, folder), { recursive: true });
17
- // Write loader
18
- const code_loader = `import { listen } from "./${src_filename}";
19
- listen();`;
20
- await fs.writeFile(path_loader, code_loader);
21
- // Write quicklink code
22
- const source = require.resolve(`quicklink/dist/${src_filename}`);
23
- await fs.copyFile(source, quickLinkDestination);
24
- }
25
- return `<script type="module" src="${path.relative(path_html, url_loader)}"></script>`;
4
+ export async function prefetch_links_in_viewport(state, html_file, appendToBody) {
5
+ await install_dependency(state, html_file, {
6
+ source: {
7
+ npm_package_name: 'quicklink',
8
+ absolute_path_to_file: '/dist',
9
+ filename: 'quicklink.mjs',
10
+ },
11
+ destination: {
12
+ folder_name: 'quicklink-2.3.0',
13
+ code_loader: `import { listen } from "./quicklink.mjs";
14
+ listen();`,
15
+ },
16
+ }, appendToBody);
26
17
  }
@@ -0,0 +1,3 @@
1
+ import * as cheerio from '@divriots/cheerio';
2
+ import type { GlobalState } from '../state.js';
3
+ export declare function processIframe(state: GlobalState, htmlfile: string, $: cheerio.CheerioAPI, imgElement: cheerio.Element, isAboveTheFold: boolean, appendToBody: Record<string, string>): Promise<void>;
@@ -0,0 +1,37 @@
1
+ import { install_dependency } from '../utils/install-dep.js';
2
+ export async function processIframe(state, htmlfile, $, imgElement, isAboveTheFold, appendToBody) {
3
+ const iframe = $(imgElement);
4
+ // Reset loading attribute
5
+ iframe.removeAttr('loading');
6
+ const lazyloadOptions = state.options.iframe.lazyload;
7
+ if (lazyloadOptions.when === 'never') {
8
+ return;
9
+ }
10
+ if (lazyloadOptions.when === 'always' ||
11
+ (lazyloadOptions.when === 'below-the-fold' && !isAboveTheFold)) {
12
+ if (lazyloadOptions.how === 'native') {
13
+ iframe.attr('loading', 'lazy');
14
+ }
15
+ else if (lazyloadOptions.how === 'js') {
16
+ const src = iframe.attr('src');
17
+ if (src) {
18
+ iframe.attr('class', 'jampack-lozad');
19
+ iframe.attr('data-src', src);
20
+ iframe.removeAttr('src');
21
+ await install_dependency(state, htmlfile, {
22
+ source: {
23
+ npm_package_name: 'lozad',
24
+ absolute_path_to_file: '/dist',
25
+ filename: 'lozad.es.js',
26
+ },
27
+ destination: {
28
+ folder_name: 'lozad-1.16',
29
+ code_loader: `import lozad from "./lozad.es.js";
30
+ const observer = lozad('.jampack-lozad', { rootMargin: '100px 0px', threshold: [0.1] });
31
+ observer.observe();`,
32
+ },
33
+ }, appendToBody);
34
+ }
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * @type import('./error.cjs').ProloadError
5
+ */
6
+ class ProloadError extends Error {
7
+ constructor(opts = {}) {
8
+ super(opts.message);
9
+ this.name = 'ProloadError';
10
+ this.code = opts.code || 'ERR_PROLOAD_INVALID';
11
+ if (Error.captureStackTrace) {
12
+ Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ }
15
+ }
16
+ /**
17
+ * @type import('./error.cjs').assert
18
+ */
19
+ function assert(bool, message, code) {
20
+ if (bool)
21
+ return;
22
+ if (message instanceof Error)
23
+ throw message;
24
+ throw new ProloadError({ message, code });
25
+ }
26
+ module.exports.ProloadError = ProloadError;
27
+ module.exports.assert = assert;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @type import('./error.cjs').ProloadError
3
+ */
4
+ export class ProloadError extends Error {
5
+ constructor(opts?: {});
6
+ code: any;
7
+ }
8
+ /**
9
+ * @type import('./error.cjs').assert
10
+ */
11
+ export function assert(bool: any, message: any, code: any): any;
@@ -0,0 +1,27 @@
1
+ export default load;
2
+ import { ProloadError } from "../error.cjs";
3
+ /**
4
+ *
5
+ * @param {string} namespace
6
+ * @param {import('../index').LoadOptions} opts
7
+ */
8
+ declare function load(namespace: string, opts?: any): Promise<{
9
+ filePath: any;
10
+ raw: any;
11
+ value: {};
12
+ } | undefined>;
13
+ declare namespace load {
14
+ export { defaultPlugins as plugins };
15
+ export function use(plugins: any): void;
16
+ }
17
+ /**
18
+ *
19
+ * @param {string} namespace
20
+ * @param {import('../index').LoadOptions} opts
21
+ */
22
+ declare function resolveConfig(namespace: string, opts?: any): Promise<any>;
23
+ declare const defaultPlugins: {
24
+ name: string;
25
+ transform(mdl: any): any;
26
+ }[];
27
+ export { ProloadError, resolveConfig as resolve };
@@ -0,0 +1,231 @@
1
+ import escalade from "escalade";
2
+ import { join, dirname, extname, resolve } from "path";
3
+ import deepmerge from "deepmerge";
4
+ import { existsSync, readdir, readFile, stat } from "fs";
5
+ import { promisify } from "util";
6
+ import { createRequire } from "module";
7
+ import requireOrImport from "./requireOrImport.mjs";
8
+ import { assert, ProloadError } from "../error.cjs";
9
+ export { ProloadError };
10
+ const toStats = promisify(stat);
11
+ const toRead = promisify(readdir);
12
+ const toReadFile = promisify(readFile);
13
+ const require = createRequire(import.meta.url);
14
+ let merge = deepmerge;
15
+ const defaultExtensions = ['js', 'cjs', 'mjs'];
16
+ const defaultFileNames = ['[name].config'];
17
+ const validNames = (namespace) => {
18
+ const extensionPlugins = load.plugins.filter(p => Array.isArray(p.extensions));
19
+ const fileNamePlugins = load.plugins.filter(p => Array.isArray(p.fileNames));
20
+ const validExtensions = [...defaultExtensions].concat(...extensionPlugins.map(p => p.extensions));
21
+ const validFileNames = [...defaultFileNames].concat(...fileNamePlugins.map(p => p.fileNames));
22
+ const result = validFileNames
23
+ .map(fileName => fileName.replace('[name]', namespace))
24
+ .reduce((acc, fileName) => {
25
+ return [...acc].concat(...validExtensions.map(ext => `${fileName}${ext ? '.' + ext.replace(/^\./, '') : ''}`));
26
+ }, []);
27
+ return result;
28
+ };
29
+ /**
30
+ * @param {any} val
31
+ * @returns {val is Record<any, any>}
32
+ */
33
+ const isObject = (val) => val != null && typeof val === "object" && Array.isArray(val) === false;
34
+ const requireOrImportWithMiddleware = (filePath) => {
35
+ let registerPlugins = load.plugins.filter((plugin) => typeof plugin.register !== "undefined");
36
+ let transformPlugins = load.plugins.filter((plugin) => typeof plugin.transform !== "undefined");
37
+ return requireOrImport(filePath, { middleware: registerPlugins }).then(async (mdl) => Promise.all(transformPlugins.map((plugin) => {
38
+ return Promise.resolve(plugin.transform(mdl)).then((result) => {
39
+ if (result)
40
+ mdl = result;
41
+ });
42
+ })).then(() => mdl));
43
+ };
44
+ /**
45
+ *
46
+ * @param {string} namespace
47
+ * @param {{ filePath: string, extension: string }} opts
48
+ * @returns {Promise<{ filePath: string, value: string }>}
49
+ */
50
+ async function resolveExtension(namespace, { filePath, extension }) {
51
+ let resolvedPath;
52
+ if (extension.startsWith("./") || extension.startsWith("../")) {
53
+ if (extname(extension) === "") {
54
+ resolvedPath = resolve(dirname(filePath), `${extension}${extname(filePath)}`);
55
+ }
56
+ if (!existsSync(resolvedPath))
57
+ resolvedPath = null;
58
+ if (!resolvedPath) {
59
+ resolvedPath = resolve(dirname(filePath), extension);
60
+ }
61
+ if (!existsSync(resolvedPath))
62
+ resolvedPath = null;
63
+ }
64
+ if (!resolvedPath) {
65
+ const pkg = require.resolve(extension, {
66
+ cwd: dirname(filePath),
67
+ });
68
+ const accepted = validNames(namespace);
69
+ for (const config of accepted) {
70
+ try {
71
+ resolvedPath = `${pkg}/${config}`;
72
+ if (resolvedPath && existsSync(resolvedPath)) {
73
+ break;
74
+ }
75
+ else {
76
+ resolvedPath = null;
77
+ }
78
+ }
79
+ catch (e) { }
80
+ }
81
+ }
82
+ if (!resolvedPath) {
83
+ resolvedPath = require.resolve(extension, { cwd: dirname(filePath) });
84
+ }
85
+ if (!resolvedPath)
86
+ return;
87
+ const value = await requireOrImportWithMiddleware(resolvedPath);
88
+ return { filePath: resolvedPath, value };
89
+ }
90
+ async function resolveExtensions(namespace, { filePath, value: raw, context }, acc = {}) {
91
+ let value = typeof raw === "function" ? await raw(context) : raw;
92
+ if (Array.isArray(value))
93
+ return value;
94
+ assert(isObject(value), `${namespace} configuration expects an "object" but encountered ${value}`);
95
+ acc = merge(acc, value);
96
+ if (!("extends" in value))
97
+ return acc;
98
+ assert(Array.isArray(value.extends), `${namespace} "extends" must be an array`);
99
+ const configs = await Promise.all(value.extends.map((extension) => resolveExtension(namespace, { filePath, extension }).then((config) => resolveExtensions(namespace, { ...config, context }, acc))));
100
+ for (const config of configs) {
101
+ acc = merge(acc, config);
102
+ }
103
+ delete acc.extends;
104
+ return acc;
105
+ }
106
+ /**
107
+ *
108
+ * @param {string} namespace
109
+ * @param {import('../index').LoadOptions} opts
110
+ */
111
+ async function resolveConfig(namespace, opts = {}) {
112
+ const accepted = validNames(namespace);
113
+ const { context, accept } = opts;
114
+ const input = opts.cwd || process.cwd();
115
+ let mustExist = true;
116
+ if (typeof opts.mustExist !== "undefined") {
117
+ mustExist = opts.mustExist;
118
+ }
119
+ if (typeof opts.merge === "function") {
120
+ merge = opts.merge;
121
+ }
122
+ let filePath;
123
+ if (typeof opts.filePath === "string") {
124
+ const absPath = opts.filePath.startsWith(".")
125
+ ? resolve(opts.filePath, input)
126
+ : opts.filePath;
127
+ if (existsSync(absPath)) {
128
+ filePath = absPath;
129
+ }
130
+ }
131
+ else {
132
+ filePath = await escalade(input, async (dir, names) => {
133
+ if (accept) {
134
+ for (const n of names) {
135
+ if (accept(n, { directory: dir }) === true)
136
+ return n;
137
+ }
138
+ }
139
+ for (const n of accepted) {
140
+ if (names.includes(n))
141
+ return n;
142
+ }
143
+ if (names.includes("config")) {
144
+ let d = join(dir, "config");
145
+ let _, stats = await toStats(d);
146
+ let entries = [];
147
+ if (stats.isDirectory()) {
148
+ entries = await toRead(d);
149
+ for (const n of accepted) {
150
+ if (entries.includes(n))
151
+ return join("config", n);
152
+ }
153
+ }
154
+ }
155
+ if (names.includes("package.json")) {
156
+ let file = join(dir, "package.json");
157
+ let _, contents = await toReadFile(file).then((r) => JSON.parse(r.toString()));
158
+ if (contents[namespace])
159
+ return "package.json";
160
+ }
161
+ });
162
+ }
163
+ if (mustExist) {
164
+ assert(!!filePath, `Unable to resolve a ${namespace} configuration`, "ERR_PROLOAD_NOT_FOUND");
165
+ }
166
+ else if (!filePath) {
167
+ return;
168
+ }
169
+ return filePath;
170
+ }
171
+ /**
172
+ *
173
+ * @param {string} namespace
174
+ * @param {import('../index').LoadOptions} opts
175
+ */
176
+ async function load(namespace, opts = {}) {
177
+ const { context } = opts;
178
+ let mustExist = true;
179
+ if (typeof opts.mustExist !== 'undefined') {
180
+ mustExist = opts.mustExist;
181
+ }
182
+ const filePath = await resolveConfig(namespace, opts);
183
+ if (mustExist) {
184
+ assert(!!filePath, `Unable to resolve a ${namespace} configuration`, 'ERR_PROLOAD_NOT_FOUND');
185
+ }
186
+ else if (!filePath) {
187
+ return;
188
+ }
189
+ let rawValue = await requireOrImportWithMiddleware(filePath);
190
+ if (filePath.endsWith('package.json'))
191
+ rawValue = rawValue[namespace];
192
+ // Important: "empty" config files will be returned as `Module {}`
193
+ // We should handle them here
194
+ if (rawValue && !(rawValue instanceof Object)) {
195
+ if (mustExist) {
196
+ assert(true, `Resolved a ${namespace} configuration, but no configuration was exported`, "ERR_PROLOAD_NOT_FOUND");
197
+ }
198
+ else {
199
+ return;
200
+ }
201
+ }
202
+ const resolvedValue = await resolveExtensions(namespace, {
203
+ filePath,
204
+ value: rawValue,
205
+ context,
206
+ });
207
+ return {
208
+ filePath,
209
+ raw: rawValue,
210
+ value: resolvedValue,
211
+ };
212
+ }
213
+ const defaultPlugins = [
214
+ {
215
+ name: "@proload/extract-default",
216
+ transform(mdl) {
217
+ if (mdl.default && Object.keys(mdl).length === 1) {
218
+ return mdl.default;
219
+ }
220
+ ;
221
+ return mdl;
222
+ },
223
+ },
224
+ ];
225
+ /** @type import('../index').Plugin[] */
226
+ load.plugins = defaultPlugins;
227
+ load.use = (plugins) => {
228
+ load.plugins = [...load.plugins, ...plugins];
229
+ };
230
+ export default load;
231
+ export { resolveConfig as resolve };
@@ -0,0 +1,7 @@
1
+ /**
2
+ *
3
+ * @param {string} filePath
4
+ */
5
+ export default function requireOrImport(filePath: string, { middleware }?: {
6
+ middleware?: any[] | undefined;
7
+ }): Promise<any>;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ import { createRequire } from 'module';
3
+ import { pathToFileURL } from 'url';
4
+ let require = createRequire(import.meta.url);
5
+ /**
6
+ *
7
+ * @param {string} filePath
8
+ */
9
+ export default async function requireOrImport(filePath, { middleware = [] } = {}) {
10
+ await Promise.all(middleware.map(plugin => plugin.register(filePath)));
11
+ return new Promise(async (resolve, reject) => {
12
+ const fileUrl = pathToFileURL(filePath).toString();
13
+ try {
14
+ const mdl = await import(fileUrl);
15
+ return resolve(mdl);
16
+ }
17
+ catch (e) {
18
+ reject(e);
19
+ }
20
+ });
21
+ }
package/dist/state.d.ts CHANGED
@@ -26,6 +26,7 @@ export declare class GlobalState {
26
26
  targets: import("lightningcss").Targets;
27
27
  compressedFiles: Set<string>;
28
28
  issues: Map<string, Issue[]>;
29
+ installed_dependencies: Set<string>;
29
30
  summary: Summary;
30
31
  summaryByExtension: Record<string, Summary>;
31
32
  reportIssue(sourceFile: string, issue: Issue): void;
package/dist/state.js CHANGED
@@ -8,6 +8,7 @@ export class GlobalState {
8
8
  this.targets = defaultTargets();
9
9
  this.compressedFiles = new Set();
10
10
  this.issues = new Map();
11
+ this.installed_dependencies = new Set();
11
12
  this.summary = {
12
13
  nbFiles: 0,
13
14
  nbFilesCompressed: 0,
@@ -0,0 +1,12 @@
1
+ import { GlobalState } from '../state.js';
2
+ export declare function install_dependency(state: GlobalState, html_file: string, options: {
3
+ source: {
4
+ npm_package_name: string;
5
+ absolute_path_to_file: string;
6
+ filename: string;
7
+ };
8
+ destination: {
9
+ folder_name: string;
10
+ code_loader: string;
11
+ };
12
+ }, appendToBody: Record<string, string>): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { createRequire } from 'node:module';
4
+ const require = createRequire(import.meta.url);
5
+ export async function install_dependency(state, html_file, options, appendToBody) {
6
+ const path_html = path.dirname('/' + html_file);
7
+ const folder = `/_jampack/${options.destination.folder_name}`;
8
+ const src_filename = options.source.filename;
9
+ const url_loader = `${folder}/loader.js`;
10
+ // Install dependency in /_jampack if not done yet
11
+ if (!state.installed_dependencies.has(options.source.npm_package_name)) {
12
+ const quickLinkDestination = path.join(state.dir, folder, src_filename);
13
+ const path_loader = path.join(state.dir, url_loader);
14
+ await fs.mkdir(path.join(state.dir, folder), { recursive: true });
15
+ // Write loader
16
+ const code_loader = options.destination.code_loader;
17
+ await fs.writeFile(path_loader, code_loader);
18
+ // Write quicklink code
19
+ const source = require.resolve(`${options.source.npm_package_name}${options.source.absolute_path_to_file}/${src_filename}`);
20
+ await fs.copyFile(source, quickLinkDestination);
21
+ state.installed_dependencies.add(options.source.npm_package_name);
22
+ }
23
+ // Add custom code to the end of body if not done yet
24
+ if (!(options.source.npm_package_name in appendToBody)) {
25
+ appendToBody[options.source.npm_package_name] = `<script type="module" src="${path.relative(path_html, url_loader)}"></script>`;
26
+ }
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divriots/jampack",
3
- "version": "0.30.3",
3
+ "version": "0.31.0",
4
4
  "cache-version": {
5
5
  "img": "v1",
6
6
  "img-ext": "v1"
@@ -28,7 +28,6 @@
28
28
  "dependencies": {
29
29
  "@commander-js/extra-typings": "^12.0.0",
30
30
  "@divriots/cheerio": "1.0.0-rc.12",
31
- "@proload/core": "^0.3.3",
32
31
  "@swc/core": "^1.4.16",
33
32
  "add": "^2.0.6",
34
33
  "browserslist": "^4.23.0",
@@ -42,6 +41,7 @@
42
41
  "html-minifier-terser": "^7.2.0",
43
42
  "kleur": "^4.1.5",
44
43
  "lightningcss": "^1.25.1",
44
+ "lozad": "^1.16.0",
45
45
  "mini-svg-data-uri": "^1.4.4",
46
46
  "ora": "^8.0.1",
47
47
  "quicklink": "^2.3.0",