Vue.js: Validation with TypeScript Vue.js: Drawbacks of being a single

Vue.js: Deploying to Production

Published on Monday, February 5, 2018 6:00:00 AM UTC in Programming & Security

When I starting playing around with Vue.js, I was also interested in what the deployment story looks like, as this often is a major pain point with other frameworks. My tests were quite satisfying, because the Vue.js Webpack template has a built-in production config that seems to work great. Testing this on a local machine worked flawlessly. The trouble started when I wanted to roll out my sample app to a "real" server. After deployment, I was staring at a blank browser window. No errors, no hints what's wrong, nothing.

When Security Strikes

These kinds of errors are the worst. First of all, in production everything is minified and scrambled, so tracking down errors is much harder anyway. But if you don't even get the slightest hint what's wrong, that's a real bummer. I checked that all files are in place and were, in fact, downloaded by the browser. Still, nothing. Enlightenment came with a special setting in the dev tools of Chrome:

image-1.png

With this you can force the browser to halt when any exception happens, and with that I found the cause of the issue: My content security settings prevented the application from starting up.

This was odd. My impression was that all dynamic content was pre-compiled into JavaScript that is delivered through script-Tags anyway. But still Chrome complained that 'unsafe-eval' is not part of my current policy! Turns out that the bundled compiler of Vue.js uses the Function constructor to create dynamic code, and this is also considered unsafe (naturally). But again: the Vue.js compiler? Didn't the compilation happen at build time? Why does the bundle even include that compiler?

Because I told it to. When you create a new project using the CLI, you are asked whether you want to use the runtime version of the framework, or the runtime + compiler version ("standalone"). The latter is the default, and - what do I know - of course I started with all the default settings. The standalone version bundles the compiler just in case you need it to dynamically compile templates at runtime. The thing is: as long as all your templates are located in .vue files, they are pre-compiled, and you can dump the compiler then.

image-2.png

I'm not sure why the standalone version of Vue.js is the default, and "recommended for most users". In my opinion it should be the other way round. Even if you don't use any of the compiler features at runtime, this setup forces you to deliver 'unsafe-eval' for your scripts with your CSP - highly undesirable!

The good thing is that you only need two minimal changes to turn your standalone setup into a runtime setup.

1. Remove the corresponding alias from the Webpack configuration.

This can be found in webpack.base.conf.js. Find the following:

resolve: {
  extensions: ['.ts', '.js', '.vue', '.json'],
  alias: {
    'vue$': 'vue/dist/vue.esm.js', // <-- remove this
    '@': resolve('src'),
  }
},

... and simply remove the alias "vue$" which points to the compiler bundle. Without that, the runtime bundle is used.

Now, you would think that this is enough, because you're quite sure non of your components is located outside of .vue files, right? Wrong!

2. Fix the one component that is not in a .vue file.

There's one component most people do forget about, because typically you never touch it once it's set up: the root component in Main.ts (or main.js if you're using JavaScript). The standalone version looks like this:

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
});

Do you see the dynamic template? :)

Turn this into a render function manually, like this:

new Vue({
  el: '#app',
  router,
  render: h => h(App) // <-- that one
})

And that's it! Deploy to your server and marvel at your app even with tied-down CSP settings.

Bonus: Mind the Minification!

Another thing I ran into was that the built-in minification scrambles component names. That again was really hard to track down. I had used the keep-alive built-in component of Vue.js to recycle one of my own components. When you do that, instead of hooking into the created lifecycle event, you use activated. But in production, my hook was never called. Until I realized: the configuration of the keep-alive component is not using the type, but a magic string (component names), and once minification strikes, the two don't match anymore. Like:

<keep-alive include="MyComponent">
  <router-view />
</keep-alive>
@Component
// is renamed to "DottoreSchiwago" or something like that during minification :(
export default class MyComponent extends Vue

The solution to this is to explicitly specify the name as one of the options for the component decorator:

@Component({
  name: "MyComponent"
})
export default class MyComponent extends Vue

That makes sure the component can be found in production, even though the actual type has been renamed to something else during the build process.

Bonus 2: Deploy to a Sub-Path

During development, the application is hosted in an ad-hoc web server in the root (typically http://localhost:8080). The production build also assumes that you will be hosting the app in the root of whatever web server/domain you target. But that's not always the case. I wanted to have the app located in a sub-folder, like http://www.example.com/myapp. Luckily, this is extremely easy to achieve.

  1. Find config/index.js in your project
  2. Change the assetsPublicPath in the build section there

Like this:

build: {
  // ...
  assetsPublicPath: '/myapp/',
  // ...
}

Make sure you don't accidentally change the same entry in the dev-section!

With that, your app runs flawlessly in a sub-folder of your domain. This, to me personally, is an amazing experience, because I've spent hours in the past getting other frameworks to work outside the root folder.

Have fun! :)

Tags: Csp · Pitfall · Vue.js · WebPack