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

feat(compiler-core): whitespace handling strategy #1600

Closed
wants to merge 1 commit into from

Conversation

CodeDaraW
Copy link
Contributor

In some cases, we may want to preserve whitespaces in the HTML template, but vue compiler only provides condense strategy currently.
This PR will make compiler support whitespace handling strategy like v2.

@Justineo
Copy link
Member

Actually I’ve been thinking maybe we should have a jsx mode which completely align with JSX, so that white spaces handling can be consistent across projects where template and JSX are mixed. In v2 the condense mode still have trivial differences with JSX. WDYT? @yyx990803

@yyx990803
Copy link
Member

yyx990803 commented Jul 16, 2020

What's the actual use for this?

@Justineo can you summarize the differences between condense mode and JSX whitespace handling?

@Justineo
Copy link
Member

Justineo commented Jul 16, 2020

vuejs/vue#9208 (comment)

condense mode is “not entirely removing leading/ending whitespaces inside an element”. It seems it’s the only difference.

@CodeDaraW
Copy link
Contributor Author

In my project, I used the parser of vue compiler to do some template conversion work, but found that the whitespaces and newlines were ignored in the final AST. So I hope the compiler can provide this strategy configuration item.

@Justineo
Copy link
Member

@CodeDaraW I think that's a valid use case and I was bringing up a related issue.

@yyx990803
Copy link
Member

In my project, I used the parser of vue compiler to do some template conversion work

Can you be more specific? Are you converting legacy HTML to Vue templates or doing transforms on Vue templates? Why would you need the whitespace to be preserved?

@ZhangJian-3ti
Copy link
Contributor

The ignored white space doesn't affect the output for browser at all. It's kinda like an optimization without any side effects.

@CyberAP
Copy link
Contributor

CyberAP commented Jul 19, 2020

I've actually been struggling with whitespace issues as well. It's not expected that mustache syntax preserves whitespace, while elements do not when passed to a slot.

<SlottedComponent>
  {{ 'this will preserve whitespace (including spaces and newline after component tag)' }}
</SlottedComponent>
<SlottedComponent>
  <span>This will ignore whitespace</span>
</SlottedComponent>

@CodeDaraW
Copy link
Contributor Author

CodeDaraW commented Aug 27, 2020

In my project, I used the parser of vue compiler to do some template conversion work

Can you be more specific? Are you converting legacy HTML to Vue templates or doing transforms on Vue templates? Why would you need the whitespace to be preserved?

Sorry to reply now, I've been busy these days.

Actually this may be more related to HTML, the situation is more complicated and I will try to make it simple:

Given a HTML fragment:

<div id="1">
    <div id="2"></div>
    <div id="3"></div>
    <div id="4"></div>
</div>

With the parser I can get an AST, do tree traversal to mark the tree nodes, and then make changes to the source HTML. For example, I want to remove all the nodes between div#2 and div#4(including spaces and lines) with the help of node location info. Since the new line nodes are ignored, I can only remove the div#3 and remain the new line nodes like:

<div id="1">
    <div id="2"></div>

    <div id="4"></div>
</div>

If the new line nodes info is provided, the new line nodes can be identified and removed(or remained up to me):

<div id="1">
    <div id="2"></div><div id="4"></div>
</div>

I'm not sure if I have clearly described the problem, and if it goes against the original intention of the design or brings other effects.

@antoniandre
Copy link

antoniandre commented Sep 29, 2020

@yyx990803,

In my project, I used the parser of vue compiler to do some template conversion work

Can you be more specific? Are you converting legacy HTML to Vue templates or doing transforms on Vue templates? Why would you need the whitespace to be preserved?

In my case, I need whitespaces to be preserved to display a snippet of code inside a <pre> tag.
I use this in all my libraries documentations.
I also have a syntax highlighter library that takes user input snippet of code as a slot (a string with whitespaces) and renders a syntax highlighted version of the snippet in a <pre> tag, preserving the whitespaces the user may have put.

This is how it renders at the moment in Vue 3.
image

Hope it makes sense.

@nfplee
Copy link

nfplee commented Oct 5, 2020

Any news on when this? This causes a problem for me as I render my code on the server (for SEO purposes) and then parse it with Vue which then changes the white-space and causes multiple issues. I'm going to hold out from upgrading until this issue is resolved.

@antoniandre
Copy link

Do we have a timeframe for this feature to be released?
Thanks!

@nfplee
Copy link

nfplee commented Oct 23, 2020

I already commented on a closed issue but just so it’s not ignored, please see my post over at stack overflow https://stackoverflow.com/questions/64432182/vue-3-removes-white-space-between-inline-block-elements which shows how this is a breaking change from v2 and how it affects the rendered output contrary to what’s been mentioned above.

@martinbean
Copy link

martinbean commented Nov 4, 2020

What’s the deal with this? Does Vue really remove whitespace between inline elements, and there’s no way to configure that behaviour? As I’m using Vue 3 with Laravel, and it’s kinda breaking web pages.

Browsers have rendered whitespace between inline elements for literally decades. So what’s the case for changing that behaviour?

Example screenshot:

Screenshot 2020-11-04 at 12 59 11

You can see there’s no whitespace around the “or” text.

@martinbean
Copy link

Any one…?

@martinbean
Copy link

Tumbleweed

Could we get a response? People have been asking about this for literally months now…

@nfplee
Copy link

nfplee commented Nov 26, 2020

@martinbean same. I ended up applying this pull request to my local build just so I could continue to use Vue 3.

@CyberAP
Copy link
Contributor

CyberAP commented Nov 26, 2020

In the meantime, you can inject whitespace with this expression: {{ ' ' }} . Yes, it's not pretty but it works.

@martinbean
Copy link

@CyberAP My issue is, if I mount a Vue component on a wrapper div (usually <div id="app"> in Laravel apps, it pretty much removes inline whitespace for all elements in my website, as per the screenshot I added in an earlier message.

This changing of browser’s default whitespace handling is just unacceptable in my opinion.

@abalashov
Copy link

Painful. This has really broken an application I attempted to upgrade to Vue 3.

@martinbean
Copy link

@abalashov Yup. This makes Vue 3 completely unusable for me.

@nfplee
Copy link

nfplee commented Dec 16, 2020

@abalashov and @martinbean I agree, Vue 3 is unusable without this fix.

@martinbean
Copy link

@yyx990803 Can we please get an official response from someone on this issue? Vue should not be usurping browser default behaviour of inline element spacing.

@abalashov
Copy link

@martinbean Agreed. I don't mean to be insolent or seem entitled - it's open-source - but it's completely broken a UIkit-styled application. All the buttons are smashed together, as are some other kinds of UI widgets. If it were a question of changed defaults, I could change the behaviour, but if there's no support for the necessary behaviour anymore, that's a serious problem.

@Justineo
Copy link
Member

See https://codesandbox.io/s/jolly-poincare-ibywi?file=/src/App.vue

Whitespace-only text nodes will be removed only when they include line feeds. If something is treated as inline content, just put them in the same line and it should just work.

/cc @vuejs/docs I think we should put this into the docs so that people won't get confused.

@CodeDaraW
Copy link
Contributor Author

@HcySunYang I've fixed the conflict.

@HcySunYang
Copy link
Member

@CodeDaraW Thanks~

yyx990803 added a commit that referenced this pull request Apr 26, 2021
- discard leading/ending whitespace inside an element
- condense preserved whitesapce into single space
@hawkeye64
Copy link

@yyx990803 Thanks for moving this along. Do we have a timeframe of when v3.1.0 will be released?

@yyx990803
Copy link
Member

This PR has been cherry picked into working branch via dee3d6a. Thanks!

@yyx990803 yyx990803 closed this May 7, 2021
3.2.0 automation moved this from In progress to Done May 7, 2021
@CodeDaraW CodeDaraW deleted the whitespace-strategy branch May 8, 2021 09:35
@antoniandre
Copy link

antoniandre commented May 25, 2021

@yyx990803,
It's really awesome to see this PR merged in! 🎉
But I still can't figure out a way to preserve whitespaces using vue-cli 4.

I've tried to add this in the vue.config.js, like in Vue 2 (vuejs/vue#10485 (comment)):

chainWebpack: config => {
   config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options => {
        options.compilerOptions.whitespace = 'preserve'
        return options
      })
}

but options.compilerOptions is not available.

image

Looking at this file dee3d6a#diff-4c790a7767e4960284e4847ad5795feb31ca94d74a138e81a90b7c044e20df6cR68, I also thought I could possibly add it in the createApp params, but it seems I can't even get the delimiters to work:

createApp({
  render: () => h(App),
  delimiters: ['${', '}'],
  whitespace: 'preserve'
})

Anyone else made it work? vuejs/vue-cli#1020 (comment)

Reproducible on this simple library: https://github.com/antoniandre/simple-syntax-highlighter/blob/next/vue.config.js
(on next branch for Vue 3)

@areve
Copy link

areve commented May 26, 2021

I have it working on all components in my app, my vite.config.js says

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          whitespace: "preserve",
        } as unknown,
      },
    }),
  ],
});

(using unknown is unattractive - not sure about that)

and my package.json has dev dependency "@vue/compiler-sfc": "3.1.0-beta.3", and dependency "vue": "3.1.0-beta.3", - I'm not sure if both are required.

@antoniandre
Copy link

@areve, it's great to see you got it working, thanks for sharing your config! 👍

I still can't make it work with slots though.
I've created a reproduction on CodeSandbox where I demo exactly the problem: https://codesandbox.io/s/interesting-cookies-dy7wn?file=/src/App.vue

The whitespaces (new lines in particular), are preserved when inside a pre tag, but not in a custom component.

image

I've placed this in the vue.config.js but it doesn't help:

export default {
  publicPath: 'truc',
  chainWebpack: (config) => {
    config.module
      .rule("vue")
      .use("vue-loader")
      .loader("vue-loader")
      .tap(options => {
        // https://github.com/vuejs/vue-next/pull/1600
        options.compilerOptions.whitespace = "preserve";
        return options
      })
  }
}

@areve
Copy link

areve commented May 27, 2021

I had a play with your CodeSandbox and I agree there does appear to be a problem here. The whitespace in slots is not preserved as I would expect. I suggest this is an oversight and should be logged as a new bug. The lack of documentation is also an issue, I'm not sure how/who/when this may get changed.

@martinbean
Copy link

Has any one gotten this working with Laravel Mix yet? No matter how I try and pass these options, whitespace still gets removed from inline elements when mounting view to a containing element, like <div id="app">.

@thecrypticace
Copy link
Contributor

@martinbean This should work for SFCs:

mix.vue({
  options: {
    compilerOptions: {
      whitespace: "preserve",
    },
  },
})

But you'll also need to configure the runtime compiler if you're using it:

app.config.compilerOptions.whitespace = "preserve"

@martinbean
Copy link

martinbean commented Jun 18, 2021

@thecrypticace Thanks. I have no idea what I’m doing wrong, but I just can’t get Vue 3 to work, at all. I just seem to encounter issue after issue.

This is what I have as a reduced test case:

<!-- resources/views/welcome.blade.php -->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
        <script defer src="{{ asset('js/app.js') }}"></script>
    </head>
    <body>
        <div id="app">
            <div class="text-center">
                <a class="btn btn-primary" href="https://google.com/" rel="external">Google</a>
                <a class="btn btn-primary" href="https://bing.com/" rel="external">Bing</a>
            </div>
            <div class="text-center">
                <test-component></test-component>
            </div>
        </div>
    </body>
</html>
// resources/js/app.js
import { createApp } from 'vue';

const app = createApp();

app.component('test-component', () => import('./components/TestComponent.vue'));

app.mount('#app');
// webpack.mix.js
const mix = require('laravel-mix');

mix
    .js('resources/js/app.js', 'public/js')
    .vue({
        options: {
            compilerOptions: {
                whitespace: 'preserve'
            }
        },
        version: 3
    });
<!-- resources/js/components/TestComponent.vue -->
<template>
    <div>
        <a class="btn btn-secondary" href="https://yahoo.com/">Yahoo!</a>
        <a class="btn btn-secondary" href="https://duckduckgo.com/">DuckDuckGo</a>
    </div>
</template>

Vue 3 just refuses to work. I get this in the console:

Uncaught TypeError: Cannot read property 'render' of undefined
    at Object.app.mount (app.js:15097)
    at app.js:16217
    at app.js:16218
    at app.js:16220

It seems to be pointed at an issue with my .mount call, but I’m mounting to an element with an ID of app, and I have a <div id="app"> in my view. I’ve tried including the app.js file with defer, moving the <script> tag to the end of the body, and I just get the same.

What’s the issue here? I’m just a little frustrated that I can’t seem to get past the first hurdle of just rending a single test component, let alone building anything with Vue 3 😢 And I so dearly want to use that lovely TypeScript goodness and Composition API!

Installed versions:

  • laravel-mix@6.0.23
  • vue@3.1.1
  • vue-loader@16.2.0

@martinbean
Copy link

OK, got Vue compiling, but I’m afraid this issue is still persisting. Take a look at the screenshot below.

The whitespace is honoured in the Vue single-file component itself, but any whitespace in my <div id="app"> is collapsed. So “Button One” and “Button Two” are butting up against each other even though there’s whitespace in the markup:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
        <script defer src="{{ asset('js/app.js') }}"></script>
    </head>
    <body>
        <div id="app">
            <div class="text-center">
                <a class="btn btn-primary" href="#">Button One</a>
                <a class="btn btn-primary" href="#">Button Two</a>
            </div>
            <test-component></test-component>
        </div>
    </body>
</html>

It’s definitely Vue, because when I force-refresh the page I can see the whitespace until the JavaScript is parsed and then it’s collapsed.

Why is Vue doing this? It’s a fundamental browser behaviour Vue is overriding here.

Screenshot 2021-06-19 at 14 38 06

@hawkeye64
Copy link

I found an alternative to this by deep-diving Vue compiler options.

simply this:

const oldPreTagFunc = conf.build.vueLoaderOptions.compilerOptions.isPreTag
vueLoaderOptions.compilerOptions.isPreTag = (tag) => tag === 'pre'
  || tag === 'q-markdown'
  || (typeof oldPreTagFunc === 'function' ? oldPreTagFunc(tag) : false)

Where my component that needs the whitespace handling starts with q-markdown.

@aztack
Copy link

aztack commented Jul 27, 2021

@areve, it's great to see you got it working, thanks for sharing your config! 👍

I still can't make it work with slots though.
I've created a reproduction on CodeSandbox where I demo exactly the problem: https://codesandbox.io/s/interesting-cookies-dy7wn?file=/src/App.vue

The whitespaces (new lines in particular), are preserved when inside a pre tag, but not in a custom component.

image

I've placed this in the vue.config.js but it doesn't help:

export default {
  publicPath: 'truc',
  chainWebpack: (config) => {
    config.module
      .rule("vue")
      .use("vue-loader")
      .loader("vue-loader")
      .tap(options => {
        // https://github.com/vuejs/vue-next/pull/1600
        options.compilerOptions.whitespace = "preserve";
        return options
      })
  }
}

try remove .loader("vue-loader")

@ewdieckman
Copy link

For those using vue-cli, the correct syntax for vue.config.js is as follows:

chainWebpack: config => {
        config.module
            .rule('vue')
            .use('vue-loader')
            .tap(options => {
                // https://github.com/vuejs/vue-next/pull/1600
                options.compilerOptions = {
                    whitespace: 'preserve'
                };
                return options
            })
    }

@duydb
Copy link

duydb commented Feb 11, 2022

For those using vue-cli, the correct syntax for vue.config.js is as follows:

chainWebpack: config => {
        config.module
            .rule('vue')
            .use('vue-loader')
            .tap(options => {
                // https://github.com/vuejs/vue-next/pull/1600
                options.compilerOptions = {
                    whitespace: 'preserve'
                };
                return options
            })
    }

It works for me. Thank you!

@yanghoxom
Copy link

yanghoxom commented Sep 22, 2023

fix this issue by compilerOptions.whitespace: 'preserve' will conflict class and style merging in vue 3 #9285

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. need guidance The approach/solution in the PR is unclear and requires guidance from maintainer to proceed further. ready to merge The PR is ready to be merged. scope: compiler
Projects
No open projects
3.2.0
Done
Development

Successfully merging this pull request may close these issues.

None yet