Update blog to Vue.js 3

I am trying to use Vue and Vite for my blog. To generate static pages I use ViteSSG. I migrated from Vue 2 with CLI and faced many problems.

There is a broken html import. Rollup tries to import all html and from /posts too. And rollup uses my loader. So it can't parse result as html. I don't know how to exclude all from posts. I found that Vite uses all html files as start points. And Vite does not have option to exclude or use just index.html. I sacrificed HTML posts. Now there are only markdown.

Routes broken. Because I use filenames with dot in url. See pull 2634. I renamed posts.

Rollout has limitations for imports and does not like post.md?more=true. See issue 4275. So I wrote new loader.

Problem with environments in template. For example I can't write in my post process.env.NODE_ENV. See issue 9829. To solve that I do replacement in my loader before Vite does.

content = content
          .replace(/\bimport\.meta/g, 'import.<wbr/>meta')
          .replace(/\bprocess\.env/g, 'process.<wbr/>env');

Dynamic imports and Rollup are two big problems. npm run serve doesn't use Rollup and the import work. But build brings new errors.

Now I found good combination of Vite, Vue and ViteSSG versions and I can build my blog. See my blog generator.

But I tried to update to Vite 4 and got new error. My dynamic import does not work. The post is loaded by ESM loader. And it says:

Unknown file extension ".md"

Why does program not respect plugins and my loader? I don't know. I will look for workaround.

UPDATE 2023-02-10

Workaround is removing the dynamic import.

./src/components/Post.vuefunction loadPostContent() {
    content.value = defineAsyncComponent(() => import("../../posts/" + post!.fileName + ".md"));
}

Rollup does not like it. But it somehow worked before. Now it doesn't.

So I must explicitly import my components for posts. I can do it in my virtual:posts module. I create the array of posts with meta information in this module. It means I can create a bunch of imports.

./posts_loader.jslet imports = '';
for(let post of allPosts) {
  const componentName = makeComponentName(post.slug);
  imports = imports + `
    import ${componentName} from '${post.path}';
  `;
}

Then I add the loaded components to the array. And return the array with components.

./posts_loader.jslet addComponentToPost = 'const allPosts = [];';
for(let post of allPosts) {
  const postVarName = makePostVarName(post.slug);
  addComponentToPost = addComponentToPost + `
    const ${postVarName} = ${JSON.stringify(post)};
    ${postVarName}.component = ${makeComponentName(post.slug)};
    allPosts.push(${postVarName});
  `;
}

return {
  code: `
    ${imports}
    ${addComponentToPost}
    export const posts = allPosts;
    export const tags = ${JSON.stringify(allTags)};
    export const routes = ${JSON.stringify(allRoutes)};
    `
};

As result post.component is a Vue component processed by ./post_loader.js. And I can use it in Post.vue.

./src/components/Post.vuefunction loadPostContent() {
  content.value = post.component;
}

It is possible to make lazy imports.

./posts_loader.jsfor(let post of allPosts) {
  const componentName = makeComponentName(post.slug);
  imports = imports + `
    const ${componentName} = () => import('${post.path}');
  `;
}
./src/components/Post.vuefunction loadPostContent() {
  content.value = defineAsyncComponent(post.component);
}

Now it generates a separate js file for each post.