Now.js Framework Documentation
Project bundles — keeping project-only code out of Now/
Project bundles — keeping project-only code out of Now/
Overview
The Now/ folder (sources in Now/js, Now/css and bundles in Now/dist) and vite.config.js are
identical in every project. They are changed upstream (nowjs → adminframework) and distributed to
every project; no file that belongs to a single project may live there.
Code that only one project uses and that needs its own bundle (a designer, a timeline, ...) lives in
js/components/<name>/
├── build.json ← tells vite how to build it
├── entry.js ← bundle entry point (imports the other files and the CSS)
├── *.js · *.css ← bundle sources
js/dist/ ← build output of every bundle in this projectvite.config.js does not need editing — when BUILD_TARGET=<name> is not a Now.js bundle, vite reads
js/components/<name>/build.json and builds into js/dist/.
build.json
{
"entry": "entry.js",
"name": "now.timeline.min.js",
"css": "now.timeline.min.css",
"libName": "NowTimeline"
}| Key | Meaning |
|---|---|
entry |
Entry file, relative to the bundle folder (default entry.js) |
name |
JS file name in js/dist/ |
css |
CSS file name in js/dist/ (produced when the entry imports CSS) |
libName |
IIFE global name — the entry's export default ends up on window[libName] |
The folder name must not be one of the Now.js bundles (core, table, graph, serviceworker,
queue, eventcalendar, editor, formbuilder, syntaxhighlighter); those names always win.
Build command
Add a script to that project's package.json (this file may differ between projects):
"build:timeline": "cross-env BUILD_TARGET=timeline vite build"and append && npm run build:timeline to "build" to have it built with everything else.
The core build empties Now/dist (emptyOutDir) every time; js/dist is never emptied, so project
bundles survive a core build and a fresh distribution of Now/.
Loading a bundle
index.php is a shared file that is the same in every project, so it does not load project bundles —
the module that needs one loads it from its own admin.js, passing on admin.js's ?v= (the service
worker serves .js files without a query cache-first, so a rebuilt file would not be picked up).
const docDesignerBundle = (() => {
const src = document.currentScript?.src || '';
const base = src.replace(/modules\/docdesigner\/admin\.js.*$/, '');
if (!base) return Promise.resolve();
const query = src.includes('?') ? src.slice(src.indexOf('?')) : '';
const css = document.createElement('link');
css.rel = 'stylesheet';
css.href = `${base}js/dist/now.docdesigner.min.css${query}`;
document.head.appendChild(css);
return new Promise((resolve, reject) => {
const js = document.createElement('script');
js.src = `${base}js/dist/now.docdesigner.min.js${query}`;
js.onload = () => resolve();
js.onerror = () => reject(new Error('Failed to load js/dist/now.docdesigner.min.js'));
document.head.appendChild(js);
});
})();
async function initDocDesigner() {
await docDesignerBundle; // the page may render before the script has loaded
// ...
}Bundles in use
| Project | Bundle | Output |
|---|---|---|
| project_manager | js/components/timeline/ (entry imports sources from modules/project/timeline/) |
js/dist/now.timeline.min.* |
| form | js/components/docdesigner/ |
js/dist/now.docdesigner.min.* |
| gcms.in.th | js/components/designer/ |
js/dist/gcms.designer.min.* |
| bluprint.in.th | js/components/migration/ (uses @e965/xlsx) |
js/dist/now.migration.min.js |