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

Client-side rendered template adds additional Carriage Return characters to DOM #9207

Closed
DominikSerafin opened this issue Dec 16, 2018 · 8 comments

Comments

@DominikSerafin
Copy link

Version

vue: 2.5.21
vue-server-renderer: 2.5.21
vue-template-compiler: 2.5.21
vue-loader: 15.4.2
webpack: 4.27.1
@babel/core: 7.2.0
@babel/plugin-transform-runtime: 7.2.0
@babel/preset-env: 7.2.0

(I'm not sure, but I think this problem was non-existent when I was using vue-loader@13)

Reproduction link

(Needs build step)

Steps to reproduce

  1. Write source code with markup elements that have new lines in it
  2. Build it & run it (or hot-reload in development environment)
  3. (More info in the image below)

What is expected?

Line feeds, carriage returns and whitespaces should be removed from the compiled template. Like here: https://vuejs.org/v2/guide/render-function.html#Template-Compilation

Hydration doesn't add additional carriage return (%0D) characters to rendered content.

Visually rendered content doesn't have any unnecessary spaces.

What is actually happening?

Line feeds, carriage returns and whitespaces remain in compiled code and on top of that render differently after SSR hydration.

Hydration adds additional carriage return (%0D) characters to rendered content.

Visually rendered content has unnecessary spaces after SSR hydration (but not before).

This behavior can be observed here: https://workaline.com/collection/vue (it looks fine before hydration kicks in)

Image explaining it a little bit more:

crlf

Configs:

Note: I've tried changing vue-loader compilerOptions.preserveWhitespace to different values - but that didn't changed anything.

webpack.base.config.js

/*------------------------------------*\
  Imports
\*------------------------------------*/
const NODE_ENV = process.env.NODE_ENV || 'development';
const path = require('path');
const postCSSPresetEnv = require('postcss-preset-env');


/*------------------------------------*\
  Options
\*------------------------------------*/
const browsersList = [
  '> 1%',
  'ie >= 10',
  'ie_mob >= 10',
  'ff >= 40',
  'chrome >= 40',
  'safari >= 7',
  'ios >= 7',
  'android >= 4.4',
]


/*------------------------------------*\
  Base
\*------------------------------------*/
var baseConfig = {

  mode: NODE_ENV,

  devtool: 'cheap-module-eval-source-map',

  module: {

    rules: [

      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          compilerOptions: {
            preserveWhitespace: false
          }
        }
      },

      {
        test: /\.js$/,
        include : path.join(__dirname, '/src'),
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
              compact: true,
              presets: [
                [
                  '@babel/preset-env', {
                    targets: {
                      browsers: browsersList,
                    }
                  },
                ],
              ],

            },
          },
        ],
      },

      {
        test: /\.scss$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'vue-style-loader',
          },
          {
            loader: 'css-loader',
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'compressed',
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [
                postCSSPresetEnv({
                  browsers: browsersList,
                }),
              ]
            },
          },

        ],
      },

      {
        test: /\.css$/,
        use: [
          {
            loader: 'vue-style-loader',
          },
          {
            loader: 'css-loader',
          },
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: () => [
                postCSSPresetEnv({
                  browsers: browsersList,
                }),
              ]
            },
          },

        ],
      },

      {
        test: /\.(png|jpe?g|gif|eot|svg|otf|ttf|woff|woff2)(\?\S*)?$/,
        use: [
          {
            loader: 'url-loader',
            query: {
              limit: 100000000,
            },
          },
        ],
      },

    ],

  },

};


/*------------------------------------*\
  Export
\*------------------------------------*/
module.exports = baseConfig;


webpack.client.config.js

/*------------------------------------*\
  Imports
\*------------------------------------*/
const path = require('path');
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const WebpackCleanupPlugin = require('webpack-cleanup-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const NODE_ENV = process.env.NODE_ENV || 'development';


/*------------------------------------*\
  Import Base Config
\*------------------------------------*/
var baseConfig = require('./webpack.base.config.js');


/*------------------------------------*\
  Client
\*------------------------------------*/
var clientConfig = webpackMerge(baseConfig, {

  entry: {
    app: path.join(__dirname, '/src/entry.client.js'),
  },

  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"' + NODE_ENV + '"',
        VUE_ENV: '"client"',
      }
    }),
    new VueLoaderPlugin(),
    new VueSSRClientPlugin(),
  ],


});


/*------------------------------------*\
  Development
\*------------------------------------*/
if (NODE_ENV === 'development') {

  clientConfig.output = {
    path: path.join(__dirname, '/dist'),
    publicPath: '/dist/',
    filename: '[name].[hash].js',
  };

}


/*------------------------------------*\
  Production
\*------------------------------------*/
if (NODE_ENV === 'production') {

  clientConfig.devtool = '';

  clientConfig.output = {
    path: path.join(__dirname, '/dist'),
    publicPath: '/dist/',
    filename: '[name].[hash].js',
  }

  clientConfig.plugins = (clientConfig.plugins || []).concat([

    new WebpackCleanupPlugin({
      preview: false,
    }),

    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
    }),

  ]);

}


/*------------------------------------*\
  Export
\*------------------------------------*/
module.exports = clientConfig;


webpack.server.config.js

/*------------------------------------*\
  Imports
\*------------------------------------*/
const path = require('path');
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const packageJSON = require('../../package.json');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const VueSSRServerPlugin  = require('vue-server-renderer/server-plugin');
const NODE_ENV = process.env.NODE_ENV || 'development';


/*------------------------------------*\
  Import Base Config
\*------------------------------------*/
var baseConfig = require('./webpack.base.config.js');


/*------------------------------------*\
  Server
\*------------------------------------*/
var serverConfig = webpackMerge(baseConfig, {

  target: 'node',

  externals: Object.keys(packageJSON.dependencies),

  entry: path.join(__dirname, '/src/entry.server.js'),

  devtool: 'source-map',

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

  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"' + NODE_ENV + '"',
        VUE_ENV: '"server"',
      }
    }),
    new VueLoaderPlugin(),
    new VueSSRServerPlugin(),
  ],

});


/*------------------------------------*\
  Export
\*------------------------------------*/
module.exports = serverConfig;

@DominikSerafin DominikSerafin changed the title Hydrated code renders additional Carriage Return characters Client-side rendered template adds additional Carriage Return characters to DOM Dec 16, 2018
@posva
Copy link
Member

posva commented Dec 16, 2018

Please, take the time to provide a boiled down repro when you report a bug. Closing until one is provided

@posva posva closed this as completed Dec 16, 2018
@DominikSerafin
Copy link
Author

@posva how can I provide repro with build step with SSR?

@posva
Copy link
Member

posva commented Dec 16, 2018

a boiled down repository that can showcase the problem with npm start or similar is fine, yes :)

@DominikSerafin
Copy link
Author

@posva will do that later today - thought you meant something like JSFiddle or CodeSandbox.

@posva
Copy link
Member

posva commented Dec 16, 2018

yeah, no pressure, whenever you can is fine!

@DominikSerafin
Copy link
Author

@posva, here's the minimal repro: https://github.com/DominikSerafin/repro-9207


Instructions:

You can start development with hot reload using npm run devmon

To build bundles for production use npm run build (the repo comes with pre-built bundles, though)

To run server in production mode using built bundles use npm run production


After you start it, you can check console - I've included there debug log comparing escaped innerHTML of affected element before and after hydration.

escaped-log

@posva posva reopened this Dec 17, 2018
@posva
Copy link
Member

posva commented Dec 18, 2018

Just checked the repro, it's still too big for a Vue bug 😅 Does it only happens with vue loader?
I checked uncommenting the preserveWhitespace section and it seems to yield the same result. You may be interested in #9208
Closing for now as the other issue is clearer about the problem and preserveWhitespace seems to be what you need

@posva posva closed this as completed Dec 18, 2018
@DominikSerafin
Copy link
Author

DominikSerafin commented Dec 18, 2018

Hi @posva thanks for taking time to check this.

I played with it a little bit more today, and it seems that either one of these two things fixes this problem:


  1. I was saving/comitting source files with CRLF endings. When I changed it to LF only, then this issue has fixed itself. But still - shouldn't Vue render template in the same way regardless if the source is saved with CRLF or LF endings?

  1. I took lots of this code from vuejs/vue-hackernews-2.0 repo which includes output.libraryTarget: 'commonjs2' only in the server config:

https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/webpack.server.config.js#L13

Which I similarly did myself:

https://github.com/DominikSerafin/repro-9207/blob/master/application/config/webpack.server.config.js#L28

But... today while trying to debug this, I also added this to my client config, then this issue also has been fixed (even with saved/commited CRLF endings).

I'm not very familiar with what exactly webpack setting output.libraryTarget does though, so I'm not sure if that's actually problem with Vue, Vue-Loader or something else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants