Dynamic AMD loading

Hello everyone!

A bit of story: I am working on multilingual project and now I am integrating momentjs into my project.
As you probably know - momentjs has a lot of i18n files for different languages. All i18n code is stored in js files.
Basically if you don’t need to support lots of languages - you just import necessary language.
But if you wish to handle all possible languages - you need to dynamically import them.

This task was very easy achievable on my previous projects with requirejs - you just type require(['some/path/to/locales' + locale], cb, errCb) and bam, its ready to use.

My configuration is webpack + babel.
I have tried to use ES6 import() to achieve the same.
Here I sucked with aurelia webpack plugin.

So, what have I done:
First of all I need to copy all i18n files, webpack.config.js:

new CopyWebpackPlugin([
      { from: 'src/locales/', to: 'locales/' },
      { from: 'node_modules/moment/locale', to: 'locales/modules/moment'}
    ]),

Then, we installing babel plugin to handle import() - yarn add babel-plugin-syntax-dynamic-import -D
And connect it in .babelrc.js:

"plugins": [
      "transform-decorators-legacy",
      "transform-class-properties",
      "syntax-dynamic-import"
    ],

Lets do, some import: import('/locales/modules/moment/en'); and compile it. Keep in mind - there is can be anything instead of en and this is impossible to know before application startup.

And… it just does not work

ERROR in ./src/main.js
Module not found: Error: Can't resolve '/locales/modules/moment/en' in 'D:\Work\TRIMS-app\src'
 @ ./src/main.js 198:2-38
 @ ./node_modules/aurelia-webpack-plugin/runtime/empty-entry.js
 @ multi aurelia-webpack-plugin/runtime/empty-entry aurelia-webpack-plugin/runtime/pal-loader-entry aurelia-bootstrapper

Also, I tried a require method with the same results.

Is there any way to “tell” aurelia webpack plugin to ignore some specific modules?
Or maybe I am something missing?

I will be grateful for any help or suggestions.

Thanks in advance.

Does this help you: https://github.com/jmblog/how-to-optimize-momentjs-with-webpack.

const webpack = require('webpack');
module.exports = {
  //...
  plugins: [
    // Ignore all locale files of moment.js
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
  ],
};

What I ended up having to do is:

  1. In my webpack config, ignore json files under locale.
  2. In a .js file that is imported / required somewhere from main.js, import all PLATFORM.module(‘locales/en/foo.json’).

That’s the only way I could get webpack to include all my json files WITHOUT DOUBLE ENCODING.

Untested.

You can try add requirejs to your webpack bundle, to keep using your existing code with requirejs.

Use special call to requirejs to avoid webpack from recognizing it as an AMD require.

PLATFORM.global.requirejs(['some/path/to/locales' + locale], cb, errCb)

The above line should be able to bypass webpack’s tracing, it would not trigger an error at bundling time, it should load AMD module at runtime.

2 Likes

Hello everyone!

Thanks for all answers.

@stevies You are right, this prevents webpack from loading all locales and skip processing of a specific modules, witch resolves a compile time error.

@khuongduybui You didn’t understand - I want to keep locale files outside of a bundle.

@huochunpeng Unfortunately requirejs internally tracks dependencies, so its impossible to use it for “part” of the project.

I did some research over this stuff - how to load AMD modules on demand with webpack.
Turns out webpack just bundle local files together, it just has not this dynamic loading feature as requirejs had already for a decade.
Oh… :persevere: Guys, modern web turned into hilarious and horrible place, where you need (AGAIN!) manually create a <script> tags (more on that later)

So here is the possible options:

  1. Compile all bundle as amd and use requirejs as module loader - this turns in to hell, when trying to bootsrtap aurelia. Did anyone try this?
  2. Use externals feature. But here you need manually put a script tag into head and then import a module. As turns out, this does not work with AMD modules especially without defined name.
  3. Include all possible locales for all libraries, projects for a lot of chunks and wait them to load.

I spend 2 days, in order to understand - I need to spend enormous amount of time on testing and fixing trying to run aurelia on requirejs just to be able to load a few AMD modules dynamically. It’s ridiculous.

So if anyone tried one of this options, please share your experience, it would be very helpful.
Did I missed something?
Is there any other ways to load AMD modules stored outside of webpack bundle?

Thanks.

With webpack copy plugin, I think this requirement can be easily solved if moment is loaded statically via html script tag. Loading locale will be as simple as creating a <script/> element and point src attribute to the locale location. If moment was bundled, you will then have to do a little bit different trick with xhr, but it shouldn’t be too difficult. Choose your preferred way and let me know if you need further help

Hello @bigopon, thank you for your reply.

Well, as I am planning to use not only moment but other libraries (like select2, airdatepicker and more) which also had their own AMD locales - handling all this stuff via <script> tag will be nightmare.

So XHR is preferred way.

Thank you.

I don’t think it’s that bad to manage with <script/>. Following is an example of what you can do with it:

const loadedLocale = Object.create(null);
export function loadMomentLocale(locale) {
  if (locale in loadedLocale) {
    return Promise.resolve(loadedLocale[locale])
  } else {
    const html = document.documentElement;
    loadedLocale[locale] = new Promise(resolve => {
      const script = document.createElement('script');
      script.src = 'locales/modules/moment/' + locale;
      script.onload = function() {
        script.onload = script.onerror = null;
        html.removeChild(script);
        resolve(true);
      };
      script.onerror = function() {
        script.onload = script.onerror = null;
        delete loadedLocale[locale];
        console.error(`Loading locale "${locale}" failed.`);
        html.removeChild(script);
        resolve(false);
      }
      html.appendChild(script);
    });
  }
}

Edit: above is for globally loaded moment. For bundled moment, it will be a little bit different, but from your reply, it was not certain if you need help or not so I didn’t go any further.

1 Like

Hello @bigopon, thanks for sharing your code.

There is nothing bad with <script> tags, but here you showed how we load dynamic stuff in 2007 - basically any developer had to write code like this.
That is why I believe - there is should be more elegant solution.

I am still digging on this question, but I cannot spend to much time because of deadlines.

By the way, html.removeChild(script); is there any reason you want to remove this tags?
As far as I know, <script> tags don’t have much impact on memory consumption and/or performance overall.

Thanks.

@indiwine I think webpack is the last bundler you would want to have for dynamic runtime module. Probably because of this requirement, you are having a hard time. And to me, appropriate solution can be a dated one but your experience may tell different story.

By the way, html.removeChild(script); is there any reason you want to remove this tags?
As far as I know, <script> tags don’t have much impact on memory consumption and/or performance overall.

I was just being responsible, cleaning my rubbish :smiley:

  1. Compile all bundle as amd and use requirejs as module loader - this turns in to hell, when trying to bootsrtap aurelia. Did anyone try this?

It’s not hell, especially when you try auto tracing from @huochunpeng, it’s nice and it’s capable. Also you can do bootstrapping the same way with this Stackblitz snippet https://stackblitz.com/edit/aurelia-javascript?file=index.js . Replace the StackBlitzLoader with requirejs and everything is pretty simple.

Since @bigopon mentioned auto tracing, here is the status.

The auto tracing aurelia-cli is in final review stage, will be released in couple of weeks. It will be a breeze to implement runtime module loading like this.