@stencil/react-output-target
Version:
React output target for @stencil/core components.
277 lines (272 loc) • 9 kB
JavaScript
import { Project as N, VariableDeclarationKind as U } from "ts-morph";
import v from "node:path";
const y = (t) => t.toLowerCase().split("-").map((o) => o.charAt(0).toUpperCase() + o.slice(1)).join(""), P = (t) => t.replace(/-([_a-z])/g, (o, a) => a.toUpperCase()), A = (t) => t.replace(/\/([a-z])/g, (o, a) => a.toUpperCase()), j = (t) => {
const o = A(t);
return P(`on-${o}`);
}, F = ({
components: t,
stencilPackageName: o,
customElementsDir: a,
defaultExport: $ = !1,
hydrateModule: l,
excludeServerSideRenderingFor: g
}) => {
const p = new N({ useInMemoryFileSystem: !0 }), r = g || [], s = l ? "" : `'use client';
`, c = `/**
* This file was automatically generated by the Stencil React Output Target.
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.${l ? `
* Do __not__ import components from this file as server side rendered components
* may not hydrate due to missing Stencil runtime. Instead, import these components through the generated 'components.ts'
* file that re-exports all components with the 'use client' directive.` : ""}
*/
`, u = `/* eslint-disable */
`, m = l ? "createComponent, createSSRComponent" : "createComponent", n = p.createSourceFile(
"component.ts",
`${s}${c}${u}
import React from 'react';
import { ${m} } from '@stencil/react-output-target/runtime';
import type { EventName, StencilReactComponent } from '@stencil/react-output-target/runtime';
`
);
for (const f of t) {
const d = f.tagName, h = y(d), E = `${h}Element`, _ = `${h}CustomEvent`;
n.addImportDeclaration({
moduleSpecifier: `${o}/${a}/${d}.js`,
namedImports: [
{
name: h,
alias: E
},
{
name: "defineCustomElement",
alias: `define${h}`
}
]
});
const I = (f.events || []).filter((e) => e.internal === !1), C = [];
for (const e of I)
if (Object.keys(e.complexType.references).length > 0) {
for (const R of Object.keys(e.complexType.references))
e.complexType.references[R].location === "global" || n.addImportDeclaration({
moduleSpecifier: o,
namedImports: [
{
name: R,
isTypeOnly: !0
}
]
});
n.addImportDeclaration({
moduleSpecifier: o,
namedImports: [
{
name: _,
isTypeOnly: !0
}
]
}), C.push({
originalName: e.name,
name: j(e.name),
type: `EventName<${_}<${e.complexType.original}>>`
});
} else
C.push({
originalName: e.name,
name: j(e.name),
type: `EventName<CustomEvent<${e.complexType.original}>>`
});
const S = `${h}Events`;
n.addTypeAlias({
name: S,
type: C.length > 0 ? `{ ${C.map((e) => `${e.name}: ${e.type}`).join(`,
`)} }` : "NonNullable<unknown>"
});
const x = `/*@__PURE__*/ createComponent<${E}, ${S}>({
tagName: '${d}',
elementClass: ${E},
// @ts-ignore - React type of Stencil Output Target may differ from the React version used in the Nuxt.js project, this can be ignored.
react: React,
events: {${C.map((e) => `${e.name}: '${e.originalName}'`).join(`,
`)}} as ${S},
defineCustomElement: define${h}
})`, D = `/*@__PURE__*/ createSSRComponent<${E}, ${S}>({
tagName: '${d}',
properties: {${f.properties.filter((e) => !!e.attribute).map((e) => `${e.name}: '${e.attribute}'`).join(`,
`)}},
hydrateModule: import('${l}')
})`, O = n.addVariableStatement({
declarationKind: U.Const,
// React as never is a hack to by-pass a @types/react issue.
declarations: [
{
name: h,
type: `StencilReactComponent<${E}, ${S}>`,
initializer: l && !r.includes(d) ? `typeof window !== 'undefined'
? ${x}
: ${D}` : x
}
]
});
$ ? n.addExportAssignment({
isExportEquals: !1,
expression: h
}) : O.setIsExported(!0);
}
return n.organizeImports(), n.formatText(), n.getFullText();
}, k = ({
components: t,
esModules: o
}) => {
const p = new N({ useInMemoryFileSystem: !0 }).createSourceFile(
"component.ts",
`'use client';
` + `/**
* This file was automatically generated by the Stencil React Output Target.
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
*/
` + `/* eslint-disable */
`
);
if (o)
for (const r of t) {
const s = r.tagName, i = y(s), c = y(r.tagName);
p.addExportDeclaration({
moduleSpecifier: `./${c}`,
namedExports: o ? [`default as ${i}`] : t.map((u) => y(u.tagName))
});
}
else
p.addExportDeclaration({
moduleSpecifier: "./components.server",
namedExports: t.map((r) => y(r.tagName))
});
return p.organizeImports(), p.formatText(), p.getFullText();
}, L = async ({
stencilPackageName: t,
components: o,
outDir: a,
customElementsDir: $,
esModules: l,
excludeComponents: g,
project: p,
hydrateModule: r,
excludeServerSideRenderingFor: s
}) => {
const i = [], c = o.filter((m) => !(m.internal === !0 || g != null && g.includes(m.tagName)));
if (c.length === 0)
return [];
const u = {};
if (l === !0)
for (const m of c) {
const n = y(m.tagName), f = v.join(a, `${n}.ts`), d = F({
components: [m],
stencilPackageName: t,
customElementsDir: $,
defaultExport: !0,
hydrateModule: r,
excludeServerSideRenderingFor: s
});
u[f] = d;
}
else {
const m = r ? (
/**
* If hydrate module is provided, we bundle all components in a single file for server side rendering.
* Further down we then create a re-export file that imports this file and exports all components with
* the 'use client' directive to ensure that the Stencil runtime is always being loaded but components
* are also being rendered server side
*/
v.join(a, "components.server.ts")
) : v.join(a, "components.ts"), n = F({
components: c,
stencilPackageName: t,
customElementsDir: $,
defaultExport: !1,
hydrateModule: r,
excludeServerSideRenderingFor: s
});
u[m] = n;
}
if (r) {
const m = v.join(a, "components.ts"), n = k({
components: c,
esModules: l
});
u[m] = n;
}
return await Promise.all(
Object.entries(u).map(async ([m, n]) => {
const f = p.createSourceFile(m, n, { overwrite: !0 });
await f.save(), i.push(f);
})
), i;
}, T = "react-output-target", q = "dist/components", w = "dist-custom-elements", b = "dist-hydrate-script", V = ({
outDir: t,
esModules: o,
stencilPackageName: a,
excludeComponents: $,
customElementsDir: l,
hydrateModule: g,
excludeServerSideRenderingFor: p
}) => {
let r = q;
return {
type: "custom",
name: T,
validate(s) {
if (l)
r = l;
else {
const i = (s.outputTargets || []).find(
(c) => c.type === w
);
if (i == null)
throw new Error(
`The '${T}' requires '${w}' output target. Add { type: '${w}' }, to the outputTargets config.`
);
if (i.dir !== void 0 && (r = i.dir), i.externalRuntime !== !1)
throw new Error(
`The '${T}' requires the '${w}' output target to have 'externalRuntime: false' set in its configuration.`
);
}
if (g && (s.outputTargets || []).find((c) => c.type === b) == null)
throw new Error(
`The '${T}' requires '${b}' output target when the 'hydrateModule' option is set. Add { type: '${b}' }, to the outputTargets config.`
);
if (!t)
throw new Error("The 'outDir' option is required.");
if (a === void 0) {
if (s.sys && s.packageJsonFilePath) {
const { name: i } = JSON.parse(s.sys.readFileSync(s.packageJsonFilePath, "utf8"));
a = i;
}
if (!a)
throw new Error(
`Unable to find the package name in the package.json file: ${s.packageJsonFilePath}. Please provide the stencilPackageName manually to the ${T} output target.`
);
}
},
async generator(s, i, c) {
const u = c.createTimeSpan(`generate ${T} started`, !0), m = c.components, n = new N(), f = await L({
outDir: t,
components: m,
stencilPackageName: a,
customElementsDir: r,
esModules: o === !0,
excludeComponents: $,
project: n,
hydrateModule: g,
excludeServerSideRenderingFor: p
});
await Promise.all(
f.map((d) => i.fs.writeFile(d.getFilePath(), d.getFullText()))
), u.finish(`generate ${T} finished`);
},
__internal_getCustomElementsDir() {
return r;
}
};
};
export {
V as reactOutputTarget
};