# Lightning Web Runtime :: Compiler

## Table of contents

-   [Introduction](#introduction)
-   [Background](#background)
    -   [Definitions](#definitions)
    -   [Rollup plugins](#rollup-plugins)
-   [APIs](#apis)
    -   [`compile()`](#compile)
        -   [Compiler input](#compiler-input)
        -   [Compiler output](#compiler-output)
-   [Examples](#examples)
    -   [Simple application](#simple-application)
    -   [LWC](#lwc)
    -   [JavaScript library](#javascript-library)
    -   [Full Compiler config object](#full-compiler-config-object)
-   [Container config mappings](#container-config-mappings)

## Introduction

The Lightning Web Runtime Compiler is a pluggable [Rollup](https://rollupjs.org/guide)-based bundler for off-core [LWC](https://developer.salesforce.com/docs/component-library/documentation/lwc)-based applications.

The Web Runtime Compiler extends the LWC/Rollup bundling functionality with the ability to inject [container services](https://salesforce.quip.com/d9ojAnuvm6fm), which can then participate in the compilation lifecycle phase.

## Background

### Definitions

**Lightning Web Runtime**: A LWC application runtime.

**container**: Initializes and builds the set of container services which make up an application.

**container service**: A service plugin, which can be (1) addressed and (2) hook into various container lifecycle phases.

**specifier**: A string import value (eg: `import('app/home')`, `import('lodash')`).

**[pivot](https://salesforce.quip.com/bqE4AR4iyZLu)**: A way to specify when a particular component/module may be loaded relative to some context (eg: 'mobile' vs 'desktop', locale, etc).

### Rollup plugins

Along with custom plugins, these Rollup plugins are automatically included and used by the Compiler:

-   [@lwc/rollup-plugin](https://www.npmjs.com/package/@lwc/rollup-plugin)
-   [rollup-plugin-node-resolve](https://github.com/rollup/rollup-plugin-node-resolve)
-   [rollup-plugin-commonjs](https://github.com/rollup/rollup-plugin-commonjs)
-   If `minify` mode is on: [terser](https://www.npmjs.com/package/terser) is used

## APIs

### `compile()`

The Web Runtime Compiler exposes one main function:

```javascript
async function compile(input):Promise<output>
```

It takes in a configuration object, and returns a `Promise` to its output.

#### Compiler input

-   [LWC compiler options](https://github.com/salesforce/lwc/tree/master/packages/%40lwc/compiler#compile):

    -   `name` (string, required): component name.
    -   `namespace` (string, required): component namespace.
    -   `files` ({ string: string, ...}, required): key/value pairs where each key is a file path and its value is a string containing the file contents.
    -   `baseDir` (string, optional, default = `""`): a directory prefix which contains contains the components being compiled.
    -   `lwcOptions` (object, optional): pass in a customized [LWC Rollup plugin](https://www.npmjs.com/package/@lwc/rollup-plugin) config object, which gets merged with a default object. The following options are available (all are optional): `rootDir`, `modules`, `exclude`, `stylesheetConfig`, and `experimentalDynamicComponent`.
    -   `outputConfig` (object[], optional):
        -   `compat` (boolean, optional, default = `false`): `true` indicates browser compatibility mode.
        -   `minify` (boolean, optional, default = `false`): `true` if the bundle should be minified.
        -   `sourcemap` (boolean, optional, default = `false`): `true` if a source map should be generated.
        -   `env` (object, optional, default = `{ NODE_ENV: 'development' }`):
            -   `NODE_ENV` (string, required) = node environment string (eg: 'production', 'development', etc).

-   [Rollup](https://rollupjs.org/guide/en/#rolluprollup) options:

    -   `input` (string, optional, default = `${namespace}/${name}`): the input path for the bundle, similar to Rollup's `input`.
    -   `external` (string[], optional, default =`['lwc']`): list of dependencies that should not be included in the bundled output, similar to Rollup's `external`.
    -   `format` (string, optional, default = 'amd'): type of output ('amd', 'cjs', 'esm', 'iife', 'umd') similar to Rollup's `output.format`.
    -   `formatConfig` (object, optional, default = `{ id: namespace + '/' + name, define: undefined }`):
        -   `amd` (object):
            -   `define` (string): function to use in place of `define`, similar to Rollup's `output.amd.define`

-   Web Runtime options:
    -   `plugins` ([Plugin](https://rollupjs.org/guide/en/#plugins-overview)[], optional, default = `[]`): array of Rollup plugins to use.
    -   `inlineConfig` (object[], optional, default = `[]`): describes the exclusion patterns for each module descriptor; array of objects with the following properites:
        -   `descriptor` (string, required): specifier which should have all dependencies inlined, EXCEPT for those in the `exclude` array. [Globs](https://www.man7.org/linux/man-pages/man7/glob.7.html) are supported.
        -   `exclude` (string[], optional, default = `[]`): array of specifiers which should be excluded from being inlined. [Globs](https://www.man7.org/linux/man-pages/man7/glob.7.html) are supported. Globstars can be used to recursively match all modules in scoped namespaces or nested namespaces. Excluded specifiers that are not listed in `external` or `exposedModules` will be inlined.
    -   `exposedModules` (string[], optional, default: `[]`): array of specifiers that are supported by the Web Runtime container.

#### Compiler output

The Compiler's output uses the same format as the [LWC compiler](https://github.com/salesforce/lwc/tree/master/packages/%40lwc/compiler#compile).

-   output (object):
    -   `success` (boolean): `true` if the Compiler encountered no fatal errors.
    -   `diagnostics` ([CompilerDiagnostic](https://github.com/salesforce/lwc/blob/044077f385e76b1661c76c4764611294251beb45/packages/%40lwc/errors/src/compiler/utils.ts#L14)[]): array of error and warning information.
    -   `result`:
        -   `code` (string): the resulting bundled code.
        -   `map` (\* | null): the generated source map, if requested.
        -   `outputConfig` (object | `undefined`): same object passed in as `[Compiler input](#compiler-input).outputConfig`.
    -   `version` (string): the version of the Compiler which bundled this code.

## Examples

### Simple application

Creating a bundle for an LWC application. Components are stored in _./src_.

All dependencies except for `lwc` will be inlined in the bundle.

```javascript
import { compile } from '@webruntime/compiler';
import * as path from 'path';

const options = {
    name: 'main',
    namespace: 'any',
    external: ['lwc'],
    baseDir: path.join(__dirname, 'src'),
    files: {
        'any/main': `
            import { createElement } from 'lwc';
            import App from 'x/app';
            const element = createElement('x-app', { is: App });
            document.body.appendChild(element);
        `,
        'x/app': `
            export default 'Hello World';
        `,
    },
};

const { success, diagnostics } = await compile(options);

if (!success) {
    for (let diagnostic of diagnostics) {
        console.error(`${diagnostic.level}: ${diagnostic.message}`);
    }
}

// Compiled Result:
// define('any/main', ['lwc'], function (lwc) { 'use strict';\n" +
//       '\n' +
//       "        var App = 'Hello World';\n" +
//       '\n' +
//       "        const element = lwc.createElement('x-app', {\n" +
//       '          is: App\n' +
//       '        });\n' +
//       '        document.body.appendChild(element);\n' +
//       '\n' +
//       '});\n',
```

### Simple application with inline config

Create a bundle for an LWC application without inlining any dependencies. Components are stored in _./src_.

All dependencies except for `lwc` will be defined separetly in the bundle.

```javascript
import { compile } from '@webruntime/compiler';
import * as path from 'path';

const options = {
    name: 'main',
    namespace: 'any',
    external: ['lwc'],
    baseDir: path.join(__dirname, 'src'),
    files: {
        'any/main': `
            import { createElement } from 'lwc';
            import App from 'x/app';
            const element = createElement('x-app', { is: App });
            document.body.appendChild(element);
        `,
        'x/app': `
            export default 'Hello World';
        `,
    },
    inlineConfig: [
        {
            descriptor: '*/*',
            exclude: ['*/*'],
        },
    ],
};

const { success, diagnostics } = await compile(options);

if (!success) {
    for (let diagnostic of diagnostics) {
        console.error(`${diagnostic.level}: ${diagnostic.message}`);
    }
}

// Compiled Result:
// define('any/main', ['lwc', 'x/app'], function (lwc, App) { 'use strict';\n" +
//       '\n' +
//       "            App = App && Object.prototype.hasOwnProperty.call(App, 'default') ? App['default'] : App;\n" +
//       '\n' +
//       "            const element = lwc.createElement('x-app', {\n" +
//       '              is: App\n' +
//       '            });\n' +
//       '            document.body.appendChild(element);\n' +
//       '\n' +
//       '});\n',
```

### LWC

Creating a bundle for a LWC stored in the `files` object:

```javascript
import { compile } from '@webruntime/compiler';

const options = {
    name: 'lwc',
    namespace: 'my',
    external: ['lwc'],
    files: {
        'my/lwc.js': `
            import { LightningElement } from 'lwc';
            export default class Lwc extends LightningElement {}
        `,
        'my/lwc.html': '<template>lwc</template>',
        'my/lwc.css': ':host { font-size: 16px; }',
    },
};

const { success, code } = await compile(options);

console.log('The bundle is: ', success ? code : 'a failure');

// Output:
// The bundle is: define('my/lwc', ['lwc'], function (lwc) { ... });
```

### JavaScript library

Create a non-LWC bundle. The JavaScript entry point must live in the `files` object:

```javascript
import { compile } from '@webruntime/compiler';
import * as fs from 'fs';
import * as path from 'path';

const filePath = path.join(__dirname, 'src', 'x', 'file.js');
const fileContents = fs.readFileSync(filePath).toString();
export const options = {
    name: 'file',
    namespace: '',
    files: {
        'file.js': fileContents,
    },
};

const { code = '' } = await compile(options);
export default code;
```

### Full Compiler config object

```
{
    // LWC
    name: 'cmp',
    input: 'x',
    files: {
        'logger.js': 'export const logger = console.log;'
    },
    baseDir: path.join(__dirname, 'apps'),
    lwcOptions: {
        stylesheetConfig: {
            customProperties: {
                resolution: {
                    type: 'module',
                    name: '@salesforce/cssvars/customProperties',
                },
            },
        },
        experimentalDynamicComponent: {
            loader: '',
            strictSpecifier: true,
        },
    },
    outputConfig: {
        compat: true,
        minify: true,
        sourcemap: true,
        env: { NODE_ENV: 'production' }
    },

    // Rollup
    external: [
        'lwc',
        'force/lds',
        'wire-service'
    ],
    format: 'amd',
    formatConfig: {
        amd: { define: 'loader.addModule' }
    },

    // Web Runtime
    plugins: [
        {
            plugin: 'src/rollup/global-rollup.js',
            config: {
                globals: {
                    'lodash': '_',
                },
            },
        },
    ],
    inlineConfig: [
       { descriptor: 'view/*', exclude: ['view/faq', 'wire-service'] },
       { descriptor: 'x/foo', exclude: ['@salesforce/**'] }
    ],
}
```

## Container config mappings

Compiler configuration options are gathered from different areas, including:

-   the calling service, possibly via a Rollup plugin (`service`)
-   the Container config object (`containerConfig`)

```
name: service
namespace: service
files: service
baseDir: containerConfig.moduleDir
stylesheetConfig: service or containerConfig.compilerConfig.lwcOptions
experimentalDynamicComponent: service or containerConfig.compilerConfig.lwcOptions
outputConfig: containerConfig.compilerConfig.outputConfig[mode]
external: containerConfig.externals, containerConfig.preloadModules, compilerConfig.external and exclusionsFrom(config.bundle)
format: service or container defaults
formatConfig: containerConfig.compilerConfig.formatConfig
plugins: service or container defaults
inlineConfig: containerConfig.bundle
```
