Vanilla’s frontend scripts use a single global build process. This is used for all internal Javscript, both in core and addons.
What does it do?
The included build process uses Typescript and Webpack to bundle typescript files to into javascript bundles.
Every addon in your current vanilla project containing entries will get built. Currently that means bundling scripts from the following addons:
vanilla
dashboard
rich-editor
foundation
subcommunities
knowledge
vanillaanalytics
- Various others.
The outputted bundles will automatically be loaded by Vanilla into the page if their addon is enabled.
Prerequisites
Node 20+ and Yarn are prerequisites to run this build tool. See our Local Setup Quickstart for setup instructions.
Composer post-install
The build is run automatically in a post-install
hook of composer. This is to ensure a composer install
provides a fully functioning vanilla setup. The equivalent command will be run:
vnla build
There are a few environmental variables that can affect how this composer post-install script is started.
VANILLA_BUILD_DISABLE_AUTO_BUILD
Setting this environmental variable will disable automatic building after a composer install. So if you have a post-checkout hook and don’t want to run the build on every checkout you can could run your install like this:
VANILLA_BUILD_DISABLE_AUTO_BUILD=true composer install
Usage of vnla build
See the README.
Source files and entries
All source files MUST be typescript files with an extension of .ts
or .tsx
and reside in the src/scripts
directory of an addon.
Entries MUST be placed directly in the src/scripts/entries
directory of an addon. Adding an entry of a given name will create an entry of that type. Currently Vanilla defines 3 common entries: forum
, admin
, and knowledge
.
Creating a common
entry will apply to all 3 sections at once. This can be used at the same time as the other sections.
This means an entry for one of those sections would be one of the following files.
/plugins/MY_PLUGIN/src/scripts/entries/forum.ts
/plugins/MY_PLUGIN/src/scripts/entries/admin.ts
/plugins/MY_PLUGIN/src/scripts/entries/knowledge.ts
/plugins/MY_PLUGIN/src/scripts/entries/common.ts
/plugins/MY_PLUGIN/src/scripts/entries/forum.tsx
/plugins/MY_PLUGIN/src/scripts/entries/admin.tsx
/plugins/MY_PLUGIN/src/scripts/entries/knowledge.tsx
/plugins/MY_PLUGIN/src/scripts/entries/common.tsx
Every other file may be imported from one of these entries.
Dynamic entries
Anytime you have secondary content of a significant size that is not the primary content of a page it should be dynamically imported. This will split everything from that import down the chain into its own javascript bundle. If the import statement is reached, that separate bundle will be dynamically loaded by the client, and the imported file’s exports returned in a Promise.
Rich Editor is a great example of this. It:
- Only loads under certain conditions (signed in user, posting permissions, on a comment/posting page).
- Very large (~400 Kb).
So in the plugins/rich-editor/src/scripts/entries/forum.ts
you can find code similar to the following:
async function startEditor() { if (pageNeedsRichEditor()) { const mountEditorModule = await import("./mountEditor" /* webpackChunkName="chunks/mountEditor" */) const mountEditor = mountEditorModule.default; mountEditor(getMountPoint()); }}
There are a few things to decouple here.
- The
import()
function returns a Promise of module. It is an asyncrounous operation and must be handled as such. - Default exports get put on a named property
default
of the imported module. Named exports will be put on a property of their name. See webpack’s import() documentation for more details. - You MUST provide a
webpackChunkName
property. Omitting it will result in a chunk named 0.min.js
or 1.min.js
in the root the sections build directory where 0 or 1 will be a automatically incrementing integer. The files will still be loaded, but providing a name allows for easier viewing of what scripts are loaded in the page.
Site Sections
Every addon may offer entrypoints for different “sections” of the site. These will get loaded based off the javascript files requested from the WebpackAssetProvider::getScripts(string $section)
or automatically depending on the return of a Page::getAssetSection()
.
forum
entries
Forum entries are loaded in what would be considered the “frontend” of the site. That is anything using the default
master view (currently default.master.tpl
).
admin
entries
Admin entries are for the administrative dashboard of the site. That is anything using the admin
master view (currently admin.master.tpl
).
Additional entries
If you wanted to create a entry for a new section (lets use mySection
as an example) you would do the following:
- Create a file
src/scripts/entries/mySection.ts
or src/scripts/entries/mySection.tsx
in your addon. - Run the build.
- Call
$assetModel->getWebpackJsFiles('mySection')
and add the resulting script files to your page.
Output files
The WebpackAssetProvider
is responsible for gathering build files. You should not be referencing them directly as their locations may change in the future. Please use WebpackAssetProvider
and use its methods instead.
Location
Output files are build into the dist
directory. Each section get’s it own folder. The folder structure of a section looks like this:
dist/forum
runtime.min.js (Webpack runtime)vendors.min.js (vendor JS. Everything from node_modules)shared.min.js (Shared code from the `@library`)addons/* (Build entry points from addons. Eg. `addons/rich-editor.mins.js`, `addons/dashboard.min.js`)bootstrap.min.js (The script the fires the `onReady()` event.)async~someChunkName.min.js
Each of these files has its own sourcemap file as well. The async~
the chunks build from dynamic import statements.