Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hot reload pages #2

Closed
jalevin opened this issue Oct 13, 2022 · 10 comments
Closed

hot reload pages #2

jalevin opened this issue Oct 13, 2022 · 10 comments
Labels
enhancement New feature or request

Comments

@jalevin
Copy link

jalevin commented Oct 13, 2022

First off- thank you for all of your work on this! Exactly what I needed.

My use-case is pretty simple. I'm writing a static site and trigger a reload when a markdown file changes.

When this happens, I need to reload the pages. Any ideas on how we might be able to either A) reload the plugin or B) reload the pages without restarting the whole server?

Example code:

import { resolve } from 'path';
import { createMpaPlugin } from 'vite-plugin-virtual-mpa'
import { defineConfig } from 'vite'
import { buildBlogContentTree } from './gen'
import shiki from 'shiki'
import markdownIt from 'markdown-it';



export default async () => {
  // load syntax highlighter
  const parser = await shiki.getHighlighter({
    theme: 'nord',
    langs: [
      'html', 'css', 'javascript', 'typescript',
      'go', 'ruby', 'lua',
      'vim',
    ]
  })
  // load markdown rendering engine
  let md = markdownIt({ html: true, highlight: (code, lang) => {
    return parser.codeToHtml(code, {lang})
  }})

  // build pages
  let pages = await buildBlogContentTree(md, 
    resolve(__dirname, 'content'),
    'blog.html'
  );

  return defineConfig({
    plugins: [
      //@ts-ignore
      HotReload(),
      createMpaPlugin({
        verbose: true,
        //@ts-ignore
        pages: pages,
        rewrites: [
          {
            from: /\/(.*.html)/,
            to: (ctx) => `/${!ctx.match[1] ? "index" : ctx.match[1]}.html`,
          },
        ],
      }),
    ]
  })
}

// determines if we should reload
function HotReload() {
  return {
    name: 'custom-hmr',
    enforce: 'post',

    // HMR
    handleHotUpdate({ file, server }) {
      if (shouldReload(file)) {
        // restart the server is a page has changed
        if(file.endsWith(".md")) server.restart()

        server.ws.send({
          type: 'full-reload',          
          path: '*'
        });
      }
    },
  }
}

const shouldReload = (file: string): boolean => {
  return file.endsWith('.html') || 
    file.endsWith('.js') || 
    file.endsWith('.css') ||
    file.endsWith('.md')
}
@emosheeep
Copy link
Owner

emosheeep commented Oct 13, 2022

It's a pleasure to help you! Could you please provide a reproduction for me? I’ll try to solve it in my free time as soon as I can. Or you can open a PR.

@jalevin
Copy link
Author

jalevin commented Oct 13, 2022

Here's an example PR. It's very messy and typing is broken.

It works by returning a callback that allows me to update the virtualPageMap and inputPageMap.

import { resolve } from 'path';
import { createMpaPlugin } from 'vite-plugin-virtual-mpa'
import { defineConfig } from 'vite'
import { buildBlogContentTree, Page } from './gen'
import shiki from 'shiki'
import markdownIt from 'markdown-it';


let md: markdownIt
let highlighter: shiki.Highlighter
let reloadPages: (pages: any) => void
let pages: Page[]

export default async () => {
  // load syntax highlighter
  highlighter = await shiki.getHighlighter({
    theme: 'nord',
    langs: [
      'html', 'css', 'javascript', 'typescript',
      'go', 'ruby', 'lua',
      'vim',
    ]
  })
  // load markdown rendering engine
  md = markdownIt({
    html: true, highlight: (code, lang) => {
      return highlighter.codeToHtml(code, { lang })
    }
  })

  // build pages
  pages = await buildBlogContentTree(md,
    resolve(__dirname, 'content'),
    'blog.html'
  );

  let plugin: Plugin_2
  [reloadPages, plugin] = createMpaPlugin({
    verbose: true,
    //@ts-ignore
    pages: pages,
    rewrites: [
      {
        from: /\/(.*.html)/,
        to: (ctx) => `/${!ctx.match[1] ? "index" : ctx.match[1]}.html`,
      },
    ],
  })

  return defineConfig({
    plugins: [
      //@ts-ignore
      HotReload(),
      //@ts-ignore
      plugin,
    ]
  })
}

// determines if we should reload
function HotReload() {
  return {
    name: 'custom-hmr',
    enforce: 'post',

    // HMR
    handleHotUpdate({ file, server }) {
      if (shouldReload(file)) {

        // if markdown, reload the pages and hit
        if (file.endsWith(".md")) {
          buildBlogContentTree(md, resolve(__dirname, 'content'), 'blog.html').then((p) => {
            pages = p
            reloadPages(pages)
            server.ws.send({
              type: 'full-reload',
              path: '*'
            });
          })
        } else {
          server.ws.send({
            type: 'full-reload',
            path: '*'
          });
        }
      }
    },
  }
}

const shouldReload = (file: string): boolean => {
  return file.endsWith('.html') ||
    file.endsWith('.js') ||
    file.endsWith('.css') ||
    file.endsWith('.md')
}

@emosheeep
Copy link
Owner

emosheeep commented Oct 14, 2022

I took a review just now, and I wonder if the problem you met is that the server didn't take any measures to update the pages map when there are some additional pages appear? I just thought the plugin doesn't work with your project. I need to evaluate whether there is a more general solution

@jalevin
Copy link
Author

jalevin commented Oct 14, 2022

Yeah- that's correct. The server will know that a file changed and attempt an action, however, there is no mechanism to update pages inside the plugin. My proof of concept returns a callback function that can be used to do this.

@emosheeep
Copy link
Owner

I made some changes on your branch and published vite-plugin-virtual-mpa@1.2.0-beta.0, you can have a try. The formal version will come soon.

{
  watchOptions: {
    // more api see src/util.ts
    handler(){
      // your custom logic here
    }
  }
}

@jalevin
Copy link
Author

jalevin commented Oct 25, 2022

This is a good refactor. The only problem is that now I need a callback on the handler function to tell the server to reload the page in my browser. For example:

        watchOptions: {
          handler: async (ctx) => {
            let p = await buildBlogContentTree(md, resolve(__dirname, 'content'), 'blog.html')
            ctx.server.ws.send({type: 'full-reload', path: '*'})
            return p
          },
        }

ctx.server.ws.send({type: 'full-reload', path: '*'}) should be called after pages are returned and the input maps are updated.

Also, as a novice typescript person, the way types are implemented was difficult for me to figure out the correct shape for the function with my LSP.

example:

image

@emosheeep
Copy link
Owner

ctx provide reloadPages method, you can completely implement the logic you want yourself. And I have updated the README, i'm sure you didn't read it.

By the way, the function of typings is to make some constraint to your code and show prompts when coding, not to describe the full view of API. If you want to take more info, you can click and jump into the source code.

image

@jalevin
Copy link
Author

jalevin commented Oct 25, 2022

Hi @emosheeep apologies, I am newer to typescript so it was misunderstanding and was editing my comment. I didn't realize I had saved. I have it working. Here is my full config.

This is great!

export default async () => {
  // load syntax highlighter
  const parser = await shiki.getHighlighter({
    theme: 'nord',
    langs: [
      'html', 'css', 'javascript', 'typescript',
      'go', 'ruby', 'lua',
      'vim',
    ]
  })

  // load markdown rendering engine
  let md = markdownIt({
    html: true, highlight: (code, lang) => {
      return parser.codeToHtml(code, { lang })
    }
  })

  // build pages
  let pages = await buildBlogContentTree(md,
    resolve(__dirname, 'content'),
    'blog.html'
  );

  return defineConfig({
    plugins: [
      createMpaPlugin({
        verbose: true,
        //@ts-ignore
        pages: pages,
        watchOptions: {
          handler: async (ctx) => {
            //@ts-ignore
            ctx.reloadPages(await buildBlogContentTree(md, resolve(__dirname, 'content'), 'blog.html'))
            ctx.server.ws.send({ type: 'full-reload', path: '*' })}
        }
      }),
    ]
  })
}

@jalevin
Copy link
Author

jalevin commented Oct 25, 2022

If you are open to it, I would be happy to help with documentation : )

I believe your solution resolves this issue so we can close.

@emosheeep
Copy link
Owner

emosheeep commented Oct 25, 2022

Never mind, I just saw your config, I remembered you want to update pages just when there are file added, so you should add events to your config:

createMpaPlugin({
  watchOptions: {
    events: ['add'],
    handler: () => {},
  }
})

@emosheeep emosheeep added the enhancement New feature or request label Jan 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants