rmarscher / virtual-module-webpack-plugin

Adds the contents of a virtual file to webpack's cached file system without writing it to disk
174 stars 17 forks source link

Any posibility to run this with watch mode? #6

Open pavelthq opened 7 years ago

pavelthq commented 7 years ago

Great job, This plugin works great, and I really like it.

I'm stuck in making this plugin works with watch mode. The use case is: 1) we get programmatically source file content and build it to complex component (multiple replacements, imports and etc) 2) we pass build string content to entry point via your plugin to webpack config 3) webpack builds this file fine and package is working fine.

Now we want to run watcher when source file is changed: this should trigger:

Any advice will be helpfull, the case is not so standard, we use multiple source files like templates then build it inside webpack file like:

import webpack from 'webpack'
import path from 'path'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import autoprefixer from 'autoprefixer'
import grab from 'ps-grab'

import VirtualModulePlugin from 'virtual-module-webpack-plugin'
import ElementCollector from './tools/webpack/elementsCollector'
import ElementsBuilder from './tools/webpack/elementsBuilder'

let config = {
  // devtool: 'eval',
  entry: {
    vendor: [
      'react',
      'react-dom',
      'classnames',
      'lodash',
      './node_modules/babel-runtime/core-js.js',
      './node_modules/babel-runtime/helpers/createClass.js',
      './node_modules/babel-runtime/helpers/inherits.js',
      './node_modules/babel-runtime/helpers/typeof.js',
      './node_modules/babel-runtime/helpers/possibleConstructorReturn.js',
      './node_modules/babel-runtime/helpers/classCallCheck.js',
      './node_modules/babel-runtime/helpers/extends.js',
      './node_modules/babel-runtime/core-js/symbol.js',
      './node_modules/babel-runtime/core-js/symbol/iterator.js',
      './node_modules/babel-runtime/core-js/object/set-prototype-of.js',
      './node_modules/babel-runtime/core-js/object/get-prototype-of.js',
      './node_modules/babel-runtime/core-js/object/define-property.js',
      './node_modules/babel-runtime/core-js/object/create.js',
      './node_modules/babel-runtime/core-js/object/assign.js',
      './node_modules/babel-runtime/core-js/object/keys.js'
    ]
  },
  output: {
    path: path.resolve(__dirname, './public/dist/'), // Assets dist path
    publicPath: '.', // Used to generate URL's
    filename: '[name].bundle.js', // Main bundle file
    chunkFilename: '[id].js'
  },
  node: {
    fs: 'empty'
  },
  module: {
    ....
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    }),
    new ExtractTextPlugin('[name].bundle.css'),
    // new webpack.optimize.UglifyJsPlugin({
    //   output: {
    //     comments: false
    //   }
    // })
    // new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()
  ]
}

let argv = process.argv

if (argv.indexOf('--quick') === -1) {
  // Get elements and push VirtualModulePlugin
  const elements = ElementCollector.getElements()
  let single = grab('--element')
  elements.forEach((item) => {
    let elementName = item.element
    if (!single || single && single === elementName) {
      let elementPath = item.path
      let template = (ElementsBuilder.generateOutput(elementPath, {
        '--uuid': elementName
      }))
      if (template) {
        config.plugins.push(
          new VirtualModulePlugin({
            moduleName: `${elementName}.js`,
            path: path.resolve(__dirname, `public/elements/${elementName}.js`),
            contents: template
          })
        )
        config.entry[ `element-${elementName}` ] = `./public/elements/${elementName}.js`
      }
    }
  })
}

The problem is that public/elements/[some-element].js is not under webpack scope (no entry points)

rmarscher commented 7 years ago

Thanks! This plugin is assuming that there probably isn't any physical file to watch - so I'm not sure we should try to add the functionality here. It should probably be an additional plugin that adds additional files to the watcher. Take a look at Webpack's bundled WatchIgnorePlugin. I think you could pretty easily copy that and reverse the functionality so the paths passed to the plugin are merged in. If you get that working, please update this issue with a link to your repository or a gist so others that might need it can find it from here. I could update the README here with a link to it and example as well.

FrancescoCioria commented 7 years ago

Hi, we're having the same issue. We use VirtualModule to generate a config file from three other files. We can easily trigger a build whenever one of the three sub-config files change, but as they're imported and used from the webpack.config, VirtualModule does not update the generated file anyway. Can you explain better the approach with WatchIgnorePlugin you were thinking of?

rmarscher commented 7 years ago

@FrancescoCioria Thanks for giving feedback. I was suggesting to create a new plugin - maybe called WatchAddPlugin - that could take a list of file paths and insert it into the watch filesystem. You could duplicate the source of the WatchIgnorePlugin, name it WatchAddPlugin and change it from removing files from the list to adding them instead. I found this conversation with the creator of Webpack who explains the watch filesystem a bit - https://github.com/webpack/webpack-dev-middleware/issues/3

But I see what you're saying. That would get webpack to rebuild on change of your additional files, but it wouldn't update the virtual module contents as those are currently only fetched when the webpack config is created.

We'd need to create some type of watch/dev mode that would have it re-fetch the contents every time within the resolvePlugin. That dev option would also pass through to the populateFilesystem function so it would allow overwriting the existing contents - currently prevented here. It would probably be required to pass in the contents as a function in that case.

Unfortunately, I probably won't have bandwidth any time soon to work on that. But I would take a pull request - especially one that adds additional test coverage for the new functionality.

Thanks.

rjgotten commented 5 years ago

You don't need to lean on file system watches as surfaced through the NodeWatchFileSystem class.

Look at how the Watching class is implemented.

Essentially, you'll want to clone it - but replace its watch() method. Said method is tapping into compiler.watchFileSystem.watch() and that's the part you'll want to replace with another mechanism for invalidating your virtual modules.

Probably by expanding the function-type signature for content with a callback that can invalidate a virtual file. E.g.

const VirtualModulePlugin = require('virtual-module-webpack-plugin');

module.exports = {
  entry: './index',
  plugins: [
    new VirtualModulePlugin({
      moduleName: './time-of-day.txt',
      contents(invalidate) {
        setTimeout(invalidate, 10e3);
        return `export default "The time is: ${Date()};"`;
      }
    })
  ]
};

Another thing you could try is to -- rather than inject files into the existing compiler.inputFileSystem which, though a nice idea, still is kind of a hack -- use the compiler.hooks.afterEnvironment hook and wrap the then set-up compiler.inputFileSystem with one that merges a virtual file system (such as memory-fs) with the regular one.

Then implement watches on your virtual file system and a means to propagate them from both the original and virtual one on the merging file system.