Template for making package namespace - craftkit code pattern
Tags: craftkit, javascript, shadowdom, webdev
Published: 2020-11-27 14:52:23

In craftkit application, modules can be also used in HTML context. To avoid conflict of module name, you would like to define your package namespace. 

This post illustrates key snippet about namespace, using implementation of your own package under the Craft.Widget namespace.

## Steps

* Define package namespace `Craft.Widget.Example`
* Implement `Craft.Widget.Example.View` as a subclass of Craft.UI.View
* Place the View on the screen
* Overview, defining your own top level namespace

## Files

``` 
Craft.Widget.Example/
├── package.json
├── index.js
├── index.min.js
├── main.js
├── View.js
├── webpack.config.js
└── index.html
``` 

### package.json

This is minimum version of package.json with craftkit.

```json:package.json
{
  "name": "Craft.Widget.Example",
  "description": "",
  "version": "0.1.0",
  "dependencies": {
    "@craftkit/craft-uikit": "^3.1.4"
  },
  "devDependencies": {}
}
``` 

### index.js

The entry point of Node.js module. 

```javascript:index.js 
'use strict';

const Example = require('./main.js');

module.exports = Example.default || Example;
``` 

### index.min.js

The entry point of HTML context. The namespace should be injected into the window object. This is done by `Craft.usePackage` function, and callbacked by it `inject` function. (When you define your own top level namespace, you should also define those functions to inject packages under the namespace. See the bottom of this post.)

```javascript:index.min.js 
'use strict';

const Example = require('./main.js');

window.Craft.usePackage(Example.default);

module.exports = Example.default || Example;
``` 

### main.js

Both Node.js and HTML context enter to this script at first. 

To be able to make another packages join to this namespace, you should define `inject` function to the exporting object. 

```javascript:main.js 
import { View } from './View.js';

const Packages = {
	View : View
};

Packages.inject = function(Craft){
	Craft.Widget = Craft.Widget || {};
	if( !Craft.Widget.Example ){
		Craft.Widget.Example = Packages;
	}
};

export default Packages;
``` 

### View.js

Main logic of this package. 

This class will be accessible by `Craft.Widget.Example.View`.

```javascript:View.js 
import * as Craft from '@craftkit/craft-uikit';

export class View extends Craft.UI.View {
	template(componentId){
		return `
			<div class="root">
				This is Example
			</div>
		`;
	}
}
``` 

### webpack.config.js

Write out package for HTML context.

```javascript:webpack.config.js
var path = require('path');

module.exports = {
	mode: 'development',
	entry: './index.min.js',
	output: {
		path: path.resolve(__dirname,'./'),
		filename: 'craft-widget-example.min.js',
		library: 'Example',
		libraryTarget: 'window',
		globalObject: 'window'
	},
	externals: {
		'@craftkit/craft-uikit' : 'Craft',
	}
};
``` 

### index.html

HTML use case. 

```html:index.html 
<html>
<head>
	<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, viewport-fit=cover"/>
	
    <script src="https://unpkg.com/@craftkit/craft-uikit/dist/craft-uikit.min.js"></script>
    
	<script src="./craft-widget-example.min.js"></script>
	
	<script>
		window.onload = function(){
			Craft.Core.Bootstrap.boot({
				didBootApplication : function(options){
					Craft.Core.Defaults.ALLOW_COMPONENT_SHORTCUT = true;
					var rootViewController = new Craft.UI.DefaultRootViewController()
					Craft.Core.Context.setRootViewController(rootViewController)
					
					rootViewController.appendView(
						new Craft.Widget.Example.View()
					);
				}
			});
		};
	</script>
</head>
<body id="CraftRoot">
</body>
</html>
``` 

## Run 

``` 
webpack --config webpack.config.js
``` 

Then open ./index.html by browser.


## Node.js context

In Node.js context, namespace is controlled under its rules. 

To use full package name, call `Craft.usePackage` for your package. 

```javascript:YourApp.js 
import * as Craft from '@craftkit/craft-uikit';
import * as Example from './craft-widget-example';
Craft.usePackage(Example);

class MyPage extends Craft.UI.View {
	viewDidLoad(callback){
		this.appendView(new Craft.Widget.Example.View());
	}
}
``` 

## Define your own namespace

When you define your own top level namespace, like `MyCompany.MyApp.Example`, you may define something like this: 

MyCompany/main.js 

```javascript:MyCompany/main.js 
const MyCompany = {};

MyCompany.usePackage = function(packages){
	packages.inject(MyCompany);
};

export default MyCompany;
``` 

MyCompany.MyApp.Example/main.js 

```javascript:MyCompany.MyApp.Example/main.js 
import { View } from './View.js';

MyCompany.MyApp = {
	View : View
};

Packages.inject = function(MyCompany){
	MyCompany.MyApp = MyCompany.MyApp || {};
	if( !MyCompany.MyApp.Example ){
		MyCompany.MyApp.Example = Packages;
	}
};

export default MyCompany;
``` 

index.min.js 

```javascript:index.min.js 
'use strict';

const Example = require('./main.js');

window.MyCompany.usePackage(Example.default);

module.exports = Example.default || Example;
``` 

index.html 

```html:index.html 
rootViewController.appendView(new MyCompany.Widget.Example.View());
``` 

YourApp.js 

```javascript:YourApp.js 
import * as MyCompany from '/mycompany';
import * as Example from './mycompany-myapp-example';
MyCompany.usePackage(Example);

class MyPage extends Craft.UI.View {
	viewDidLoad(callback){
		this.appendView(new MyCompany.MyApp.Example.View());
	}
}
```