diff --git a/.gitignore b/.gitignore
index 9e2c5aa0d..dc27a187d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,4 +31,6 @@ yarn-error.log*
**/runner-results
parallel-weights.json
-**/src/tempcodes
\ No newline at end of file
+**/src/tempcodes
+# Generated from apps/app/scripts/copy-vendor-assets.mjs during install/build.
+apps/app/public/vendor/
diff --git a/apps/api/package.json b/apps/api/package.json
index 9aeaeda02..b6a7cc950 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -9,7 +9,7 @@
"main": "dist/src/index.js",
"scripts": {
"build:assets": "npx tsx scripts/copy-assets-to-dist.ts",
- "build": "tsc --build && npm run build:assets",
+ "build": "tsc --build tsconfig.build.json && npm run build:assets",
"dev": "tsx watch src/index.ts",
"lint": "eslint --fix",
"lint:check": "eslint",
diff --git a/apps/api/tsconfig.build.json b/apps/api/tsconfig.build.json
new file mode 100644
index 000000000..e46f41e18
--- /dev/null
+++ b/apps/api/tsconfig.build.json
@@ -0,0 +1,12 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "tsBuildInfoFile": "../../.tsbuildinfo/apps/api.build.tsbuildinfo"
+ },
+ "references": [
+ {
+ "path": "../../packages/shared"
+ }
+ ],
+ "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"]
+}
diff --git a/apps/app/eslint.config.mjs b/apps/app/eslint.config.mjs
index d680e0156..3be2ee38f 100644
--- a/apps/app/eslint.config.mjs
+++ b/apps/app/eslint.config.mjs
@@ -6,6 +6,11 @@ import { createBaseConfig, reactConfig } from "@doenet-tools/eslint-config";
export default tseslint.config(
...createBaseConfig(import.meta.dirname),
...reactConfig,
+ {
+ // Vendor files are copied from node_modules for runtime use, so they
+ // should not be linted as part of the app source tree.
+ ignores: ["public/vendor/**"],
+ },
{
rules: {
"@typescript-eslint/triple-slash-reference": "off",
diff --git a/apps/app/package.json b/apps/app/package.json
index e4763e1d8..64fb3eda7 100644
--- a/apps/app/package.json
+++ b/apps/app/package.json
@@ -7,9 +7,10 @@
"./src/types": "./src/types.ts"
},
"scripts": {
- "build:assets": "vite build",
- "build": "tsc --build && npm run build:assets",
- "dev": "cross-env NODE_ENV=production vite",
+ "prepare:vendor": "node scripts/copy-vendor-assets.mjs",
+ "build:assets": "npm run prepare:vendor && vite build",
+ "build": "tsc --build tsconfig.build.json && npm run build:assets",
+ "dev": "npm run prepare:vendor && cross-env NODE_ENV=production vite",
"lint": "eslint --fix",
"lint:check": "eslint",
"test": "cypress open",
diff --git a/apps/app/scripts/copy-vendor-assets.mjs b/apps/app/scripts/copy-vendor-assets.mjs
new file mode 100644
index 000000000..e2f2e2392
--- /dev/null
+++ b/apps/app/scripts/copy-vendor-assets.mjs
@@ -0,0 +1,25 @@
+import { cp, mkdir, readdir, rm } from "fs/promises";
+import { dirname, join } from "path";
+import { fileURLToPath } from "url";
+
+const scriptDir = dirname(fileURLToPath(import.meta.url));
+const appDir = dirname(scriptDir);
+const repoRoot = dirname(dirname(appDir));
+
+// The v0.6 -> v0.7 syntax upgrader is only used from editor settings, but its
+// published bundle includes a large worker. Copying the runtime files into
+// public/vendor keeps that code out of Vite's main app bundle.
+const sourceDir = join(repoRoot, "node_modules", "@doenet", "v06-to-v07");
+const targetDir = join(appDir, "public", "vendor", "doenet", "v06-to-v07");
+
+await rm(targetDir, { recursive: true, force: true });
+await mkdir(targetDir, { recursive: true });
+
+const files = await readdir(sourceDir);
+for (const file of files) {
+ // The package entrypoint dynamically imports the hashed worker file, so ship
+ // both files together and let the browser load them on demand at runtime.
+ if (file === "index.js" || /^lib_doenetml_worker_bg-.*\.js$/.test(file)) {
+ await cp(join(sourceDir, file), join(targetDir, file));
+ }
+}
diff --git a/apps/app/src/index.tsx b/apps/app/src/index.tsx
index 1b3f1e097..5aa11b968 100644
--- a/apps/app/src/index.tsx
+++ b/apps/app/src/index.tsx
@@ -24,28 +24,15 @@ import {
loader as sharedActivitiesLoader,
SharedActivities,
} from "./paths/SharedActivities";
-import {
- loader as activityViewerLoader,
- ActivityViewer,
-} from "./paths/ActivityViewer";
import { loader as assignedLoader, Assigned } from "./paths/Assigned";
import {
loader as assignmentResponseOverviewLoader,
AssignmentData as AssignmentResponseOverview,
} from "./paths/AssignmentResponseOverview";
-import {
- loader as assignmentResponseStudentLoader,
- AssignmentResponseStudent,
-} from "./paths/AssignmentResponseStudent";
import {
action as enterClassCodeAction,
EnterClassCode,
} from "./paths/EnterClassCode";
-import {
- loader as assignmentViewerLoader,
- action as assignmentViewerAction,
- AssignmentViewer,
-} from "./paths/AssignmentViewer";
import { loader as studentsLoader, Students } from "./paths/Students";
import {
loader as studentAssignmentScoresLoader,
@@ -63,12 +50,6 @@ import {
loader as editorHeaderLoader,
EditorHeader,
} from "./paths/editor/EditorHeader";
-import {
- DoenetMLComparison,
- loader as doenetMLComparisonLoader,
- action as doenetMLComparisonAction,
-} from "./paths/DoenetMLComparison";
-import { mathjaxConfig } from "@doenet/doenetml-iframe";
import { SignIn, action as signInAction } from "./paths/SignIn";
import {
ConfirmSignIn,
@@ -83,14 +64,6 @@ import {
LibraryActivities,
loader as libraryActivitiesLoader,
} from "./paths/LibraryActivities";
-import {
- DocEditorViewMode,
- loader as docEditorViewModeLoader,
-} from "./paths/editor/DocEditorViewMode";
-import {
- loader as docEditorEditModeLoader,
- DocEditorEditMode,
-} from "./paths/editor/DocEditorEditMode";
import {
CompoundEditorViewMode,
loader as compoundEditorViewModeLoader,
@@ -105,10 +78,6 @@ import {
} from "./paths/editor/EditorSettingsMode";
import axios, { AxiosError } from "axios";
import { loadShareStatus } from "./popups/ShareMyContentModal";
-import {
- DocEditorHistoryMode,
- loader as docEditorHistoryModeLoader,
-} from "./paths/editor/DocEditorHistoryMode";
import {
DocEditorRemixMode,
loader as docEditorRemixModeLoader,
@@ -122,12 +91,64 @@ import {
loader as sharedWithMeLoader,
} from "./paths/SharedWithMe";
import { editorUrl } from "./utils/url";
-import { ScratchPad, loader as scratchPadLoader } from "./paths/ScratchPad";
import { About } from "./paths/About";
-import { RawViewer, loader as rawViewerLoader } from "./paths/RawViewer";
import { GetInvolved } from "./paths/GetInvolved";
import { Events } from "./paths/Events";
import { QuickLinks } from "./paths/QuickLinks";
+import { mathjaxConfig } from "./utils/mathjaxConfig";
+
+async function loadActivityViewerRoute() {
+ const route = await import("./paths/ActivityViewer");
+ return { loader: route.loader, Component: route.ActivityViewer };
+}
+
+async function loadAssignmentResponseStudentRoute() {
+ const route = await import("./paths/AssignmentResponseStudent");
+ return { loader: route.loader, Component: route.AssignmentResponseStudent };
+}
+
+async function loadAssignmentViewerRoute() {
+ const route = await import("./paths/AssignmentViewer");
+ return {
+ loader: route.loader,
+ action: route.action,
+ Component: route.AssignmentViewer,
+ };
+}
+
+async function loadDoenetMLComparisonRoute() {
+ const route = await import("./paths/DoenetMLComparison");
+ return {
+ loader: route.loader,
+ action: route.action,
+ Component: route.DoenetMLComparison,
+ };
+}
+
+async function loadDocEditorEditModeRoute() {
+ const route = await import("./paths/editor/DocEditorEditMode");
+ return { loader: route.loader, Component: route.DocEditorEditMode };
+}
+
+async function loadDocEditorViewModeRoute() {
+ const route = await import("./paths/editor/DocEditorViewMode");
+ return { loader: route.loader, Component: route.DocEditorViewMode };
+}
+
+async function loadDocEditorHistoryModeRoute() {
+ const route = await import("./paths/editor/DocEditorHistoryMode");
+ return { loader: route.loader, Component: route.DocEditorHistoryMode };
+}
+
+async function loadScratchPadRoute() {
+ const route = await import("./paths/ScratchPad");
+ return { loader: route.loader, Component: route.ScratchPad };
+}
+
+async function loadRawViewerRoute() {
+ const route = await import("./paths/RawViewer");
+ return { loader: route.loader, Component: route.RawViewer };
+}
const router = createBrowserRouter([
{
@@ -243,10 +264,9 @@ const router = createBrowserRouter([
},
{
path: "activityViewer/:contentId",
- loader: activityViewerLoader,
+ lazy: loadActivityViewerRoute,
action: genericAction,
errorElement: ,
- element: ,
},
{
path: "documentEditor/:contentId",
@@ -257,14 +277,12 @@ const router = createBrowserRouter([
children: [
{
path: "edit",
- loader: docEditorEditModeLoader,
- element: ,
+ lazy: loadDocEditorEditModeRoute,
errorElement: ,
},
{
path: "view",
- loader: docEditorViewModeLoader,
- element: ,
+ lazy: loadDocEditorViewModeRoute,
errorElement: ,
},
{
@@ -276,9 +294,8 @@ const router = createBrowserRouter([
},
{
path: "history",
- loader: docEditorHistoryModeLoader,
+ lazy: loadDocEditorHistoryModeRoute,
action: genericAction,
- element: ,
errorElement: ,
},
{
@@ -335,9 +352,7 @@ const router = createBrowserRouter([
},
{
path: "activityCompare/:contentId/:compareId",
- loader: doenetMLComparisonLoader,
- action: doenetMLComparisonAction,
- element: ,
+ lazy: loadDoenetMLComparisonRoute,
errorElement: ,
},
{
@@ -355,8 +370,7 @@ const router = createBrowserRouter([
},
{
path: "assignedData/:contentId",
- loader: assignmentResponseStudentLoader,
- element: ,
+ lazy: loadAssignmentResponseStudentRoute,
errorElement: ,
},
{
@@ -368,8 +382,7 @@ const router = createBrowserRouter([
},
{
path: "assignmentData/:contentId/:studentUserId",
- loader: assignmentResponseStudentLoader,
- element: ,
+ lazy: loadAssignmentResponseStudentRoute,
errorElement: ,
},
{
@@ -386,9 +399,7 @@ const router = createBrowserRouter([
},
{
path: "code/:classCode",
- loader: assignmentViewerLoader,
- action: assignmentViewerAction,
- element: ,
+ lazy: loadAssignmentViewerRoute,
errorElement: ,
},
{
@@ -417,17 +428,15 @@ const router = createBrowserRouter([
},
{
path: "scratchPad",
- loader: scratchPadLoader,
+ lazy: loadScratchPadRoute,
action: genericAction,
errorElement: ,
- element: ,
},
],
},
{
path: "/embed/:viewId",
- element: ,
- loader: rawViewerLoader,
+ lazy: loadRawViewerRoute,
errorElement: (
diff --git a/apps/app/src/paths/AssignmentResponseOverview.tsx b/apps/app/src/paths/AssignmentResponseOverview.tsx
index d09e04d86..dee559ea9 100644
--- a/apps/app/src/paths/AssignmentResponseOverview.tsx
+++ b/apps/app/src/paths/AssignmentResponseOverview.tsx
@@ -46,14 +46,13 @@ import {
DoenetmlVersion,
UserInfoWithEmail,
} from "../types";
-import { isActivitySource } from "@doenet/assignment-viewer";
import {
compileActivityFromContent,
contentTypeToName,
getIconInfo,
} from "../utils/activity";
import { BiDownArrowAlt, BiUpArrowAlt } from "react-icons/bi";
-import { ActivitySource } from "@doenet-tools/shared";
+import { ActivitySource, isActivitySource } from "@doenet-tools/shared";
import { EditAssignmentSettings } from "../widgets/editor/EditAssignmentSettings";
import { DateTime } from "luxon";
import { NameBar } from "../widgets/NameBar";
diff --git a/apps/app/src/paths/editor/DoenetMLVersionComponents.tsx b/apps/app/src/paths/editor/DoenetMLVersionComponents.tsx
index 98032056a..a65fa3a91 100644
--- a/apps/app/src/paths/editor/DoenetMLVersionComponents.tsx
+++ b/apps/app/src/paths/editor/DoenetMLVersionComponents.tsx
@@ -18,7 +18,7 @@ import {
import { DoenetmlVersion } from "../../types";
import axios from "axios";
import { optimistic } from "../../utils/optimistic_ui";
-import { updateSyntaxFromV06toV07 } from "@doenet/v06-to-v07";
+import { updateSyntaxFromV06toV07 } from "../../utils/v06ToV07";
/**
* Renders a DoenetML version selector.
@@ -159,6 +159,8 @@ async function performSyntaxUpgrade(
const source: string = data.source;
+ // This upgrade helper is loaded from public/vendor on demand so the large
+ // converter bundle doesn't inflate the default app asset build.
const update = await updateSyntaxFromV06toV07(source);
const upgraded = update.xml;
diff --git a/apps/app/src/utils/mathjaxConfig.ts b/apps/app/src/utils/mathjaxConfig.ts
new file mode 100644
index 000000000..e6eb9974b
--- /dev/null
+++ b/apps/app/src/utils/mathjaxConfig.ts
@@ -0,0 +1,18 @@
+export const mathjaxConfig = {
+ tex: {
+ tags: "ams",
+ macros: {
+ lt: "<",
+ gt: ">",
+ amp: "&",
+ var: ["\\mathrm{#1}", 1],
+ csch: "\\operatorname{csch}",
+ sech: "\\operatorname{sech}",
+ erf: "\\operatorname{erf}",
+ },
+ displayMath: [["\\[", "\\]"]],
+ },
+ output: {
+ displayOverflow: "linebreak",
+ },
+};
diff --git a/apps/app/src/utils/v06ToV07.ts b/apps/app/src/utils/v06ToV07.ts
new file mode 100644
index 000000000..6a8bcc739
--- /dev/null
+++ b/apps/app/src/utils/v06ToV07.ts
@@ -0,0 +1,36 @@
+type SyntaxUpdateResult = {
+ xml: string;
+ vfile: {
+ messages: unknown[];
+ };
+};
+
+type SyntaxUpdaterModule = {
+ updateSyntaxFromV06toV07: (
+ source: string,
+ options?: unknown,
+ ) => Promise;
+};
+
+let syntaxUpdaterModule: Promise | null = null;
+// Load the syntax upgrader from public/vendor instead of bundling it into the
+// app, since it is only needed when someone explicitly runs the upgrade action.
+const syntaxUpdaterModulePath = "/vendor/doenet/v06-to-v07/index.js";
+
+async function loadSyntaxUpdater() {
+ syntaxUpdaterModule ??= import(
+ /* @vite-ignore */
+ // Keep this as a runtime URL so Vite doesn't try to pull the large
+ // converter bundle and its worker back into the app asset build.
+ syntaxUpdaterModulePath
+ ) as Promise;
+
+ return await syntaxUpdaterModule;
+}
+
+export async function updateSyntaxFromV06toV07(source: string) {
+ // Cache the module promise so repeated upgrades in one session do not
+ // re-fetch the vendored bundle.
+ const module = await loadSyntaxUpdater();
+ return await module.updateSyntaxFromV06toV07(source);
+}
diff --git a/apps/app/tsconfig.build.json b/apps/app/tsconfig.build.json
new file mode 100644
index 000000000..3a4b82906
--- /dev/null
+++ b/apps/app/tsconfig.build.json
@@ -0,0 +1,19 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "tsBuildInfoFile": "../../.tsbuildinfo/apps/app.build.tsbuildinfo",
+ "types": ["vite/client", "node"]
+ },
+ "references": [
+ {
+ "path": "../../packages/shared"
+ }
+ ],
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
+ "exclude": [
+ "src/**/*.cy.ts",
+ "src/**/*.cy.tsx",
+ "src/**/*.test.ts",
+ "src/**/*.test.tsx"
+ ]
+}
diff --git a/package.json b/package.json
index e2a5fb4a5..61d930024 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"packages/*"
],
"scripts": {
- "postinstall": "npm run prisma:generate --workspace @doenet-tools/api",
+ "postinstall": "npm run prisma:generate --workspace @doenet-tools/api && npm run prepare:vendor --workspace @doenet-tools/app",
"setup": "node scripts/setup.js",
"db:setup": "npm run prisma:migrate-dev --workspace @doenet-tools/api && npm run prisma:seed --workspace @doenet-tools/api",
"format": "prettier . --write",
diff --git a/tsconfig.json b/tsconfig.json
index 7ccadb4be..0ca0772c2 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,10 +2,9 @@
{
"files": [],
"references": [
- { "path": "apps/web" },
- { "path": "apps/app" },
- { "path": "apps/api" },
{ "path": "packages/shared" },
- { "path": "packages/e2e-tests" }
+ { "path": "apps/web" },
+ { "path": "apps/app/tsconfig.build.json" },
+ { "path": "apps/api/tsconfig.build.json" }
]
}