There are many ways to format JavaScript modules: AMD, CommonJS, UMD, ES6, global script. I've seen projects that structure their source code in whatever way they want and run a build process to generate a dist directory containing code in all the above formats. This has the advantage that the user of the code can just pick whichever format is most applicable to his environment.
This method works fine as long as the module has no dependencies on other modules. In the case where the modules must import other modules, there are implied complications. For example RequireJS uses a config file that looks like:
requirejs.config({
paths: {
'jquery': 'js/lib/jquery',
'ember': 'js/lib/ember',
'handlebars': 'js/lib/handlebars',
'underscore': 'js/lib/underscore'
}
});
Other loaders have equivalent mechanisms for mapping import paths.
If jQuery is a dependency, should the module import it from the path 'jquery'? What if the system in which it is being incorporated stores jQuery at the path 'libs/jquery'? In this case, is it the responsibility of the author of the system incorporating jQuery to provide aliases in the configuration of the import path?
This questioning strongly suggests that a truly reusable module must provide code formatted in all module formats as well as document clearly upon what libraries (and versions thereof) it depends and document what import paths at which those libraries are assumed to exist.
For example I could author a fancy jQuery plugin that I distribute in AMD, CommonJS, ES6, and global variations. I would document that this plugin depends on jQuery version 2.0 imported through the path 'jquery_on_a_path_that_confuses_you'. The would-be user of this plugin must copy the plugin into his project and then configure his module loader or build tool to export jQuery at the path 'jquery_on_a_path_that_confuses_you'.
As far as I can tell:
There is no standard for what to use for import paths.
There is no standard way to express the dependency, version, and import path requirements to the user of a piece of code.
There is no standard remedy to deal with clashing import paths or load multiple versions of a library.
Does there exist any plan to deal with this strange arrangement? To me it seems a little crazy to have module systems that don't know how to name their modules. Am I wrong?
You may want to check jspm.io + SystemJS which is a relatively new package manager and universal module loader which is increasing in popularity.
Please find below some presentations and article on the subject I found useful:
https://www.youtube.com/watch?v=MXzQP38mdnE,
https://vimeo.com/65042246,
https://www.youtube.com/watch?v=szJjsduHBQQ,
http://javascriptplayground.com/blog/2014/11/js-modules-jspm-systemjs/
Late with the answer, but if you're after writing plain JS code (without jQuery or other frameworks), I've found that there's the deploader.js repo, which you can use to wrap any kind of JS into modules and do dependency loading.
May worth checking out.
Related
Context
I am creating a library with 2 ways of initialization:
Automatic - I download some stuff for you asynchronously, then initialize.
Manual - You already downloaded the stuff before, then I initialize the library immediately (sync).
I have successfully implemented tree-shakable libraries in the past. What we would normally do, is separate the code into two modules, and let the app developer choose which one to import, thus allowing tree-shaking the other part. Similarly to this:
import { LibraryAsyncModule } from 'my-library'; // 🔁 Or LibrarySyncModule
#NgModule({
imports: [LibraryAsyncModule] // 🔁 Or LibrarySyncModule
})
export class AppModule { }
What I want to accomplish ✔
To reduce the learning curve of using my library, I'm trying to design is a single imported module which includes the relevant module and allows tree shaking the other. The following diagram shows the desired structure.
What I want to avoid 🚫
I could create factory providers that will detect the config passed to forRoot() and load the corresponding module at runtime. However, importing the modules at runtime turns initialization to async, and will also prevent angular from bundling the used module with the app.
I need a build time solution. My prototypes show that simply including both sync and async modules in the core module result in both being bundled.
How would that single module look like? Any ideas / suggestions? 🤔
Your approach should work, but you will need to enable it.
Since Tree-shaking works only with es6 modules you need to publish your lib with it & specify module property in your package.json.
// package.json
{
...
"main": "dist/index.js",
"module": "dist/es/index.js", // <- this file should contain es modules not commonjs
...
}
This setup will tell bundlers (webpack in your case) to use the es modules export and allow them to enable tree-shaking feature.
I recommend for package devs to use tsdx cli tool which does this automatically and supports many other features such as TypeScript support etc.
npx tsdx create mylib
I'm migrating/moving a project based on require.js to webpack v3. Since all my modules are using the following syntax:
define([modules,..], function(mod1,..)
Which declares which modules to use, and assigns the modules to the variables in the anonymous function. This seems to be deprecated since v2 of webpack. I can't find any information about this (except for the documentation for web pack v1).
Should I rewrite all my modules to the commonjs (including dependencies) or are there any smart way to use the AMD modules?
Help much appreciated :-)
Regards
AMD never found much use outside of requirejs so likely you will need to convert. There are tools that will help:
https://github.com/anodynos/uRequire can convert code from AMD -> UMD / CommonJS
There are caveats from (https://github.com/anodynos/uRequire/wiki/nodejs-Template):
Runtime translation of paths like models/PersonModel to ../../models/PersonModel, depending on where it was called from. You 'll still get build-time translated bundleRelative paths, to their nodejs fileRelative equivalent.
For most projects this is not an issue.
Can't use the asynchronous version of require(['dep'], function(dep){...})
You should be able to use the synchronous version of require. If using webpack2 you can use System.import or require.ensure
Can't run requirejs loader plugins, like text!... or json!...
You will find webpack version of all of these plugins
There's no mapping of /, ie webRootMap etc or using the requirejs.config's {baseUrl:"...."} or {paths:"lib":"../../lib"}
This can be replicated with https://www.npmjs.com/package/babel-plugin-module-alias
The CaptEmulation's answer is not valid for newer Webpack versions. Webpack supports AMD natively (neither additional loaders, nor plugins need to be installed). A thorough instruction is available here: https://webpack.js.org/api/module-methods.
This fact may easily go unnoticed when one tries to rewrite a RequireJS-based build to Webpack, as RequireJS uses relative paths without the trailing ./, e.g.
define('app/dep1', function(dep1) { ... });
which will not pass in Webpack without additional configuration (assuming that both require.config.js and webpack.config.js are in the same directory):
{
resolve: {
modules: [ './', ... ] // other entries possible here
}
}
Is there a way to use a CommonJS module on a site like plnkr, JSFiddle, or JS Bin?
I'd want to turn it into a global.
This is for easily providing demos without having to use UMD.
I'd find the Github repos and then reference the source files using rawgit.com.
requirebin is a jsbin like enviroment that allows for modules built using browserify, but I am not aware of a way to use an unpublished module
You can use https://www.skypack.dev/, it adjusts any npm package to be used in an ESM-ready environment
For example, if you want to use a library that uses UMD (Universal Module Definition) or CJS (Common JS), you can use skypack and the module will be converted to ESM (ES Modules).
Working case
The canvas-sketch-util/random library uses CJS to import and export some modules (it use require('something') inside), but the code is converted to ESM with that service, and can run directly in the user's browser
<script type="module">
// Use 'https://cdn.skypack.dev/' + 'npm package name' + '#version its optional'
import random from 'https://cdn.skypack.dev/canvas-sketch-util#1.10.0/random'
console.log('A random number: ', random.range(1,10))
</script>
Non-working case
The same library doesn't work if we use https://unpkg.com/, as it only distributes what is ready, and the code gives error (at the very beginning of the file there are already some require functions from CJS):
<script type="module">
import random from 'https://unpkg.com/canvas-sketch-util#1.10.0/random'
console.log('A random number: ', random.range(1,10))
</script>
Important
It's important to use type="module" inside the script, jsfiddle and codePen already do that, it can work locally too.
Each library has an export differently, you must understand how the library is being exported to be able to import it into your code. Here are some examples of different ways of importing, not all cases, you have to know which way of import is the right way.
<script type="module">
// Usually when don't have a default export
import * as libOne from 'https://cdn.skypack.dev/lib-one'
libOne.initSomething({someconf:true})
// OR
libOne(someParam1, someParam2)
// Usually when export is just one function/object
import libTwo from 'https://cdn.skypack.dev/lib-two'
libTwo(someParam1, someParam2)
// Usually when there are several things inside the lib and you only want to use one
import { libThree } from 'https://cdn.skypack.dev/lib-three'
libThree(someParam1, someParam2)
</script>
Final considerations - 2022
Their website ( https://www.skypack.dev/ ) says the following
Skypack is free to use for personal and commercial purposes, forever. The basic CDN is production-ready and is backed by Cloudflare, Google Cloud, and AWS. We're fully committed to building a core piece of infrastructure you can rely on.
So it seems to be something you can trust
I'm currently working on a big JavaScript project for which we want to define our own API. I'm using RequireJS as my dependency loader and it suits me just fine, allowing me to define modules in their respective file. I do not make use of my own namespace, a module returns an instance, which can be used in other modules, i.e.:
define(
['imported_module'],
function(module){
module.doSomething();
}
)
However as the number of files grows, I'd like to decide how to structure these files in folders. Currently I use the following scheme to name my files:
[projectname].[packagename].[ModuleName]
An example could be stackoverflow.util.HashMap.js. I would like to introduce a project folder, a folder per package and rename the files to the module name, like:
stackoverflow/util/HashMap.js
This structures my code quite neatly into folders, however the filename reflects only the module now. I'd like to define some kind of routing to be able to define how RequireJS should look for files. Example:
The file
stackoverflow/util/stackoverflow.util.HashMap.js
Should be importable by the statement
define(['stackoverflow.util.HashMap'],function(HashMap){});
Has anyone experience with structuring large JavaScript projects and if so, could you share your approach?
You shouldn't specify the routing info on your js file names, those are the namespace and folder paths' jobs. So stackoverflow/util/HashMap.js is just fine. And you can use define("stackoverflow/util/HashMap", ....) to tell the dependency.
If you need to put your modules in a different folders, you can config paths for your loader, see this manual from RequireJS API.
There's no best way for structure your js files. But put the root namespace in a src folder is always a good practice. You can see the dojo source code and YUI source code and use similar ways for your project. They both are large scale Javascript projects.
actually it's better to get js lib routing to load all js using standard interface: "js.yoursite.com/lib-0.2.js" there should be a router (php or other, and able to cache queries). So there you could determine and control whole pathes that you use. Because common jquery plugin should stay at one dir, with jquery, and your own custom plugins not.
And there you control each project by it's own rules:
jquery/
plugins/
jquery.prettyPhoto.js
jquery.min.js
mySuperJS/
stable.0/ -- there your production version for 1.0 branch
module.js
0.1/
module.js
0.2/
module.js
0.3/
module.js
myOtherlib/
stable.0/ -- production version for all 0.* versions
stable.1/ -- production version for all 1.0 versions
0.1/
0.2/
0.3/
0.4/
0.4.1/
0.4.1.18/
We're using such structure around a year and it's the best for us. But sometimes we use more complex solution and separate all modules for libs, plugins, tools, components and apps.
Hello with RequireJS I can set a base path like this: base : './app/' so when I am in ./app/foo/bar/ for example and I have a script where I use require('foo'); RequireJS then would search for ./app/foo.js and not in node_module folder or in ./app/foo/bar/foo.js this comes handy when you have a kind of structure where it would be much cleaner for you as a developer to see the dependencies instead of having ../../foo.js. I could have ./app/foo.js and ./app/foo/foo.js and ./app/foo/bar/foo.js it would be much more cleaner to have:
require('foo');
require('foo/foo');
require('foo/bar/foo');
rather than:
require('../../foo');
require('../foo');
require('./foo');
Now you could say why not change the name and not have foo everywhere, let's say that we can't for any reason…
Another lack of feature that I see in node's require method against RequireJS is the ability of setting path mapping, if I have a directory named ./app/super-sized-directory-name/ in RequireJS I could simply do 'big-dir' : 'super-sized-directory-name' and then I could simply use require('./app/big-dir/foo') with Node.js's require method this is not possible as far as I know…
--alias, -a Register an alias with a colon separator: "to:from"
Example: --alias 'jquery:jquery-browserify'
You can register aliases with browserify, so that covers your renaming.
As for your rooted absolute paths, that can't really be done. As mentioned modul8 has a namespacing mechanism to solve this.
I would recommend you pong SubStack in #stackvm on freenode and ask him directly.
It may or may not help you, but I believe the Dojo Frameworks AMD Loader is API compatible with RequireJS and providing you are using a new microkernel does not pollute the global namespace.
I believe it only has require() and define() in the global namespace now.
Anyway their method of dealing with this is to do something like:
require(["dojo/node!util"], function(util){
// Module available as util
});
The documentation is at http://dojotoolkit.org/reference-guide/1.8/dojo/node.html
Use uRequire which provides a 'bridge' between nodejs require and AMD define modules, without reinventing the wheel (it is build on top of the two standards). It basically converts modules from AMD or commonJS format to the other format or UMD that runs smoothly on both nodejs & the browser.
It is also translating dependency paths with flexible path conventions, so you can have either '../../foo' or 'bar/foo' depending on which makes more sense at the point you are at.
Your AMD or UMD modules are loaded asynchronously on browser (using AMD/requireJs or other AMD loader) and on node the asynchronous require(['dep1', 'dep2'], function(dep1,dep2){...}) is also simulated.