Skip to content

Instantly share code, notes, and snippets.

@levibostian
Last active April 15, 2020 20:31
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save levibostian/96cc285d4235d73f09cdc22f2590ccba to your computer and use it in GitHub Desktop.
Save levibostian/96cc285d4235d73f09cdc22f2590ccba to your computer and use it in GitHub Desktop.
webpack, Tachyons, pug, Vue.js web app.

Today, single page web apps are driving many websites that we use each and every day. Instead of having your browser request a new web page for each and every action you perform on a web page, single page web apps may load all in one request to smoothly and quickly transition with every action you perform.

When building single page web apps, you may decide to retrieve all of the HTML, CSS and Javascript with one single page load or dynamically load these resources as the user moves about your site. Either way, it can be a pain to bundle all of these assets together for the end user to download from your web server. This is where webpack comes into play.

webpack does all of the heavy lifting bundling all of your HTML, CSS and Javascript together. If you write your site all from scratch or depend on dependencies from npm, webpack takes care of packaging it all together for you. It has the ability to take your single page web app, cut out all of the code you don't need, then package it up into 1 or more packages for your user to download. How awesome is that?!

Recently, I decided to build my first single page web app. I did what any other beginner might do. Jumped right into learning React. It's community supported. Widely adopted. Stable. Developers seem to like it. Why not?

While working with React, I was introduced quickly to webpack. webpack is officially supported by React and the community behind it. If you want to develop in React, chances are you will touch webpack and for good reason.

Since working with React, I have stumbled across another framework, Vue.js. Vue.js is a newer project that has tons of promise behind it. It is not adopted to the level of React quite yet, but still have a very active community behind it. After seeing the examples and reading some articles online I wanted to give it a shot.

My current stack of choice at the moment is: Vue.js as the single page web app framework, Tachyons for all of my CSS needs, pug (formerly Jade) for writing my HTML, and of course webpack to bundle everything up to upload to the web server.

You may decide to use handlebars instead of pug. React instead of Vue.js. Or Basscss instead of Tachyons. Or maybe you want to skip an HTML template language and CSS frameworks all together and use plain ol' HTML/CSS from scratch. The tools you decide to use does not matter. I simply want to show you how to package them all together using webpack and you can then play around with each to find what fits right for you. Remember, tools don't matter. They just might make your life easier sometimes.

Getting everything to work together is not complex and is pretty quick. But you must get it just right. Here is how I did so.

First off, here are the versions of my dependencies for you to refer to:

Vue.js 2.0.1 (built via Vue-cli 2.4.0)
webpack 1.13.2
pug 2.0.0-beta6
Tachyons 4.5.4

All the latest as of today.

  • First off, we are going to install some of our initial dependencies to build our basic project. I prefer to use yarn to install all of my dependencies, but you may use npm as well. I have both commands listed below.
$> npm install -g webpack # or yarn global add webpack
$> npm install -g vue-cli # or yarn global add vue-cli

Here, we are installing vue-cli which is used to generate a Vue.js web app. This is one reason I love Vue.js so far. Jump starts development with the latest version of Vue.js and an initial file structure that works great for Vue.js projects. We are also installing webpack which we will use to build our Vue.js project that vue-cli creates for us next.

Time to create something.

$> vue init webpack unicorn_web_app

This is telling vue-cli to build a Vue.js project using webpack with the project name "unicorn_web_app". You change "unicorn_web_app" to whatever you want of course.

While the command runs, it will ask you some questions. Go ahead and hit the Enter key on your keyboard for all of the questions to accept the default configuration:

When you are complete, do the following:

$> cd unicorn_web_app # change "unicorn_web_app" to the name you used above to generate your Vue.js project.
$> npm install # or yarn
$> npm run dev

After you run the above commands, your Vue.js web app should appear in your browser!

vue-cli just did a lot of the hard work for us. If you want to continue writing ES6 Javascript and decide not to use a CSS framework such as Tachyons or a HTML template language such as pug, then you are done! Jump into the very well done Vue.js getting started guide and get developing. Else, lets continue.

pug

Install pug into our project:

$> npm install pug --save-dev # or yarn add pug --dev

Getting the HTML template language pug running with Vue.js is also a breeze (can you start to see a couple reasons why I am loving Vue.js so far?!). In fact, it's technically already done for you. We have to make 1 little change and that is it.

Take your favorite text editor and open up the file: unicorn_web_app/src/App.vue. You will see the following piece of code below:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
  </div>
</template>

<script>
import Hello from './components/Hello'

export default {
  name: 'app',
  components: {
    Hello
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

This file contains the structure for what makes Vue.js a single page web app framework. We have our App component that all of our other web page components will be injected into. At the very top of the file we have our HTML:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
  </div>
</template>

Vue.js is pretty cool in that it works with lots of HTML template languages out of the box. So really, all you need to do is take the very top line of your HTML above:

<template>

and specify we want to use pug:

<template lang="pug">

Now write pug HTML code as you always do for your template:

<template lang="pug">
#app
    img(src='./assets/logo.png')
    hello
</template>

Feel free to do the same to the unicorn_web_app/src/components/Hello.vue file as well:

<template lang="pug">
.hello
  h1 {{ msg }}
  h2 Essential Links
  ul
    li
      a(href='https://vuejs.org', target='_blank') Core Docs
    li
      a(href='https://forum.vuejs.org', target='_blank') Forum
    li
      a(href='https://gitter.im/vuejs/vue', target='_blank') Gitter Chat
    li
      a(href='https://twitter.com/vuejs', target='_blank') Twitter
    br
    li
      a(href='http://vuejs-templates.github.io/webpack/', target='_blank') Docs for This Template
  h2 Ecosystem
  ul
    li
      a(href='http://router.vuejs.org/', target='_blank') vue-router
    li
      a(href='http://vuex.vuejs.org/', target='_blank') vuex
    li
      a(href='http://vue-loader.vuejs.org/', target='_blank') vue-loader
    li
      a(href='https://github.com/vuejs/awesome-vue', target='_blank') awesome-vue
</template>

Tachyons

Install Tachyons into our project:

$> npm install tachyons --save-dev # or yarn add tachyons --dev

The last piece and the most challenging piece for me to complete. Up until now, this has been very quick and simple.

There are things that you need to know to get Tachyons working in your project.

  1. You need to configure a Webpack loader for CSS (read more about Webpack loaders if you wish. Long story short, they are plugins that tell Webpack how to handle certain file types or to create build tasks.)
  2. Use Webpack's string language to tell your Vue.js .vue and .js files that you want to use Tachyons and where to find it.

Again, I am not talking specifically about Tachyons here. In fact, I found a GitHub issue online how someone got Bootstrap CSS installed in their React app using Webpack in order for me to get Tachyons working with Vue.js. This is simply how to get external CSS files from node_modules/ to compile into our Webpack bundles.

Step 1

On with step 1. The Webpack documentation about CSS gives us an idea on how to solve this task. We must install some dependencies:

$> npm install style-loader css-loader extract-text-webpack-plugin --save-dev # or yarn add style-loader css-loader extract-text-webpack-plugin --dev

Then, in our unicorn_web_app/build/webpack.base.conf.js file, lets make some changes.

At the top of the file, add the requirement for the ExtractTextPlugin dependency:

var path = require('path')
var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var ExtractTextPlugin = require('extract-text-webpack-plugin')  // <----- add this

Lastly, you need to add the configuration of ExtractTextPlugin to the webpack.base.conf.js file. See below the 6 lines of code added:

module.exports = {
  entry: {
    app: './src/main.js'
  },
  plugins: [                                                 // <----- Create plugin array
    new ExtractTextPlugin('[name].css', {allChunks: true})   // <----- Add ExtractTextPlugin plugin
  ],                                                         // <----- Close plugin array
  output: {
    path: config.build.assetsRoot,
    publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
    filename: '[name].js'
  },
  resolve: {
    extensions: ['', '.js', '.vue'],
    fallback: [path.join(__dirname, '../node_modules')],
    alias: {
      'vue$': 'vue/dist/vue',
      'src': path.resolve(__dirname, '../src'),
      'assets': path.resolve(__dirname, '../src/assets'),
      'components': path.resolve(__dirname, '../src/components')
    }
  },
  resolveLoader: {
    fallback: [path.join(__dirname, '../node_modules')]
  },
  module: {
    preLoaders: [
      {
        test: /\.vue$/,
        loader: 'eslint',
        include: projectRoot,
        exclude: /node_modules/
      },
      {
        test: /\.js$/,
        loader: 'eslint',
        include: projectRoot,
        exclude: /node_modules/
      }
    ],
    loaders: [
      {
        test: /\.vue$/,
        loader: 'vue'
      },
      {
        test: /\.js$/,
        loader: 'babel',
        include: projectRoot,
        exclude: /node_modules/
      },
      {
        test: /\.json$/,
        loader: 'json'
      },
      {                                                                   // <------ Create a new JSON object to add a CSS loader.  
        test: /\.css$/,                                                   // <------ This tells Webpack that we want to handle css files.
        loader: ExtractTextPlugin.extract('style-loader', 'css-loader')   // <------ this tells Webpack *how* to handle css files.
      },                                                                  // <------ close JSON object for our CSS loader.
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  eslint: {
    formatter: require('eslint-friendly-formatter')
  },
  vue: {
    loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }),
    postcss: [
      require('autoprefixer')({
        browsers: ['last 2 versions']
      })
    ]
  }
}

Step 2

Awesome. Webpack now knows how to handle CSS through the loader we created above.

Webpack knows how to handle CSS but we have not given it any CSS to handle. This is our last step. Lets load up Tachyons CSS.

Open up src/main.js in your favorite text editor. You will see the below code:

import Vue from 'vue'
import App from './App'

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
})

Pretty simple. This is the entry point for our Vue.js web app. It is here that we will add the Tachyons dependency which allows us to use it across our Vue.js project.

Webpack has it's own string language used to tell Webpack what loader to use for modules. Well, we want to load the Tachyons module and have Webpack use the ExtractTextPlugin that we configured in step 1 above.

Add this one line to our src/main.js file:

import Vue from 'vue'
import App from './App'
import '!style!css!tachyons/css/tachyons.css'   // <------ Add this one line to load the Tachyons module.

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
})

To quickly dissect what we just added, there are 2 pieces. There is the: !style!css! half of our import string and the last half: tachyons/css/tachyons.css.

The first half, !style!css!, is the Webpack string language telling Webpack this is a CSS file and it should use the ExtractTextPlugin to load it for us.

The second half, tachyons/css/tachyons.css, is the path in the node_modules/ directory to get to our Tachyons.css file. When we installed Tachyons into this project using, npm install tachyons --save-dev, Tachyons installed itself in the directory: node_modules/tachyons/css/tachyons.css. Therefore, we are telling Webpack where to find Tachyons via: tachyons/css/tachyons.css.

Tachyons CSS classes are now available for us to use in our project. Lets test it out. Open up src/components/Hello.vue in your favorite text editor and add some Tachyons to the pug HTML.

Our Hello.vue template file before Tachyons styling:

Change the line: h2 Ecosystem in src/components/Hello.vue to: h2.ba.bg-blue Ecosystem. This adds a border around the h2 block and a blue background color.

Save the file. If you still have npm run dev running, Webpack will reload your changes automatically for you! Go ahead and view http://localhost:8080/ in your browser to check out your changes! If you cancelled the npm run dev command, just run it again.

Check out our Vue.js single page web app now after some Tachyons styling!


We did it. We created a single page web app using Vue.js, styled it via Tachyons CSS, designed it via pug HTML, and packaged it all together using Webpack.

What's next?

  • We have been using the command: npm run dev this whole time during development of our Vue.js web app. If you instead run npm run build, Webpack will create a dist/ directory for us containing a static version of our web app! Push this dist/ directory up to your web hosting provider, GitHub pages, Firebase hosting, and your new single page web app is ready to run in the wild.
  • Make something! Go build a Vue.js web app!

Want to do more?

  • Do more with Vue.js by checking out their awesome docs
  • Make beautiful web app pages via Tachyons fast and easy by checking out their great docs
  • Learn more about pug at their website and use this handy tool if you want to convert some HTMl code to pug with ease.
  • Webpack is just getting started and the community is booming. Go to their community website to see more great docs such as this one.

Levi Bostian @levibostian about.me

@levibostian
Copy link
Author

Here are the photos for the post.

after_tachyons

before_tachyons

unicorn_web_app_vue_cli

vue_cli_generated_page

@TheLarkInn
Copy link

This is where Webpack comes into play. this may be a small nitpick but webpack always lowercased w.

@estrattonbailey
Copy link

Yeah looks really good man, nice writeup! I think beginners should be able to follow this really well. Glad you pointed out specifics about loaders and string language, in particular. Good call on linking to all-the-things as well, might be good to also link to the docs of the CSS loader/plugin. If you have a repo for this test project def link to that too.

use this handle tool if you want to convert - *handy?
pug (formally Jade) - *formerly

@levibostian
Copy link
Author

Oh? Sorry @estrattonbailey and @TheLarkInn I thought github would email me when I get comments on this? Guess not.

I made a note to make these edits tomorrow. I'll have them done shortly!

Thanks much you two!

@levibostian
Copy link
Author

@estrattonbailey and @TheLarkInn, your suggestions have been complete. Thanks for that!

@robjwood
Copy link

Great write-up. This is the first time I've managed to get this combination of technologies working together. Thanks.

@benface
Copy link

benface commented Apr 30, 2017

Very cool read, thank you. I'm just confused about this part:

The first half, !style!css!, is the Webpack string language telling Webpack this is a CSS file and it should use the ExtractTextPlugin to load it for us.

Actually, it seems that you are not using the ExtractTextPlugin at all. The initial ! character in the require'd string means that any loader defined in the configuration will not be used. From the Webpack 1.x docs (Webpack 2 hadn't been released when you wrote the post):

It’s possible to overwrite any loaders in the configuration by prefixing the entire rule with !.

So basically, you're not using the rule you defined in the configuration, and instead only using the style and css loaders to load Tachyons. You could remove your rule and ExtractTextPlugin altogether, or remove !style!css!; both would work just fine. :)

@groksrc
Copy link

groksrc commented May 2, 2017

👍

@typerory
Copy link

typerory commented Nov 9, 2017

Hey Levi,

Thanks for this! I've been trying to get Tachyons going on a new project. I've followed your instructions but I'm getting this:

`This dependency was not found:

  • !style!css!tachyons/css/tachyons.css in ./src/main.js`

What do you think?

@superrandres
Copy link

Hello help me please, I'm using vue and pug, everything works fine but when I make a change to a .pug file which is being included from my component the live reload does not work, I did everything I found but nothing, thanks in advance:

It's my code: https://github.com/superrandres/ejemplo-de-pug-y-vue

@jjosef
Copy link

jjosef commented Dec 13, 2017

BTW, most vue projects are using webpack 2/3 now, so you might want to update the import string to use '!style-loader!css-loader!' instead.

@asconix
Copy link

asconix commented Dec 15, 2017

Super nice writeup! I went through it and every part of the stack plays together. The only thing as @jjosef mentioned: webpack 2/3 users should use this:

// eslint-disable-next-line import/no-webpack-loader-syntax
import '!style-loader!css-loader!tachyons/css/tachyons.css'

The first line (comment line) tells ESlint not to complain about the one inline import.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment