JavaScript Mapping for Modules


On this page:

JavaScript Default Mapping for Modules

Slice modules maps to a JavaScript scope and to a TypeScript namespace with the same name as the Slice module. The mapping preserves the nesting of the Slice definitions. For example:

Slice
module M1
{
    // Definitions for M1 here...
    module M2
    {
        // Definitions for M2 here...
    }
}

// ...

module M1    // Reopen M1
{
    // More definitions for M1 here...
}

The mapping for this definitions is equivalent to the following code:

JavaScript
(function(module, require, exports)
{
    var Ice = ...
    var _ModuleRegistry = Ice._ModuleRegistry;
    var M1 = _ModuleRegistry.module("M1");
 
    // Definitions for M1 here...
 
    M1.M2 = _ModuleRegistry.module("M1.M2");
 
    // Definitions for M2 here...
 
    // More definitions for M1 here...

    exports.M1 = M1;
}(...));
TypeScript
export namespace M1
{
     // Definitions for M1 here...
     namespace M2
     {
          // Definitions for M2 here...
     }
}

export namespace M1
{
     // More definitions for M1 here...
}

The generated code exports the top-level modules. For browsers it adds symbols to the global window object and for NodeJS it uses the native exports object.

When developing for NodeJS, you include the various Ice components with require statements as shown below:

JavaScript
const Ice = require("ice").Ice;
const IceGrid = require("ice").IceGrid;
const IceStorm = require("ice").IceStorm;
...
TypeScript
import {Ice, IceGrid, IceStorm} from "ice";
...


Importing the generated code for your own Slice definitions works the same way:

JavaScript
var M1 = require("M1Defs").M1;
TypeScript
import {M1} from "./M1Defs";


This example assumes the generated Slice definitions are in the file M1Defs.js.

In a browser application, the necessary JavaScript files are usually loaded via HTML script tags:

<script type="text/javascript" src="Ice.js"></script>
<script type="text/javascript" src="M1Defs.js"></script>
<script type="text/javascript">
    // You can now access Ice and M1 as global objects
</script>

The file Ice.js shown above only provides definitions for the Ice run time; you would need to explicitly include the corresponding files for any other Ice components that your application needs, such as IceGrid.js.

JavaScript ESM Mapping for Modules

Ice 3.7 introduces support for an alternative mapping for Slice modules. The alternate mapping differs from the standard mapping in the way that modules are imported and exported. With this new mapping the import and export are done using the standard JavaScript import and export statements.

Like with the default mapping, each Slice module maps to a JavaScript object with the same name, and only the top-level modules are exported. Using our previous example we need to add the [["js:es6-module"]] file metadata to enable the new module mapping:

Slice
[["js:es6-module"]]
module M1
{
    // Definitions for M1 here...
    module M2
    {
        // Definitions for M2 here...
    }
}

// ...

module M1    // Reopen M1
{
    // More definitions for M1 here...
}

This definition maps to the corresponding JavaScript definitions:

JavaScript
import { Ice } from "ice";
const _ModuleRegistry = Ice._ModuleRegistry;
//...
let M1 = _ModuleRegistry.module("M1");
//...
M1.M2 = _ModuleRegistry.module("M1.M2");
// ...
export { M1 };

This doesn't affect the generated TypeScript declarations as the JavaScript generated code still export and import the same objects but it will affect how the modules are resolved by the TypeScript compiler in this case you will want to use the TypeScript compiler argument --module es6 to tell the compiler that you want to resolve imports using es6 modules.


Using "ice" module in the browser

The "ice" NPM package can be used for web browser applications, in conjunction with a module bundler that has support for CommonJS modules like WebPack, Rollup, or Vite. You have to configure the bundler to exclude the "fs" and "net" modules that are only required for NodeJS applications. See bellow for detailed configuration examples:

// webpack.config.js
module.exports = {
    target: 'web',
    mode: 'development',
    entry: './src/index.js',
    output: {
        filename: 'bundle.js'
    },
    resolve: {
        fallback: {
            fs: false,
            net: false
        }
    }
};

See https://webpack.js.org/configuration/externals/

// rollup.config.js 
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default {
    input: 'src/index.js',
    plugins: [commonjs(), nodeResolve()],
    output: {
        file: 'dist/bundle.js',
        format: 'iife',
        globals: {
            net: '{}',
            fs: '{}'
        }
    },
};

See https://rollupjs.org/

With Vite there is no additional configuration required

See https://vitejs.dev/

JavaScript modules

The file metadata [["js:module:<name>"]] can be used to map a set of Slice modules possibly defined across several Slice files to a single JavaScript module, the metadata instruct the Gulp Ice Builder to bundle together the definitions for a given module, the metadata also affects the import statements generated by Slice-to-JavaScript compiler, when a Slice file is included by other file using the same js:module metadata, the compiler will generate an import statement for the corresponding JavaScript generated file, when including the Slice file from a file using a different js:module metadata, the compiler will generate an import statement to import the other file js:module. For example:

Slice
// Chat.ice
[["js:es6-module", "js:module:chat"]]
module Chat
{
...
}

// Util.ice
[["js:es6-module", "js:module:chat"]]
module Util
{
...
}


The Gulp Ice Builder will generate a bundle for the "chat" module named chat.js, when the typescript option is enabled it will also generate a TypeScript definitions file chat.d.ts, you can use the generated code by importing the generated chat.js file from your application as in:

index.js
import {Chat, Util} from "./chat";
...

The generated code for Slice definitions in a different module, will import the "chat" module as show in the following sample

Slice
// App.ice
[["js:es6-module"]]


#include <Chat.ice>

module Demo
{
...
}

index.js
import {Chat} from "chat";
...

This is convenient when the generated code for the module is distributed as a JavaScript package, user of the package will be able to use the Slice definitions for the package and have their generated code depend on the JavaScript package rather than have a dependency on the generated files.

See Also