diff --git a/package.json b/package.json index 8e0a8c64..f082a5fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@code-dot-org/ml-playground", - "version": "0.0.48", + "version": "0.0.49", "private": false, "repository": { "type": "git", diff --git a/src/assetPath.ts b/src/assetPath.ts index c72d4324..def6ebb7 100644 --- a/src/assetPath.ts +++ b/src/assetPath.ts @@ -1,7 +1,22 @@ -( - global as unknown as Record -).__ml_playground_asset_public_path__ = './assets/'; - +// Consumer-facing setter for the ml-playground asset base path. +// +// The function writes a single property on the shared page global, +// which the main bundle reads at startup (see `setPublicPath.ts`) to +// assign webpack's `__webpack_public_path__`. That assignment is what +// controls image and chunk URLs at runtime. +// +// Ordering: call this BEFORE the ml-playground main bundle is loaded +// (i.e. before `require('@code-dot-org/ml-playground')` or any import +// that resolves through the main entry). Asset URLs are computed when +// each asset module first evaluates, so a late call can't retroactively +// rewrite them. The natural CommonJS pattern (`setAssetPath(...); const +// ml = require('@code-dot-org/ml-playground'); ...`) is correct; +// pure-ESM consumers should call this then `await import(...)` the +// main entry. +// +// No module-init side effect on purpose — the fallback for "consumer +// never set it" lives in `setPublicPath.ts` as `|| './'`, so there's +// one place to look when chasing publicPath behavior. export const setAssetPath = (path: string): void => { ( global as unknown as Record diff --git a/src/indexDev.tsx b/src/indexDev.tsx index 7f52a5e2..2aa07138 100644 --- a/src/indexDev.tsx +++ b/src/indexDev.tsx @@ -1,4 +1,5 @@ import './assetPath'; +import './setPublicPath'; import {initAll, instructionsDismissed} from './index'; import queryString from 'query-string'; import {ModelDataToSave, SaveResponse} from './types'; diff --git a/src/indexProd.tsx b/src/indexProd.tsx index fc08e620..b1205f15 100644 --- a/src/indexProd.tsx +++ b/src/indexProd.tsx @@ -1 +1,2 @@ +import './setPublicPath'; export {initAll, instructionsDismissed} from './index'; diff --git a/src/setPublicPath.ts b/src/setPublicPath.ts new file mode 100644 index 00000000..0c5e4e56 --- /dev/null +++ b/src/setPublicPath.ts @@ -0,0 +1,33 @@ +// Wire the runtime asset-path global into webpack's publicPath. +// +// Image imports (e.g. `import bot from './ai-bot-body.png'`) and any +// other webpack `asset` / `asset/resource` modules resolve their URLs +// against webpack's runtime `publicPath`. The default is `'auto'`, +// which infers the path from where the host page loaded the script — +// not what we want when the package is embedded into a consumer like +// apps's dashboard, which serves these images from a different base +// path (e.g. `/blockly/media/skins/ailab/`). +// +// Webpack exposes `__webpack_public_path__` as a magic variable: any +// assignment to it at runtime updates the publicPath used by +// subsequently-evaluated asset modules. We pull the desired base from +// the same global the dataset/manifest loader already uses +// (`__ml_playground_asset_public_path__`), so the consumer has one +// knob to turn. +// +// This module MUST be imported before any module that pulls in an +// image — i.e. it should be the first import in the entry file. +// ECMAScript guarantees depth-first evaluation order: when an entry +// does `import './setPublicPath'; import './index';`, this module's +// body runs to completion before `./index` starts evaluating, so all +// downstream asset modules see the updated `__webpack_public_path__`. +// `__webpack_public_path__` is a webpack magic variable: at build time +// webpack rewrites assignments to it as updates to its runtime +// `__webpack_require__.p`. ESLint doesn't understand this — it sees a +// `let` with a single assignment and no read, hence the disables. +/* eslint-disable prefer-const, @typescript-eslint/no-unused-vars */ +declare let __webpack_public_path__: string; +__webpack_public_path__ = + (global as unknown as Record) + .__ml_playground_asset_public_path__ || './'; +/* eslint-enable prefer-const, @typescript-eslint/no-unused-vars */ diff --git a/webpack.config.js b/webpack.config.js index aac3eaa5..f34f9bb0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -38,7 +38,14 @@ const commonConfig = { }, }, generator: { - filename: 'assets/images/[name][ext][query]', + // URLs resolve to `<__webpack_public_path__>images/` + // at runtime. The consumer sets the public path via + // `__ml_playground_asset_public_path__` (see + // `src/setPublicPath.ts`); leaving an `assets/` prefix here + // would double up against consumer paths that already end + // in `assets/` or similar, so the prefix lives entirely on + // the consumer side. + filename: 'images/[name][ext][query]', }, }, ],