create-mashup-app
v3.2.0
Published
Bootstrapper and build abstraction for creating targetprocess mashup integration apps.
Downloads
923
Readme
Create Mashup App
Application bootstrapper and build abstraction for rapid prototyping of Targetprocess UI mashups & integration mashups.
Main philosophy is 'run 1 command and start hacking'.
While your build is performed by create-mashup-app
, you are guaranteed to have a stable and tried build and dev server configuration that just works (hopefully) out of the box and covers most of the cases.
In other words, you:
- don't have to be familiar with
tau.mashups
API - don't need to write long and tiresome typescript, babel, PostCSS and other configurations
- don't need to bang your head against the wall thinking about how to exclude modules provided by mashup API from your resulting bundle.
Moreover, you can build your front-end code and serve it statically from a separate server instance running in the cluster, and manage mashup deployments by yourself.
What's included
babel 7
with latestenv
andreact
presets for using latest ES stuff and JSX, respectively. Just create a.babelrc
in your root if you want to customize it.- TypeScript via
rollup-plugin-typescript2
that works out of the box. You can also specify your owntsconfig.json
and.d.ts
type definitions. You can include the following type definiton to make it work with CSS modules:
declare module '*.css' {
const cssModule: Record<string, string>;
export default cssModule;
}
postcss
for your CSS-related stuff, with a sprinkle of useful plugins for importing, nesting, variables, mixins and basically everything else you might want to expect from modern CSS. It also has css modules enabled, and appends name and version from yourpackage.json
to each CSS class you import so that you avoid polluting global CSS scope. It also hasinject
option set totrue
, so any styles you import magically appear as style tags on the page. Having your ownpostcss.config.js
(or.postcssrc
or any other supported config format) in root allows you to specify your own configuration.- Entry point wrapper for your code that calls
tau.mashups
API for you behind the scenes, adds all dependencies you list, and replacesimport
s of modules with appropriate TP mashup dependencies you mark withprovideModule
. - Tree-shaking that works out of the box and fast build times brought to you by rollup.
- (beta) Code splitting support and minimal external chunks loader for your mashup. You can use dynamic
import()
syntax anywhere in your code - just make sure to provide correctbasePath
that points to a location where your chunks are avaliable.
Installation
Install globally (to generate new application):
> npm install -g create-mashup-app
Install as dependency (to handle build and dev server):
> cd <your-project-folder>
> npm install --save-dev create-mashup-app
Usage
Create new app from scratch:
> create-mashup-app generate [folder]
This command generates a barebones mashup, with all build configuration abstracted away behind create-mashup-app
dependency.
3 templates are supported as of the moment:
- minimal
package.json
, mashup.json
and nothing else but your code in src/index.js
. Bare minumum for when you need to start fresh, or when you're implimenting something relatively simple.
- react
Generates a bit more complex mashup with dummy react component that gets inserted into document
when mashup is loaded. Includes a configured test environment and 1 dummy test, basic rules for linting and useful commands for executing them in package.json
.
- typescript
Same as minimal, but with support for typescript, and index.ts
as entry point.
Build existing app:
> create-mashup-app build [folder] [--no-minify] [--no-dependency-replace] [--mashup] [--basePath=<url>]
This assumes that [folder]
or current directory has correct mashup config.
Build output is written to dist
folder in [folder]
.
Build by default assumes that created mashup is used as an integration, i.e. has an integration on TP side that registers js file produced in build as registerExternalModule
.
If you want to build a mashup to be included directly into TP straight away, you can use --mashup
with create-mashup-app build
, or specify "target": "mashup"
in your mashup.json
.
If your mashup uses dynamic imports and code splitting, be sure to provide correct --base-url
of your chunks.
Run development server on existing app:
> create-mashup-app devServer [folder] [--port <port>] [--no-minify] [--no-dependency-replace]
This assumes that [folder]
or current directory has correct mashup config.
Dev server serves your dist
directory statically. As such, any previous build assets may be overwritten.
Mashup configuration
Your app should provide mashup configuration in any of the ways supported by cosmiconfig
, i.e. via:
mashup
property in yourpackage.json
.mashuprc
/.mashuprc.json
/.mashuprc.yaml
/mashuprc.yml
file.mashuprc.js
ormashup.config.js
file that exports an object
mashup.config.js
is the root configuration file for your mashup while it's controlled by create-mashup-app
. It is generated automatically when you create a new app, and consumed every time you build or run dev server with create-mashup-app
on an existing app.
A typical mashup config will look like this:
{
"name": "My Awesome Mashup",
"moduleName": "tp3/integrations/my-awesome-mashup",
"entry": "src/index.js",
"cssVariables": {
"spacing": "5px"
},
"dependencies": [
{"mashupDependency": "react", "provideModule": "react"},
{"mashupDependency": "tp3/api/settings/v1"},
{"mashupDependency": "Underscore", "provideModule": "underscore"},
...
]
}
Below is the breakdown for each of the fields:
name
- This is the visible user-friendly name for your mashup. It will only be used as mashup name in Mashup Manager, so feel free to choose whatever you seem fit.moduleName
- This is module identifier for your mashup. While there are no theoretical boundaries on what you can use as module name, you should try to provide a technically succint namespaced value here.entry
- This field points to the entry point of your mashup. Typically, mashups are created by callingtau.mashups.addDependency(...).addMashup('awesome-mashup', function(dependency) {...})
.create-mashup-app
, however, abstracts that API away, by using dependencies from your mashup.json, simplifying their usage and adding support for bundle module replacement. The file specified inentry
must contain a named export functioninitialize
:cssVariables
can be used to provide additional variables forposcss-simple-vars
. You can use a string path to constants file (it is expected to export an object with variables viamodule.exports
). You can also use a function frommashup.config.js
that returns an object, or provide the object in-place.
export function initialize() {
console.log('I am a mashup!');
}
or
module.exports = {
initialize() {
console.log('I am a mashup that is afraid of using ES2015 import/export!');
}
};
This function is called when your mashup and all of its dependencies are loaded.
dependencies
- An array of your mashup dependencies. The list of all avaliable dependencies can be found in targetprocesspublicModules.registry.js
You can specify each dependency as a string or as an object withmashupDependency
key:
...
"dependencies": [
"react",
{"mashupDependency": "tp3/api/settings/v1"}
]
...
All mashup dependencies are available for importing as if they were regular modules. If you have a module with the same name you are importing from node_modules, it will be replaced:
import TpSettings from 'tp3/api/settings/v1';
import React from 'react';
...
You can additionally specify provideModule
key for your dependency object to create a different alias for that particular mashp module:
...
"dependencies": [
{"mashupDependency": "Underscore", "provideModule": "underscore"},
{"mashupDependency": "react", "provideModule": "tp-react"},
{"mashupDependency": "tp3/api/settings/v1", "provideModule": "@targetprocess/api/settings"}
]
...
import _ from 'underscore'; // TP module 'Underscore' is used
import React from 'react'; // react from your own node_modules is used
import TpReact from 'tp-react'; // TP module 'react' is used
import SettingsApi from '@targetprocess/api/settings'; // TP module 'tp3/api/settings/v1' is used
When your code is bundled normally, all node_modules imports are included with your bundle. For example, with the above code you get entire react
codebase in your bundle.
Some library dependencies, including react
and react-dom
, are already provided by TP mashup API, so carrying your own copy of React or jQuery with your mashup is very inefficient.
Tree-shaking
create-mashup-app
uses rollup for bundle generation, which statically analyzes your code for imports and exports. Refer to https://rollupjs.org/guide/en#tree-shaking
for more details.
If your bundle size is still too large, you can try to use pureExternalModules
and propertyReadSideEffects
rollup config options (more on them here) to perform more aggressive tree-shaking.
Code splitting
Code splitting occurs whenever you use dynamic import statement in your code. Each dynamic import spawns a separate chunk that will be loaded on runtime when requested.
create-mashup-app
bundles a tiny dependency loader with your code that can load dynamic imports. It also requires that you build your app with --basePath
argument or basePath
mashup config option, containing a correct base URL from your dynamically-loaded chunks. For TP usage, this is most commonly a relative URL, like /svc/my-awesome-mashup/
.
When determining chunk URLs, our tiny dependency loader will try to use tau.mashups.resolveIntegrationUrl
, if it's present (for more info on how it works, see this lengthy but very important doc). For it to version your mashup URLs correctly, you also need to specify integrationName
in your mashup config. If you don't, mashup name (from mashup config name or pakcage.json package name) will be used instead, but a warning will be issued on build since it is recommended to specify integration URL explicitly to avoid unexpected behavior because of some minor differences between mashup name and integration name in your mashup's docker image config.
Code splitting will also try to isolate common chunks and inline common chunks used by entry point with entry point bundle. That means that you should try to minimize usage of common imports and utility modules within your entry point to make your entry point bundle as tiny as possible. See code splitting 101 in docs for more info.
Code splitting assumes that webpage has a correct implementation of A+ Promises available from window.Promise
, which currently shouldn't be an issue.
Providing public API
A mashup created by create-mashup-app
can register public modules which can be used by other mashups. In order to provide one or more public modules, you need to perform a few things:
- Create a module which will be exposed publicly e.g.
src/api/mashup-public-api.js
with the following content:
export default {
sayHello() {
console.log('hello from mashup public API');
}
};
- Create a javascript file which provides all public API modules exposed by the mashup, e.g. a file
src/api/index.js
with the following content:
module.exports = {
'tau/api/hello/v1': () => require('./mashup-public-api').default
// any other public modules can go here
};
Only CommonJS is allowed inside public api entry file.
Keys of the exported objects are the names of the modules which can be used by other mashups.
Please note that this file should not have any dependencies in a form of require
or import
statements (the file is loaded and evaluated before dependencies specified in mashup.json
are initialized)
- Add
publicApi
property in themashup.json
file:
{
...
"publicApi": "src/api/index.js"
...
}
Now you can use this module in another mashup:
tau.mashups.addDependency('tau/api/hello/v1').addMashup(function(api) {
api.sayHello();
});
Customizing
create-mashup-app
is quite narrow in the way of customizing your build process. This is intentional. The basic idea is that you get a default configuration that just works, along with some best practices on how to handle your mashup integration.
Custom .babelrc
You can place .babelrc
file in your root folder and provide your own babel configuration. Note that doing so will override default presets, so you'll have to include env
, react
and other transforms you consider useful for your app.
create-mashup-app
uses babel 7. Make sure you use correct plugin versions, e.g. @babel/plugin-proposal-object-rest-spread
and not babel-plugin-transform-object-rest-spread
.
Custom PostCSS config
Much like .babelrc
, you can provide your own custom postcss config in any manner you seem fit. Preferred way is through postcss.js.config
.
Custom rollup config
Customizing rollup config generally goes a bit against the main philosophy of create-mashup-app
. It's fine for smaller tweaks, like adding new plugins, but if you need to drastically customize build process, consider using eject
instead.
You can specify configTransform
property in mashup config that points to JS file:
{
...
"configTransform": "./rollup.config.transform.js",
}
or you can provide it as a function in mashup.config.js
/.mashuprc.js
:
module.exports = {
name: '...',
...
configTransform: function(config) {
...
}
}
Config transform will be invoked with basic rollup config object as parameter and is expected to return a new, transformed rollup config.
For example, if youo want to add rollup-plugin-string
, you can do it with the following transform:
const string = require('rollup-plugin-string');
module.exports = function(config) {
return {
...config,
plugins: [
...config.plugins,
string({
include: '**/*.html'
})
]
};
};
Eject
Eject is currently not supported, but will be added in future versions.
create-mashup-app eject
will remove create-mashup-app
from your package dependencies, transfer all required transitive dependencies to your own package.json
and will generate all configuration files required to run build and dev server without it, much like eject in create-react-app
.
This is irreversible.
Known issues
Sourcemaps are broken as of
3.0.0-alpha.1
version.Dependencies are replaced incorrectly in object literals with concise property syntax. An expression like
import configurator from 'tau/configurator';
const obj = {
configurator
}
will be replaced with something like
const obj = {
window.__layoutrenderer001_we5eh_deps['configurator']
}
and such replacement will lead to syntax error during build.
The error also may occur due to code-splitting in places where reference to variable with content of dynamic chunk is used.
3. create-mashup-app
won't work nicely with React.lazy
. React internal check expects that default export will be placed into default
key of module object. Rollup will generate an object with default
key only if some named exports are placed in the same file. If file contains only default export, Rollup may return it directly from generated module close. In such case it will be necessary to adjust dynamic import as follows:
const LazyComponent = React.lazy(() => import('./Component').then(component => ({default: component})))