wc-compiler 0.6.2 → 0.8.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.
package/dist/wcc.dist.cjs CHANGED
@@ -3,8 +3,6 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var fs = require('fs');
6
- var path = require('path');
7
- var url = require('url');
8
6
 
9
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
10
8
 
@@ -27,7 +25,6 @@ function _interopNamespace(e) {
27
25
  }
28
26
 
29
27
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
30
- var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
31
28
 
32
29
  function noop() { }
33
30
 
@@ -27632,8 +27629,6 @@ var serialize = function(node, options) {
27632
27629
 
27633
27630
  /* eslint-disable max-depth, complexity */
27634
27631
 
27635
- url.pathToFileURL(`${process.cwd()}/`).href;
27636
-
27637
27632
  // TODO same hack as definitions
27638
27633
  // https://github.com/ProjectEvergreen/wcc/discussions/74
27639
27634
  let string;
@@ -27647,7 +27642,7 @@ function getParse$1(html) {
27647
27642
  }
27648
27643
 
27649
27644
  function getParser(moduleURL) {
27650
- const isJSX = path__default["default"].extname(moduleURL.pathname) === '.jsx';
27645
+ const isJSX = moduleURL.pathname.split('.').pop() === 'jsx';
27651
27646
 
27652
27647
  if (!isJSX) {
27653
27648
  return;
@@ -27819,14 +27814,52 @@ function parseJsxElement(element, moduleContents = '') {
27819
27814
  return string;
27820
27815
  }
27821
27816
 
27817
+ // TODO handle if / else statements
27818
+ // https://github.com/ProjectEvergreen/wcc/issues/88
27819
+ function findThisReferences(context, statement) {
27820
+ const references = [];
27821
+ const isRenderFunctionContext = context === 'render';
27822
+ const { expression, type } = statement;
27823
+ const isConstructorThisAssignment = context === 'constructor'
27824
+ && type === 'ExpressionStatement'
27825
+ && expression.type === 'AssignmentExpression'
27826
+ && expression.left.object.type === 'ThisExpression';
27827
+
27828
+ if (isConstructorThisAssignment) {
27829
+ // this.name = 'something'; // constructor
27830
+ references.push(expression.left.property.name);
27831
+ } else if (isRenderFunctionContext && type === 'VariableDeclaration') {
27832
+ statement.declarations.forEach(declaration => {
27833
+ const { init, id } = declaration;
27834
+
27835
+ if (init.object && init.object.type === 'ThisExpression') {
27836
+ // const { description } = this.todo;
27837
+ references.push(init.property.name);
27838
+ } else if (init.type === 'ThisExpression' && id && id.properties) {
27839
+ // const { description } = this.todo;
27840
+ id.properties.forEach((property) => {
27841
+ references.push(property.key.name);
27842
+ });
27843
+ }
27844
+ });
27845
+ }
27846
+
27847
+ return references;
27848
+ }
27849
+
27822
27850
  function parseJsx(moduleURL) {
27823
27851
  const moduleContents = fs__default["default"].readFileSync(moduleURL, 'utf-8');
27824
- string = '';
27825
-
27826
- const tree = Parser$2.extend(jsx()).parse(moduleContents, {
27852
+ // would be nice if we could do this instead, so we could know ahead of time
27853
+ // const { inferredObservability } = await import(moduleURL);
27854
+ // however, this requires making parseJsx async, but WCC acorn walking is done sync
27855
+ const hasOwnObservedAttributes = undefined;
27856
+ let inferredObservability = false;
27857
+ let observedAttributes = [];
27858
+ let tree = Parser$2.extend(jsx()).parse(moduleContents, {
27827
27859
  ecmaVersion: 'latest',
27828
27860
  sourceType: 'module'
27829
27861
  });
27862
+ string = '';
27830
27863
 
27831
27864
  simple(tree, {
27832
27865
  ClassDeclaration(node) {
@@ -27834,29 +27867,46 @@ function parseJsx(moduleURL) {
27834
27867
  const hasShadowRoot = moduleContents.slice(node.body.start, node.body.end).indexOf('this.attachShadow(') > 0;
27835
27868
 
27836
27869
  for (const n1 of node.body.body) {
27837
- if (n1.type === 'MethodDefinition' && n1.key.name === 'render') {
27838
- for (const n2 in n1.value.body.body) {
27839
- const n = n1.value.body.body[n2];
27840
-
27841
- if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
27842
- const html = parseJsxElement(n.argument, moduleContents);
27843
- const elementTree = getParse$1(html)(html);
27844
- const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
27845
-
27846
- applyDomDepthSubstitutions(elementTree, undefined, hasShadowRoot);
27847
-
27848
- const finalHtml = serialize(elementTree);
27849
- const transformed = parse$1(`${elementRoot}.innerHTML = \`${finalHtml}\`;`, {
27850
- ecmaVersion: 'latest',
27851
- sourceType: 'module'
27852
- });
27853
-
27854
- n1.value.body.body[n2] = transformed;
27870
+ if (n1.type === 'MethodDefinition') {
27871
+ const nodeName = n1.key.name;
27872
+ if (nodeName === 'render') {
27873
+ for (const n2 in n1.value.body.body) {
27874
+ const n = n1.value.body.body[n2];
27875
+
27876
+ if (n.type === 'VariableDeclaration') {
27877
+ observedAttributes = [
27878
+ ...observedAttributes,
27879
+ ...findThisReferences('render', n)
27880
+ ];
27881
+ } else if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
27882
+ const html = parseJsxElement(n.argument, moduleContents);
27883
+ const elementTree = getParse$1(html)(html);
27884
+ const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
27885
+
27886
+ applyDomDepthSubstitutions(elementTree, undefined, hasShadowRoot);
27887
+
27888
+ const finalHtml = serialize(elementTree);
27889
+ const transformed = parse$1(`${elementRoot}.innerHTML = \`${finalHtml}\`;`, {
27890
+ ecmaVersion: 'latest',
27891
+ sourceType: 'module'
27892
+ });
27893
+
27894
+ n1.value.body.body[n2] = transformed;
27895
+ }
27855
27896
  }
27856
27897
  }
27857
27898
  }
27858
27899
  }
27859
27900
  }
27901
+ },
27902
+ ExportNamedDeclaration(node) {
27903
+ const { declaration } = node;
27904
+
27905
+ if (declaration && declaration.type === 'VariableDeclaration' && declaration.kind === 'const' && declaration.declarations.length === 1) {
27906
+ if (declaration.declarations[0].id.name === 'inferredObservability') {
27907
+ inferredObservability = Boolean(node.declaration.declarations[0].init.raw);
27908
+ }
27909
+ }
27860
27910
  }
27861
27911
  }, {
27862
27912
  // https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
@@ -27864,6 +27914,68 @@ function parseJsx(moduleURL) {
27864
27914
  JSXElement: () => {}
27865
27915
  });
27866
27916
 
27917
+ // TODO - signals: use constructor, render, HTML attributes? some, none, or all?
27918
+ if (inferredObservability && observedAttributes.length > 0 && !hasOwnObservedAttributes) {
27919
+ let insertPoint;
27920
+ for (const line of tree.body) {
27921
+ // test for class MyComponent vs export default class MyComponent
27922
+ if (line.type === 'ClassDeclaration' || (line.declaration && line.declaration.type) === 'ClassDeclaration') {
27923
+ const children = !line.declaration
27924
+ ? line.body.body
27925
+ : line.declaration.body.body;
27926
+ for (const method of children) {
27927
+ if (method.key.name === 'constructor') {
27928
+ insertPoint = method.start - 1;
27929
+ break;
27930
+ }
27931
+ }
27932
+ }
27933
+ }
27934
+
27935
+ let newModuleContents = escodegen.generate(tree);
27936
+
27937
+ // TODO better way to determine value type?
27938
+ /* eslint-disable indent */
27939
+ newModuleContents = `${newModuleContents.slice(0, insertPoint)}
27940
+ static get observedAttributes() {
27941
+ return [${[...observedAttributes].map(attr => `'${attr}'`).join(',')}]
27942
+ }
27943
+
27944
+ attributeChangedCallback(name, oldValue, newValue) {
27945
+ function getValue(value) {
27946
+ return value.charAt(0) === '{' || value.charAt(0) === '['
27947
+ ? JSON.parse(value)
27948
+ : !isNaN(value)
27949
+ ? parseInt(value, 10)
27950
+ : value === 'true' || value === 'false'
27951
+ ? value === 'true' ? true : false
27952
+ : value;
27953
+ }
27954
+ if (newValue !== oldValue) {
27955
+ switch(name) {
27956
+ ${observedAttributes.map((attr) => {
27957
+ return `
27958
+ case '${attr}':
27959
+ this.${attr} = getValue(newValue);
27960
+ break;
27961
+ `;
27962
+ }).join('\n')}
27963
+ }
27964
+
27965
+ this.render();
27966
+ }
27967
+ }
27968
+
27969
+ ${newModuleContents.slice(insertPoint)}
27970
+ `;
27971
+ /* eslint-enable indent */
27972
+
27973
+ tree = Parser$2.extend(jsx()).parse(newModuleContents, {
27974
+ ecmaVersion: 'latest',
27975
+ sourceType: 'module'
27976
+ });
27977
+ }
27978
+
27867
27979
  return tree;
27868
27980
  }
27869
27981
 
@@ -27933,9 +28045,10 @@ function registerDependencies(moduleURL, definitions, depth = 0) {
27933
28045
  ImportDeclaration(node) {
27934
28046
  const specifier = node.source.value;
27935
28047
  const isBareSpecifier = specifier.indexOf('.') !== 0 && specifier.indexOf('/') !== 0;
28048
+ const extension = specifier.split('.').pop();
27936
28049
 
27937
28050
  // TODO would like to decouple .jsx from the core, ideally
27938
- if (!isBareSpecifier && ['.js', '.jsx'].includes(path__default["default"].extname(specifier))) {
28051
+ if (!isBareSpecifier && ['js', 'jsx'].includes(extension)) {
27939
28052
  const dependencyModuleURL = new URL(node.source.value, moduleURL);
27940
28053
 
27941
28054
  registerDependencies(dependencyModuleURL, definitions, nextDepth);
@@ -28019,9 +28132,9 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti
28019
28132
  }
28020
28133
  }
28021
28134
 
28022
- async function renderToString(elementURL) {
28135
+ async function renderToString(elementURL, wrappingEntryTag = true) {
28023
28136
  const definitions = [];
28024
- const elementTagName = await getTagName(elementURL);
28137
+ const elementTagName = wrappingEntryTag && await getTagName(elementURL);
28025
28138
  const isEntry = !!elementTagName;
28026
28139
  const elementInstance = await initializeCustomElement(elementURL, undefined, undefined, definitions, isEntry);
28027
28140
 
@@ -28030,7 +28143,7 @@ async function renderToString(elementURL) {
28030
28143
  : elementInstance.innerHTML;
28031
28144
  const elementTree = getParse(elementHtml)(elementHtml);
28032
28145
  const finalTree = await renderComponentRoots(elementTree, definitions);
28033
- const html = elementTagName ? `
28146
+ const html = wrappingEntryTag && elementTagName ? `
28034
28147
  <${elementTagName}>
28035
28148
  ${serialize(finalTree)}
28036
28149
  </${elementTagName}>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wc-compiler",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "description": "Experimental native Web Components compiler.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,6 +9,11 @@
9
9
  "main": "src/wcc.js",
10
10
  "type": "module",
11
11
  "author": "Owen Buckley <owen@thegreenhouse.io>",
12
+ "keywords": [
13
+ "Web Components",
14
+ "JSX",
15
+ "Greenwood"
16
+ ],
12
17
  "license": "MIT",
13
18
  "engines": {
14
19
  "node": ">=14"
@@ -22,12 +27,12 @@
22
27
  },
23
28
  "scripts": {
24
29
  "clean": "rimraf ./dist",
25
- "lint": "eslint \"*.*js\" \"./src/**/**/*.js*\" \"./test/**/**/*.js*\"",
30
+ "lint": "ls-lint && eslint \"*.*js\" \"./src/**/**/*.js*\" \"./docs/**/*.md\" \"./test/**/**/*.js*\"",
26
31
  "develop": "concurrently \"nodemon --watch src --watch docs -e js,md,css,html,jsx ./build.js\" \"http-server ./dist --open\"",
27
32
  "build": "node ./build.js",
28
33
  "serve": "node ./build.js && http-server ./dist --open",
29
34
  "start": "npm run develop",
30
- "test": "mocha --exclude \"./test/cases/jsx/**\" --exclude \"./test/cases/custom-extension/**\" \"./test/**/**/*.spec.js\"",
35
+ "test": "mocha --exclude \"./test/cases/jsx*/**\" --exclude \"./test/cases/custom-extension/**\" \"./test/**/**/*.spec.js\"",
31
36
  "test:exp": "c8 node --experimental-loader ./test-exp-loader.js ./node_modules/mocha/bin/mocha \"./test/**/**/*.spec.js\"",
32
37
  "test:tdd": "npm run test -- --watch",
33
38
  "test:tdd:exp": "npm run test:exp -- --watch",
@@ -42,6 +47,7 @@
42
47
  "parse5": "^6.0.1"
43
48
  },
44
49
  "devDependencies": {
50
+ "@ls-lint/ls-lint": "^1.10.0",
45
51
  "@mapbox/rehype-prism": "^0.8.0",
46
52
  "@rollup/plugin-commonjs": "^22.0.0",
47
53
  "@rollup/plugin-json": "^4.1.0",
@@ -50,6 +56,8 @@
50
56
  "chai": "^4.3.6",
51
57
  "concurrently": "^7.1.0",
52
58
  "eslint": "^8.14.0",
59
+ "eslint-plugin-markdown": "^3.0.0",
60
+ "eslint-plugin-no-only-tests": "^2.6.0",
53
61
  "http-server": "^14.1.0",
54
62
  "jsdom": "^19.0.0",
55
63
  "mocha": "^9.2.2",
package/src/jsx-loader.js CHANGED
@@ -6,10 +6,7 @@ import escodegen from 'escodegen';
6
6
  import fs from 'fs';
7
7
  import jsx from 'acorn-jsx';
8
8
  import { parse, parseFragment, serialize } from 'parse5';
9
- import path from 'path';
10
- import { URL, pathToFileURL } from 'url';
11
9
 
12
- const baseURL = pathToFileURL(`${process.cwd()}/`).href;
13
10
  const jsxRegex = /\.(jsx)$/;
14
11
 
15
12
  // TODO same hack as definitions
@@ -25,7 +22,7 @@ function getParse(html) {
25
22
  }
26
23
 
27
24
  export function getParser(moduleURL) {
28
- const isJSX = path.extname(moduleURL.pathname) === '.jsx';
25
+ const isJSX = moduleURL.pathname.split('.').pop() === 'jsx';
29
26
 
30
27
  if (!isJSX) {
31
28
  return;
@@ -197,14 +194,52 @@ function parseJsxElement(element, moduleContents = '') {
197
194
  return string;
198
195
  }
199
196
 
197
+ // TODO handle if / else statements
198
+ // https://github.com/ProjectEvergreen/wcc/issues/88
199
+ function findThisReferences(context, statement) {
200
+ const references = [];
201
+ const isRenderFunctionContext = context === 'render';
202
+ const { expression, type } = statement;
203
+ const isConstructorThisAssignment = context === 'constructor'
204
+ && type === 'ExpressionStatement'
205
+ && expression.type === 'AssignmentExpression'
206
+ && expression.left.object.type === 'ThisExpression';
207
+
208
+ if (isConstructorThisAssignment) {
209
+ // this.name = 'something'; // constructor
210
+ references.push(expression.left.property.name);
211
+ } else if (isRenderFunctionContext && type === 'VariableDeclaration') {
212
+ statement.declarations.forEach(declaration => {
213
+ const { init, id } = declaration;
214
+
215
+ if (init.object && init.object.type === 'ThisExpression') {
216
+ // const { description } = this.todo;
217
+ references.push(init.property.name);
218
+ } else if (init.type === 'ThisExpression' && id && id.properties) {
219
+ // const { description } = this.todo;
220
+ id.properties.forEach((property) => {
221
+ references.push(property.key.name);
222
+ });
223
+ }
224
+ });
225
+ }
226
+
227
+ return references;
228
+ }
229
+
200
230
  export function parseJsx(moduleURL) {
201
231
  const moduleContents = fs.readFileSync(moduleURL, 'utf-8');
202
- string = '';
203
-
204
- const tree = acorn.Parser.extend(jsx()).parse(moduleContents, {
232
+ // would be nice if we could do this instead, so we could know ahead of time
233
+ // const { inferredObservability } = await import(moduleURL);
234
+ // however, this requires making parseJsx async, but WCC acorn walking is done sync
235
+ const hasOwnObservedAttributes = undefined;
236
+ let inferredObservability = false;
237
+ let observedAttributes = [];
238
+ let tree = acorn.Parser.extend(jsx()).parse(moduleContents, {
205
239
  ecmaVersion: 'latest',
206
240
  sourceType: 'module'
207
241
  });
242
+ string = '';
208
243
 
209
244
  walk.simple(tree, {
210
245
  ClassDeclaration(node) {
@@ -212,29 +247,46 @@ export function parseJsx(moduleURL) {
212
247
  const hasShadowRoot = moduleContents.slice(node.body.start, node.body.end).indexOf('this.attachShadow(') > 0;
213
248
 
214
249
  for (const n1 of node.body.body) {
215
- if (n1.type === 'MethodDefinition' && n1.key.name === 'render') {
216
- for (const n2 in n1.value.body.body) {
217
- const n = n1.value.body.body[n2];
218
-
219
- if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
220
- const html = parseJsxElement(n.argument, moduleContents);
221
- const elementTree = getParse(html)(html);
222
- const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
223
-
224
- applyDomDepthSubstitutions(elementTree, undefined, hasShadowRoot);
225
-
226
- const finalHtml = serialize(elementTree);
227
- const transformed = acorn.parse(`${elementRoot}.innerHTML = \`${finalHtml}\`;`, {
228
- ecmaVersion: 'latest',
229
- sourceType: 'module'
230
- });
231
-
232
- n1.value.body.body[n2] = transformed;
250
+ if (n1.type === 'MethodDefinition') {
251
+ const nodeName = n1.key.name;
252
+ if (nodeName === 'render') {
253
+ for (const n2 in n1.value.body.body) {
254
+ const n = n1.value.body.body[n2];
255
+
256
+ if (n.type === 'VariableDeclaration') {
257
+ observedAttributes = [
258
+ ...observedAttributes,
259
+ ...findThisReferences('render', n)
260
+ ];
261
+ } else if (n.type === 'ReturnStatement' && n.argument.type === 'JSXElement') {
262
+ const html = parseJsxElement(n.argument, moduleContents);
263
+ const elementTree = getParse(html)(html);
264
+ const elementRoot = hasShadowRoot ? 'this.shadowRoot' : 'this';
265
+
266
+ applyDomDepthSubstitutions(elementTree, undefined, hasShadowRoot);
267
+
268
+ const finalHtml = serialize(elementTree);
269
+ const transformed = acorn.parse(`${elementRoot}.innerHTML = \`${finalHtml}\`;`, {
270
+ ecmaVersion: 'latest',
271
+ sourceType: 'module'
272
+ });
273
+
274
+ n1.value.body.body[n2] = transformed;
275
+ }
233
276
  }
234
277
  }
235
278
  }
236
279
  }
237
280
  }
281
+ },
282
+ ExportNamedDeclaration(node) {
283
+ const { declaration } = node;
284
+
285
+ if (declaration && declaration.type === 'VariableDeclaration' && declaration.kind === 'const' && declaration.declarations.length === 1) {
286
+ if (declaration.declarations[0].id.name === 'inferredObservability') {
287
+ inferredObservability = Boolean(node.declaration.declarations[0].init.raw);
288
+ }
289
+ }
238
290
  }
239
291
  }, {
240
292
  // https://github.com/acornjs/acorn/issues/829#issuecomment-1172586171
@@ -242,17 +294,80 @@ export function parseJsx(moduleURL) {
242
294
  JSXElement: () => {}
243
295
  });
244
296
 
297
+ // TODO - signals: use constructor, render, HTML attributes? some, none, or all?
298
+ if (inferredObservability && observedAttributes.length > 0 && !hasOwnObservedAttributes) {
299
+ let insertPoint;
300
+ for (const line of tree.body) {
301
+ // test for class MyComponent vs export default class MyComponent
302
+ if (line.type === 'ClassDeclaration' || (line.declaration && line.declaration.type) === 'ClassDeclaration') {
303
+ const children = !line.declaration
304
+ ? line.body.body
305
+ : line.declaration.body.body;
306
+ for (const method of children) {
307
+ if (method.key.name === 'constructor') {
308
+ insertPoint = method.start - 1;
309
+ break;
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ let newModuleContents = escodegen.generate(tree);
316
+
317
+ // TODO better way to determine value type?
318
+ /* eslint-disable indent */
319
+ newModuleContents = `${newModuleContents.slice(0, insertPoint)}
320
+ static get observedAttributes() {
321
+ return [${[...observedAttributes].map(attr => `'${attr}'`).join(',')}]
322
+ }
323
+
324
+ attributeChangedCallback(name, oldValue, newValue) {
325
+ function getValue(value) {
326
+ return value.charAt(0) === '{' || value.charAt(0) === '['
327
+ ? JSON.parse(value)
328
+ : !isNaN(value)
329
+ ? parseInt(value, 10)
330
+ : value === 'true' || value === 'false'
331
+ ? value === 'true' ? true : false
332
+ : value;
333
+ }
334
+ if (newValue !== oldValue) {
335
+ switch(name) {
336
+ ${observedAttributes.map((attr) => {
337
+ return `
338
+ case '${attr}':
339
+ this.${attr} = getValue(newValue);
340
+ break;
341
+ `;
342
+ }).join('\n')}
343
+ }
344
+
345
+ this.render();
346
+ }
347
+ }
348
+
349
+ ${newModuleContents.slice(insertPoint)}
350
+ `;
351
+ /* eslint-enable indent */
352
+
353
+ tree = acorn.Parser.extend(jsx()).parse(newModuleContents, {
354
+ ecmaVersion: 'latest',
355
+ sourceType: 'module'
356
+ });
357
+ }
358
+
245
359
  return tree;
246
360
  }
247
361
 
248
362
  // --------------
249
363
 
250
364
  export function resolve(specifier, context, defaultResolve) {
251
- const { parentURL = baseURL } = context;
365
+ const { parentURL } = context;
252
366
 
253
367
  if (jsxRegex.test(specifier)) {
254
368
  return {
255
- url: new URL(specifier, parentURL).href
369
+ url: new URL(specifier, parentURL).href,
370
+ shortCircuit: true
256
371
  };
257
372
  }
258
373
 
@@ -265,7 +380,8 @@ export async function load(url, context, defaultLoad) {
265
380
 
266
381
  return {
267
382
  format: 'module',
268
- source: escodegen.generate(jsFromJsx)
383
+ source: escodegen.generate(jsFromJsx),
384
+ shortCircuit: true
269
385
  };
270
386
  }
271
387
 
package/src/wcc.js CHANGED
@@ -8,7 +8,6 @@ import escodegen from 'escodegen';
8
8
  import { getParser, parseJsx } from './jsx-loader.js';
9
9
  import { parse, parseFragment, serialize } from 'parse5';
10
10
  import fs from 'fs';
11
- import path from 'path';
12
11
 
13
12
  function getParse(html) {
14
13
  return html.indexOf('<html>') >= 0 || html.indexOf('<body>') >= 0 || html.indexOf('<head>') >= 0
@@ -74,9 +73,10 @@ function registerDependencies(moduleURL, definitions, depth = 0) {
74
73
  ImportDeclaration(node) {
75
74
  const specifier = node.source.value;
76
75
  const isBareSpecifier = specifier.indexOf('.') !== 0 && specifier.indexOf('/') !== 0;
76
+ const extension = specifier.split('.').pop();
77
77
 
78
78
  // TODO would like to decouple .jsx from the core, ideally
79
- if (!isBareSpecifier && ['.js', '.jsx'].includes(path.extname(specifier))) {
79
+ if (!isBareSpecifier && ['js', 'jsx'].includes(extension)) {
80
80
  const dependencyModuleURL = new URL(node.source.value, moduleURL);
81
81
 
82
82
  registerDependencies(dependencyModuleURL, definitions, nextDepth);
@@ -160,9 +160,9 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti
160
160
  }
161
161
  }
162
162
 
163
- async function renderToString(elementURL) {
163
+ async function renderToString(elementURL, wrappingEntryTag = true) {
164
164
  const definitions = [];
165
- const elementTagName = await getTagName(elementURL);
165
+ const elementTagName = wrappingEntryTag && await getTagName(elementURL);
166
166
  const isEntry = !!elementTagName;
167
167
  const elementInstance = await initializeCustomElement(elementURL, undefined, undefined, definitions, isEntry);
168
168
 
@@ -171,7 +171,7 @@ async function renderToString(elementURL) {
171
171
  : elementInstance.innerHTML;
172
172
  const elementTree = getParse(elementHtml)(elementHtml);
173
173
  const finalTree = await renderComponentRoots(elementTree, definitions);
174
- const html = elementTagName ? `
174
+ const html = wrappingEntryTag && elementTagName ? `
175
175
  <${elementTagName}>
176
176
  ${serialize(finalTree)}
177
177
  </${elementTagName}>