vuejs/vue-ssr-webpack-plugin and webpack 5

Created on 12 Oct 2020  Â·  17Comments  Â·  Source: vuejs/vue

What problem does this feature solve?

github missing repository vuejs/vue-ssr-webpack-plugin, i update webpack to version 5 and have error
[vue-server-renderer-webpack-plugin] webpack config output.libraryTarget should be "commonjs2".

What does the proposed API look like?

in my output.libraryTarget is libraryTarget: 'commonjs2'

ssr feature request

Most helpful comment

I am facing the same problem. What I found out is that it seem like in webpack 5 compiler.options.output.libraryTarget was replaced with compiler.options.output.library.type. Not sure if it's always so 'cause I didn't find any information about that in changelog. To pass the validation I had to change validation function of the loader like this:

var validate = function (compiler) {
  if (compiler.options.target !== 'node') {
    warn('webpack config `target` should be "node".');
  }
-  if (compiler.options.output && compiler.options.output.libraryTarget !== 'commonjs2') {
+  if (compiler.options.output && compiler.options.output.library.type !== 'commonjs2') {
    warn('webpack config `output.libraryTarget` should be "commonjs2".');
  }

  if (!compiler.options.externals) {
    tip(
      'It is recommended to externalize dependencies in the server build for ' +
      'better build performance.'
    );
  }
};

Also, again, in my case, entry points are now objects with name and size fields. So I had to modify stuff below:

- var entryAssets = entryInfo.assets.filter(isJS);
+ var entryAssets = entryInfo.assets.filter(file => isJS(file.name));

if (entryAssets.length > 1) {
  throw new Error(
    "Server-side bundle should have one single entry file. " +
    "Avoid using CommonsChunkPlugin in the server config."
  )
}

var entry = entryAssets[0];
- if (!entry || typeof entry !== 'string') {
+ if (!entry || typeof entry.name !== 'string') {
  throw new Error(
    ("Entry \"" + entryName + "\" not found. Did you specify the correct entry option?")
  )
}

var bundle = {
+ entry: entry.name,
  files: {},
  maps: {}
};

After these changes my bundle compiled correctly.

thanks

All 17 comments

I am facing same issue when updated to webpack 5 today. My current versions are"webpack": "5.0.0-rc.6", "vue-server-renderer": "2.6.10".

yep.... problem not quit, i use
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12"
so sad

I am facing the same problem. What I found out is that it seem like in webpack 5 compiler.options.output.libraryTarget was replaced with compiler.options.output.library.type. Not sure if it's always so 'cause I didn't find any information about that in changelog. To pass the validation I had to change validation function of the loader like this:

var validate = function (compiler) {
  if (compiler.options.target !== 'node') {
    warn('webpack config `target` should be "node".');
  }
-  if (compiler.options.output && compiler.options.output.libraryTarget !== 'commonjs2') {
+  if (compiler.options.output && compiler.options.output.library.type !== 'commonjs2') {
    warn('webpack config `output.libraryTarget` should be "commonjs2".');
  }

  if (!compiler.options.externals) {
    tip(
      'It is recommended to externalize dependencies in the server build for ' +
      'better build performance.'
    );
  }
};

Also, again, in my case, entry points are now objects with name and size fields. So I had to modify stuff below:

- var entryAssets = entryInfo.assets.filter(isJS);
+ var entryAssets = entryInfo.assets.filter(file => isJS(file.name));

if (entryAssets.length > 1) {
  throw new Error(
    "Server-side bundle should have one single entry file. " +
    "Avoid using CommonsChunkPlugin in the server config."
  )
}

var entry = entryAssets[0];
- if (!entry || typeof entry !== 'string') {
+ if (!entry || typeof entry.name !== 'string') {
  throw new Error(
    ("Entry \"" + entryName + "\" not found. Did you specify the correct entry option?")
  )
}

var bundle = {
+ entry: entry.name,
  files: {},
  maps: {}
};

After these changes my bundle compiled correctly.

Also, I think it's worth mentioning that even after my build succeeded I couldn't launch my bundle due to this error:

TypeError: Cannot read property 'file' of undefined
    at {{YOUR_PATH_TO_PROJECT}}/node_modules/vue-server-renderer/build.dev.js:9074:24

The problem was in function that starts on line 9056:

TemplateRenderer.prototype.renderScripts = function renderScripts (context) {
  var this$1 = this;

  if (this.clientManifest) {
    var initial = this.preloadFiles.filter(function (ref) {
        var file = ref.file;

        return isJS(file);
      });
    var async = (this.getUsedAsyncFiles(context) || []).filter(function (ref) {
        var file = ref.file;

        return isJS(file);
      });
-   var needed = [initial[0]].concat(async, initial.slice(1));
+   var needed = (initial.length ? [initial[0]] : []).concat(async, initial.slice(1));
    return needed.map(function (ref) {
        var file = ref.file;

      return ("<script src=\"" + (this$1.publicPath) + file + "\" defer></script>")
    }).join('')
  } else {
    return ''
  }
};

It failed 'cause for some reason initial is now empty.
So var needed = [initial[0]].concat(async, initial.slice(1)); ended up as [undefined]. I fixed this as shown above. And only after that my build launched fine.

What problem does this feature solve?

github missing repository vuejs/vue-ssr-webpack-plugin, i update webpack to version 5 and have error
[vue-server-renderer-webpack-plugin] webpack config output.libraryTarget should be "commonjs2".

What does the proposed API look like?

in my output.libraryTarget is libraryTarget: 'commonjs2'

How to solve this problem?

I am facing the same problem. What I found out is that it seem like in webpack 5 compiler.options.output.libraryTarget was replaced with compiler.options.output.library.type. Not sure if it's always so 'cause I didn't find any information about that in changelog. To pass the validation I had to change validation function of the loader like this:

var validate = function (compiler) {
  if (compiler.options.target !== 'node') {
    warn('webpack config `target` should be "node".');
  }
-  if (compiler.options.output && compiler.options.output.libraryTarget !== 'commonjs2') {
+  if (compiler.options.output && compiler.options.output.library.type !== 'commonjs2') {
    warn('webpack config `output.libraryTarget` should be "commonjs2".');
  }

  if (!compiler.options.externals) {
    tip(
      'It is recommended to externalize dependencies in the server build for ' +
      'better build performance.'
    );
  }
};

Also, again, in my case, entry points are now objects with name and size fields. So I had to modify stuff below:

- var entryAssets = entryInfo.assets.filter(isJS);
+ var entryAssets = entryInfo.assets.filter(file => isJS(file.name));

if (entryAssets.length > 1) {
  throw new Error(
    "Server-side bundle should have one single entry file. " +
    "Avoid using CommonsChunkPlugin in the server config."
  )
}

var entry = entryAssets[0];
- if (!entry || typeof entry !== 'string') {
+ if (!entry || typeof entry.name !== 'string') {
  throw new Error(
    ("Entry \"" + entryName + "\" not found. Did you specify the correct entry option?")
  )
}

var bundle = {
+ entry: entry.name,
  files: {},
  maps: {}
};

After these changes my bundle compiled correctly.

thanks

Hi guys, facing the same issue. Is an update coming soon?

Danail-Irinkov, this fix work fine. node_modules/vue-server-renderer/ search in files. BUT work only on build mode, my dev mode die. New version webpack-dev-middleware, i don't know how fix.

Sure I applied the changes in local, but will have to fork for the main server if thats the solution.
however I am still unable to render my routes....
[ { message: '[prerender-spa-plugin] Unable to prerender all routes!',
details: undefined,
stack:
'Error: [prerender-spa-plugin] Unable to prerender all routes!\n at PrerendererInstance.initialize.th
en.then.then.then.then.then.then.then.catch.err (F:\PROmo\PROmo\promo\node_modules\prerender-spa-plugin\
\es6\index.js:147:33)\n at process._tickCallback (internal/process/next_tick.js:68:7)' } ]

I am prerendering just my login and signup pages..., I tried excluding some imported elements, but still cant prerender the login page...

It used to work fine before I updated to webpack5

new PrerenderSPAPlugin({
  staticDir: path.join(__dirname, '../dist'),
  routes: ['/login', '/signup'],
  renderer: new PrerenderSPAPlugin.PuppeteerRenderer({
    renderAfterTime: 5000,
    captureAfterTime: 5000,
    maxConcurrentRoutes: 2,
  }),
  server: {
    port: 5000
  },
  minify: {
    collapseBooleanAttributes: true,
    collapseWhitespace: true,
    decodeEntities: true,
    keepClosingSlash: true,
    sortAttributes: true
  },
}),

@yyx990803 will you accept Pull Request if we fix it?

I am also running into this issue with the following dependencies

"webpack": "~5.4.0",
"vue": "~2.6.12",
"vue-router": "~3.4.7",
"vue-server-renderer": "~2.6.12",

My webpack server config contains

const config = {
  target: 'node',

  entry: './src/server/main.js',

  output: {
    filename: 'server-bundle.js',
    path: path.resolve(__dirname, 'dist'),
    libraryTarget: 'commonjs2',
  },
 ...
}

I am getting 2 errors

(1)
[vue-server-renderer-webpack-plugin] webpack configoutput.libraryTargetshould be "commonjs2".

My libraryTarget does have commonjs2.

(2)

5% emitting emit vue-server-pluginC:\MY_APP_FOLDER\node_modules\vue-server-renderer\server-plugin.js:76
      throw new Error(
      ^

Error: Entry "main" not found. Did you specify the correct entry option?
    at C:\MY_APP_FOLDER\node_modules\vue-server-renderer\server-plugin.js:76:13

I do have the main.js file at src/server/main.js as what is specified in the webpack config

@se22as checkout this comment.

(1)
[vue-server-renderer-webpack-plugin] webpack configoutput.libraryTargetshould be "commonjs2".

My libraryTarget does have commonjs2.

This problem is resolved by this code adjustment:

-  if (compiler.options.output && compiler.options.output.libraryTarget !== 'commonjs2') {
+  if (compiler.options.output && compiler.options.output.library.type !== 'commonjs2') {

(2)
Error: Entry "main" not found. Did you specify the correct entry option?
at C:\MY_APP_FOLDER\node_modules\vue-server-renderer\server-plugin.js:76:13
```

I do have the main.js file at src/server/main.js as what is specified in the webpack config

This problem is resolved by this code adjustment:

- if (!entry || typeof entry !== 'string') {
+ if (!entry || typeof entry.name !== 'string') {

P.S. You should probably check the comment I linked above to resolve all issues related to this package.

@efremenkovan thank you for your reply. Isn't the code change mentioned above involving changing code in node-modules. If that is the case, then as soon as i reinstall (or a colleague installs the deps) this code change has to be done again. If I have understood that correctly then this is not practical for an app i am about to release

@se22as yeah, you are right about it being impractical, but there is no other workaround for now. The best thing you can do in case you really want to use webpack 5 in your project - create your own git repo with fixes and use it as plugin placeholder 'till it's updated. But I am not sure if it's worth it. I guess for prod. it's still better to use webpack v4 'cause it's wide support.

@se22as @efremenkovan https://www.npmjs.com/package/patch-package is very good for this use case

Also, I think it's worth mentioning that even after my build succeeded I couldn't launch my bundle due to this error:

TypeError: Cannot read property 'file' of undefined
    at {{YOUR_PATH_TO_PROJECT}}/node_modules/vue-server-renderer/build.dev.js:9074:24

The problem was in function that starts on line 9056:

TemplateRenderer.prototype.renderScripts = function renderScripts (context) {
  var this$1 = this;

  if (this.clientManifest) {
    var initial = this.preloadFiles.filter(function (ref) {
        var file = ref.file;

        return isJS(file);
      });
    var async = (this.getUsedAsyncFiles(context) || []).filter(function (ref) {
        var file = ref.file;

        return isJS(file);
      });
-   var needed = [initial[0]].concat(async, initial.slice(1));
+   var needed = (initial.length ? [initial[0]] : []).concat(async, initial.slice(1));
    return needed.map(function (ref) {
        var file = ref.file;

      return ("<script src=\"" + (this$1.publicPath) + file + "\" defer></script>")
    }).join('')
  } else {
    return ''
  }
};

It failed 'cause for some reason initial is now empty.
So var needed = [initial[0]].concat(async, initial.slice(1)); ended up as [undefined]. I fixed this as shown above. And only after that my build launched fine.

Instead of fixing build.dev.js this way I think it's better to deal with the source of the problem which is that the initial list is not getting populated which must happen for SSR to work properly. In client-plugin:

    var initialFiles = uniq(Object.keys(stats.entrypoints)
-      .map(function (name) { return stats.entrypoints[name].assets })
+      .map(function (name) { return stats.entrypoints[name].assets.map(asset => asset.name) })
      .reduce(function (assets, all) { return all.concat(assets); }, [])
      .filter(function (file) { return isJS(file) || isCSS(file); }));

Also one other fix I needed to make in client-plugin for production SSR was:

    stats.modules.forEach(function (m) {
      // ignore modules duplicated in multiple chunks
      if (m.chunks.length === 1) {
        var cid = m.chunks[0];
        var chunk = stats.chunks.find(function (c) { return c.id === cid; });
        if (!chunk || !chunk.files) {
          return
        }
-        var id = m.identifier.replace(/\s\w+$/, ''); // remove appended hash
+        var id = m.identifier.replace(/\|\w+$/, ''); // remove appended hash
        var files = manifest.modules[hash(id)] = chunk.files.map(fileToIndex);
        // find all asset modules associated with the same chunk
        assetModules.forEach(function (m) {
          if (m.chunks.some(function (id) { return id === cid; })) {
            files.push.apply(files, m.assets.map(fileToIndex));
          }
        });
      }
    });

It looks like in webpack 5 the hash to a module identifier is appended with a | instead of a space.

i try to find a way to work well with webpack 4 and 5:
both server-plugin and client-plugin need to be compatible

image
image

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wufeng87 picture wufeng87  Â·  3Comments

6pm picture 6pm  Â·  3Comments

paulpflug picture paulpflug  Â·  3Comments

bfis picture bfis  Â·  3Comments

seemsindie picture seemsindie  Â·  3Comments