Dynamic publicPath When Rendering Pages with Vue SSR - vue.js

We're happy users of Vue and its server-side rendering module, Vue SSR. One of the requirements of my project is that we be able to dynamically adjust Webpack's publicPath at runtime, so that we can obtain assets from our different CDNs (we have two, one for test and one for prod).
We are able to accomplish this easily on the client-side using the __webpack_public_path__ free variable, and you can also override the publicPath within the SSR client manifest for asset URLs injected into the <head>.
Yet we continue to have issues with asset URLs that are housed directly within our templates and are rendered by SSR. For example, imagine if we had the following image within our tag:
<img src="~#/test.png" />
Our goal is that, on both the server and the client, we could adjust that URL to be prefixed how we please via publicPath. There doesn't seem to be a way to dynamically update the publicPath once the vue-ssr-server-manifest.json has been generated, the resulting URL ends up being something relative like /static/test.png or whatever we original cited in our Webpack config.
Per our project constraints, it's not possible to rebuild our SSR bundle, so we need to do this at runtime. We've tried adding placeholder values as our publicPath, e.g. __CUSTOM_WEBPACK_PUBLIC_PATH__, and replacing them in the server bundle at runtime, but that ends up being ineffective since publicPath is also embedded in other Webpack generated files.
Wondering if there is a cleaner way to achieve what we need directly via Vue SSR, or if this is something we just can't configure at runtime. Thanks for reading!

Late follow-up here, but we eventually solved this issue.
We found that setting __webpack_public_path__ globally in our Node.js process did result in the correct public path being applied to our assets in our server bundle.
Once that global is present both on the window (e.g. client-side), and globally in the node process (e.g. server-side), things started working as we wanted.

We faced similar type of problems in our webapp as well. BTW, we implemented a CDN plugin for vue.
export const CDNPlugin = {
install(Vue, { CDN, assetsManifest }) {
Vue.prototype.$cdn = {
...CDN,
asset(name) {
return `${CDN.baseUrl}${assetsManifest[name]}`;
},
resource(filepath) {
return `${CDN.baseUrl}/resources/${filepath}`;
}
};
}
};
Install this plugin both of your ssr and csr file.
Vue.use(CDNPlugin, {
CDN: { baseUrl: 'https://my.static.cdn.com' },
assetsManifest: yourAssetManifestObject,
});
And the usage of this CDN plugin inside vue template is as below
<img :src="$cdn.asset('relative/path/to/asset/style.css')">
If you think it is helping a bit, then I can share more regarding our implementation.

I spent an entire day trying to figure this out. In the end I used this:
https://github.com/agoldis/webpack-require-from
Worked like a charm, client side and server. Be aware you need to set a global.MY_BASE_URL in your node/server somewhere AND you need to inject a window.MY_BASE_URL somewhere in your HTML. Then just configure webpack.
plugins.push(new WebpackRequireFrom({variableName: 'MY_BASE_URL'}));

Similar problem occurred in my project, and finally I worked it out.
Ryan's answer really helps, but there is one thing I want to clear up. __webpack_public_path__ is a LOCAL variable in webpack bundled code, which means __webpack_public_path__ and global.__webpack_public_path__ is not the same. You need to do something like
__webpack_public_path__ = process.env.ASSET_PATH;
to specify public path (https://webpack.js.org/guides/public-path/#on-the-fly FYI).
Last, please make sure your process.env.ASSET_PATH is not undefined, maybe you have to set it manually to global in your server code.
global.process.env.ASSET_PATH = process.env.ASSET_PATH;

Related

Nuxt choosing layout before initialize

I use nuxt-generate and nuxt/device package. which provides the device type but for the layout since I use nuxt-generate at first load app uses default layout. not the one provided by nuxt/device. but after i go another page it starts using correct layout. so the problem is how can i have the app use layout at first initialize. or at least change it after initialize. is there any way to do these?
this is how i try to choose layout but never works at first open.
layout (context) {
if(context.isMobile) {
return 'mobile'
} else if (context.isDesktop) {
return 'default'
}
},
when you use nuxt-generate nuxt generates a series of static html files, I don't think there is an easy way to solve this, but there can be a series of workarounds:
use nuxt in ssr mode, so that you have a way of knowing who is calling that particular resource
make a distinction at the level of routes like (/desktop/*** and /mobile/***) where both routes lead to a different layout and in the webserver configuration do a redirect on the correct route
do a middleware that redirects to the correct route /desktop /mobile, but I don't recommend it because you lose the advantage of static generation of nuxt

Vue template render without all the extra

I have a very, very simple set of Vue components that all work. These are a simple addition on top of an existing C# app.
At the moment, these are .html files (brought into the app inside <script> tags) and .js files loaded by reference.
These all work, are very light weight, and I love it.
Now I want to compile the HTML for each component into a Vue render function, and the .js into one minified .js file.
The .js to .min.js is pretty standard, but I cannot figure out how to get the HTML to compile into the Vue render function. Everything I've found involves a LOT of overhead and running a web server which seems a massive overkill for an html->script transform, and I don't need a full web application spun up. I only need my existing, simple templates transformed to something more friendly for production than my long-form html templates getting dumped to the page.
I'm not entirely opposed to turning the .html+.js into .vue files, but just doing that doesn't seem to overcome my issue.
I cannot figure out how to get the HTML to compile into the Vue render function
To generate a render function from a Vue template string (e.g., read from an HTML file), you could use vue-template-compiler like this:
const compiler = require('vue-template-compiler')
const output = compiler.compile('<div>{{msg}}</div>')
console.log(output) // => { render: "with(this){return _c('div',[_v(_s(msg))])}" }
Everything I've found involves a LOT of overhead and running a web server which seems a massive overkill for an html->script transform
The "web server" you mention is provided by Webpack CLI, and is intended to faciliate development. You don't need to use the dev server. The article indeed describes several steps in manually setting up a project to build Vue single-file-components, but a simpler method is to use Vue CLI to automatically scaffold such a project.

Dynamic component path in vuejs

I have a divided my vuejs application in to components and whenever i update the architecture of the application, I need to change the component path wherever added. This makes the application maintenance difficult. So I have added the component path to settings.json file and with that I am trying to load the component. But this is not working. Please see the code below.
import Registration from platform.urls.uiComponent+'Account/Registration'
Does any one have any idea how to set dynamic component path in vuejs ?
According to this answer it is not possible to do that. There is however an alternative way to achieve what you want to do if you use a tool such as webpack. In webpack configuration you can define an alias for a directory e.g. components, which you can use and afterwards change in a centralized location.

How to intercept component loading with a loader plugin in aurelia (jspm)?

I want to intercept the module loading of Aurelia to redirect some calls.
To do such things the aurelia-loader has a addPlugin() interface. You add a suffix like !myplugin to a resource to mark that it should be loaded using that plugin.
Now when I do this with my own component, it loads the JS file but the name for the HTML template is messed up. Like from the resource name my-comp!myplugin it will load my-comp.js but tries to find my-comp!myplugin.html which does not match the plugin name anymore.
I have provided a gist with that issue here: https://gist.run/?id=c7ed477bc652540ed8b0702d843f1832
The loader plugin code in main.js is basically:
loader.addPlugin('gate', {
fetch(address) {
console.info('Intercepted:', address);
var tmpParts = address.split('.');
var extension = tmpParts[tmpParts.length - 1].toLowerCase();
if (extension == 'css') {
console.debug('Loading as styles', address);
return loader.loadText(address);
} else if(extension == 'html') {
console.debug('Loading as template:', address);
return loader.loadTemplate(address);
} else {
console.debug('Loading as module:', address);
return loader.loadModule(address);
}
}
});
Using it like this in a template (marked with Issue 1 in the gist):
<require from="./comp2!gate"></require>
After that is should be possible to load the component like this:
<comp2></comp2>
Or even like this:
<compose view-model="./comp2!gate"></compose>
Instead the name for the template is messed up, the browser console says:
Failed to load resource: the server responded with a status of 404 ()
https://gist.host/run/1485182959149/comp2!gate.html
The expected name of the template would be https://gist.host/run/1485182959149/comp2.html!gate (including the plugin)
How can I fix the loader plugin to work correctly?
The loader is aurelia-loader-default 1.0.0, JSPM is 0.16.39, Node is 6.5.0, NPM 3.10.5.
I have added a second gist.run: https://gist.run/?id=1ddd4233e3afc40d89eb64b751e1dd8f
It is a bit shorter. When I specify the view using #useView decorator in comp2.js (marked with issue 4), it works - but I cannot specify a loader plugin with #useView. I would expect it to load the view with the same loader plugin or be able to specify a loader plugin with #useView.
Okay, I found a solution which looks like it can work with less code and patching and it works between two gists.
This gist contains the external component comp3:
https://gist.run/?id=dc837978a514011e13c872dbad92ae3f
This gist is the basic Aurelia app with a plugin and a small patch to the applyPluginToUrl method of the loader:
https://gist.run/?id=39e6fdacefc9e5c69b42a5e8c9049384
If the gist URLs are fixed, it should work for everyone. The Aurelia app loads comp3 from the first gist and displays it (you see the purple border define in the comp3-view).
There is one caveat at the moment: No support for CSS as SystemJS adds the extension .js to them, looks like I have to take care of that.
I do not like the path to loader.applyPluginToUrl but SystemJS does not really support plugin chaining and so the order must be correct. Also this solution requires all external components to set #useView including the loader plugin.
Any better approaches?

React native and redux global environment variables

I am trying to find the best way to store environment variables such as API and CDN servers DNS.
I saw my other answers, most recommended on one of the following:
Store in global - Don't like it. Hate throwing stuff to global, much rather using proper scopes. Also, seems a bit of an anti-pattern, but if you disagree I'de love to hear your arguments.
Keep the environment data in a separate file, and load current environment from that file - won't work for me as those are dynamic variables that might change (switching to another environment, CDN changes etc.)
Pass it via props - I don't like the idea of passing the config down 7 components whenever I need the CDN to show an image.
Ideally, I would like to have an option to import a lib in different places in the app. Basically something like this:
# when app starts, somewhere in the master container
import envConfig from './envConfig'
# load config from API
API.loadconfig().than((data) => envConfig.setConfig(data))
Then, in other files
import envConfig from './envConfig'
const cdn = envConfig.cdn;
Does that make any sense? is there a better way to achieve the same goal?
How can I make an import module to have a state?
In my project, I had to pass down some global variables so that I can access them anywhere in my app. Here's how I achieved that-
Wrap your application inside a global component like App.
Render children inside that App like this-
<div>
{React.cloneElement(this.props.children, { ...this.props.globalState })}
</div>
If you're using Navigator in your app, you can pass global variables through the Navigator. Please refer to this file to see how to exactly implement that. It's 4 months old code, please forgive me for that.
Good luck!