codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Follow publication

Passing configuration to Vue.js

Michał Męciński
codeburst
Published in
6 min readJan 8, 2018

--

How to pass configuration and initial application state from the server to a Vue.js application?

In the previous article I wrote about injecting dependencies into Vue components and the Vuex store. I also demonstrated how factory functions can be used to pass dependencies and configuration options to objects and simple functions.

As a reminder, here’s the final main.js file from that article:

import Vue from 'vue'
import makeI18n from './i18n'
import makeAjax from './services/ajax'
import makeStore from './store'
import App from './components/App.vue'
const i18n = makeI18n( 'en' );
const ajax = makeAjax( 'http://example.com' );
const store = makeStore( i18n, ajax );
new Vue( {
el: '#app',
i18n,
ajax,
store,
render: h => h( App )
} );

As you can see, we pass the locale to the i18n module and the address of the server to the ajax module. However, these options are still hard-coded in the script.

Let’s suppose that both the locale and the address of the server are based on some condition, for example the user’s preferred language or the URL of the page. Instead of hard-coding these values in the script, we want to be able to retrieve them from the server.

Of course we could retrieve these options using an AJAX request. However, such solution would be inefficient, because an additional request would be required every time the application is loaded. That would cause an unnecessary delay before the application is initialized and rendered.

Injecting the configuration into the page

A common solution to this problem is to inject the configuration directly into the body of the page. This works if page is dynamically rendered by the server, for example by a PHP script, and the server can determine these configuration options and pass them to the client-side code.

This can be done in many ways, for example using HTML data attributes, but the most flexible way is to embed JSON data in a separate <script> tag:

<script id="config" type="application/json">{"locale":"en","baseURL":"http:\/\/example.com"}
</script>

It has an id attribute to make it easy to distinguish it from other script tags on the same page. Also it’s content type is set to application/json to prevent the browser from interpreting the data as JavaScript code. When a browser encounters such script tag, it doesn’t execute it in any way, but the tag and its content is available in the DOM tree, so it’s a great solution for storing data of any type.

Note that the slash characters in the URL string are escaped with a backslash. That might seem unnecessary, because the forward slashes don’t have any special meaning in the context of a JSON string. To understand why this is important, imagine this scenario where some user input becomes a part of the JSON data:

<script id="config" type="application/json">
{"locale":"en","userName":"</script><h1>Hello!</h1><script>"}
</script>

The HTML parser will interpret the </script> tag as the end of the script, even though it appears inside a string, because it doesn’t know anything about JSON syntax. Then it will interpret the following <h1> tag as part of the document body. So from the browser’s perspective, the above markup is interpreted like this:

<script id="config" type="application/json">
{"locale":"en","userName":"
</script>
<h1>Hello!</h1>
<script>"}</script>

This could lead to potentially dangerous injections. So we take advantage of the fact that the JSON parser interprets the \/ sequence inside a string as a regular slash, and the following data is correctly (and safely) interpreted:

<script id="config" type="application/json">
{"locale":"en","userName":"<\/script><h1>Hello!<\/h1><script>"}
</script>

The json_encode() function in PHP already escapes slashes like that by default, so it’s safe to use.

Here’s an example PHP code that injects configuration data into the page:

<?php
$code[ 'locale' ] = get_user_locale();
$code[ 'baseURL' ] = get_server_base_url();
?>
<script id="config" type="application/json">
<?php echo json_encode( $config ) ?>
</script>

The client-side script can now read the configuration from the script tag using the following code:

const configElement = document.getElementById( 'config' );
const config = JSON.parse( configElement.innerHTML );
const i18n = makeI18n( config.locale );
const ajax = makeAjax( config.baseURL );

As you can see, we simply find the script element using its id, then we parse its content as a JSON object. Finally, we pass the configuration options to the factory functions which initialize our modules.

Note that the document must be loaded before the script is executed, otherwise the script element will not be found in the DOM tree. A common way to ensure that is to include the script tag which loads the client-side code at the end of the document body, just after the configuration data.

Passing the configuration explicitly

A slightly different approach is to modify the main.js script so that it exports a function:

import Vue from 'vue'
import makeI18n from './i18n'
import makeAjax from './services/ajax'
import makeStore from './store'
import App from './components/App.vue'
export function main( { locale, baseURL } ) {
const i18n = makeI18n( locale );
const ajax = makeAjax( baseURL );
const store = makeStore( i18n, ajax );
new Vue( {
el: '#app',
i18n,
ajax,
store,
render: h => h( App )
} );
}

To make the exported function accessible, the script must be bundled as a library. I described how this can be done using webpack in one of my previous articles. In short, the webpack.config.js file needs to include the library option in the output section, for example:

output: {
path: path.resolve( __dirname, './assets' ),
filename: 'js/myapp.js',
library: 'MyApp'
}

This makes the exported symbols available as properties of an object assigned to the global variable MyApp. So the main() function in our script can be called like this:

<script>
MyApp.main({"locale":"en","baseURL":"http:\/\/example.com"});
</script>

This requires just a small change in the server-side code:

<?php
$code[ 'locale' ] = get_user_locale();
$code[ 'baseURL' ] = get_server_base_url();
?>
<script>
MyApp.main(<?php echo json_encode( $config ) ?>);
</script>

This solution has two advantages. The script tag which loads the client-side code can now be included in the page header, so it can be loaded and compiled by the browser before the page is fully loaded. Only the call to main() must be included at the end of the page body.

The second advantage is that the configuration options are more explicit, because they are declared as parameters to the main() function. You can use whichever solution works best for you in a particular scenario.

Initial Vuex state

Very often the Vue.js application also needs to load some initial data at startup, for example the initial state of the Vuex store. Loading this data using an AJAX request adds a delay before the application is rendered and ready to use.

To avoid this delay, the initial state of the application can be injected directly into the page just like the configuration.

Let’s modify main.js in the following way:

import Vue from 'vue'
import makeI18n from './i18n'
import makeAjax from './services/ajax'
import makeStore from './store'
import App from './components/App.vue'
export function main( { locale, baseURL, ...initialState } ) {
const i18n = makeI18n( locale );
const ajax = makeAjax( baseURL );
const store = makeStore( i18n, ajax, initialState );
new Vue( {
el: '#app',
i18n,
ajax,
store,
render: h => h( App )
} );
}

You can see that now all options except locale and baseURL are assigned to the initialState object and passed to the makeStore() function.

Let’s modify store/index.js accordingly:

import Vue from 'vue'
import Vuex from 'vuex'
import makeModule1 from './modules/module1'
Vue.use( Vuex );export default function makeStore( i18n, ajax, initialState ) {
return new Vuex.Store( {
state: makeState( initialState ),
actions: makeActions( i18n, ajax ),
modules: {
module1: makeModule1( i18n, ajax, initialState )
}
} );
}
function makeState( initialState ) {
return {
...
};
}
function makeActions( i18n, ajax ) {
return {
...
};
}

The state of the Vuex store is now created by a factory function which takes the injected data as a parameter.

Note that I borrowed the idea of injecting the initial state of the application into the page from this article by Anthony Gore.

Keep in mind that if the initial state contains a large amount of data, it will take longer to load the page. In some scenarios, it might be best to only inject the most important information which is needed during the first render of the application. This will make it load and render faster, and the remaining data can be loaded asynchronously using an AJAX request.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Written by Michał Męciński

Author of open source projects at www.mimec.org, technical writer, co-founder of www.bulletcode.com

Responses (4)

Write a response