Skip to content
Cay Henning edited this page Apr 22, 2024 · 20 revisions

Materia Widget Development Kit

The MWDK is an NPM package that allows developers to make widgets independent of the Materia platform, with live reloading and a handful of additional features to facilitate faster development. Additional documentation related to the MWDK's features are available on the docs site.

Requirements:

  • Node v16 or later
  • Yarn

Installation

To install MWDK, run: yarn add materia-widget-development-kit

MWDK should then be listed under the dependencies section of your widget's package.json:

"dependencies": {
    "materia-widget-development-kit": "3.0.0"
  }

Note that this is a required dependency of all Materia widgets.

Additionally, the following entries must be added to the scripts section of your widget's package.json:

"start": "mwdk-start",
"build": "mwdk-build-prod",
"build-dev": "mwdk-build-dev",

What each script does:

  • mwdk-start: This starts the Express server in development mode.
  • mwdk-build-prod: This builds webpack in production mode. See the output in /build folder.
  • mwdk-build-dev: This builds webpack in development mode. See the output in /build folder.

NPM vs Yarn

While both may work, we do not maintain a NPM lockfile (package-lock.json or npm-shrinkwrap.json) in the repo. Therefore, we recommend using yarn.

Included Packages

The MWDK comes with its own dependencies, including webpack, babel-loader, and much more. You do not need to add these to your own project's package.json.

Webpack Config

The bane of everyone's existence, Webpack. Here's a rundown of what you'll need to compile your widget correctly!

See an example configuration for React.

Entries

The entries is an object containing your source files. Each entry is an array of files that make up a page in your widget, such as the player, creator, and score screen. Each entry should list the HTML file first, followed by any JavaScript files and CSS files. This order is essential for Webpack to generate the necessary files.

Example entries object for a React widget
const entries = {
	'player': [
		path.join(srcPath, 'player.html'),
		path.join(srcPath, 'player.js'),
		path.join(srcPath, 'player.scss')
	],
	'creator': [
		path.join(srcPath, 'creator.html'),
		path.join(srcPath, 'creator.js'),
		path.join(srcPath, 'creator.scss')
	],
	'scoreScreen': [
		path.join(srcPath, 'scoreScreen.html'),
		path.join(srcPath, 'scoreScreen.js'),
		path.join(srcPath, 'scoreScreen.scss')
	]
}

NOTE: If you include CSS files in your React files (e.g. import './some-component.scss'), then you don't need to include the CSS file in the entry array.

Example entries object for an AngularJS widget
const entries = {
	'creator': [
		path.join(srcPath, 'creator.html'),
		path.join(srcPath, 'creator.coffee'),
		path.join(srcPath, 'creator.scss'),
	],
	'player': [
		path.join(srcPath, 'player.html'),
		path.join(srcPath, 'player.coffee'),
		path.join(srcPath, 'player.scss'),
	],
	'scorescreen': [
		path.join(srcPath, 'scorescreen.html'),
		path.join(srcPath, 'scorescreen.coffee'),
		path.join(srcPath, 'scorescreen.scss'),
	]
}

Can I use the default entries? Yes! The MWDK does provide default entries; however, we recommend creating your own entries since they will be different depending on the technology stack you're using.

What's included in the default entries?
const srcPath = path.join(process.cwd(), 'src') + path.sep
const getDefaultEntries = () => ({
	'creator': [
		`${srcPath}creator.html`,
		`${srcPath}creator.js`,
		`${srcPath}creator.scss`
	],
	'player': [
		`${srcPath}player.html`,
		`${srcPath}player.js`,
		`${srcPath}player.scss`
	]
})

Copy List

This is an array of source files that we'd like to copy over to the final build. We highly recommend looking at what is already being copied by default (see below) so you don't have to write any additional code.

How to get the default copy list from the MWDK and append any assets you'd like to copied:

const widgetWebpack = require('materia-widget-development-kit/webpack-widget')
const copy = widgetWebpack.getDefaultCopyList()
const outputPath = path.join(process.cwd(), 'build')
const copyList = copy.concat([
	{
		from: path.join(__dirname, 'src', '_guides', 'assets'),
		to: path.join(outputPath, 'guides', 'assets'),
		toType: 'dir'
	},
])
What's included in the default copy list?
The MWDK will check to see if these files exist; if so, it will copy them over.
const defaultCopyList = [
	{
		from: `${srcPath}demo.json`,
		to: `${outputPath}demo.json`,
	},
	{
		from: `${srcPath}install.yaml`,
		to: outputPath,
	},
	{
		from: `${srcPath}_icons`,
		to: `${outputPath}img`,
		toType: 'dir'
	},
	{
		from: `${srcPath}_score`,
		to: `${outputPath}_score-modules`,
		toType: 'dir'
	},
	{
		from: `${srcPath}_screen-shots`,
		to: `${outputPath}img/screen-shots`,
		toType: 'dir'
	},
	{
		from: `${srcPath}_screen-shots`,
		to: `${outputPath}img/screen-shots`,
		toType: 'dir'
	}
]

Rules

Also known as webpack loaders, these define what to do with your files.

How to grab the default rules:

const rules = widgetWebpack.getDefaultRules()

How to append a custom loader:

const myCustomLoader = {
	// do something!
}
const customRules = [
	...rules,
	myCustomLoader
]

If you wanted to include only a few of the default rules:

const customRules = [
	rules.reactLoader,
        rules.loadHTMLAndReplaceMateriaScripts,
	rules.loadAndPrefixSASS,
	rules.copyImages,
	myCustomLoader
]

Default Rules

Here are the names for each of the default rules and what they do. You do not need to transfer the code samples into your own code. They are only here to make it transparent exactly they're doing. See above for how to properly include them.

reactLoader
The React Loader uses [Babel](https://babeljs.io/). This loader finds any .js and .jsx files and converts them to JavaScript that all browsers will understand.
reactLoader: {
	test: /\.(js|jsx)$/,
	exclude: /node_modules/,
	use: {
		loader: 'babel-loader',
		options: {
			presets: [
				'@babel/preset-env',
				'@babel/preset-react'
			]
		}
	}
},
If you need to define other options for Babel, create a `.babelrc` file.
loaderDoNothingToJs This loader is used for processing regular JavaScript files.
loaderDoNothingToJs: {
	test: /\.js$/i,
	exclude: /node_modules|_guides|guides/,
	type: 'javascript/auto'
},
loaderCompileCoffee This loader process coffee files by translating them to JS.
loaderCompileCoffee: {
	test: /\.coffee$/i,
	exclude: /node_modules/,
	type: 'javascript/auto',
	use: [
		{
			loader: 'coffee-loader',
			options: {
				transpile:{
					presets: [
						'@babel/preset-env'
					]
				}
			}
		}
	],
},
copyImages This loader will look at all the images, fonts, etc. in the HTML source files
copyImages: {
	test: /\.(jpe?g|png|gif|svg|ico|ttf|eot|woff|woff2)$/i,
	exclude: /node_modules/,
	type: 'asset/resource',
	generator: {
		filename: '[name][ext]'
	}
}
loadHTMLAndReplaceMateriaScripts (Required) This loader replaces any materia scripts such as `materia.creatorcore.js` and `materia.enginecore.js` with the ones used by MWDK.
loadHTMLAndReplaceMateriaScripts: {
	test: /\.html$/i,
	exclude: /node_modules|_guides|guides/,
	type: 'asset/source',
	use: [
		{
			loader: 'string-replace-loader',
			options: { multiple: materiaJSReplacements }
		},
	]
},
loadAndPrefixSASS This loader will process any SASS/SCSS/CSS files and comes with an autoprefixer which adds any necessary vendor prefixes.
loadAndPrefixSASS: {
	test: /\.s(a|c)ss|css$/i,
	exclude: /node_modules\/(?!(materia-widget-development-kit\/templates)\/).*/,
	use: [
		{
			loader: MiniCssExtractPlugin.loader
		},
		{
			loader: "css-loader",
			options: {
				url: false,
				esModule: false
			},
		},
		{
			// postcss-loader is needed to run autoprefixer
			loader: 'postcss-loader',
			options: {
				postcssOptions: {
					// add autoprefixer, tell it what to prefix
					plugins: [
						require('autoprefixer')({
							overrideBrowserslist: browserList
						})
					],
				}
			},
		},
		"sass-loader",
	],
}

Bring It All Together

Next, create the options object that we will pass to the MWDK with the entries, copy list, and rules we defined.

let options = {
	entries,
	copyList,
	moduleRules
}

Finally, steal borrow the webpack config from the MWDK, and pass it the options object we created. Then, expose the config as a module for MWDK to grab and use!

const widgetWebpack = require('materia-widget-development-kit/webpack-widget')
let buildConfig = widgetWebpack.getLegacyWidgetBuildConfig(options)

module.exports = buildConfig

Starting your widget

Run yarn start in the widget's root directory. The widget will be hosted at localhost:8118 by default.

To change the port, you must update process.env.PORT:

  1. Run export PORT=1234
  2. Run yarn start as usual

The default node environment is development. If you wish to change this to production, you may set process.env.NODE_ENV in a similar fashion:

  1. Run export NODE_ENV=production
  2. Run yarn start as usual

Installing to Materia

Use the Download Package option on either the Player or Creator page to either download a compiled .wigt file locally (it's just a .zip file, but renamed) or install directly to your local instance of Materia. This requires a currently running local instance of Materia in Docker.

Widget Authoring

Visit the Developing Materia Widgets section of our docs site for comprehensive documentation related to authoring a widget.

Updating from MWDK versions < 3.0

Visit our page detailing the changes required to implement MWDK v3.x in your widget.

Troubleshooting

If you experience issues with setting up the Webpack configuration, we recommend looking at the configuration used by similar widgets.