
Security News
The Changelog Podcast: Practical Steps to Stay Safe on npm
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.
@meowwolf/meowwolf-main
Advanced tools
A starter template for both Client & Power projects.
Before starting to work with this template, please take some time to read through the documentation.
The Meow Wolf main website includes a comprehensive script loader that automatically loads all necessary JavaScript modules. This script loader supports both development and production environments.
Add the following script to your HTML <head> section:
<script>
const CONFIG = {
version: 'meowwolf-main@1.0.5',
};
/**
* @typedef {Object} Script
* @property {string} route - The path of the script (e.g., '/global/auth.js').
* @property {boolean} [async=true] - Whether the script should be loaded asynchronously.
*/
/**
* @typedef {Object} LoaderConfig
* @property {string} version - The version string for the CDN URL (e.g., 'meowwolf-main@0.3.0').
* @property {string} [localUrl='http://localhost:3000'] - The base URL for the local development server.
* @property {string} [prodCdn='https://cdn.jsdelivr.net/npm/@meowwolf/'] - The base URL for the production CDN.
*/
/**
* Asynchronously loads an array of scripts based on the provided configuration.
*
* @param {LoaderConfig} config - The configuration object for the script loader.
* @param {Script[]} scripts - An array of script objects to load.
* @returns {Promise<Object>} A promise that resolves with the results of the script loading operations.
*/
async function loadScriptsFromServer(config, scripts) {
// validate inputs
if (!config || typeof config !== 'object') {
throw new Error('A valid configuration object must be provided.');
}
if (!Array.isArray(scripts) || scripts.length === 0) {
throw new Error('A non-empty array of scripts must be provided.');
}
if (!config.version) {
throw new Error('Config must include a "version" property.');
}
const isLocal = !!localStorage.getItem('local');
const localUrl = config.localUrl || 'http://localhost:3000';
const prodCdn = config.prodCdn || 'https://cdn.jsdelivr.net/npm/@meowwolf/';
const server = isLocal ? localUrl : `${prodCdn}${config.version}/dist`;
// find insertion point
const insertionPoint = document.currentScript || document.scripts[document.scripts.length - 1];
if (!insertionPoint || !insertionPoint.parentNode) {
throw new Error('Could not find a valid script element to insert new scripts after.');
}
// create and load scripts ---
const promises = scripts.map((scriptInfo) => {
return new Promise((resolve, reject) => {
const el = document.createElement('script');
el.src = `${server}${scriptInfo.route}`;
// Clear async/defer logic: if async is explicitly false, use defer; otherwise use async
if (scriptInfo.async === false) {
el.defer = true;
el.async = false;
} else {
el.async = true;
el.defer = false;
}
el.onload = () => resolve({ script: scriptInfo.route, status: 'success' });
el.onerror = (error) => {
console.error(`Failed to load script: ${scriptInfo.route}`, error);
reject({ script: scriptInfo.route, status: 'failed', error });
};
insertionPoint.parentNode.insertBefore(el, insertionPoint.nextSibling);
});
});
// await All and Return Results ---
const settledResults = await Promise.allSettled(promises);
const results = {
successful: [],
failed: [],
total: scripts.length,
};
settledResults.forEach((result) => {
if (result.status === 'fulfilled') {
results.successful.push(result.value.script);
} else {
results.failed.push(result.reason);
}
});
// Enhanced logging and error reporting
if (results.failed.length > 0) {
console.error(`Script loading completed with ${results.failed.length} failures:`, results.failed);
console.warn(`Successfully loaded ${results.successful.length}/${results.total} scripts`);
} else {
console.log(`âś… Successfully loaded all ${results.successful.length} scripts`);
}
return results;
}
// scripts to be loaded
const scriptsToLoad = [
{ route: '/global/auth.js', async: false },
{ route: '/global/ui.js', async: false },
{ route: '/global/tracking.js', async: false },
{ route: '/index.js', async: false },
];
// invoke the script loader
loadScriptsFromServer(CONFIG, scriptsToLoad).catch((error) => {
console.error('A critical error occurred in the script loader:', error);
});
</script>
For Webflow projects, use this minified version that works in both development and production:
<script>
const CONFIG = { version: 'meowwolf-main@1.0.5' };
async function loadScriptsFromServer(config, scripts) {
if (!config || typeof config !== 'object') throw new Error('A valid configuration object must be provided.');
if (!Array.isArray(scripts) || scripts.length === 0)
throw new Error('A non-empty array of scripts must be provided.');
if (!config.version) throw new Error('Config must include a "version" property.');
const isLocal = !!localStorage.getItem('local');
const localUrl = config.localUrl || 'http://localhost:3000';
const prodCdn = config.prodCdn || 'https://cdn.jsdelivr.net/npm/@meowwolf/';
const server = isLocal ? localUrl : `${prodCdn}${config.version}/dist`;
const insertionPoint = document.currentScript || document.scripts[document.scripts.length - 1];
if (!insertionPoint || !insertionPoint.parentNode)
throw new Error('Could not find a valid script element to insert new scripts after.');
const promises = scripts.map((scriptInfo) => {
return new Promise((resolve, reject) => {
const el = document.createElement('script');
el.src = `${server}${scriptInfo.route}`;
if (scriptInfo.async === false) {
el.defer = true;
el.async = false;
} else {
el.async = true;
el.defer = false;
}
el.onload = () => resolve({ script: scriptInfo.route, status: 'success' });
el.onerror = (error) => {
console.error(`Failed to load script: ${scriptInfo.route}`, error);
reject({ script: scriptInfo.route, status: 'failed', error });
};
insertionPoint.parentNode.insertBefore(el, insertionPoint.nextSibling);
});
});
const settledResults = await Promise.allSettled(promises);
const results = { successful: [], failed: [], total: scripts.length };
settledResults.forEach((result) => {
if (result.status === 'fulfilled') results.successful.push(result.value.script);
else results.failed.push(result.reason);
});
if (results.failed.length > 0) {
console.error(`Script loading completed with ${results.failed.length} failures:`, results.failed);
console.warn(`Successfully loaded ${results.successful.length}/${results.total} scripts`);
} else console.log(`âś… Successfully loaded all ${results.successful.length} scripts`);
return results;
}
const scriptsToLoad = [
{ route: '/global/auth.js', async: false },
{ route: '/global/ui.js', async: false },
{ route: '/global/tracking.js', async: false },
{ route: '/index.js', async: false },
];
loadScriptsFromServer(CONFIG, scriptsToLoad).catch((error) => {
console.error('A critical error occurred in the script loader:', error);
});
</script>
Benefits for Webflow:
To enable development mode (load scripts from localhost), set the local flag in localStorage:
// Enable development mode
localStorage.setItem('local', 'true');
// Disable development mode
localStorage.removeItem('local');
When development mode is enabled, scripts will be loaded from http://localhost:3000 instead of the production CDN.
By default, scripts are loaded from the production CDN:
https://cdn.jsdelivr.net/npm/@meowwolf/meowwolf-main@1.0.5/distversion property in CONFIG to match your desired versionThe script loader supports several configuration options:
const CONFIG = {
version: 'meowwolf-main@1.0.5', // Required: Version to load
localUrl: 'http://localhost:3000', // Optional: Custom local development URL
prodCdn: 'https://cdn.jsdelivr.net/npm/@meowwolf/', // Optional: Custom CDN URL
};
The scripts are loaded in the following order to ensure proper dependencies:
/global/auth.js - Authentication and user management/global/ui.js - UI components and interactions/global/tracking.js - Analytics and tracking functionality/index.js - Main application entry pointAll scripts are loaded with async: false to ensure proper execution order.
The script loader includes comprehensive error handling:
The script loader is compatible with all modern browsers that support:
This template contains some preconfigured development tools:
This template requires the use of pnpm. You can install pnpm with:
npm i -g pnpm
To enable automatic deployments to npm, please read the Continuous Deployment section.
The quickest way to start developing a new project is by creating a new repository from this template.
Once the new repository has been created, update the package.json file with the correct information, specially the name of the package which has to be unique.
After creating the new repository, open it in your terminal and install the packages by running:
pnpm install
If this is the first time using Playwright and you want to use it in this project, you'll also have to install the browsers by running:
pnpm playwright install
You can read more about the use of Playwright in the Testing section.
It is also recommended that you install the following extensions in your VSCode editor:
To build the files, you have two defined scripts:
pnpm dev: Builds and creates a local server that serves all files (check Serving files on development mode for more info).pnpm build: Builds to the production directory (dist).When you run pnpm dev, two things happen:
watch mode. Every time that you save your files, the project will be rebuilt.http://localhost:3000 that serves all your project files. You can import them in your Webflow projects like:<script defer src="http://localhost:3000/{FILE_PATH}.js"></script>
/bin/build.js.If you need to build multiple files into different outputs, you can do it by updating the build settings.
In bin/build.js, update the ENTRY_POINTS array with any files you'd like to build:
const ENTRY_POINTS = ['src/home/index.ts', 'src/contact/whatever.ts', 'src/hooyah.ts', 'src/home/other.ts'];
This will tell esbuild to build all those files and output them in the dist folder for production and in http://localhost:3000 for development.
CSS files are also supported by the bundler. When including a CSS file as an entry point, the compiler will generate a minified version in your output folder.
You can define a CSS entry point by either:
bin/build.js config. See previous section for reference.// src/index.ts
import './index.css';
CSS outputs are also available in localhost during development mode.
Path aliases are very helpful to avoid code like:
import example from '../../../../utils/example';
Instead, we can create path aliases that map to a specific folder, so the code becomes cleaner like:
import example from '$utils/example';
You can set up path aliases using the paths setting in tsconfig.json. This template has an already predefined path as an example:
{
"paths": {
"$utils/*": ["src/utils/*"]
}
}
To avoid any surprises, take some time to familiarize yourself with the tsconfig enabled flags.
As previously mentioned, this library has Playwright included as an automated testing tool.
All tests are located under the /tests folder. This template includes a test spec example that will help you catch up with Playwright.
After installing the dependencies, you can try it out by running pnpm test.
Make sure you replace it with your own tests! Writing proper tests will help improve the maintainability and scalability of your project in the long term.
By default, Playwright will also run pnpm dev in the background while the tests are running, so your files served under localhost:3000 will run as usual.
You can disable this behavior in the playwright.config.ts file.
If you project doesn't require any testing, you should disable the Tests job in the CI workflow by commenting it out in the .github/workflows/ci.yml file. This will prevent the tests from running when you open a Pull Request.
In general, your development workflow should look like this:
CHANGELOG.md, you should also merge that one. If you have automatic npm deployments enabled, Changesets will also publish this new version on npm.If you need to work on several features before publishing a new version on npm, it is a good practise to create a development branch where to merge all the PR's before pushing your code to master.
This template contains a set of predefined scripts in the package.json file:
pnpm dev: Builds and creates a local server that serves all files (check Serving files on development mode for more info).pnpm build: Builds to the production directory (dist).pnpm lint: Scans the codebase with ESLint and Prettier to see if there are any errors.pnpm lint:fix: Fixes all auto-fixable issues in ESLint.pnpm check: Checks for TypeScript errors in the codebase.pnpm format: Formats all the files in the codebase using Prettier. You probably won't need this script if you have automatic formatting on save active in your editor.pnpm test: Will run all the tests that are located in the /tests folder.pnpm test:headed: Will run all the tests that are located in the /tests folder visually in headed browsers.pnpm release: This command is defined for Changesets. You don't have to interact with it.pnpm run update: Scans the dependencies of the project and provides an interactive UI to select the ones that you want to update.This template contains a set of helpers with proper CI/CD workflows.
When you open a Pull Request, a Continuous Integration workflow will run to:
pnpm lint and pnpm check commands under the hood.pnpm test command under the hood.If any of these jobs fail, you will get a warning in your Pull Request and should try to fix your code accordingly.
Note: If your project doesn't contain any defined tests in the /tests folder, you can skip the Tests workflow job by commenting it out in the .github/workflows/ci.yml file. This will significantly improve the workflow running times.
Changesets allows us to generate automatic changelog updates when merging a Pull Request to the master branch.
Before starting, make sure to enable full compatibility with Changesets in the repository.
To generate a new changelog, run:
pnpm changeset
You'll be prompted with a few questions to complete the changelog.
Once the Pull Request is merged into master, a new Pull Request will automatically be opened by a changesets bot that bumps the package version and updates the CHANGELOG.md file.
You'll have to manually merge this new PR to complete the workflow.
If an NPM_TOKEN secret is included in the repository secrets, Changesets will automatically deploy the new package version to npm.
See how to automatically deploy updates to npm for more info.
Some repositories may not have the required permissions to let Changesets interact with the repository.
To enable full compatibility with Changesets, go to the repository settings (Settings > Actions > General > Workflow Permissions) and define:
Enabling this setting for your organization account (Account Settings > Actions > General) could help streamline the process. By doing so, any new repos created under the org will automatically inherit the setting, which can save your teammates time and effort. This can only be applied to organization accounts at the time.
As mentioned before, Changesets will automatically deploy the new package version to npm if an NPM_TOKEN secret is provided.
This npm token should be:
Once you're logged into the npm account, you can get an access token by following this guide.
The access token must be then placed in a repository secret named NPM_TOKEN.
Update LocalStorage
local=true
Run
pnpm dev
FAQs
Moew Wolf Global code
We found that @meowwolf/meowwolf-main demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 13 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.

Security News
Experts push back on new claims about AI-driven ransomware, warning that hype and sponsored research are distorting how the threat is understood.

Security News
Ruby's creator Matz assumes control of RubyGems and Bundler repositories while former maintainers agree to step back and transfer all rights to end the dispute.