koala73/worldmonitor · error · Error

no module entry script in ${dashboardPath} — run: ${BUILD_CO

Error message

no module entry script in ${dashboardPath} — run: ${BUILD_COMMANDS.dashboard}

What it means

deferredDashboardAppDependencies locates the dashboard's `<script type="module" src=...>` entry in dashboard.html to inspect its loader for the deferred App chunk import. If no module entry script tag (or an src outside assets/) is found, it throws with a hint to run the dashboard build. This keeps the preload-dependency measurement anchored to a real production entry chunk.

Solutions

  1. Run BUILD_COMMANDS.dashboard to regenerate a production dashboard.html with a hashed module entry
  2. Inspect the <script type="module"> tag in dist/dashboard.html and restore it if the template was edited
  3. Ensure the entry src resolves under assets/ (check Vite base/assetsDir) so assetFileNameFromUrl can extract the file name
  4. If the HTML shape legitimately changed, update the regex in deferredDashboardAppDependencies

Example fix

<!-- before: template missing module entry -->
<!-- no script tag -->
<!-- after -->
<script type="module" src="/assets/index-abc123.js"></script>
Defensive patterns

Strategy: validation

Validate before calling

const html = readFileSync(join(distDir, 'dashboard.html'), 'utf8');
if (!/<script\b[^>]*type=["']module["'][^>]*src=["'][^"']+\.js["']/i.test(html)) throw new Error('dashboard.html has no module entry script — run the production build');

Type guard

const hasModuleEntry = (html) => /<script\b[^>]*\btype=["']module["'][^>]*\bsrc=["'][^"']+\.js["']/i.test(html);

Try / catch

try {
  const deps = deferredDashboardAppDependencies(distDir);
} catch (e) {
  if (e.message.includes('no module entry script')) {
    console.error('Production dashboard build missing module entry; rebuild.');
    process.exitCode = 1;
  } else throw e;
}

Prevention

When it happens

Trigger: Calling deferredDashboardAppDependencies on dashboard.html lacking a `<script type="module" src=".../assets/*.js">` tag — dev-mode HTML (no hashed module script), HTML where the module script lost its type="module" attribute, or the src URL not under /assets/ so assetFileNameFromUrl returns null.

Common situations: Budget check run before the production build; Vite config switched to a legacy/no-module output; someone hand-edited index.html template and removed or altered the module script tag; regex mismatch after changing attribute order/quotes in the template.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/8eebdc39e258771a. Report an issue: GitHub.

Appendix: source

Thrown at scripts/bundle-budgets.mjs:201

/**
 * The asset files Vite preloads for the dashboard entry's deferred
 * `import('./App-<hash>.js')`, in preload-list order and including CSS, or
 * null when the entry has no such import (App is then in the initial payload).
 *
 * Vite emits the loader as `P(() => import('./App-x.js')…, __vite__mapDeps([…]))`,
 * where the indices point into one `m.f=[…]` table and the list starts with
 * the imported chunk itself. Anything that breaks that shape throws: a parser
 * that silently measured the wrong list would let the application graph grow
 * unseen.
 */
export function deferredDashboardAppDependencies(distDir) {
  const { dashboardPath, html } = readDashboardHtml(distDir);
  const entryUrl = /<script\b[^>]*\btype=["']module["'][^>]*\bsrc=["']([^"']+\.js(?:[?#][^"']*)?)["']/i
    .exec(html)?.[1];
  const entryName = entryUrl ? assetFileNameFromUrl(entryUrl) : null;
  if (!entryName) {
    throw new Error(`no module entry script in ${dashboardPath} — run: ${BUILD_COMMANDS.dashboard}`);
  }
  const entryPath = join(distDir, 'assets', entryName);
  let source;
  try {
    source = readFileSync(entryPath, 'utf8');
  } catch (error) {
    throw new Error(`cannot read dashboard entry ${entryPath}: ${error.message}`);
  }

  const appImport = /import\(\s*["']\.\/(App-[A-Za-z0-9_-]{8}\.js)["']\s*\)/.exec(source);
  if (!appImport) return null;
  const appFile = appImport[1];
  const unsupported = (reason) => new Error(
    `${entryPath}: ${reason} for the deferred ./${appFile} import — the Vite loader shape changed; `
    + 'update deferredDashboardAppDependencies in scripts/bundle-budgets.mjs',
  );

  const afterImport = source.slice(appImport.index + appImport[0].length);

View on GitHub (pinned to 7d06c8633d)