Snowpack: Add ability for plugins to provide virtual files

Created on 23 Sep 2020  路  12Comments  路  Source: snowpackjs/snowpack

Original Discussion: https://github.com/pikapkg/snowpack/issues/1082
/cc @aaronshaf

Background

Some plugins have a need to inject code into your site. React Fast Refresh is a good example of this, it current transforms every HTML file to add the following:

<script>
  function debounce(e,t){let u;return()=>{clearTimeout(u),u=setTimeout(e,t)}}
  const exports = {};
  ${REACT_REFRESH_CODE}
  exports.performReactRefresh = debounce(exports.performReactRefresh, 30);
  window.$RefreshRuntime$ = exports;
  window.$RefreshRuntime$.injectIntoGlobalHook(window);
  window.$RefreshReg$ = () => {};
  window.$RefreshSig$ = () => (type) => type;
</script>

This works well for us, but some users are trying to provide their own HTML for web development. This causes issues in this plugin, where React Refresh expects that setup script to have been run.

Feature Request

We can improve this story by letting the React Refresh plugin provide this code as an individual script that any external HTML file can load. This way, the plugin can just add a "

All 12 comments

I think this is a good idea

My use case is this:
I want to implement a sort of "nextjs using snowpack" where routes are defined by the files system, to do this i would need to inject a custom index.js file with custom code for the routing

The problems that could arise are:

  • what if a file with same name exists? maybe add an option to overwrite it
  • should this new file be processed by load and transform?

@remorses you can also have a look at this experimental feature to connect middleware directly into Snowpack's dev server: https://github.com/pikapkg/snowpack/blob/master/snowpack/src/types/snowpack.ts#L156-L160

Is this the same use case as covered by @rollup/plugin-virtual? If so, they seem to prevent name clashing by putting all virtual modules into a namespace prefixed by '\0virtual:'.

My use case is to have import icons from './icons/*.svg' doing a glob of that directory and using svgstore and svgo to turn it into an SVG thingy.

I already have a rollup plugin that does this.

Similar to @remorses I'm trying to build a storybook-like solution to showcase components in isolation. For that, I need to find the files with *.stories.js and import each of them in a virtual module. I found this plugin in rollup. Can I use it with snowpack?

https://github.com/rollup/plugins/tree/master/packages/virtual#usage

This would be a great feature. Building storybook-like tool too.

I wonder if *.stories.js support could be added using our current plugin system:

resolve: {
  input: [".stories.js"],
  output: [".js"],
},
load(options) {
  // TODO: Build all *.stories.js file into JS (or maybe HTML?)
}

Support for deep file extensions like 'input: [".stories.js"] was only just added yesterday in #1973, so I guess that's only possible recently. That support will go out in the next v3.0.0-rc release!

Oh interesting. Even if that works, would you have to restart the dev server after adding a new story?

My current approach (with Parcel) was to watch the filesystem and generate a js file that dynamically imports all found stories. This worked pretty well and enabled code-splitting too. However, it's annoying to keep this file around and was hoping that it could be somehow virtual.

But having some first class support from snowpack would be amazing.

(Btw, thanks for this amazing tool!)

Ah gotcha, yea that seems more related to this request: https://github.com/snowpackjs/snowpack/issues/1165

Although I hadn't considered using a virtual file to handle this, and building the file based on the state of the filesystem. That's a really interesting idea, and may actually a better solution for #1165.

Although I hadn't considered using a virtual file to handle this, and building the file based on the state of the filesystem. That's a really interesting idea, and may actually a better solution for #1165.

Yea, if I understand the importAll proposal correctly, it would just load all *.stories.js modules at the same time. However, I think having lazy load / code-splitting is really important for this use-case (something that Storybook doesn't support today and it limits its scaling).

Just to illustrate, this is my auto-generated file (an import for each story found):

import { lazy } from "react";
const foo$middle = lazy(() =>
  import("../../../src/foo.stories.tsx").then((module) => {
    return {
      default: module.Middle,
    };
  })
);
/*...*/

It turns found stories into dynamic imports wrapped by React's lazy. This file is imported by the app / navigation component and updated when chokidar detects a file-system update.

Maybe there is some better approach altogether.

Yea, that's good feedback, and I think Option 1 or 3 is a better choice for that reason. it's been a while since I've commented in that thread, i'll give an update now.

Was this page helpful?
0 / 5 - 0 ratings