In the process of developing a document templater service, I am faced with the challenge of handling numerous document templates (contracts, protocols, etc.) written in Vue. The concept revolves around clients sending props in the body, which are then passed to the Vue component (document template). Subsequently, the rendered document is sent back to the client as a response. Vue proves to be an ideal fit for this task due to its user-friendly nature and flexibility required for handling complex templates.
However, the complexity arises from the varied script parts in each type of document. This makes it impossible to simply extract the template part of the component and render it with context. Instead, I need to transpile all sections of my Vue template (script setup + TypeScript, template, and CSS) before specifying the context (props) for my component.
To achieve the transpilation process, I am experimenting with webpack (vue-loader, ts-loader) using a commonjs configuration. Utilizing ESM would require a substantial refactor of my Express app, which is currently quite large.
The issue lies in importing the bundle produced by webpack. Whenever I attempt to import it with the following code snippet:
export async function loadSSRTemplate(templateName: string) {
// Although I use commonjs, this section operates on a typescript layer that is not yet compiled
// @ts-ignore
const bundle = await import('@/ssr/dist/main.js');
console.log(bundle.default); //this always returns undefined
return bundle[templateName]; //similarly, this doesn't work as intended
}
I struggle to find a method to import my transpiled Vue components from the bundle so they can seamlessly integrate with createSSRApp and the h() method within my templater service.
While I understand that the problem may not be apparent without delving into the complete configuration and structure of Vue components, I seek guidance from those who have encountered similar scenarios in the past. Is this feasible or am I investing my efforts in vain?
PS: If there are alternative technologies that could address this challenge effectively, feel free to suggest them. Nonetheless, if possible, I prefer performing the transpilation beforehand via build processes for enhanced performance.
My tech stack: Templates: Vue 3 + TypeScript + Composition API (setup script) + Webpack 5.9
Service: Node 18 (Express) + TypeScript. Dockerized using Node:18. Execution through ts-node-dev with commonjs config
EDIT: For those interested in further details, here is my webpack configuration:
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const { VueLoaderPlugin } = require('vue-loader/dist/index');
module.exports = {
target: 'node',
mode: 'development',
entry: './webpack-entry-point.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
libraryTarget: 'commonjs2',
},
externals: nodeExternals({
allowlist: /\.css$/,
}),
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
options: { appendTsSuffixTo: [/\.vue$/] },
},
{
test: /\.vue$/,
loader: 'vue-loader',
},
],
},
resolve: {
alias: {
'@templates': path.resolve(__dirname, './templates'),
},
extensions: ['.ts', '.js', '.vue', '.json'],
},
plugins: [new VueLoaderPlugin()],
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
The entry point comprises a simple JavaScript file that imports all top-level components and exports them, for example:
import NajomnaZmluva from '@templates/reality_module/NajomnaZmluva.vue';
export { NajomnaZmluva };
Here is a glimpse of the render method implementation in my templater service:
async render(id: string, data: any): Promise<string> {
if (!process.env.SSR_DOCUMENT_TEMPLATE_PATH) {
console.error('SSR_DOCUMENT_TEMPLATE_PATH is not defined');
throw new UnknownServerError();
}
const documentTemplate = await this.retrieve(id);
if (!documentTemplate.schema) {
throw new BadRequestError(
responsesConstants.errors.badRequestError.schemaNotDefined,
);
}
this.validateSchema(
documentTemplate.schema as Record<string, schemaType>,
data,
);
//The template name will be dynamically specified; the hardcoded value here serves testing purposes only
const Template = await loadSSRTemplate('NajomnaZmluva');
//I cannot determine if the code below functions correctly since I am stuck at loading the bundle
const ssrDocument = createSSRApp({
template: h(Template, { props: data }),
});
return await renderToString(ssrDocument);
}
EDIT: Progress is being made. I will share the solution in a few hours for those eager to delve deeper into this topic.