mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-03 14:58:18 +00:00
Reduce production bundles while preserving host registration
This commit is contained in:
1 parent
b9dcb0d4c7
commit
b8d4ff2c16
10 files changed
+133
-20
No files matched your search
@@ -47,6 +47,12 @@ npm run test:e2e:browser-apps:interop # Run WebApp → WebPeer → CLI in Com
|
|||||||
npm run test:e2e:obsidian:local-suite # Run the real Obsidian local suite
|
npm run test:e2e:obsidian:local-suite # Run the real Obsidian local suite
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Production minification
|
||||||
|
|
||||||
|
Production builds remove unused declarations and shorten local identifiers while retaining class and function names used by module discovery and diagnostic stacks. `terser.config.mjs` also preserves property names, top-level bindings, and identifiers visible to direct `eval` calls. Keep these boundaries when changing compression settings; `test/build/terserConfig.unit.spec.ts` checks the generated code against host properties, persisted keys, reflective names, and exception stacks.
|
||||||
|
|
||||||
|
Compare bundle bytes and gzip bytes from the same source, dependency artefacts, and build inputs. Validate import-time registration and the resulting plug-in in the affected real-runtime workflows. The iOS syntax check is one part of compatibility verification; it does not measure performance or replace physical-device testing.
|
||||||
|
|
||||||
### Tips
|
### Tips
|
||||||
|
|
||||||
Select the narrowest unit, integration, CLI E2E, or real Obsidian E2E command that owns the behaviour being changed. The obsolete mocked browser Harness has been retired.
|
Select the narrowest unit, integration, CLI E2E, or real Obsidian E2E command that owns the behaviour being changed. The obsolete mocked browser Harness has been retired.
|
||||||
|
|||||||
+1
-1
@@ -194,7 +194,7 @@ const context = await esbuild.context({
|
|||||||
platform: "browser",
|
platform: "browser",
|
||||||
metafile: true,
|
metafile: true,
|
||||||
sourcemap: prod ? false : "inline",
|
sourcemap: prod ? false : "inline",
|
||||||
treeShaking: false,
|
treeShaking: true,
|
||||||
outfile: "main_org.js",
|
outfile: "main_org.js",
|
||||||
mainFields: ["browser", "module", "main"],
|
mainFields: ["browser", "module", "main"],
|
||||||
minifyWhitespace: false,
|
minifyWhitespace: false,
|
||||||
|
|||||||
@@ -120,6 +120,13 @@ export default defineConfig([
|
|||||||
"@typescript-eslint/restrict-template-expressions": "warn",
|
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ["src/apps/cli/vite.config.ts", "src/apps/webapp/vite.config.ts"],
|
||||||
|
rules: {
|
||||||
|
// Vite loads these configurations before the application source alias is available.
|
||||||
|
"livesync-imports/prefer-source-alias": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
files: ["src/apps/browser/**/*.{ts,svelte}", "src/apps/webapp/**/*.ts"],
|
files: ["src/apps/browser/**/*.{ts,svelte}", "src/apps/webapp/**/*.ts"],
|
||||||
rules: {
|
rules: {
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
import { defaultServerConditions, defaultServerMainFields, defineConfig } from "vite";
|
import { defaultServerConditions, defaultServerMainFields, defineConfig } from "vite";
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||||
import { fileURLToPath, fs, isBuiltin, path } from "@vrtmrz/livesync-commonlib/node";
|
import { fileURLToPath, isBuiltin, path } from "@vrtmrz/livesync-commonlib/node";
|
||||||
|
import { readVersion } from "../readVersion";
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const resolve = (...args: string[]) => path.resolve(...args).replace(/\\/g, "/");
|
const resolve = (...args: string[]) => path.resolve(...args).replace(/\\/g, "/");
|
||||||
const repoRoot = path.resolve(__dirname, "../../..");
|
const repoRoot = path.resolve(__dirname, "../../..");
|
||||||
|
|
||||||
function readVersion(filePath: string): string | undefined {
|
|
||||||
const parsed: unknown = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
||||||
if (typeof parsed !== "object" || parsed === null || !("version" in parsed)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return typeof parsed.version === "string" ? parsed.version : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageVersion = readVersion(path.resolve(repoRoot, "package.json"));
|
const packageVersion = readVersion(path.resolve(repoRoot, "package.json"));
|
||||||
const manifestVersion = readVersion(path.resolve(repoRoot, "manifest.json"));
|
const manifestVersion = readVersion(path.resolve(repoRoot, "manifest.json"));
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { fs } from "@vrtmrz/livesync-commonlib/node";
|
||||||
|
|
||||||
|
/** Read a string-valued version field from a JSON file during Node builds. */
|
||||||
|
export function readVersion(filePath: string): string | undefined {
|
||||||
|
const parsed: unknown = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
||||||
|
if (typeof parsed !== "object" || parsed === null || !("version" in parsed)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return typeof parsed.version === "string" ? parsed.version : undefined;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { readVersion } from "./readVersion";
|
||||||
|
|
||||||
|
describe("readVersion", () => {
|
||||||
|
let directory: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
directory = mkdtempSync(join(tmpdir(), "livesync-read-version-"));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(directory, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns only string version values", () => {
|
||||||
|
const filePath = join(directory, "version.json");
|
||||||
|
writeFileSync(filePath, JSON.stringify({ version: "1.2.3" }));
|
||||||
|
expect(readVersion(filePath)).toBe("1.2.3");
|
||||||
|
|
||||||
|
for (const contents of ["null", "[]", "{}", '{"version": 42}', '{"version": null}']) {
|
||||||
|
writeFileSync(filePath, contents);
|
||||||
|
expect(readVersion(filePath)).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves file read and JSON parse errors", () => {
|
||||||
|
expect(() => readVersion(join(directory, "missing.json"))).toThrow();
|
||||||
|
|
||||||
|
const filePath = join(directory, "invalid.json");
|
||||||
|
writeFileSync(filePath, "not JSON");
|
||||||
|
expect(() => readVersion(filePath)).toThrow(SyntaxError);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||||
import { fileURLToPath, fs, path } from "@vrtmrz/livesync-commonlib/node";
|
import { fileURLToPath, path } from "@vrtmrz/livesync-commonlib/node";
|
||||||
|
import { readVersion } from "../readVersion";
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const repoRoot = path.resolve(__dirname, "../../..");
|
const repoRoot = path.resolve(__dirname, "../../..");
|
||||||
|
|
||||||
function readVersion(filePath: string): string | undefined {
|
|
||||||
const parsed: unknown = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
||||||
if (typeof parsed !== "object" || parsed === null || !("version" in parsed)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return typeof parsed.version === "string" ? parsed.version : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageVersion = readVersion(path.resolve(repoRoot, "package.json"));
|
const packageVersion = readVersion(path.resolve(repoRoot, "package.json"));
|
||||||
const manifestVersion = readVersion(path.resolve(repoRoot, "manifest.json"));
|
const manifestVersion = readVersion(path.resolve(repoRoot, "manifest.json"));
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
|
|||||||
+6
-1
@@ -60,7 +60,12 @@ const terserOption = {
|
|||||||
hoist_vars: true,
|
hoist_vars: true,
|
||||||
// toplevel: "vars",
|
// toplevel: "vars",
|
||||||
},
|
},
|
||||||
mangle: false,
|
// Keep property keys, reflective names, and eval-visible bindings stable.
|
||||||
|
mangle: {
|
||||||
|
properties: false,
|
||||||
|
toplevel: false,
|
||||||
|
eval: false,
|
||||||
|
},
|
||||||
|
|
||||||
ecma: 2018, // specify one of: 5, 2015, 2016, etc.
|
ecma: 2018, // specify one of: 5, 2015, 2016, etc.
|
||||||
enclose: false, // or specify true, or "args:values"
|
enclose: false, // or specify true, or "args:values"
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { runInNewContext } from "node:vm";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { minify } from "terser";
|
||||||
|
import { terserOption } from "../../terser.config.mjs";
|
||||||
|
|
||||||
|
const fixture = `
|
||||||
|
const host = require("obsidian");
|
||||||
|
class ModuleLog {
|
||||||
|
constructor(configuration) { this.savedSettings = configuration; }
|
||||||
|
read(longParameterName) {
|
||||||
|
const longIntermediateValue = host.prefix + longParameterName;
|
||||||
|
return { savedSettings: this.savedSettings, message: longIntermediateValue };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function renderMessage(inputValue) { return new ModuleLog({ persistedField: "unchanged" }).read(inputValue); }
|
||||||
|
function readWithEval(longEvalParameter) { return eval("longEvalParameter"); }
|
||||||
|
function failWithName() { throw new Error("fixture failure"); }
|
||||||
|
module.exports = { ModuleLog, renderMessage, readWithEval, failWithName };
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe("production minifier", () => {
|
||||||
|
it("shortens local identifiers while preserving host properties, module names, and diagnostics", async () => {
|
||||||
|
const result = await minify(fixture, { ...terserOption, sourceMap: false });
|
||||||
|
expect(result.code).toBeDefined();
|
||||||
|
const sandbox = {
|
||||||
|
module: {
|
||||||
|
exports: {} as {
|
||||||
|
ModuleLog: { name: string };
|
||||||
|
renderMessage(input: string): unknown;
|
||||||
|
readWithEval(input: string): string;
|
||||||
|
failWithName(): never;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
require: (name: string) => {
|
||||||
|
expect(name).toBe("obsidian");
|
||||||
|
return { prefix: "host:" };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
runInNewContext(result.code!, sandbox);
|
||||||
|
const api = sandbox.module.exports;
|
||||||
|
expect(api.ModuleLog.name).toBe("ModuleLog");
|
||||||
|
expect(api.renderMessage.name).toBe("renderMessage");
|
||||||
|
expect(api.renderMessage("sample")).toEqual({
|
||||||
|
savedSettings: { persistedField: "unchanged" },
|
||||||
|
message: "host:sample",
|
||||||
|
});
|
||||||
|
expect(api.readWithEval("evaluated")).toBe("evaluated");
|
||||||
|
let failure: Error | undefined;
|
||||||
|
try {
|
||||||
|
api.failWithName();
|
||||||
|
} catch (error) {
|
||||||
|
failure = error as Error;
|
||||||
|
}
|
||||||
|
expect(failure?.stack).toContain("failWithName");
|
||||||
|
expect(result.code).not.toContain("longParameterName");
|
||||||
|
expect(result.code).not.toContain("longIntermediateValue");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,6 +12,11 @@ Earlier releases remain available in the 1.0 release history, the 1.0 preview hi
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
- The plug-in bundle is smaller, reducing the download for installation and updates.
|
||||||
|
- Builds remove unused code and shorten local identifiers while retaining class and function names for diagnostics.
|
||||||
|
|
||||||
### Diagnostics
|
### Diagnostics
|
||||||
|
|
||||||
- Service log calls stay with the plug-in instance that registered them. Its log connection is released after the full shutdown, so stale instances cannot receive later service messages.
|
- Service log calls stay with the plug-in instance that registered them. Its log connection is released after the full shutdown, so stale instances cannot receive later service messages.
|
||||||
|
|||||||
Reference in new issue
Block a user