question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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

See original GitHub issue

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;

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
posvacommented, Dec 16, 2018

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

0reactions
DominikSerafincommented, 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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

python - Additional carriage return created when writing mako ...
I am using the mako template library to generate ...
Read more >
HTML and CSS Tutorial - Nanyang Technological University
Basic HTML and CSS Tutorial for the beginners.
Read more >
<input type="text"> - HTML: HyperText Markup Language | MDN
In addition to the attributes that operate on all <input> elements regardless ... The text must not include carriage returns or line feeds....
Read more >
13.2 Parsing HTML documents - HTML Standard
The rules for parsing XML documents into DOM trees are covered by the ... Some algorithms feed the parser by directly adding characters...
Read more >
DOM Enlightenment - Exploring the relationship between ...
Cody Lindley is a client-side engineer (aka front-end developer) and recovering ... (e.g. text characters in an html document including carriage returns and ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found