####################
Client integration
####################

***************
Getting started
***************

The client component is the single client-side toolkit that can be used with all of our HOOPS Envision for Web server components, or as a pure client-side toolkit. It is provided in a minified Javascript UMD bundle with additionnal TypeScript definitions. The quickest way to start is to take a look at our examples :

* :doc:`/examples/build-your-first-app` provides a quick introduction to using the client component in a Web application.

* :doc:`/examples/examples` provides a set of complete example applications using client and server components.

Quick testing in HTML
=====================

Most of our examples are loading the component as a global variable in a script tag. When the component is loaded directly from an HTML file, it will load as a global variable named "cee" that contains every sub-modules. This is also the default behavior when the usage context can not be detected.

.. code-block:: html

    <script src="./CeeEnvisionWebComponents.js"></script>
    <script>
        const session = new cee.CloudSession();
        ...
    </script>


************
Installation
************

1. If your application is about to use a specific server-side component (ug, cug, vs), follow first the "Getting started" installation section of the documentation of your server-side component :

    * :doc:`/guide/server-remote-model` for Remote Model
    * :doc:`/guide/server-constant-remote-model` for Constant Remote Model
    * :doc:`/guide/viz-streamer/getting-started` for Visualization Streamer

For a pure client-side usage (geo, usg), proceed directly to step 2.

2. Copy the content of the "client" folder of the HOOPS distribution to your client project. To learn more about the content of the distribution, please refer to the :doc:`/guide/distribution` page.

..
   Note for later: Should we add a dummy package.json file to help with installation in a traditional node_modules setup ?

3. If you plan to use the Remote Model (ug.RemoteModel), you will have to install an additionnal dependency : ``socket.io-client``. To install it, run ``npm install socket.io-client``.


**********************************
Integration in your build workflow
**********************************

The client component is distributed as a UMD module. It can be included natively with AMD and CommonJS. A transformation step is necessary to integrate it with ECMAScript modules. If the module type cannot be detected, or is not compatible with the UMD format, it will load as a global variable. The distribution also includes TypeScript definitions.

Below, you will find some tips and examples to integrate the Envision Web client to your workflow :

JavaScript
==========

1. With a bundler and/or a transpilation pipeline
-------------------------------------------------

To use import with a named local variable, and benefit from module encapsulation, you will need to use a transformation step. In most cases, adding a CommonJS transformation plugin to your favorite bundler will be sufficient to be able to use the UMD module with CommonJS, ESM and AMD targets interchangeably.

In most cases, you will need to use the usual default import syntax to load the module :

.. code-block:: javascript

    import cee from './CeeEnvisionWebComponents.js'; // ECMAScript module syntax
    ...

    // or

    const cee = require('./CeeEnvisionWebComponents.js'); // CommonJS syntax
    ...

    // or
    
    require(['./CeeEnvisionWebComponents.js'], function(cee) { // AMD syntax
        ...
    });

Here are some tips to help you integrate the UMD bundle in your project with popular bundlers :
    
a. Webpack
^^^^^^^^^^^

    Use the ``type: "javascript/auto"`` option in your javascript rule in the webpack config file to make the bundle compatible with most JS module types, including UMD. This setting works as well with a built-in Webpack rule (e.g. without a "use" option) than with complex ``Babel`` and ``SWC`` transformation pipelines.

    .. code-block:: javascript

        ...
        module: {
            rules: [
                {
                    test: /\.m?js$/,
                    exclude: /node_modules/,
                    type: "javascript/auto",
                    use: {
                        ...
                    }
                },
            ],
        }
        ...

    Beware : Contrary to other methods described in this section, ESM modules must be imported with the ``import * as`` syntax or by submodule with the ``import { MyClass } from`` syntax.

    .. code-block:: javascript

        import * as cee from './CeeEnvisionWebComponents.js';
        const session = new cee.CloudSession();
        // or
        import { CloudSession } from './CeeEnvisionWebComponents.js';
        const session = new CloudSession();
        ...

    - For CommonJS, use a regular default require statement as describe in the begginning of this section.

    - For AMD, the example should work for most cases. However, you may need more configuration when loading multiples AMD and UMD modules that depends on each others.

b. Rollup
^^^^^^^^^^

    A minimal ``rollup.config.js`` file should look like this :

    .. code-block:: javascript
        
        import resolve from '@rollup/plugin-node-resolve';
        import commonjs from '@rollup/plugin-commonjs';

        const production = !process.env.ROLLUP_WATCH;

        export default {
            input: 'index.js',
            output: {
                sourcemap: true,
                format: 'iife', // Change to your prefered output format. E.g. 'esm' for ES modules.
                name: 'app',
                file: 'dist/index.js'
            },
            plugins: [
                resolve({
                    browser: true,
                }),
                commonjs(),
            ],
        };

    That setup works as well with ESM, CJS and AMD modules pipelines. Use the default syntax described at the beginning of this section.

c. Vite
^^^^^^^^^

    Use the ``vite-plugin-commonjs`` to load the UMD as CommonJS, and take advantage of the compatibility between CommonJS and ES modules. Here is an example of ``vite.config.js`` configuration :

    .. code-block:: javascript

        import { defineConfig } from 'vite';
        import commonjs from 'vite-plugin-commonjs';

        export default defineConfig({
            plugins: [commonjs()],
        });

    Much like the rollup example, all the default import syntaxes described at the beginning of the section should work.

..
   - It seems the SWC is not usable as standalone CLI version, thougth it works well with . Plus, its shipped bundler is deprecated. So let's not confuse users with a SWC section until things are cleared up by a specialist of that tool.


2. Native JavaScript
--------------------

a. ESM
^^^^^^^

    Importing the client module with an ESM-style import will cause it to load as a global variable, unless you use a native CommonJS platform. This is a limitation of the UMD format. To overcome this limitation, you will need to use a transformation stage as described in sections above.    Importing the client module with an ESM-style import will cause it to load as a global variable, unless you use a native CommonJS plateform. This is a limitation of the UMD format. To overcome this limitation, you will need to use a transformation stage as described in sections above.

    .. code-block:: javascript

        import cee from './CeeEnvisionWebComponents.js'; // Loading an UMD module in a ES module context creates an empty import object.
        const session = new cee.CloudSession(); // Causes an error while accessing any sub-module.
        ...

    instead do :

    .. code-block:: javascript

        import './CeeEnvisionWebComponents.js'; // loads the UMD module as global "cee".
        const session = new cee.CloudSession();
        ...

b. CommonJS / Node.js
^^^^^^^^^^^^^^^^^^^^^

    If you use a Node-based environment, you can use the CommonJS ``require`` directly to load the bundle. 

    .. code-block:: javascript

        const cee = require('./CeeEnvisionWebComponents.js');
        const session = new cee.CloudSession();
        ...

    If your code executes in a browser, you will need to use a transformation pipeline that supports CJS modules. See the `1. With a bundler and/or a transpilation pipeline`_ section above for more details.

c. Electron
^^^^^^^^^^^^^^

    For the Electron platform specifically :

    - On the client side, use the bundling strategy described in the `1. With a bundler and/or a transpilation pipeline`_ section.
    - On the server side, you can use the CommonJS require to load the bundle directly (see the "Native CommonJS" section above).

d. AMD with RequireJS
^^^^^^^^^^^^^^^^^^^^^

    AMD is natively supported by the UMD format. If you use RequireJS as a module loader, you can load the Envision Web component as follows :

    HTML

    .. code-block:: html

        <script src="./require.js"></script>
        <script>
            require(['./index.js'], function(myApp){
                myApp();
            });
        </script>

    JavaScript

    .. code-block:: javascript

        require(["./CeeEnvisionWebComponents.js"], function(cee) {
            const session = new cee.CloudSession();
            ...
        });

    If you use AMD in a bundled project, read the Bundling section above.

TypeScript
===========

The TypeScript setup is very much similar to the JavaScript one. To use the client component as a TypeScript module, the easiest path is to use a UMD -> CommonJS -> ESM transformation pipeline such as the one described in the `1. With a bundler and/or a transpilation pipeline`_ section above. Often, an additionnal typescript plugins will be needed.

In most cases, the transpilation can make use of the Typescript definitions to provide named imports :

.. code-block:: javascript

        import * as cee from './CeeEnvisionWebComponents.js';
        const session = new cee.CloudSession();

        // or

        import { CloudSession } from './CeeEnvisionWebComponents.js';
        const session = new CloudSession();
        ... 

CommonJS and AMD syntaxes are also supported :

.. code-block:: javascript

    const cee = require('./CeeEnvisionWebComponents.js'); // CommonJS syntax
    ...

    // or
    
    require(['./CeeEnvisionWebComponents.js'], function(cee) { // AMD syntax
        ...
    });

1. Bundler / transpiler specific configuration
----------------------------------------------

a. Webpack
^^^^^^^^^^
    The ``ts-loader`` should work seamlessly witout the need for further configuration :

    ``webpack.config.js``

    .. code-block:: javascript

        ...
        {
            test: /\.tsx?$/,
            exclude: /node_modules/,
            use: {
                loader: "ts-loader",
                options: {
                    transpileOnly: true,
                },
            },
        },
        ...

    You can then use both ``import * as`` or ``import { MyClass } from`` syntax.

b. Rollup
^^^^^^^^^^

    Here is an example of minimal ``rollup.config.js`` file that should work out of the box :
    
    .. code-block:: javascript
        
        import resolve from '@rollup/plugin-node-resolve';
        import commonjs from '@rollup/plugin-commonjs';
        import typescript from '@rollup/plugin-typescript';

        const production = !process.env.ROLLUP_WATCH;

        export default {
            input: 'index.ts',
            output: {
                sourcemap: true,
                format: 'iife',
                name: 'app',
                file: 'dist/index.js'
            },
            plugins: [
                resolve({
                    browser: true,
                }),
                typescript({
                    sourceMap: !production,
                    inlineSources: !production,
                    strict: true
                }),
                commonjs(),
            ],
        };

c. Vite 
^^^^^^^^^^

    The ``vite-plugin-commonjs`` plugin can be used as well to integrate our bundle with Typescript. However, the drawback of that method is that it does not allow the use of Typescript definitions and only allows to import the module as a default import :
  
    .. code-block:: javascript
  
        import cee from './CeeEnvisionWebComponents.js';// import as default
        ...

    There is probably a better, more complex way to do it, by using a lower-level rollup configuration as the one provided above.

    .. TODO: integrate a rollup config to use Typescript definition in that specific case ?

d. TSC
^^^^^^^^^^

    Depending on your target module type, you will need to adapt your tsconfig.json file as follows :

    - For ESM or IIFE targets, you will need to load UMD modules as global variables. In the "compilerOptions" section of your tsconfig.json file, set the ``allowUmdGlobalAccess`` to ``true``. Then import the module with that syntax : ``import './CeeEnvisionWebComponents.js';``.

    - For AMD targets, a minimal tsconfig is sufficient and allow more :

        ``tsconfig.json``

        .. code-block:: json

            {
                "compilerOptions": {
                    "target": "es6",
                    "module": "amd",
                    "types" : [],
                    "outDir": "./dist",
                    "strict": true
                },
                "files": [
                    "index.ts",
                ],
            }
            See our examples for more details.

        This setup has the advantage to allow the use of both ``import * as`` and ``import { MyClass } from`` syntaxes.


2. Optional TypeScript configuration
-------------------------------------

The following options are not necessary, but recommended when bundling Javascript. Add these in your tsconfig.json file to ensure the JavaScript is imported correctly, that is it not loaded as a global, and to prevent conflicts with type definitions :

``tsconfig.json``

.. code-block:: json

    {
        "compilerOptions": {
            ...
            "esModuleInterop": true,
            "allowUmdGlobalAccess": false,
            "isolatedModules": true,
            "allowJs": true,
            ...
        }
    }


******
F.A.Q.
******

**Is Envision Web available as a NPM package ?**

No, there is no NPM package at the moment. It might change in the future.

**Is Envision Web available as a native ESM module ?**

No, you need to use a bundler to convert it, unless your are fine with using a global variable. See the examples provided in the `1. With a bundler and/or a transpilation pipeline`_ section to read more about the course of action to take.

**Can I use Envision Web with frameworks like React, Angular or Vue ?**

Yes absolutely. See the "Example Applications" page for more details.

**One of the example on this page is not working, what should I do ?**

The context of your project can affect the way building tools behave in many ways. For example, most build tools will check their own configuration files, but also the content of package.json. Typescript tools can also check tsconfig.json and their own configuration files. Try to keep your config files as simple as possible. If necessary, try to build a simple project from scratch to isolate the problem.

If you still have issues, please contact our support.