requirees
v1.0.23
Published
Simple AMD module loader with version support
Downloads
1,214
Maintainers
Readme
Require-es
A microfrontend module loader Installation Migration from RequireJs Register a package Require a package Override amd dependencies Require.when - ONLY load after explicit define/register Define a package RequireEs options RequireEs events WebPack / Rollup bundling for RequireEs Custom elements support
A microfrontend module loader
Require-es will:
- allow micro-frontends to share dependencies
- help finding compatible package-versions
The project will be structured in 3 parts:
- provide a module loader which:
- loads amd modules
- loads multiple filetypes: js / json / xml / txt / html / css / wasm
- registers multiple versions of a single package
- can set default-versions
- easily migrates from RequireJs
- emits events to allow usage monitoring
- create a require-es-server, where:
- packages can be registered
- usage of package-version can be tracked
- module loading can become predictable
- predictive http2-pushes are possible
- create a webpack/rollup loader
Installation
Add the package to your project
npm install requirees --save
Serve the file in node_modules/requirees/build/requirees.js in the header of your webpage (9Kb Gzipped)
<!DOCTYPE html>
<html>
<head>
<title>Pagetitle</title>
<script src="scripts/requirees.js"></script>
</head>
<body></body>
</html>
Migration from RequireJs
RequireEs allows a soft transition from RequireJs. This means that most syntax of RequireJs, is also supported in RequireEs:
- define(packageName, [dependencies], factory);
- define([dependencies], factory);
- require(['packageName'], packageInstance => {});
- requirejs(['packageName'], packageInstance => {});
- require('packageName');
- requirejs.config({paths});
In a later version, require.config({shim}) will be supported as well.
More info on RequireJs: https://requirejs.org/
Register a package
Usage
require.register({
packageName1(@)(version)(.filetype)(-default): [
url(.filetype),
url(.filetype),
url(.filetype),
...
],
packageName2(@)(version)(.filetype)(-default): {
versions,
url,
urls
}
});
Key | Description --- | --- PackageName | the name of the package Version | the version-number in string format: major.minor(.patch)(.build)(-releaseCandidate) Filetype | the file-extension - js/css/txt/xml/json/html/wasm/tag Default | indicates the default version Versions | array of versions to fill out in the URL (use placeholder ${version} in the url-string) Url | single url string Urls | multiple urls for 1 package
Note - Determining the version number, happens in this order:
- Is a 'versions' attribute present in the package-value
- Is '@version' present in the package-name
- Is a version present in the url (cdnjs.com/17.0.2/package.js)
Samples
//register react, version '17.0.2'
require.register({
react: 'https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js'
});
//register react, versions '0.14.9', '15.6.2', '15.7.0', '16.14.0' (set as default), '17.0.2'
require.register({
react: {
versions: ['0.14.9', '15.6.2', '15.7.0'],
url: 'https://cdnjs.cloudflare.com/ajax/libs/react/${version}/react.min.js'
},
'[email protected]': 'https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js',
'[email protected]': 'https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js'
});
//register react, versions '15.7.0', '16.14.0', '17.0.2'
require.register({
react: [
'https://cdnjs.cloudflare.com/ajax/libs/react/15.7.0/react.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js',
]
});
//register bootstrap, both css and js
require.register({
bootstrap: [
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.css",
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.bundle.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css"
]
});
Require a package
Usage
//async await
const packageInstance = await require('packageName(@)(^)(~)(*)(version)(.filetype)');
//mulitple packages
const [pckg1, packg2] = await require([
'pckg1(@)(^)(~)(*)(version)(.filetype)',
'pckg2(@)(^)(~)(*)(version)(.filetype)'
]);
//cjs style (only works when a package is already loaded once, or in a defined factory)
const packageInstance = require('packageName(@)(^)(~)(*)(version)(.filetype)');
//using promises
requirees('packageName(@)(^)(~)(*)(version)(.filetype)').then(packageInstance);
//multiple packages
requirees([
'pckg1(@)(^)(~)(*)(version)(.filetype)',
'pckg2(@)(^)(~)(*)(version)(.filetype)'
]).then(
([pckg1, pckg2]) => {}
);
//using callbacks
require(['packageName(@)(^)(~)(*)(version)(.filetype)'], package => {});
//multiple packages
require([
'pckg1(@)(^)(~)(*)(version)(.filetype)',
'pckg2(@)(^)(~)(*)(version)(.filetype)'
], (pckg1, pckg2) => {});
//without registration
await require(url);
Key | Description --- | --- PackageName | the name of the package Version | the version-number in string format: major.minor(.patch)(.build)(-releaseCandidate) Filetype | the file-extension - js/css/txt/xml/json/html/wasm/tag; if no filetype is specified ALL filetypes will be loaded
- | load highest version ^ | load highest minor-version ~ | load highest patch-version
Note - Determining the version number happens in this order
- Find best version match, if any versionnumber is specified
- Find the default, if no versionnumber is specified
- Take the highest version number if no default, nor versionnumber are specified
Note - If no filetype is specified all registered filetypes will be loaded
Samples
//register bootstrap
require.register({
bootstrap: [
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.css",
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.bundle.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css"
]
})
//load both css & js from the highest available version (4.6.0)
//4.6.0 will be the default (no default is set)
const bootstrap = await require('bootstrap');
//4.6.0 is the highest registered version
const bootstrap = await require('bootstrap@*');
//load only the css tag from version 3.4.1
const bootstrapCssTag = await require('[email protected]'); //fixed version number
//load only the js tag from version 3.x
const bootstrapJs = await require('bootstrap.js@^3.0.0'); //find highest minor
//try to load bootstrap 3.4.2
const bootstrap = await require('[email protected]');
//will result in a console.warn("package not found");
//version 3.4.2 was not registered using define(), require.register, nor require.config({paths})
//download without registration
const bootstrap = await require('https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.bundle.min.js')
Override amd dependencies
Require-es provides more flexibility on loading multiple versions of a given package. Unfortunately some amd/umd-packages include predefined dependencies. These can lead to unwanted / unexpected behavior.
###Example: react-dom + react Let's take a look at the conflict below: react 16 + react 18
require.register({
'react': [
'https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.production.min.js'
],
'react-dom': [
'https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.production.min.js'
]
});
const ReactDom = await require('react-dom@^16.0.0');
//react-dom version 16.14.0 will download/initialize (it's the highest match within major 16)
//react version 18.0.0 will download/initialize...
//this happens because react-dom has a dependency on 'react' baked into the module itself: define(['react'], factoryReactDom(React){})
//see code-snippet below
Why did react 18.0.0 initialize, when react-dom 16.x is required? The answer can be found in the react-dom source-code itself: https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js
...nction"===typeof define&&define.amd?define(["exports","react"],ea):(I=I||self,ea(I.ReactDOM={},...
Pay close attention to the dependencies set by the react-dom definition: define(["exports","react"]). This instructs require-es to load "react" (without version number specified) before the "react-dom" factory runs. If no "react" version is specified, the default version will be loaded (if no default is specified, the highest version becomes the default)
Fix hardcoded amd-dependencies
To avoid mixing up versions, require-es allows dependency overrides.
This is done by providing the dependencyOverrides object while registering an amd module.
Syntax
require.register({
packageName: {
url: 'urlToDownloadTheAmdModule',
dependencyOverrides: {
dependencyName: 'newDependencyName(@version)'
}
}
})
Example:
require.register({
'react-dom': [
{
url: 'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.production.min.js',
dependencyOverrides: { react: '[email protected]' }
},
{
url: 'https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js',
dependencyOverrides: { react: '[email protected]' }
}
],
'react': [
'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.production.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js'
]
});
This will override any hardcoded dependency on "react" to "[email protected]" (or "[email protected]") in both react-dom packages. The react-dom factory will now receive a matching react-version.
Looking back at the react-dom source code:
https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js
...nction"===typeof define&&define.amd?define(["exports","react"],ea):(I=I||self,ea(I.ReactDOM={},...
//will become
...nction"===typeof define&&define.amd?define(["exports","[email protected]"],ea):(I=I||self,ea(I.ReactDOM={},...
https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.production.min.js
...nction"===typeof define&&define.amd?define(["exports","react"],eb):(M=M||self,eb(M.ReactDOM={},...
//will become
...nction"===typeof define&&define.amd?define(["exports","[email protected]"],eb):(M=M||self,eb(M.ReactDOM={},...
Require.when() - only return explicitly defined/registered packages
By default requirees will always try to download a package, even when no registration/definition is available.
Example
require('myUnkownPackage')
//tries to download './myUnkownPackage.js'.
Require.when() ensures the requested package are explicitly defined/registered! If the requested package is not defined/registered yet, require.when will wait for an explicit define/register.
Usage
//wait for a single package to get defined/registered
require.when('packageName(@)(^)(~)(*)(version)(.filetype)').then(packageInstance => {});
//wait for multiple packages to get defined/registered
require.when([
'pckg1(@)(^)(~)(*)(version)(.filetype)',
'pckg2(@)(^)(~)(*)(version)(.filetype)'
]).then((pckg1, pckg2) => {});
//using callbacks instead of promises
require.when('packageName(@)(^)(~)(*)(version)(.filetype)', callback, failCallback)
Samples
require.when('myUnkownPackage')
.then(p => console.log('my unknown package is defined now', p))
//waits until someone explicitly defines the package
//the promise above will resolve with the 'myUnknownPackage'-exports after 5 seconds
setTimeout(
() => define('myUnkownPackage', () => exports),
5000
);
require.when('three@^0.100.0')
.then(three => console.log('three is registered and usable now', three))
//waits until someone explicitly registers the package
//
setTimeout(
() => require.register({
three: "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.148.0/three.min.js"
}),
5000
);
Define a package
Usage (follows AMD-pattern)
//named define
define(packageName(@)(version)(.filetype)(-default), [dependencies], factory);
//anonymous define
define([dependencies], factory);
Key | Description --- | --- PackageName | the name of the package Version | the version-number in string format: major.minor(.patch)(.build)(-releaseCandidate) Filetype | the file extension - js/css/txt/xml/json/html/wasm/tag Default | indicates the default version Dependencies | package names (or urls) on which this package is dependent Factory | function / json / text / HTMLElement / xml / ...
Note - The dependency naming rules are equal to the require naming rules: packageName(@)(^)(~)(*)(version)(.filetype)
Note - The datatype of the factory can be different, depending on the filetype:
- js: function
- json: json-text (gets converted to JSON) / json-object
- css: css-text (gets converted to a script-tag)
- txt: text
- xml: text
- html: html-text (gets converted to an HTMLElement) / HTMLElement-object
Samples
//define an amd module
define('hello.js', ['react@^16.0.0'], (react) => {
function fn1(){}
return {fn1}
});
//define a CJS module
define('hello.js', [], () => {
const react = require('react');
function fn1(){}
module.exports = {fn1};
});
//define a json object
define('hello.json', [], '{"foo": "bar"}');
//define css
define('hello.css', [], 'body{background-color: red}');
//define a specific version of hello.css
define('[email protected]', [], 'body{background-color: red}');
//define an html fragment
//gets converted to an HTMLElement when required + will automatically add the CSS to the page
define('hello.html', ['hello.css@^1.0.0'], '<div>bla</div>');
//alternative
define('hello.html', [], document.createElement('div'));
//define r.js style
define('react', reactFactoryFn);
//using version number
define('[email protected]', react1702FactoryFn);
RequireEs options
require.config({
allowRedefine: false,
invokeNonMatchedDefines: true
});
Key | Description --- | --- allowRedefine | default: false;false: you cannot change the factory, for a given package (after it gets required for the first time)true: the factory can be changed at any time, next require will use the new factory invokeNonMatchedDefines | default: false; automatically invoke anonymous defines which could not be matched to any package in the RequireEs register.
RequireEs events
Usage
//subscribe to an event
require.on(evtName, callback);
//require.subscribe is a synonym for require.on
require.subscribe(evtName, callback);
//publish an event
require.publish(evtName, payload);
//spy on all events
require.addWireTap(callback);
Predefined events
Event | Trigger --- | --- requirees.pre-define | when define gets called, but the factory is not stored into the registry yet requirees.define | when a package factory was added to the registry requirees.pre-register | when require.register or require.config({paths}) are called, but the package it not added to the registry yet requirees.register | when a package was added to the registry requirees.pre-file-load | before an actual file/factory load is happening requirees.file-load | when a file/factory load has completed requirees.wiretaps | on all events requirees.scripttag.preadd | triggers before requirees appends a script-tag to the dom requirees.scripttag.added | tiggers after requirees appended a script-tag to the dom requirees.styletag.preadd | triggers before requirees appends a script-tag to the dom requirees.styletag.added | tiggers after requirees appended a style-tag to the dom
Samples
//listen to all files being loaded
requirees.subscribe('requirees.pre-file-load', ({package}) => {
console.log(`start loading package: ${package.name}`);
})
//listen to all events being triggered
requirees.addWireTap(console.log);
//alternative:
requirees.subscribe('requirees.wiretaps', console.log);
//create custom events
requirees.subscribe('hendrik.sayHello', data => {
console.log('hello!!', data)
});
requirees.publish('hendrik.sayHello', {foo: 'bar'});
WebPack / Rollup bundling for RequireEs
Intro
In the examples below project-dependencies 'jquery', 'react' and 'lodash' will be removed from your bundle and loaded using RequireEs.
To make this happen:
- Add RequireEs to the top of your HTML-document
- Register your dependencies on the document (This will happen automatically using RequireEs-server, coming soon)
- Load your AMD (/UMD) bundle through RequireEs (recommended), or using a script-tag
Samples for Webpack and Rollup bundling can be found in the sections below.
//require the AMD module
require.register({
jquery: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js',
react: [
'https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js'
],
lodash: [
'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js'
]
});
//require the application package
await require('/scripts/myLib.amd.js');
If your package is added through a script-tag, call require.config({invokeNonMatchedDefines: true}) to invoke the factory immediately:
<script>
requirees.config({
invokeNonMatchedDefines: true
})
</script>
<script src="/scripts/myLib.amd.js"></script>
Build your bundle AMD style
Webpack
module.exports = {
//...
output: {
libraryTarget: 'amd',
filename: 'scripts/myLib.amd.js'
},
//...
externals: {
jquery: 'jquery@*',
react: 'react@^17.0.0',
lodash: 'lodash@^4.17.0'
},
};
Rollup
export default {
//...
output: {
format: 'amd',
file: 'scripts/myLib.amd.js',
external: ['react', 'jquery', 'lodash'],
paths: {
react: 'react@^17.0.0',
jquery: 'jquery@*',
lodash: 'lodash@^4.17.0'
}
},
//...
};
Build your bundle UMD style
Webpack
module.exports = {
//...
output: {
libraryTarget: 'umd',
filename: 'scripts/myLib.amd.js',
library: 'myLib'
},
//...
externals: {
jquery: {
root: '$',
amd: 'jquery@*',
commonjs: 'jquery',
commonjs2: 'jquery'
},
react: {
root: 'React',
amd: 'react@^17.0.0',
commonjs: 'react',
commonjs2: 'react'
},
lodash: {
root: '_',
amd: 'lodash@^4.17.0',
commonjs: 'lodash',
commonjs2: 'lodash'
}
},
};
Rollup
export default {
//...
output: {
format: 'umd',
file: 'scripts/myLib.amd.js',
name: 'myLib',
external: ['react', 'jquery', 'lodash'],
paths: {
react: 'react@^17.0.0',
jquery: 'jquery@*',
lodash: 'lodash@^4.17.0'
},
globals: {
react: 'React',
jquery: '$',
lodash: '_'
}
},
//...
};