Supporting all the frameworks and build systems took days of research and practicing. This article goes through lessons learnt and how to structure a project while supporting the following:
This page is based on the ag-Grid project on Github. Check the project for a full working example of the concepts below. The project is written in TypeScript, however you do not need TypeScript to use the project.
You do not need to be using, or even be a fan of, Angular 2 or Typescript for them to be useful for you. You can learn from them and benefit from what they bring to the community even if you're a Babel and React guy.
We are going to be generating a lot of files. To make this easy to work with, we define two core folders as follows:
Internal modules in Typescript allow you to structure your 'pre ECMA 6 modules' code into separate files and then have Typescript combine all the files together (similar to bundling via Browserify and Webpack) and provide a level of namespacing away from the global scope. This was great back in the day before Browserify and WebPack, as it allowed splitting up your project into multiple files and then have the Typescript compiler bundle your files into one file.
Do not do any of this!! It lacks support for CommonJS or ECMA 6 modules. Instead you should use External Modules.
Do not use TypeScript internal modules, delete them from your code, forget they exist, and move to TypeScript external modules.
External modules in TypeScript are what you see in ag-Grid and Angular 2 code and this is what works best with CommonJS as it gets compiled down to CommonJS 'require' functions which is what most of the rest of the world is using, incluing the React community.
Next is the compile settings for TypeScript. ag-Grid uses Gulp for compiling TypeScript and has the TypeScript settings in the Gulp file. The portion of the gulpfile.js of interest is as follows:
The item of interest for now is module: 'commonjs'. TypeScript supports the following 4 Modules: commonjs, amd, system and umd. This is what we think about them:
So from the above, commonjs modules is the one to go for as it is still popular and can be used by all the other popular module loading systems.
Once your project has CommonJS files, another project can use your project using CommonJS.
For example someone can include your file using Node dependencies and the following code:
This is great, it works, but it's long winded that the client has to include 'dist/lib' in each call. To get around this:
You can have as many 'main' files as you like, giving you the option of splitting the modules out. However this only makes sense for very large projects where splitting out helps. It is standard practice to put these main files in the root of your project.
The use of the main files is optional, but highly recommend for the following reasons:
Similar to exposing the CommonJS modules, you should expose the definition files. Do this
by creating a definition file with the same name as the module file. In ag-Grid, this file
is called main.d.ts and contains lines like the following:
Now, when a client is using TypeScript and imports your project via CommonJS or ECMA 6 modules, your IDE will be able to pick up the definition files automatically.
The problem with the above is it assumes your client will be using a module loading system. That is where
WebPack is to the rescue. It takes a CommonJS module and generates a bundle that exposes
the shared component on the global namespace. This will allow your clients to use your component 'the old way'
by just referencing your script directly from the HTML page. In the ag-Grid project, similar to TypeScript, Webpack is also configured
inside the Gulp file. You have the option of webpack.config.js instead of having the settings in Gulp, again
no advantage, it's just the preference of ag-Grid to keep the config inside the Gulp file.
What you should note are the following options:
This technique, btw, is what Angular 2 uses to create its UMD version of Angular 2.0.
If your client is using CommonJS, then they can require / import CSS files in their
code. You could include the require / import in your component code, however doing so
would require advance knowledge of what packaging plug-in will be used. For example
some plugins reference CSS files like this:
Because you can't know, the safest is to let the client reference the CSS in the client code.
Angular 2.0 and React components have dependencies on their associated libraries. If you want to use either of these libraries, you have to make them as dependencies in your project. The best way to do this is as peer dependencies (peerDependencies in package.json) so that the client can control what version of the library to use and your component will use what's provided to it. This has the following impacts:
And that's it. The world of packaging is changing, so I don't know for how long the above will be relevant. However you can take it from me, ag-Grid is used by thousands of people, the above system is tried and tested and does work.