diff --git a/packages/lwc-engine/jest.config.js b/packages/lwc-engine/jest.config.js
index 9ec582e029..81d027c15a 100644
--- a/packages/lwc-engine/jest.config.js
+++ b/packages/lwc-engine/jest.config.js
@@ -1,6 +1,11 @@
+const path = require('path');
const BASE_CONFIG = require('../../scripts/jest/base.config');
module.exports = {
...BASE_CONFIG,
+
displayName: 'lwc-engine',
+ moduleNameMapper: {
+ 'test-utils': path.resolve(__dirname, 'scripts/jest/test-utils.js'),
+ },
};
diff --git a/packages/lwc-engine/package.json b/packages/lwc-engine/package.json
index 7041777912..18f47bb9b1 100644
--- a/packages/lwc-engine/package.json
+++ b/packages/lwc-engine/package.json
@@ -15,6 +15,7 @@
},
"devDependencies": {
"concurrently": "^3.5.1",
+ "lwc-template-compiler": "0.24.2",
"rollup": "0.60.1",
"rollup-plugin-inject": "^1.4.1",
"rollup-plugin-node-resolve": "^3.0.2",
diff --git a/packages/lwc-engine/scripts/jest/test-utils.js b/packages/lwc-engine/scripts/jest/test-utils.js
new file mode 100644
index 0000000000..5369949906
--- /dev/null
+++ b/packages/lwc-engine/scripts/jest/test-utils.js
@@ -0,0 +1,27 @@
+const { compileToFunction } = require('lwc-template-compiler');
+
+const TEMPLATE_CACHE = Object.create(null);
+
+/**
+ * Compiles a template string and returns the instantiated function.
+ *
+ * @param {string} source The template string
+ * @param {object=} config The template configuration
+ * @param {object=} config.modules The map of the modules used in the template
+ * @returns {function}
+ */
+function compileTemplate(source, config = {}) {
+ const { modules = {} } = config;
+
+ // Check if the same template has already been compiled
+ if (!(source in TEMPLATE_CACHE)) {
+ TEMPLATE_CACHE[source] = compileToFunction(source);
+ }
+
+ const templateFactory = TEMPLATE_CACHE[source];
+ return templateFactory(modules);
+}
+
+module.exports = {
+ compileTemplate,
+};
diff --git a/packages/lwc-engine/src/framework/__tests__/class-list.spec.ts b/packages/lwc-engine/src/framework/__tests__/class-list.spec.ts
index a1a2565125..d06b6c7e9d 100644
--- a/packages/lwc-engine/src/framework/__tests__/class-list.spec.ts
+++ b/packages/lwc-engine/src/framework/__tests__/class-list.spec.ts
@@ -1,3 +1,5 @@
+import { compileTemplate } from 'test-utils';
+
import { createElement, LightningElement } from '../main';
import { getHostShadowRoot } from '../html-element';
@@ -5,14 +7,22 @@ describe('class-list', () => {
describe('integration', () => {
it('should support outer className', () => {
class ChildComponent extends LightningElement {}
- function html($api) {
- return [$api.c('x-child', ChildComponent, { className: 'foo' })];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ {
+ modules: { 'x-child': ChildComponent }
+ }
+ );
+
class MyComponent extends LightningElement {
render() {
return html;
}
}
+
const elm = createElement('x-foo', { is: MyComponent });
document.body.appendChild(elm);
const childElm = getHostShadowRoot(elm).querySelector('x-child');
diff --git a/packages/lwc-engine/src/framework/__tests__/component.spec.ts b/packages/lwc-engine/src/framework/__tests__/component.spec.ts
index 0468dc01b7..6a10e1cad4 100644
--- a/packages/lwc-engine/src/framework/__tests__/component.spec.ts
+++ b/packages/lwc-engine/src/framework/__tests__/component.spec.ts
@@ -1,3 +1,5 @@
+import { compileTemplate } from 'test-utils';
+
import { createElement, LightningElement } from '../main';
import { getHostShadowRoot } from '../html-element';
@@ -10,15 +12,20 @@ describe('component', function() {
return this.value;
}
}
-
MyComponent.publicProps = {
breakfast: {
config: 1
}
};
- function html($api) {
- return [$api.c('x-component', MyComponent, {})];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ {
+ modules: { "x-child": MyComponent }
+ }
+ );
class Parent extends LightningElement {
value = 'salad';
get lunch() {
@@ -29,7 +36,6 @@ describe('component', function() {
return html;
}
}
-
Parent.publicProps = {
lunch: {
config: 1
@@ -39,7 +45,7 @@ describe('component', function() {
const elm = createElement('x-foo', { is: Parent });
document.body.appendChild(elm);
expect(elm.lunch).toBe('salad');
- expect(getHostShadowRoot(elm).querySelector('x-component').breakfast).toBe('pancakes');
+ expect(getHostShadowRoot(elm).querySelector('x-child').breakfast).toBe('pancakes');
});
it('should allow calling public getters when element is accessed by querySelector', function() {
@@ -54,9 +60,15 @@ describe('component', function() {
config: 0
}
};
- function html($api) {
- return [$api.c('x-child', MyChild, {})];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ {
+ modules: { "x-child": MyChild }
+ }
+ );
class MyComponent extends LightningElement {
callChildM() {
value = this.template.querySelector('x-child').m;
@@ -93,9 +105,11 @@ describe('component', function() {
});
it('should be render reactive', function() {
- function html($api, $cmp, $slotset, $ctx) {
- return [$api.h('div', { key: 0 }, [$api.d($cmp.validity)])];
- }
+ const html = compileTemplate(
+ `
+ {validity}
+ `
+ );
class MyComponent extends LightningElement {
state = { value: 0 };
@@ -172,9 +186,15 @@ describe('component', function() {
config: 3
}
};
- function html($api) {
- return [$api.c('x-child', MyChild, {})];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ {
+ modules: { "x-child": MyChild }
+ }
+ );
class MyComponent extends LightningElement {
render() {
return html;
@@ -185,6 +205,7 @@ describe('component', function() {
}
}
MyComponent.publicMethods = ['run'];
+
const elm = createElement('x-foo', { is: MyComponent });
document.body.appendChild(elm);
expect(elm.run()).toBe('eggs');
@@ -335,16 +356,12 @@ describe('component', function() {
describe('styles', function() {
it('should handle string styles', function() {
let calledCSSText = false;
- function html($api, $cmp) {
- return [$api.h(
- "section",
- {
- key: 0,
- style: $cmp.state.customStyle
- },
- []
- )];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ );
class MyComponent extends LightningElement {
state = {
customStyle: 'color: red'
@@ -373,16 +390,12 @@ describe('component', function() {
it('should handle undefined properly', function() {
let calledCSSTextWithUndefined = false;
- function html($api, $cmp, $slotset, $ctx) {
- return [$api.h(
- "section",
- {
- key: 0,
- style: $cmp.state.customStyle
- },
- []
- )];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ );
class MyComponent extends LightningElement {
state = {
customStyle: undefined
@@ -412,16 +425,11 @@ describe('component', function() {
});
it('should handle null properly', function() {
- function html($api, $cmp) {
- return [$api.h(
- "section",
- {
- key: 0,
- style: $cmp.state.customStyle
- },
- []
- )];
- }
+ const html = compileTemplate(
+ `
+
+ `,
+ );
class MyComponent extends LightningElement {
state = {
customStyle: null
@@ -438,16 +446,11 @@ describe('component', function() {
});
it('should diff between style objects and strings correctly', function() {
- function html($api, $cmp, $slotset, $ctx) {
- return [$api.h(
- "section",
- {
- key: 0,
- style: $cmp.customStyle
- },
- []
- )];
- }
+ const html = compileTemplate(
+ `
+
+ `,
+ );
class MyComponent extends LightningElement {
customStyle: {
color: 'red'
@@ -559,9 +562,15 @@ describe('component', function() {
}
}
MyChild.publicMethods = ['m'];
- function html($api) {
- return [$api.c('x-child', MyChild, {})];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ {
+ modules: { "x-child": MyChild }
+ }
+ );
class MyComponent extends LightningElement {
callChildM() {
this.template.querySelector('x-child').m();
@@ -588,9 +597,15 @@ describe('component', function() {
}
}
MyChild.publicMethods = ['m'];
- function html($api) {
- return [$api.c('x-child', MyChild, {})];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ {
+ modules: { "x-child": MyChild }
+ }
+ );
class MyComponent extends LightningElement {
getChildAttribute() {
this.template.querySelector('x-child').getAttribute('title');
@@ -616,9 +631,15 @@ describe('component', function() {
}
}
MyChild.publicMethods = ['m'];
- function html($api) {
- return [$api.c('x-child', MyChild, {})];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ {
+ modules: { "x-child": MyChild }
+ }
+ );
class MyComponent extends LightningElement {
setChildAttribute() {
this.template.querySelector('x-child').setAttribute('title', 'foo');
@@ -644,9 +665,15 @@ describe('component', function() {
}
}
MyChild.publicMethods = ['m'];
- function html($api) {
- return [$api.c('x-child', MyChild, {})];
- }
+
+ const html = compileTemplate(
+ `
+
+ `,
+ {
+ modules: { "x-child": MyChild }
+ }
+ );
class MyComponent extends LightningElement {
removeChildAttribute() {
this.template.querySelector('x-child').removeAttribute('title');
diff --git a/packages/lwc-engine/src/framework/__tests__/html-element.spec.ts b/packages/lwc-engine/src/framework/__tests__/html-element.spec.ts
index 8cf684dc7c..66307ffd51 100644
--- a/packages/lwc-engine/src/framework/__tests__/html-element.spec.ts
+++ b/packages/lwc-engine/src/framework/__tests__/html-element.spec.ts
@@ -1,3 +1,5 @@
+import { compileTemplate } from 'test-utils';
+
import { createElement, register, unwrap } from '../main';
import { getHostShadowRoot, LightningElement } from '../html-element';
import assertLogger from '../../shared/assert';
@@ -12,11 +14,19 @@ describe('html-element', () => {
}
Child.publicMethods = ['setFoo'];
+ const html = compileTemplate(`
+
+
+
+ `, {
+ modules: {
+ 'x-child': Child
+ }
+ });
+
class Parent extends LightningElement {
render() {
- return ($api) => {
- return [$api.c('x-child', Child, {})]
- }
+ return html;
}
}
const element = createElement('should-set-attribute-on-host-element-when-element-is-nested-in-template', { is: Parent });
diff --git a/packages/lwc-template-compiler/src/__tests__/index.spec.ts b/packages/lwc-template-compiler/src/__tests__/index.spec.ts
index 378c20f34b..b16cbbfe4f 100644
--- a/packages/lwc-template-compiler/src/__tests__/index.spec.ts
+++ b/packages/lwc-template-compiler/src/__tests__/index.spec.ts
@@ -1,4 +1,21 @@
-import compiler from '../index';
+import compiler, { compileToFunction } from '../index';
+
+function prettify(str) {
+ return str.toString()
+ .replace(/^\s+|\s+$/, '')
+ .split('\n')
+ .map(line => line.trim())
+ .filter(line => line.length)
+ .join('\n');
+}
+
+function functionMatchCode(fn, code) {
+ return expect(
+ prettify(fn.toString()),
+ ).toContain(
+ prettify(code),
+ );
+}
describe('option validation', () => {
it('validated presence of options', () => {
@@ -18,3 +35,75 @@ describe('option validation', () => {
);
});
});
+
+describe('compileToFunction', () => {
+ it('should compile correctly simple components', () => {
+ const renderFn = compileToFunction(`
+
+ Hello world!
+
+ `);
+
+ functionMatchCode(renderFn, `
+ function tmpl($api, $cmp, $slotset, $ctx) {
+ const {
+ t: api_text,
+ h: api_element
+ } = $api;
+
+ return [api_element("h1", {
+ key: 1
+ }, [api_text("Hello world!")])];
+ }
+
+ return tmpl;
+ `);
+ });
+
+ it('should add component lookups if necessary', () => {
+ const renderFn = compileToFunction(`
+
+
+
+ `);
+
+ functionMatchCode(renderFn, `
+ const _xFoo = modules["x-foo"];
+
+ function tmpl($api, $cmp, $slotset, $ctx) {
+ const {
+ c: api_custom_element
+ } = $api;
+
+ return [api_custom_element("x-foo", _xFoo, {
+ key: 1
+ }, [])];
+ }
+
+ return tmpl;
+ `);
+ });
+
+ it('should add template metadata if necessary', () => {
+ const renderFn = compileToFunction(`
+
+
+
+ `);
+
+ functionMatchCode(renderFn, `
+ function tmpl($api, $cmp, $slotset, $ctx) {
+ const {
+ s: api_slot
+ } = $api;
+
+ return [api_slot("", {
+ key: 1
+ }, [], $slotset)];
+ }
+ tmpl.slots = [""];
+
+ return tmpl;
+ `);
+ });
+});
diff --git a/packages/lwc-template-compiler/src/codegen/formatters/function.ts b/packages/lwc-template-compiler/src/codegen/formatters/function.ts
new file mode 100644
index 0000000000..126ca5156d
--- /dev/null
+++ b/packages/lwc-template-compiler/src/codegen/formatters/function.ts
@@ -0,0 +1,43 @@
+import * as t from 'babel-types';
+
+import State from '../../state';
+import {
+ identifierFromComponentName,
+ generateTemplateMetadata,
+} from '../helpers';
+import {
+ TEMPLATE_FUNCTION_NAME,
+ TEMPLATE_MODULES_PARAMETER,
+} from '../../shared/constants';
+
+function moduleNameToLookup(name: string): t.VariableDeclaration {
+ const localIdentifier = identifierFromComponentName(name);
+
+ return t.variableDeclaration('const', [
+ t.variableDeclarator(
+ localIdentifier,
+ t.memberExpression(
+ t.identifier(TEMPLATE_MODULES_PARAMETER),
+ t.stringLiteral(name),
+ true,
+ ),
+ ),
+ ]);
+}
+
+export function format(
+ templateFn: t.FunctionDeclaration,
+ state: State,
+): t.Program {
+ const lookups = state.dependencies.map(cmpClassName =>
+ moduleNameToLookup(cmpClassName),
+ );
+ const metadata = generateTemplateMetadata(state);
+
+ return t.program([
+ ...lookups,
+ templateFn,
+ ...metadata,
+ t.returnStatement(t.identifier(TEMPLATE_FUNCTION_NAME)),
+ ]);
+}
diff --git a/packages/lwc-template-compiler/src/codegen/formatters/module.ts b/packages/lwc-template-compiler/src/codegen/formatters/module.ts
new file mode 100644
index 0000000000..da071bb5f9
--- /dev/null
+++ b/packages/lwc-template-compiler/src/codegen/formatters/module.ts
@@ -0,0 +1,32 @@
+import * as t from 'babel-types';
+
+import State from '../../state';
+import {
+ identifierFromComponentName,
+ generateTemplateMetadata,
+} from '../helpers';
+
+function moduleNameToImport(name: string): t.ImportDeclaration {
+ const localIdentifier = identifierFromComponentName(name);
+
+ return t.importDeclaration(
+ [t.importDefaultSpecifier(localIdentifier)],
+ t.stringLiteral(name),
+ );
+}
+
+export function format(
+ templateFn: t.FunctionDeclaration,
+ state: State,
+): t.Program {
+ const imports = state.dependencies.map(cmpClassName =>
+ moduleNameToImport(cmpClassName),
+ );
+ const metadata = generateTemplateMetadata(state);
+
+ return t.program([
+ ...imports,
+ t.exportDefaultDeclaration(templateFn),
+ ...metadata,
+ ]);
+}
diff --git a/packages/lwc-template-compiler/src/codegen/helpers.ts b/packages/lwc-template-compiler/src/codegen/helpers.ts
index 3eb79e3e70..4a31f321c8 100644
--- a/packages/lwc-template-compiler/src/codegen/helpers.ts
+++ b/packages/lwc-template-compiler/src/codegen/helpers.ts
@@ -1,8 +1,10 @@
import * as t from 'babel-types';
import * as toCamelCase from 'camelcase';
+import State from '../state';
import { isElement } from '../shared/ir';
import { IRElement } from '../shared/types';
+import { TEMPLATE_FUNCTION_NAME } from '../shared/constants';
export function identifierFromComponentName(name: string): t.Identifier {
return t.identifier(`_${toCamelCase(name)}`);
@@ -23,14 +25,6 @@ export function getMemberExpressionRoot(
return current as t.Identifier;
}
-export function importFromComponentName(name: string): t.ImportDeclaration {
- const localComponentIdentifier = identifierFromComponentName(name);
- return t.importDeclaration(
- [t.importDefaultSpecifier(localComponentIdentifier)],
- t.stringLiteral(name),
- );
-}
-
export function objectToAST(
obj: object,
valueMapper: (key: string) => t.Expression,
@@ -81,3 +75,27 @@ export function destructuringAssignmentFromObject(
),
]);
}
+
+export function generateTemplateMetadata(state: State): t.ExpressionStatement[] {
+ const metadataExpressions: t.ExpressionStatement[] = [];
+
+ // Generate the slots property on template function if slots are defined in the template:
+ // tmpl.slots = ['', 'x']
+ if (state.slots.length) {
+ const slotsProperty = t.memberExpression(
+ t.identifier(TEMPLATE_FUNCTION_NAME),
+ t.identifier('slots'),
+ );
+
+ const slotsArray = t.arrayExpression(
+ state.slots.map((slot) => t.stringLiteral(slot)),
+ );
+
+ const slotsMetadata = t.assignmentExpression('=', slotsProperty, slotsArray);
+ metadataExpressions.push(
+ t.expressionStatement(slotsMetadata),
+ );
+ }
+
+ return metadataExpressions;
+}
diff --git a/packages/lwc-template-compiler/src/codegen/index.ts b/packages/lwc-template-compiler/src/codegen/index.ts
index 5a46d19198..5951cbbc55 100644
--- a/packages/lwc-template-compiler/src/codegen/index.ts
+++ b/packages/lwc-template-compiler/src/codegen/index.ts
@@ -34,7 +34,6 @@ import Stack from '../shared/stack';
import {
identifierFromComponentName,
- importFromComponentName,
objectToAST,
getMemberExpressionRoot,
isTemplate,
@@ -46,6 +45,24 @@ import {
import CodeGen from './codegen';
+import { format as formatModule } from './formatters/module';
+import { format as formatFunction } from './formatters/function';
+
+const TEMPLATE_FUNCTION = template(
+ `function ${TEMPLATE_FUNCTION_NAME}(
+ ${TEMPLATE_PARAMS.API},
+ ${TEMPLATE_PARAMS.INSTANCE},
+ ${TEMPLATE_PARAMS.SLOT_SET},
+ ${TEMPLATE_PARAMS.CONTEXT}
+ ) {
+ APIS;
+ SLOTS;
+ CONTEXT;
+ return STATEMENT;
+ }`,
+ { sourceType: 'module' },
+);
+
function transform(
root: IRNode,
codeGen: CodeGen,
@@ -437,49 +454,7 @@ function transform(
return (stack.peek() as t.ArrayExpression).elements[0] as t.Expression;
}
-/**
- * Generate metadata that will be attached to the template function
- */
-function generateTemplateMetadata(state: State): t.ExpressionStatement[] {
- const metadataExpressions: t.ExpressionStatement[] = [];
-
- // Generate the slots property on template function if slots are defined in the template
- // tmpl.slots = ['', 'x']
- if (state.slots.length) {
- const slotsProperty = t.memberExpression(
- t.identifier(TEMPLATE_FUNCTION_NAME),
- t.identifier('slots'),
- );
-
- const slotsArray = t.arrayExpression(
- state.slots.map((slot) => t.stringLiteral(slot)),
- );
-
- const slotsMetadata = t.assignmentExpression('=', slotsProperty, slotsArray);
- metadataExpressions.push(
- t.expressionStatement(slotsMetadata),
- );
- }
-
- return metadataExpressions;
-}
-
-const TEMPLATE_FUNCTION = template(
- `export default function ${TEMPLATE_FUNCTION_NAME}(
- ${TEMPLATE_PARAMS.API},
- ${TEMPLATE_PARAMS.INSTANCE},
- ${TEMPLATE_PARAMS.SLOT_SET},
- ${TEMPLATE_PARAMS.CONTEXT}
- ) {
- APIS;
- SLOTS;
- CONTEXT;
- return STATEMENT;
- }`,
- { sourceType: 'module' },
-);
-
-export default function(templateRoot: IRElement, state: State): CompilationOutput {
+function generateTemplateFunction(templateRoot: IRElement, state: State): t.FunctionDeclaration {
const codeGen = new CodeGen();
const statement = transform(templateRoot, codeGen, state);
@@ -525,24 +500,29 @@ export default function(templateRoot: IRElement, state: State): CompilationOutpu
);
}
- const content = TEMPLATE_FUNCTION({
+ return TEMPLATE_FUNCTION({
APIS: apis,
SLOTS: slots,
CONTEXT: context,
STATEMENT: statement,
- }) as t.ExportDefaultDeclaration;
+ }) as t.FunctionDeclaration;
+}
- const intro = state.dependencies.map((cmpClassName) => (
- importFromComponentName(cmpClassName)
- ));
+function format({ config }: State) {
+ switch (config.format) {
+ case 'function':
+ return formatFunction;
- const outro = generateTemplateMetadata(state);
+ default:
+ return formatModule;
+ }
+}
+
+export default function(templateRoot: IRElement, state: State): CompilationOutput {
+ const templateFunction = generateTemplateFunction(templateRoot, state);
- const program = t.program([
- ...intro,
- content,
- ...outro,
- ]);
+ const formatter = format(state);
+ const program = formatter(templateFunction, state);
const { code } = generate(program);
return {
diff --git a/packages/lwc-template-compiler/src/config.ts b/packages/lwc-template-compiler/src/config.ts
index 7fc52c091a..bba074985d 100644
--- a/packages/lwc-template-compiler/src/config.ts
+++ b/packages/lwc-template-compiler/src/config.ts
@@ -1,3 +1,5 @@
+export type Format = 'module' | 'function';
+
export interface Config {
/**
* Enable computed member expression in the template. eg:
@@ -10,10 +12,20 @@ export interface Config {
export interface ResolvedConfig {
computedMemberExpression: boolean;
+
+ /**
+ * Internal configuration for the output format of the template. Accepts:
+ * * "module": generates a ES module, and use import statements to reference component
+ * constructor.
+ * * "inline": generates a function, and requires component constructor to be passed
+ * as parameter.
+ */
+ format: Format;
}
-const DEFAULT_CONFIG = {
+const DEFAULT_CONFIG: ResolvedConfig = {
computedMemberExpression: false,
+ format: 'module',
};
const REQUIRED_OPTION_NAMES = new Set([]);
diff --git a/packages/lwc-template-compiler/src/index.ts b/packages/lwc-template-compiler/src/index.ts
index 0ff8087f3d..38bdabb701 100644
--- a/packages/lwc-template-compiler/src/index.ts
+++ b/packages/lwc-template-compiler/src/index.ts
@@ -4,6 +4,7 @@ import { mergeConfig, Config } from './config';
import parse from './parser';
import generate from './codegen';
+import { TEMPLATE_MODULES_PARAMETER } from './shared/constants';
import { CompilationMetadata, CompilationWarning } from './shared/types';
export default function compiler(
@@ -51,3 +52,31 @@ export default function compiler(
},
};
}
+
+export function compileToFunction(source: string): Function {
+ const options = mergeConfig({});
+ options.format = 'function';
+
+ const state = new State(source, options);
+
+ const parsingResults = parse(source, state);
+
+ for (const { message, level } of parsingResults.warnings) {
+ if (level === 'error') {
+ throw new Error(message);
+ } else if (level === 'warning') {
+ /* tslint:disable-next-line:no-console */
+ console.warn(message);
+ } else {
+ /* tslint:disable-next-line:no-console */
+ console.log(message);
+ }
+ }
+
+ if (!parsingResults.root) {
+ throw new Error(`Invalid template`);
+ }
+
+ const { code } = generate(parsingResults.root, state);
+ return new Function(TEMPLATE_MODULES_PARAMETER, code);
+}
diff --git a/packages/lwc-template-compiler/src/shared/constants.ts b/packages/lwc-template-compiler/src/shared/constants.ts
index 2929ab2b1a..a4a5ff8ab3 100644
--- a/packages/lwc-template-compiler/src/shared/constants.ts
+++ b/packages/lwc-template-compiler/src/shared/constants.ts
@@ -1,3 +1,5 @@
+export const TEMPLATE_MODULES_PARAMETER: string = 'modules';
+
export const TEMPLATE_FUNCTION_NAME: string = 'tmpl';
export const TEMPLATE_PARAMS: { [label: string]: string } = {