In the browser, run Vite!

Posted by bmyster on Mon, 10 Jan 2022 08:22:50 +0100

First official account Front end from advanced to admission , welcome to pay attention.

Hello, I'm ssh. I saw Francois Valdy announce that he made it when I was pushing surfing the other day browser-vite , Vite was successfully run in the browser. This aroused my interest. How to run a Vite heavily dependent on node on the browser? Next, let's explore and uncover the secret with me.

In short, the principle of

  • Service Worker : HTTP server used to replace Vite.

  • Web Worker : run browser vite to handle the main thread.

  • The file system is replaced by an in memory emulated file system.

  • Convert the import of special extensions (. Ts,. TSX,. SCSS...).

Challenges encountered

No real file system

Vite A lot of work has been done with the file system. Reading project files, monitoring file changes, globs processing, etc... these are difficult to implement in the memory file system simulated by the browser, so browser vite deletes monitoring, globs and configuration files to reduce the complexity.

The project files are saved in the in memory file system, so Broswer vite and vite plugins can handle them normally.

No "node_modules"

Vite dependent node_modules to resolve dependencies. They'll be turned off at startup Dependent pre bundling To optimize.

Also, in order to reduce complexity, Broswer Vite carefully deleted node from Vite_ Modules parsing and dependency prepackaging.

Therefore, users using browser vite need to create one Vite plugin To resolve the raw module import.

Regular expression "last line assertion"

Some of the code in Vite uses Post assertion . On node JS, but Safari doesn't support it.

So the author rewrites these rules.

Hot renewal (HMR)

Vite used it WebSockets To synchronize code changes between the server (node) and the client (browser).

In browser vite, the server is ServiceWorker + Vite worker, and the client is iframe. Therefore, the author switches WebSockets to use post message for iframe.

How to use

As of the time of writing this article, this tool has not been used out of the box. If you want to use it, you need to read a lot of Vite internal processing details.

If you are interested, you can stay focused browser-vite's README To get the latest usage.

install

Install the browser vite NPM package.

$ npm install --save browser-vite

perhaps

$ npm install --save vite@npm:browser-vite

To rewrite the import of "vite" to "browser vite"

Iframe - browser vite window

You need an iframe to display the internal pages provided by browser vite.

Service Worker - Web server in browser

The Service Worker captures a specific url request from the iframe.

One use workbox Examples:

workbox.routing.registerRoute(
  /^https?:\/\/HOST/BASE_URL\/(\/.*)$/,
  async ({
    request,
    params,
    url,
  }: import('workbox-routing/types/RouteHandler').RouteHandlerCallbackContext): Promise<Response> => {
    const req = request?.url || url.toString();
    const [pathname] = params as string[];
    // send the request to vite worker
    const response = await postToViteWorker(pathname)
    return response;
  }
);

In most cases, the message sent to "Vite Worker" is postMessage and broadcast-channel.

Vite Worker - process request

Vite Worker is a Web Worker that processes requests captured by service workers.

Example of creating a Vite server:

import {
  transformWithEsbuild,
  ModuleGraph,
  transformRequest,
  createPluginContainer,
  createDevHtmlTransformFn,
  resolveConfig,
  generateCodeFrame,
  ssrTransform,
  ssrLoadModule,
  ViteDevServer,
  PluginOption
} from 'vite';

export async function createServer = async () => {
  const config = await resolveConfig(
    {
      plugins: [
        // virtual plugin to provide vite client/env special entries (see below)
        viteClientPlugin,
        // virtual plugin to resolve NPM dependencies, e.g. using unpkg, skypack or another provider (browser-vite only handles project files)
        nodeResolvePlugin,
        // add vite plugins you need here (e.g. vue, react, astro ...)
      ]
      base: BASE_URL, // as hooked in service worker
      // not really used, but needs to be defined to enable dep optimizations
      cacheDir: 'browser',
      root: VFS_ROOT,
      // any other configuration (e.g. resolve alias)
    },
    'serve'
  );
  const plugins = config.plugins;
  const pluginContainer = await createPluginContainer(config);
  const moduleGraph = new ModuleGraph((url) => pluginContainer.resolveId(url));

  const watcher: any = {
    on(what: string, cb: any) {
      return watcher;
    },
    add() {},
  };
  const server: ViteDevServer = {
    config,
    pluginContainer,
    moduleGraph,
    transformWithEsbuild,
    transformRequest(url, options) {
      return transformRequest(url, server, options);
    },
    ssrTransform,
    printUrls() {},
    _globImporters: {},
    ws: {
      send(data) {
        // send HMR data to vite client in iframe however you want (post/broadcast-channel ...)
      },
      async close() {},
      on() {},
      off() {},
    },
    watcher,
    async ssrLoadModule(url) {
      return ssrLoadModule(url, server, loadModule);
    },
    ssrFixStacktrace() {},
    async close() {},
    async restart() {},
    _optimizeDepsMetadata: null,
    _isRunningOptimizer: false,
    _ssrExternals: [],
    _restartPromise: null,
    _forceOptimizeOnRestart: false,
    _pendingRequests: new Map(),
  };

  server.transformIndexHtml = createDevHtmlTransformFn(server);

  // apply server configuration hooks from plugins
  const postHooks: ((() => void) | void)[] = [];
  for (const plugin of plugins) {
    if (plugin.configureServer) {
      postHooks.push(await plugin.configureServer(server));
    }
  }

  // run post config hooks
  // This is applied before the html middleware so that user middleware can
  // serve custom content instead of index.html.
  postHooks.forEach((fn) => fn && fn());

  await pluginContainer.buildStart({});
  await runOptimize(server);
  
  return server;
}

Pseudocode for processing requests through browser vite:

import {
  transformRequest,
  isCSSRequest,
  isDirectCSSRequest,
  injectQuery,
  removeImportQuery,
  unwrapId,
  handleFileAddUnlink,
  handleHMRUpdate,
} from 'vite/dist/browser';

...

async (req) => {
  let { url, accept } = req
  const html = accept?.includes('text/html');
  // strip ?import
  url = removeImportQuery(url);
  // Strip valid id prefix. This is prepended to resolved Ids that are
  // not valid browser import specifiers by the importAnalysis plugin.
  url = unwrapId(url);
  // for CSS, we need to differentiate between normal CSS requests and
  // imports
  if (isCSSRequest(url) && accept?.includes('text/css')) {
    url = injectQuery(url, 'direct');
  }
  let path: string | undefined = url;
  try {
    let code;
    path = url.slice(1);
    if (html) {
      code = await server.transformIndexHtml(`/${path}`, fs.readFileSync(path,'utf8'));
    } else {
      const ret = await transformRequest(url, server, { html });
      code = ret?.code;
    }
    // Return code reponse
  } catch (err: any) {
    // Return error response
  }
}

see Vite internal middleware source code For more details.

How does it compare to Stackblitz WebContainers

"WebContainers" : run node in the browser js

Stackblitz's WebContainers can also run Vite in the browser. You can go to Vite gracefully New has a working environment.

The author said that he is not an expert in WebContainers, but in short, browser Vite simulates FS and HTTPS servers at the Vite level, and WebContainers at the node JS level simulates FS and many other things, and Vite can run on it with only some additional modifications.

It can put node_modules are stored in the browser's WebContainer. But it won't run npm or yarn directly, probably because it takes up too much space. They link these commands to Turbo ————Their package manager.

WebContainers can also run other frameworks, such as Remix,SvelteKit or Astro.

It's amazing ✨ This is exciting đŸ¤¯ The author shows great respect to the team of WebContainer. The Stackblitz team is awesome!

One disadvantage of WebContainers is that it currently Can only run on Chrome , but it may happen soon Run on Firefox . Browser vite is currently available for Chrome, Firefox and Safari browsers.

In short, WebContainers run Vite at a lower level of abstraction. Browser Vite runs at a higher level of abstraction, very close to Vite itself.

For example, for those retro gamers, browser vite is a bit like UltraHLE (Nintendo N64 simulator) 🕹ī¸đŸ˜Š

(*) gametechwiki.com: high / low level simulator

The author's next plan

Browser vite is the core of the solution planned by the author. It is intended to gradually extend to their full range of products:

  • Backlight.dev
  • Components.studio
  • WebComponents.dev
  • Replic.dev (new app to be released)

Looking forward to the future, the author will continue to invest in browser vite and report to the upstream. Last month they also announced Sponsor Evan You and Patak to support Vite To support this amazing project.

Want to know more?

reference material

Topics: Javascript node.js Firefox github npm