Skip to content
Cay Henning edited this page Apr 18, 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",
  • 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.

Webpack Config

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

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.

// For example, here we get the default copy list from the MWDK and append the assets used by the player and creator guides.
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
]

The Default Rules, explained

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'
			]
		}
	}
},
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 available at localhost:8118.

To change the port, update process.env.PORT by running export PORT=1234 before yarn start.

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 by running export NODE_ENV=production before yarn start.

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.

Widget URLs Change

As of MWDK v3.0.0, you can access your widget using the same urls as Materia.

Creator: /mwdk/widgets/1-mwdk/create

Player: /mwdk/player/

Clone this wiki locally