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

Absolute path for assets in development mode, allowing correct paths when app is added to legacy website #2394

Closed
olemarius opened this issue Mar 5, 2021 · 17 comments · Fixed by #5104

Comments

@olemarius
Copy link

Is your feature request related to a problem? Please describe.
Need image paths to be absolute in order to preview vue app running on legacy website.

Legacy website example:

<!-- legacy website running on a local domain mydomain.com.localhost-->
<body>
<h1>My legacy website with vite / vue app added on top</h1>
<div id="app">
  <div>
     ...
    <!-- path is relative to localhost:3000, but the source for the app 
    is located at mydomain.com.localhost/packages/my-vite-app/index.html  -->
    <img src="/assets/image.jpg">
    ...
  </div> 
</div>
<script src="http://localhost:3000/app.ts"></script>
</body>

Describe the solution you'd like
A way to enable absolute paths when running dev so that it's possible to work and preview the vue app directly on the legacy website. Alternatively, add a how-to to the docs as I've seen people requesting something similar.

Describe alternatives you've considered
I've tried to find a way to rewrite urls via vue() vite plugin, server.proxy or rollup rewrite plugin which works for builds but not for development.

@underfin
Copy link
Member

underfin commented Mar 6, 2021

Look like the base option will help you out, doc is here https://vitejs.dev/guide/build.html#public-base-path

@olemarius
Copy link
Author

@underfin yea that works for production (I'm already using it), but I need it to work in development. The only way I can think of is to check whether it's dev or prod in the code itself, and generating the url's based on that. But I was hoping to find a less verbose approach.

package.json:

...
"dev": "vite --base=http://localhost:3000/ --force",
...

vite.config.js

export default ({ command }) =>
    defineConfig({
        resolve: {
            alias: [
                {
                    find: '@',
                    replacement: resolve(__dirname, 'src'),
                },
            ],
        },
        build: {
            target: command === 'serve' ? 'esnext' : 'es2015',
            minify: command === 'serve' ? false : 'terser',
            outDir: '../../dist/vue/sidebar-vue3/',
            emptyOutDir: true,
            rollupOptions: {
                input: {
                    sidebar: resolve(__dirname, 'index.html'),
                },
                output: {
                    entryFileNames: `assets/[name].${pkg.version}.js`,
                    chunkFileNames: `assets/[name].${pkg.version}.js`,
                    assetFileNames: `assets/[name].${pkg.version}.[ext]`,
                }
            },
        },
        plugins: [
            vue(),
            WindiCSS({
                preflight: {
                    includeBase: false,
                },
            }),
            htmlPlugin(command),
        ],
    });

Would writing a rollup plugin that prepend base to urls with enforce: pre option work? Any pointers for how to solve this would be greatly appreciated.

@olemarius
Copy link
Author

olemarius commented Mar 7, 2021

Wrote a quick plugin to test, and it worked :-)

absolutify-paths.ts

import { Plugin, } from 'vite';
import { CustomPluginOptions } from 'rollup';
export const absolutifyPaths = (options: CustomPluginOptions = {}): Plugin => {
    const { strings = [], enforce = 'pre', apply = 'serve' } = options;
    return {
        name: 'absolutify-paths',
        enforce: enforce,
        apply: apply,
        transform: (code: string, id: string) => {
            let transformedCode = code;
            strings.forEach((str) => {
                if (code.includes(str[0])) {
                    transformedCode = transformedCode.split(str[0]).join(str[1]);
                }
            });
            return {
                code: transformedCode,
                map: null
            };
        },
    };
};

vite.config.js

import { absolutifyPaths } from 'absolutify-paths';
absolutifyPaths({
    strings: [
        ['url(/src', 'url(http://localhost:3000/src/'],
        ['url(\'/src/', 'url(\'http://localhost:3000/src/'],
        ['src="/src/', 'src="http://localhost:3000/src/'],
        ['\'/src/', '\'http://localhost:3000/src/'],
    ]
}),

Update! I've updated the code above as the previous replace function didn't work on all searched strings. Also added some more examples in the strings option

@leevigraham
Copy link

Related: #1539

@daaku
Copy link
Contributor

daaku commented May 19, 2021

Based on the conversation in #1539 it seems possibly like a feature to leave out, but let me leave my $0.02 explaining 2 use cases where I'm trying to work around it:

Chrome Extensions and Electron (well, I'm using something similar not Electron per-se) apps are two examples where this would be beneficial, as there isn't another webserver running and the relative base URL may not be accurate.

Current goodness:

  • Specifying HMR settings triggers using the absolute URL, this is great.
  • The Vite Client is loading JS (and CSS) assets using the initial URL it was loaded from, this is great.

Making base work in development would fix:

  • CSS url() references.
  • Asset imports.

HTML references not working is an acceptable compromise IMHO.

FWIW, setting <base href="http://localhost:8000"> is also an acceptable workaround, though it creates other issues.

@Akryum
Copy link
Contributor

Akryum commented Jul 19, 2021

Workaround plugin:

{
  enforce: 'pre',
  apply: 'serve',
  transform: (code, id) => {
    return {
      code: code.replace(/\/src\/(.*)\.(svg|png)/, 'https://webpack.livestorm.local/src/$1.$2'),
      map: null,
    }
  },
}

@jrmyio
Copy link

jrmyio commented Jul 22, 2021

Workaround plugin:

{
  enforce: 'pre',
  apply: 'serve',
  transform: (code, id) => {
    return {
      code: code.replace(/\/src\/(.*)\.(svg|png)/, 'https://webpack.livestorm.local/src/$1.$2'),
      map: null,
    }
  },
}

Thanks @Akryum! Awesome!

Finally a solution that doesn't require me to manually patch vite.

Referencing some of the issues I came accros that can benefit from this more or less:
#2254
https://github.com/innocenzi/laravel-vite/issues/31
#2196
#1539
#2394

I ended up having to add @fs:

{
            enforce: 'pre',
            apply: 'serve',
            transform: (code, id) => {
                return {
                    code: code
                        .replace(
                            /\/src\/(.*)\.(svg|jpg|png|webp)/,
                            'http://localhost:3000/src/$1.$2'
                        )
                        .replace(
                            /\/@fs\/(.*)\.(svg|jpg|png|webp)/,
                            'http://localhost:3000/@fs/$1.$2'
                        ),
                    map: null
                };
            }
        }

Also, @daaku is right #2394 (comment), there are many non-standard web environments where you are not in control of the protocol or server that has the HTML page, thus you require all your assets to be prefixed with a base path (including in development).

@khalwat
Copy link
Contributor

khalwat commented Jul 22, 2021

I, too, would like to see this implemented, as it'd address: #2196

...which remains a point of contention for using Vite with any server-rendered CMS or hybrid setup.

@engram-design
Copy link

Would also love to see this addressed in some form. For me, #2394 (comment) works a treat for now.

@khalwat
Copy link
Contributor

khalwat commented Jul 22, 2021

Using the technique described here to create a plugin to handle this works great... if the CSS rules are in a .css file that is directly imported.

If the rules, however are in an @import, it appears the code isn't processed.

e.g.:

With this in css/app.css:

.has-background {
    background-image: url('/src/pills.jpg');
}
import '@/css/app.css';

The plugin processes it. However if instead I have this in css/app.css:

@import './pages/homepage.css';

...and this inside of css/pages/homepage.css:

.has-background {
    background-image: url('/src/pills.jpg');
}

...then the URL is not rewritten (though the CSS rule is included as expected).

Am I missing something here in terms of what I'd need to do in order to get this to work with PostCSS @import'd styles?

To clarify, I am using this plugin code:

    {
      name: 'asset-fixer',
      enforce: 'pre',
      apply: 'serve',
      transform: (code, id) => {
        return {
          code: code.replace(/\/src\/(.*)\.(svg|jp?g|png|webp)/, 'http://localhost:3000/src/$1.$2'),
          map: null,
        }
      },
    },

from @Akryum that works great otherwise (thanks for that)

@Akryum
Copy link
Contributor

Akryum commented Jul 22, 2021

Final version I'm using:

{
  enforce: 'pre',
  apply: 'serve',
  transform: (code, id) => {
    code = code.replace(/(from '|import\(')(\/src|~?@)\/(.*)\.(svg|png|mp3|mp4)/g, '$1https://webpack.livestorm.local/src/$3.$4?import=')
    code = code.replace(/(?<!local)(\/src|~?@)\/(.*)\.(svg|png|mp3|mp4)/g, 'https://webpack.livestorm.local/src/$2.$3')
    return {
      code,
      map: null,
    }
  },
}

Change local in (?<!local) to the end of the domain you are using.

@khalwat
Copy link
Contributor

khalwat commented Jul 22, 2021

ah, I have it sorted. I needed to change enforce to be post so that it runs after PostCSS has done its import thing.

Here's what is working for me:

    {
      name: 'static-asset-fixer',
      enforce: 'post',
      apply: 'serve',
      transform: (code, id) => {
        return {
          code: code.replace(/\/src\/(.*)\.(svg|jp?g|png|webp)/g, 'http://localhost:3000/src/$1.$2'),
          map: null,
        }
      },
    },

Also updated article: https://nystudio107.com/blog/using-vite-js-next-generation-frontend-tooling-with-craft-cms#vite-processed-assets

@Akryum
Copy link
Contributor

Akryum commented Jul 23, 2021

@khalwat That doesn't seem to cover all the cases for me.

@khalwat
Copy link
Contributor

khalwat commented Jul 23, 2021

@Akryum which cases is it not covering for you?

@Akryum
Copy link
Contributor

Akryum commented Jul 23, 2021

I didn't check but I tried it and it didn't work, some urls were not patched

@Akryum
Copy link
Contributor

Akryum commented Jul 23, 2021

BTW you'll want to add the g flag to the regex

@Akryum
Copy link
Contributor

Akryum commented Jul 30, 2021

Newer version of the plugin:

{
  name: 'asset-base-url',
  enforce: 'post',
  transform: (code, id) => {
    code = code.replace(/(from |import\()("|'|`)(\/src|~?@|\/@fs\/@)\/(.*?)\.(svg|png|mp3|mp4)/g, '$1$2https://webpack.livestorm.local/src/$4.$5?import=')
    code = code.replace(/(?<!local)(\/src|~?@|\/@fs\/@)\/(.*?)\.(svg|png|mp3|mp4)/g, 'https://webpack.livestorm.local/src/$2.$3')
    return {
      code,
      map: null,
    }
  },
}

@github-actions github-actions bot locked and limited conversation to collaborators Oct 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants