@stencil/angular-output-target 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,13 +32,28 @@ export const proxyOutputs = (instance: any, el: any, events: string[]) => {
32
32
  events.forEach(eventName => instance[eventName] = fromEvent(el, eventName));
33
33
  }
34
34
 
35
- export function ProxyCmp(opts: { inputs?: any; methods?: any }) {
36
- const decorator = function(cls: any) {
37
- if (opts.inputs) {
38
- proxyInputs(cls, opts.inputs);
35
+ export const defineCustomElement = (tagName: string, customElement: any) => {
36
+ if (
37
+ customElement !== undefined &&
38
+ typeof customElements !== 'undefined' &&
39
+ !customElements.get(tagName)
40
+ ) {
41
+ customElements.define(tagName, customElement);
42
+ }
43
+ }
44
+
45
+ // tslint:disable-next-line: only-arrow-functions
46
+ export function ProxyCmp(opts: { tagName: string, customElement?: any, inputs?: any; methods?: any }) {
47
+ const decorator = function (cls: any) {
48
+ const { tagName, customElement, inputs, methods } = opts;
49
+
50
+ defineCustomElement(tagName, customElement);
51
+
52
+ if (inputs) {
53
+ proxyInputs(cls, inputs);
39
54
  }
40
- if (opts.methods) {
41
- proxyMethods(cls, opts.methods);
55
+ if (methods) {
56
+ proxyMethods(cls, methods);
42
57
  }
43
58
  return cls;
44
59
  };
@@ -1,2 +1,2 @@
1
1
  import type { ComponentCompilerMeta } from '@stencil/core/internal';
2
- export declare const createComponentDefinition: (componentCorePackage: string, distTypesDir: string, rootDir: string) => (cmpMeta: ComponentCompilerMeta) => string;
2
+ export declare const createComponentDefinition: (componentCorePackage: string, distTypesDir: string, rootDir: string, includeImportCustomElements?: boolean, customElementsDir?: string) => (cmpMeta: ComponentCompilerMeta) => string;
@@ -1,6 +1,5 @@
1
- import path from 'path';
2
- import { dashToPascalCase, isRelativePath, normalizePath } from './utils';
3
- export const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir) => (cmpMeta) => {
1
+ import { dashToPascalCase, normalizePath } from './utils';
2
+ export const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir, includeImportCustomElements = false, customElementsDir = 'components') => (cmpMeta) => {
4
3
  // Collect component meta
5
4
  const inputs = [
6
5
  ...cmpMeta.properties.filter((prop) => !prop.internal).map((prop) => prop.name),
@@ -19,44 +18,34 @@ export const createComponentDefinition = (componentCorePackage, distTypesDir, ro
19
18
  if (inputs.length > 0) {
20
19
  directiveOpts.push(`inputs: ['${inputs.join(`', '`)}']`);
21
20
  }
22
- if (outputs.length > 0) {
23
- directiveOpts.push(`outputs: ['${outputs.map((output) => output.name).join(`', '`)}']`);
24
- }
25
21
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
26
- const typePath = path.parse(path.join(componentCorePackage, path.join(cmpMeta.sourceFilePath, '').replace(path.join(rootDir, 'src'), distTypesDir)));
27
- const importPath = normalizePath(path.join(typePath.dir, typePath.name));
28
22
  const outputsInterface = new Set();
29
23
  const outputReferenceRemap = {};
30
24
  outputs.forEach((output) => {
31
25
  Object.entries(output.complexType.references).forEach(([reference, refObject]) => {
32
- // Add import line for each local/import reference, and add new mapping name
33
- // outputReferenceRemap should be updated only if the import interface is set in outputsInterface
34
- // This will prevent global types to be remapped
26
+ // Add import line for each local/import reference, and add new mapping name.
27
+ // `outputReferenceRemap` should be updated only if the import interface is set in outputsInterface,
28
+ // this will prevent global types to be remapped.
35
29
  const remappedReference = `I${cmpMeta.componentClassName}${reference}`;
36
- if (refObject.location === 'local') {
37
- outputReferenceRemap[reference] = remappedReference;
38
- outputsInterface.add(`import { ${reference} as ${remappedReference} } from '${importPath}';`);
39
- }
40
- else if (refObject.location === 'import') {
30
+ if (refObject.location === 'local' || refObject.location === 'import') {
41
31
  outputReferenceRemap[reference] = remappedReference;
42
- const interfacePath = normalizePath(isRelativePath(refObject.path) ? path.join(typePath.dir, refObject.path) : refObject.path);
43
- outputsInterface.add(`import { ${reference} as ${remappedReference} } from '${interfacePath}';`);
32
+ let importLocation = componentCorePackage;
33
+ if (componentCorePackage !== undefined) {
34
+ const dirPath = includeImportCustomElements ? `/${customElementsDir || 'components'}` : '';
35
+ importLocation = `${normalizePath(componentCorePackage)}${dirPath}`;
36
+ }
37
+ outputsInterface.add(`import type { ${reference} as ${remappedReference} } from '${importLocation}';`);
44
38
  }
45
39
  });
46
40
  });
47
- const lines = [
48
- '',
49
- `${[...outputsInterface].join('\n')}
50
- export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {}
51
- ${getProxyCmp(inputs, methods)}
52
- @Component({
53
- ${directiveOpts.join(',\n ')}
54
- })
55
- export class ${tagNameAsPascal} {`,
41
+ const componentEvents = [
42
+ '' // Empty first line
56
43
  ];
57
44
  // Generate outputs
58
- outputs.forEach((output) => {
59
- lines.push(` /** ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}*/`);
45
+ outputs.forEach((output, index) => {
46
+ componentEvents.push(` /**
47
+ * ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}
48
+ */`);
60
49
  /**
61
50
  * The original attribute contains the original type defined by the devs.
62
51
  * This regexp normalizes the reference, by removing linebreaks,
@@ -70,8 +59,23 @@ export class ${tagNameAsPascal} {`,
70
59
  .replace(/\n/g, ' ')
71
60
  .replace(/\s{2,}/g, ' ')
72
61
  .replace(/,\s*/g, ', '));
73
- lines.push(` ${output.name}!: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
62
+ componentEvents.push(` ${output.name}: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
63
+ if (index === outputs.length - 1) {
64
+ // Empty line to push end `}` to new line
65
+ componentEvents.push('\n');
66
+ }
74
67
  });
68
+ const lines = [
69
+ '',
70
+ `${[...outputsInterface].join('\n')}
71
+ export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {${componentEvents.length > 1 ? componentEvents.join('\n') : ''}}
72
+
73
+ ${getProxyCmp(cmpMeta.tagName, includeImportCustomElements, inputs, methods)}
74
+ @Component({
75
+ ${directiveOpts.join(',\n ')}
76
+ })
77
+ export class ${tagNameAsPascal} {`,
78
+ ];
75
79
  lines.push(' protected el: HTMLElement;');
76
80
  lines.push(` constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
77
81
  c.detach();
@@ -83,13 +87,13 @@ export class ${tagNameAsPascal} {`,
83
87
  lines.push(`}`);
84
88
  return lines.join('\n');
85
89
  };
86
- function getProxyCmp(inputs, methods) {
90
+ function getProxyCmp(tagName, includeCustomElement, inputs, methods) {
87
91
  const hasInputs = inputs.length > 0;
88
92
  const hasMethods = methods.length > 0;
89
- const proxMeta = [];
90
- if (!hasInputs && !hasMethods) {
91
- return '';
92
- }
93
+ const proxMeta = [
94
+ `tagName: \'${tagName}\'`,
95
+ `customElement: ${includeCustomElement ? dashToPascalCase(tagName) + 'Cmp' : 'undefined'}`
96
+ ];
93
97
  if (hasInputs)
94
98
  proxMeta.push(`inputs: ['${inputs.join(`', '`)}']`);
95
99
  if (hasMethods)
package/dist/index.cjs.js CHANGED
@@ -3,16 +3,12 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var path = require('path');
6
- var util = require('util');
7
- var fs = require('fs');
8
6
  var os = require('os');
9
7
 
10
8
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
9
 
12
10
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
13
- var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
14
11
 
15
- const readFile = util.promisify(fs__default['default'].readFile);
16
12
  const toLowerCase = (str) => str.toLowerCase();
17
13
  const dashToPascalCase = (str) => toLowerCase(str)
18
14
  .split('-')
@@ -66,14 +62,12 @@ function relativeImport(pathFrom, pathTo, ext) {
66
62
  }
67
63
  return normalizePath(`${relativePath}/${path__default['default'].basename(pathTo, ext)}`);
68
64
  }
69
- function isRelativePath(path) {
70
- return path && path.startsWith('.');
71
- }
72
- async function readPackageJson(rootDir) {
65
+ async function readPackageJson(config, rootDir) {
66
+ var _a;
73
67
  const pkgJsonPath = path__default['default'].join(rootDir, 'package.json');
74
68
  let pkgJson;
75
69
  try {
76
- pkgJson = await readFile(pkgJsonPath, 'utf8');
70
+ pkgJson = (await ((_a = config.sys) === null || _a === void 0 ? void 0 : _a.readFile(pkgJsonPath, 'utf8')));
77
71
  }
78
72
  catch (e) {
79
73
  throw new Error(`Missing "package.json" file for distribution: ${pkgJsonPath}`);
@@ -91,7 +85,7 @@ const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
91
85
  const NON_ASCII_REGEX = /[^\x00-\x80]+/;
92
86
  const SLASH_REGEX = /\\/g;
93
87
 
94
- const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir) => (cmpMeta) => {
88
+ const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir, includeImportCustomElements = false, customElementsDir = 'components') => (cmpMeta) => {
95
89
  // Collect component meta
96
90
  const inputs = [
97
91
  ...cmpMeta.properties.filter((prop) => !prop.internal).map((prop) => prop.name),
@@ -110,44 +104,34 @@ const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir)
110
104
  if (inputs.length > 0) {
111
105
  directiveOpts.push(`inputs: ['${inputs.join(`', '`)}']`);
112
106
  }
113
- if (outputs.length > 0) {
114
- directiveOpts.push(`outputs: ['${outputs.map((output) => output.name).join(`', '`)}']`);
115
- }
116
107
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
117
- const typePath = path__default['default'].parse(path__default['default'].join(componentCorePackage, path__default['default'].join(cmpMeta.sourceFilePath, '').replace(path__default['default'].join(rootDir, 'src'), distTypesDir)));
118
- const importPath = normalizePath(path__default['default'].join(typePath.dir, typePath.name));
119
108
  const outputsInterface = new Set();
120
109
  const outputReferenceRemap = {};
121
110
  outputs.forEach((output) => {
122
111
  Object.entries(output.complexType.references).forEach(([reference, refObject]) => {
123
- // Add import line for each local/import reference, and add new mapping name
124
- // outputReferenceRemap should be updated only if the import interface is set in outputsInterface
125
- // This will prevent global types to be remapped
112
+ // Add import line for each local/import reference, and add new mapping name.
113
+ // `outputReferenceRemap` should be updated only if the import interface is set in outputsInterface,
114
+ // this will prevent global types to be remapped.
126
115
  const remappedReference = `I${cmpMeta.componentClassName}${reference}`;
127
- if (refObject.location === 'local') {
128
- outputReferenceRemap[reference] = remappedReference;
129
- outputsInterface.add(`import { ${reference} as ${remappedReference} } from '${importPath}';`);
130
- }
131
- else if (refObject.location === 'import') {
116
+ if (refObject.location === 'local' || refObject.location === 'import') {
132
117
  outputReferenceRemap[reference] = remappedReference;
133
- const interfacePath = normalizePath(isRelativePath(refObject.path) ? path__default['default'].join(typePath.dir, refObject.path) : refObject.path);
134
- outputsInterface.add(`import { ${reference} as ${remappedReference} } from '${interfacePath}';`);
118
+ let importLocation = componentCorePackage;
119
+ if (componentCorePackage !== undefined) {
120
+ const dirPath = includeImportCustomElements ? `/${customElementsDir || 'components'}` : '';
121
+ importLocation = `${normalizePath(componentCorePackage)}${dirPath}`;
122
+ }
123
+ outputsInterface.add(`import type { ${reference} as ${remappedReference} } from '${importLocation}';`);
135
124
  }
136
125
  });
137
126
  });
138
- const lines = [
139
- '',
140
- `${[...outputsInterface].join('\n')}
141
- export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {}
142
- ${getProxyCmp(inputs, methods)}
143
- @Component({
144
- ${directiveOpts.join(',\n ')}
145
- })
146
- export class ${tagNameAsPascal} {`,
127
+ const componentEvents = [
128
+ '' // Empty first line
147
129
  ];
148
130
  // Generate outputs
149
- outputs.forEach((output) => {
150
- lines.push(` /** ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}*/`);
131
+ outputs.forEach((output, index) => {
132
+ componentEvents.push(` /**
133
+ * ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}
134
+ */`);
151
135
  /**
152
136
  * The original attribute contains the original type defined by the devs.
153
137
  * This regexp normalizes the reference, by removing linebreaks,
@@ -161,8 +145,23 @@ export class ${tagNameAsPascal} {`,
161
145
  .replace(/\n/g, ' ')
162
146
  .replace(/\s{2,}/g, ' ')
163
147
  .replace(/,\s*/g, ', '));
164
- lines.push(` ${output.name}!: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
148
+ componentEvents.push(` ${output.name}: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
149
+ if (index === outputs.length - 1) {
150
+ // Empty line to push end `}` to new line
151
+ componentEvents.push('\n');
152
+ }
165
153
  });
154
+ const lines = [
155
+ '',
156
+ `${[...outputsInterface].join('\n')}
157
+ export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {${componentEvents.length > 1 ? componentEvents.join('\n') : ''}}
158
+
159
+ ${getProxyCmp(cmpMeta.tagName, includeImportCustomElements, inputs, methods)}
160
+ @Component({
161
+ ${directiveOpts.join(',\n ')}
162
+ })
163
+ export class ${tagNameAsPascal} {`,
164
+ ];
166
165
  lines.push(' protected el: HTMLElement;');
167
166
  lines.push(` constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
168
167
  c.detach();
@@ -174,13 +173,13 @@ export class ${tagNameAsPascal} {`,
174
173
  lines.push(`}`);
175
174
  return lines.join('\n');
176
175
  };
177
- function getProxyCmp(inputs, methods) {
176
+ function getProxyCmp(tagName, includeCustomElement, inputs, methods) {
178
177
  const hasInputs = inputs.length > 0;
179
178
  const hasMethods = methods.length > 0;
180
- const proxMeta = [];
181
- if (!hasInputs && !hasMethods) {
182
- return '';
183
- }
179
+ const proxMeta = [
180
+ `tagName: \'${tagName}\'`,
181
+ `customElement: ${includeCustomElement ? dashToPascalCase(tagName) + 'Cmp' : 'undefined'}`
182
+ ];
184
183
  if (hasInputs)
185
184
  proxMeta.push(`inputs: ['${inputs.join(`', '`)}']`);
186
185
  if (hasMethods)
@@ -269,7 +268,7 @@ const VALUE_ACCESSOR_EVENTTARGETS = ` '(<VALUE_ACCESSOR_EVENT>)': 'handleChan
269
268
  async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
270
269
  const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
271
270
  const rootDir = config.rootDir;
272
- const pkgData = await readPackageJson(rootDir);
271
+ const pkgData = await readPackageJson(config, rootDir);
273
272
  const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
274
273
  await Promise.all([
275
274
  compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
@@ -304,14 +303,39 @@ function generateProxies(components, pkgData, outputTarget, rootDir) {
304
303
  /* auto-generated angular directive proxies */
305
304
  import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, NgZone } from '@angular/core';
306
305
  import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
307
- const typeImports = !outputTarget.componentCorePackage
308
- ? `import { ${IMPORT_TYPES} } from '${normalizePath(componentsTypeFile)}';`
309
- : `import { ${IMPORT_TYPES} } from '${normalizePath(outputTarget.componentCorePackage)}';`;
306
+ /**
307
+ * Generate JSX import type from correct location.
308
+ * When using custom elements build, we need to import from
309
+ * either the "components" directory or customElementsDir
310
+ * otherwise we risk bundlers pulling in lazy loaded imports.
311
+ */
312
+ const generateTypeImports = () => {
313
+ let importLocation = outputTarget.componentCorePackage ? normalizePath(outputTarget.componentCorePackage) : normalizePath(componentsTypeFile);
314
+ importLocation += outputTarget.includeImportCustomElements ? `/${outputTarget.customElementsDir || 'components'}` : '';
315
+ return `import ${outputTarget.includeImportCustomElements ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
316
+ };
317
+ const typeImports = generateTypeImports();
318
+ let sourceImports = '';
319
+ /**
320
+ * Build an array of Custom Elements build imports and namespace them
321
+ * so that they do not conflict with the React wrapper names. For example,
322
+ * IonButton would be imported as IonButtonCmp so as to not conflict with the
323
+ * IonButton React Component that takes in the Web Component as a parameter.
324
+ */
325
+ if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
326
+ const cmpImports = components.map(component => {
327
+ const pascalImport = dashToPascalCase(component.tagName);
328
+ return `import { ${pascalImport} as ${pascalImport}Cmp } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir ||
329
+ 'components'}/${component.tagName}.js';`;
330
+ });
331
+ sourceImports = cmpImports.join('\n');
332
+ }
310
333
  const final = [
311
334
  imports,
312
335
  typeImports,
336
+ sourceImports,
313
337
  components
314
- .map(createComponentDefinition(outputTarget.componentCorePackage, distTypesDir, rootDir))
338
+ .map(createComponentDefinition(outputTarget.componentCorePackage, distTypesDir, rootDir, outputTarget.includeImportCustomElements, outputTarget.customElementsDir))
315
339
  .join('\n'),
316
340
  ];
317
341
  return final.join('\n') + '\n';
package/dist/index.js CHANGED
@@ -1,9 +1,6 @@
1
1
  import path from 'path';
2
- import { promisify } from 'util';
3
- import fs from 'fs';
4
2
  import { EOL } from 'os';
5
3
 
6
- const readFile = promisify(fs.readFile);
7
4
  const toLowerCase = (str) => str.toLowerCase();
8
5
  const dashToPascalCase = (str) => toLowerCase(str)
9
6
  .split('-')
@@ -57,14 +54,12 @@ function relativeImport(pathFrom, pathTo, ext) {
57
54
  }
58
55
  return normalizePath(`${relativePath}/${path.basename(pathTo, ext)}`);
59
56
  }
60
- function isRelativePath(path) {
61
- return path && path.startsWith('.');
62
- }
63
- async function readPackageJson(rootDir) {
57
+ async function readPackageJson(config, rootDir) {
58
+ var _a;
64
59
  const pkgJsonPath = path.join(rootDir, 'package.json');
65
60
  let pkgJson;
66
61
  try {
67
- pkgJson = await readFile(pkgJsonPath, 'utf8');
62
+ pkgJson = (await ((_a = config.sys) === null || _a === void 0 ? void 0 : _a.readFile(pkgJsonPath, 'utf8')));
68
63
  }
69
64
  catch (e) {
70
65
  throw new Error(`Missing "package.json" file for distribution: ${pkgJsonPath}`);
@@ -82,7 +77,7 @@ const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
82
77
  const NON_ASCII_REGEX = /[^\x00-\x80]+/;
83
78
  const SLASH_REGEX = /\\/g;
84
79
 
85
- const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir) => (cmpMeta) => {
80
+ const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir, includeImportCustomElements = false, customElementsDir = 'components') => (cmpMeta) => {
86
81
  // Collect component meta
87
82
  const inputs = [
88
83
  ...cmpMeta.properties.filter((prop) => !prop.internal).map((prop) => prop.name),
@@ -101,44 +96,34 @@ const createComponentDefinition = (componentCorePackage, distTypesDir, rootDir)
101
96
  if (inputs.length > 0) {
102
97
  directiveOpts.push(`inputs: ['${inputs.join(`', '`)}']`);
103
98
  }
104
- if (outputs.length > 0) {
105
- directiveOpts.push(`outputs: ['${outputs.map((output) => output.name).join(`', '`)}']`);
106
- }
107
99
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
108
- const typePath = path.parse(path.join(componentCorePackage, path.join(cmpMeta.sourceFilePath, '').replace(path.join(rootDir, 'src'), distTypesDir)));
109
- const importPath = normalizePath(path.join(typePath.dir, typePath.name));
110
100
  const outputsInterface = new Set();
111
101
  const outputReferenceRemap = {};
112
102
  outputs.forEach((output) => {
113
103
  Object.entries(output.complexType.references).forEach(([reference, refObject]) => {
114
- // Add import line for each local/import reference, and add new mapping name
115
- // outputReferenceRemap should be updated only if the import interface is set in outputsInterface
116
- // This will prevent global types to be remapped
104
+ // Add import line for each local/import reference, and add new mapping name.
105
+ // `outputReferenceRemap` should be updated only if the import interface is set in outputsInterface,
106
+ // this will prevent global types to be remapped.
117
107
  const remappedReference = `I${cmpMeta.componentClassName}${reference}`;
118
- if (refObject.location === 'local') {
119
- outputReferenceRemap[reference] = remappedReference;
120
- outputsInterface.add(`import { ${reference} as ${remappedReference} } from '${importPath}';`);
121
- }
122
- else if (refObject.location === 'import') {
108
+ if (refObject.location === 'local' || refObject.location === 'import') {
123
109
  outputReferenceRemap[reference] = remappedReference;
124
- const interfacePath = normalizePath(isRelativePath(refObject.path) ? path.join(typePath.dir, refObject.path) : refObject.path);
125
- outputsInterface.add(`import { ${reference} as ${remappedReference} } from '${interfacePath}';`);
110
+ let importLocation = componentCorePackage;
111
+ if (componentCorePackage !== undefined) {
112
+ const dirPath = includeImportCustomElements ? `/${customElementsDir || 'components'}` : '';
113
+ importLocation = `${normalizePath(componentCorePackage)}${dirPath}`;
114
+ }
115
+ outputsInterface.add(`import type { ${reference} as ${remappedReference} } from '${importLocation}';`);
126
116
  }
127
117
  });
128
118
  });
129
- const lines = [
130
- '',
131
- `${[...outputsInterface].join('\n')}
132
- export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {}
133
- ${getProxyCmp(inputs, methods)}
134
- @Component({
135
- ${directiveOpts.join(',\n ')}
136
- })
137
- export class ${tagNameAsPascal} {`,
119
+ const componentEvents = [
120
+ '' // Empty first line
138
121
  ];
139
122
  // Generate outputs
140
- outputs.forEach((output) => {
141
- lines.push(` /** ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}*/`);
123
+ outputs.forEach((output, index) => {
124
+ componentEvents.push(` /**
125
+ * ${output.docs.text} ${output.docs.tags.map((tag) => `@${tag.name} ${tag.text}`)}
126
+ */`);
142
127
  /**
143
128
  * The original attribute contains the original type defined by the devs.
144
129
  * This regexp normalizes the reference, by removing linebreaks,
@@ -152,8 +137,23 @@ export class ${tagNameAsPascal} {`,
152
137
  .replace(/\n/g, ' ')
153
138
  .replace(/\s{2,}/g, ' ')
154
139
  .replace(/,\s*/g, ', '));
155
- lines.push(` ${output.name}!: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
140
+ componentEvents.push(` ${output.name}: EventEmitter<CustomEvent<${outputTypeRemapped.trim()}>>;`);
141
+ if (index === outputs.length - 1) {
142
+ // Empty line to push end `}` to new line
143
+ componentEvents.push('\n');
144
+ }
156
145
  });
146
+ const lines = [
147
+ '',
148
+ `${[...outputsInterface].join('\n')}
149
+ export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {${componentEvents.length > 1 ? componentEvents.join('\n') : ''}}
150
+
151
+ ${getProxyCmp(cmpMeta.tagName, includeImportCustomElements, inputs, methods)}
152
+ @Component({
153
+ ${directiveOpts.join(',\n ')}
154
+ })
155
+ export class ${tagNameAsPascal} {`,
156
+ ];
157
157
  lines.push(' protected el: HTMLElement;');
158
158
  lines.push(` constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
159
159
  c.detach();
@@ -165,13 +165,13 @@ export class ${tagNameAsPascal} {`,
165
165
  lines.push(`}`);
166
166
  return lines.join('\n');
167
167
  };
168
- function getProxyCmp(inputs, methods) {
168
+ function getProxyCmp(tagName, includeCustomElement, inputs, methods) {
169
169
  const hasInputs = inputs.length > 0;
170
170
  const hasMethods = methods.length > 0;
171
- const proxMeta = [];
172
- if (!hasInputs && !hasMethods) {
173
- return '';
174
- }
171
+ const proxMeta = [
172
+ `tagName: \'${tagName}\'`,
173
+ `customElement: ${includeCustomElement ? dashToPascalCase(tagName) + 'Cmp' : 'undefined'}`
174
+ ];
175
175
  if (hasInputs)
176
176
  proxMeta.push(`inputs: ['${inputs.join(`', '`)}']`);
177
177
  if (hasMethods)
@@ -260,7 +260,7 @@ const VALUE_ACCESSOR_EVENTTARGETS = ` '(<VALUE_ACCESSOR_EVENT>)': 'handleChan
260
260
  async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
261
261
  const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
262
262
  const rootDir = config.rootDir;
263
- const pkgData = await readPackageJson(rootDir);
263
+ const pkgData = await readPackageJson(config, rootDir);
264
264
  const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
265
265
  await Promise.all([
266
266
  compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
@@ -295,14 +295,39 @@ function generateProxies(components, pkgData, outputTarget, rootDir) {
295
295
  /* auto-generated angular directive proxies */
296
296
  import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, NgZone } from '@angular/core';
297
297
  import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
298
- const typeImports = !outputTarget.componentCorePackage
299
- ? `import { ${IMPORT_TYPES} } from '${normalizePath(componentsTypeFile)}';`
300
- : `import { ${IMPORT_TYPES} } from '${normalizePath(outputTarget.componentCorePackage)}';`;
298
+ /**
299
+ * Generate JSX import type from correct location.
300
+ * When using custom elements build, we need to import from
301
+ * either the "components" directory or customElementsDir
302
+ * otherwise we risk bundlers pulling in lazy loaded imports.
303
+ */
304
+ const generateTypeImports = () => {
305
+ let importLocation = outputTarget.componentCorePackage ? normalizePath(outputTarget.componentCorePackage) : normalizePath(componentsTypeFile);
306
+ importLocation += outputTarget.includeImportCustomElements ? `/${outputTarget.customElementsDir || 'components'}` : '';
307
+ return `import ${outputTarget.includeImportCustomElements ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
308
+ };
309
+ const typeImports = generateTypeImports();
310
+ let sourceImports = '';
311
+ /**
312
+ * Build an array of Custom Elements build imports and namespace them
313
+ * so that they do not conflict with the React wrapper names. For example,
314
+ * IonButton would be imported as IonButtonCmp so as to not conflict with the
315
+ * IonButton React Component that takes in the Web Component as a parameter.
316
+ */
317
+ if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
318
+ const cmpImports = components.map(component => {
319
+ const pascalImport = dashToPascalCase(component.tagName);
320
+ return `import { ${pascalImport} as ${pascalImport}Cmp } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir ||
321
+ 'components'}/${component.tagName}.js';`;
322
+ });
323
+ sourceImports = cmpImports.join('\n');
324
+ }
301
325
  const final = [
302
326
  imports,
303
327
  typeImports,
328
+ sourceImports,
304
329
  components
305
- .map(createComponentDefinition(outputTarget.componentCorePackage, distTypesDir, rootDir))
330
+ .map(createComponentDefinition(outputTarget.componentCorePackage, distTypesDir, rootDir, outputTarget.includeImportCustomElements, outputTarget.customElementsDir))
306
331
  .join('\n'),
307
332
  ];
308
333
  return final.join('\n') + '\n';
@@ -1,12 +1,12 @@
1
1
  import path from 'path';
2
- import { relativeImport, normalizePath, sortBy, readPackageJson } from './utils';
2
+ import { relativeImport, normalizePath, sortBy, readPackageJson, dashToPascalCase } from './utils';
3
3
  import { createComponentDefinition } from './generate-angular-component';
4
4
  import { generateAngularDirectivesFile } from './generate-angular-directives-file';
5
5
  import generateValueAccessors from './generate-value-accessors';
6
6
  export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
7
7
  const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
8
8
  const rootDir = config.rootDir;
9
- const pkgData = await readPackageJson(rootDir);
9
+ const pkgData = await readPackageJson(config, rootDir);
10
10
  const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
11
11
  await Promise.all([
12
12
  compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
@@ -41,14 +41,39 @@ export function generateProxies(components, pkgData, outputTarget, rootDir) {
41
41
  /* auto-generated angular directive proxies */
42
42
  import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, NgZone } from '@angular/core';
43
43
  import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';\n`;
44
- const typeImports = !outputTarget.componentCorePackage
45
- ? `import { ${IMPORT_TYPES} } from '${normalizePath(componentsTypeFile)}';`
46
- : `import { ${IMPORT_TYPES} } from '${normalizePath(outputTarget.componentCorePackage)}';`;
44
+ /**
45
+ * Generate JSX import type from correct location.
46
+ * When using custom elements build, we need to import from
47
+ * either the "components" directory or customElementsDir
48
+ * otherwise we risk bundlers pulling in lazy loaded imports.
49
+ */
50
+ const generateTypeImports = () => {
51
+ let importLocation = outputTarget.componentCorePackage ? normalizePath(outputTarget.componentCorePackage) : normalizePath(componentsTypeFile);
52
+ importLocation += outputTarget.includeImportCustomElements ? `/${outputTarget.customElementsDir || 'components'}` : '';
53
+ return `import ${outputTarget.includeImportCustomElements ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
54
+ };
55
+ const typeImports = generateTypeImports();
56
+ let sourceImports = '';
57
+ /**
58
+ * Build an array of Custom Elements build imports and namespace them
59
+ * so that they do not conflict with the React wrapper names. For example,
60
+ * IonButton would be imported as IonButtonCmp so as to not conflict with the
61
+ * IonButton React Component that takes in the Web Component as a parameter.
62
+ */
63
+ if (outputTarget.includeImportCustomElements && outputTarget.componentCorePackage !== undefined) {
64
+ const cmpImports = components.map(component => {
65
+ const pascalImport = dashToPascalCase(component.tagName);
66
+ return `import { ${pascalImport} as ${pascalImport}Cmp } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir ||
67
+ 'components'}/${component.tagName}.js';`;
68
+ });
69
+ sourceImports = cmpImports.join('\n');
70
+ }
47
71
  const final = [
48
72
  imports,
49
73
  typeImports,
74
+ sourceImports,
50
75
  components
51
- .map(createComponentDefinition(outputTarget.componentCorePackage, distTypesDir, rootDir))
76
+ .map(createComponentDefinition(outputTarget.componentCorePackage, distTypesDir, rootDir, outputTarget.includeImportCustomElements, outputTarget.customElementsDir))
52
77
  .join('\n'),
53
78
  ];
54
79
  return final.join('\n') + '\n';
package/dist/types.d.ts CHANGED
@@ -5,6 +5,8 @@ export interface OutputTargetAngular {
5
5
  directivesUtilsFile?: string;
6
6
  valueAccessorConfigs?: ValueAccessorConfig[];
7
7
  excludeComponents?: string[];
8
+ includeImportCustomElements?: boolean;
9
+ customElementsDir?: string;
8
10
  }
9
11
  export declare type ValueAccessorTypes = 'text' | 'radio' | 'select' | 'number' | 'boolean';
10
12
  export interface ValueAccessorConfig {
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Config } from '@stencil/core/internal';
1
2
  import type { PackageJSON } from './types';
2
3
  export declare const toLowerCase: (str: string) => string;
3
4
  export declare const dashToPascalCase: (str: string) => string;
@@ -5,4 +6,4 @@ export declare function sortBy<T>(array: T[], prop: (item: T) => string): T[];
5
6
  export declare function normalizePath(str: string): string;
6
7
  export declare function relativeImport(pathFrom: string, pathTo: string, ext?: string): string;
7
8
  export declare function isRelativePath(path: string): boolean | "";
8
- export declare function readPackageJson(rootDir: string): Promise<PackageJSON>;
9
+ export declare function readPackageJson(config: Config, rootDir: string): Promise<PackageJSON>;
package/dist/utils.js CHANGED
@@ -1,7 +1,4 @@
1
1
  import path from 'path';
2
- import { promisify } from 'util';
3
- import fs from 'fs';
4
- const readFile = promisify(fs.readFile);
5
2
  export const toLowerCase = (str) => str.toLowerCase();
6
3
  export const dashToPascalCase = (str) => toLowerCase(str)
7
4
  .split('-')
@@ -58,11 +55,12 @@ export function relativeImport(pathFrom, pathTo, ext) {
58
55
  export function isRelativePath(path) {
59
56
  return path && path.startsWith('.');
60
57
  }
61
- export async function readPackageJson(rootDir) {
58
+ export async function readPackageJson(config, rootDir) {
59
+ var _a;
62
60
  const pkgJsonPath = path.join(rootDir, 'package.json');
63
61
  let pkgJson;
64
62
  try {
65
- pkgJson = await readFile(pkgJsonPath, 'utf8');
63
+ pkgJson = (await ((_a = config.sys) === null || _a === void 0 ? void 0 : _a.readFile(pkgJsonPath, 'utf8')));
66
64
  }
67
65
  catch (e) {
68
66
  throw new Error(`Missing "package.json" file for distribution: ${pkgJsonPath}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stencil/angular-output-target",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Angular output target for @stencil/core components.",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.js",