Cover

Vue TypeScript Without a Module Bundler

June 6, 2017
No Comments.

I’ve been feeling kind of old school lately. I’ve been pining for just writing client-side code and watching it work. That’s not the world that we’re in these days.

Writing web apps has become complicated. Transpilation has made some thing really awesome, but it also has complicated the field. Webpack, Browserify, Babel and even TypeScript have all make our lives easier and awful at the same time.

I loved this tweet about our current situation:

i remember the days when people still programmed in low-level languages such as untranspiled javascript
— yan (@bcrypt) June 4, 2017

So I decided to try and use Vue without having to setup Webpack or Browserify. I know that’s sacrilege but as a thought experiment I like how it turned out.

So the plan was this:

  • Use TypeScript so my own code was typesafe
  • Expect that JS libraries were just loaded on the page before my code
  • Enable debugging in the browser using Typescript and map files
  • Minify when I deployed

Again, this was a though experiment as I feel like setting up webpack or browserify

How I Did It

First, I created a directory under wwwroot with all my TypeScript:

image

The idea was that each of these files would get compiled as I saved a file using the .tsconfig file to specify that:

{
  "compilerOptions": {
    "target": "es5",
    "outFile": "wwwroot/lib/site/app.js",
    "sourceMap": true,
    "declaration": true,
    "mapRoot": "/lib/site/",
    "removeComments": true,
    "module": "none"
  },
  "include": [ "wwwroot/app/**/*.ts"],
  "compileOnSave": true
}

A couple of interesting settings here that if you’re used to using a packager might surprise you:

  • outFile: This means *all* my TypeScript is compiled into one large .js file. Yeah.
  • module: Because I’m not using a loader or packager,  it creates the .js without modularizing it.

So what does my TypeScript look like?

/// <reference path="common/validators.ts" />
/// <reference path="common/filters.ts" />
/// <reference path="common/datepicker.ts" />
module CodeCamp {

  // External JS Libraries
  declare var Vue: any;
  declare var VeeValidate: any;
  declare var VueResource: any;

  export let App = {

    setup: function() {
      
      Vue.use(VeeValidate);
      Vue.use(VueResource);
      CodeCamp.Common.createValidators();
      CodeCamp.Common.createFilters();
      CodeCamp.Common.createDatePicker();
      Vue.config.errorHandler = function (err, vm, info) {
        console.log(err);
      }
    },

    bootstrap: function (theView: any) {
      this.setup();
      new Vue(theView);
    },
  }
}

Let’s walk through this to understand what I’m doing.

  • Reference Paths: are links to other .ts files that I’m referencing. I hate this hack, but it works for my needs. The trick here isn’t just a hint for intellisense, it also tells TypeScript in what order to compile the .ts files. Since the app.js needs the createValidators, createFilters, and **createDatePicker **to all exist before it runs, the TypeScript compiler will make sure they are defined higher in our one JavaScript file. I didn’t know it did this either ; )
  • module: Essentially just creates a namespace for me since I don’t want any of this code in the global scope.
  • declare var: This creates an variable that already exists. In this case, it’s my external JavaScript files. I could have opted to try and get the typelibs for TypeScript, but I didn’t care. I am used to developing against these libraries in JavaScript.
  • export: This is used to expose objects from the namespace (e.g. object like here, or functions, or classes).

My goal here was to have one set of TypeScript that would be loaded on any page that needed Vue. Each page would bootstrap itself to load that part of the Vue code it needed. For that I created a partial view in ASP.NET MVC:

@section Scripts {
  @Html.Partial("_VueScripts", "join")
}

This partial view took the string of which view to load and did this:

@model string

<environment names="Development">
  <script src="~/lib/jquery-ui/jquery-ui.js"></script>
  <script src="~/lib/lodash/dist/lodash.js"></script>
  <script src="~/lib/moment/moment.js"></script>
  <script src="~/lib/vue/dist/vue.js"></script>
  <script src="~/lib/vee-validate/dist/vee-validate.js"></script>
  <script src="~/lib/vue-resource/dist/vue-resource.js"></script>
  <script src="~/lib/vue-router/dist/vue-router.js"></script>
  <script src="~/lib/site/app.js"></script>
</environment>
<environment names="Staging,Production">
  <script src="~/lib/jquery-ui/jquery-ui.min.js"></script>
  <script src="~/lib/lodash/dist/lodash.min.js"></script>
  <script src="~/lib/moment/min/moment.min.js"></script>
  <script src="~/lib/vue/dist/vue.min.js"></script>
  <script src="~/lib/vee-validate/dist/vee-validate.min.js"></script>
  <script src="~/lib/vue-resource/dist/vue-resource.min.js"></script>
  <script src="~/lib/vue-router/dist/vue-router.min.js"></script>
  <script src="~/lib/site/app.min.js"></script>
</environment>
<script>
  (function bootstrapVueModel() {
    CodeCamp.@(Model)();
  })();
</script>

So I’m loading different versions in development and production, but that little script at the bottom does the magic. It calls a bootstraper function that each vue implements (inside the CodeCamp namespace). For example:

///<reference path="./joinView.ts" />
module CodeCamp {
  export function join() {
    CodeCamp.App.bootstrap(CodeCamp.JoinView);
  }
}

Looking at the Code

The project I did this for is an open source ASP.NET Core project that we use to run the Atlanta Code Camp. Feel free to grab the source and dig in:

https://github.com/shawnwildermuth/corecodecamp

So What?

I am not sure that this exercise was anything more than me pining for a time before the setup for getting started with a library didn’t require a CLI to scaffold a whole project.  I’m not super happy with how this works (as I wish that the typelib stuff in TypeScript were easier to setup, but this might be a Vue problem; and I wish that there were an easy loader that would load scripts as they need them instead of the one giant file approach so I could use require and import better). But this is closer how I want to be working. I am just not sure how to get there.

At the end of the day I suspect I’ll end up with webpack and just live with the 100 lines of setup for webpack that every project needs. I just hate the way I spend more time setting a project up than actually writing code.

Feel free to flame me, but please avoid the lazy “webpack is awesome, you’re not doing it right” posts.