diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..ae88958
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,23 @@
+version: 2
+jobs:
+  build:
+    working_directory: ~/rollup-plugin-vue
+    docker:
+      - image: circleci/node:8.5.0
+    steps:
+      - checkout
+      - run:
+          name: Install yarn if required
+          command: curl -o- -s -L https://yarnpkg.com/install.sh | bash
+      - restore_cache:
+          key: dependency-cache-{{ checksum "package.json" }}
+      - run:
+          name: Install package dependencies
+          command: yarn --no-progress install
+      - save_cache:
+          key: dependency-cache-{{ checksum "package.json" }}
+          paths:
+            - node_modules
+      - run:
+          name: Test
+          command: npm test
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..9d5248e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..15241dd
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,4 @@
+{
+  "root": true,
+  "extends": ["plugin:vue-libs/recommended"]
+}
diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000..a6e05fd
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,171 @@
+declare module VueComponentCompiler {
+  /**
+   * Parse SFC file into block descriptors.
+   *
+   * @param content Contents of the SFC.
+   * @param file Filepath (used for cache key & in generated source maps)
+   */
+  export function parse(content: string, file: string, config: ParserConfig): SFCDescriptor
+
+  /**
+   * Compile styles for SFC
+   *
+   * @param styles List of styles to process.
+   * @param file SFC file path
+   */
+  export function compileStyle(style: StyleCompilerSource, file: string, config: StyleCompilerConfig): Promise<Array<StyleCompilerOutput>>
+
+  /**
+   * Compile template to render functions
+   *
+   * @param template Template to compile
+   * @param file SFC file path
+   */
+  export function compileTemplate(template: TemplateCompilerSource, file: string, config: TemplateCompilerConfig): Promise<TemplateCompilerOutput>
+
+  /**
+   * Assemble processed parts of SFC.
+   *
+   * @param source Processed sources with resolvable identifiers (`id`)
+   * @param file SFC file path
+   */
+  export function assemble(source: AssemblerSource, file: string, config: AssemblerConfig): string
+
+  /**
+   * Generate scope id for SFC, used in scoped styles.
+   *
+   * @param file SFC file path
+   * @param context file is required in context
+   * @param key
+   */
+  export function generateScopeId(file: string, context: string, key?: string): string
+
+  type ParserConfig = {
+    needMap: boolean
+  }
+
+  type SFCDescriptor = {
+    script: ScriptDescriptor
+    styles: Array<StyleDescriptor>
+    template: TemplateDescriptor
+    customBlocks: Array<BlockDescriptor>
+  }
+
+  type BlockDescriptor = {
+    type: string // tag
+    content: string
+    start: number
+    end: number
+    attrs: Array<{ name: string, value: string | boolean}>
+  }
+
+  type StyleDescriptor = BlockDescriptor & {
+    scoped?: boolean
+    module?: string | boolean
+    lang?: string
+    src?: string
+  }
+
+  type ScriptDescriptor = BlockDescriptor & {
+    lang?: string
+    src?: string
+  }
+
+  type TemplateDescriptor = BlockDescriptor & {
+    lang?: string
+    src?: string
+  }
+
+  type CompilerSource = {
+    code: string
+    map?: object // prev source map
+  }
+
+  type StyleCompilerSource = CompilerSource & {
+    descriptor: StyleDescriptor
+  }
+
+  type StyleCompilerConfig = {
+    scopeId: string // used for scoped styles.
+    needMap?: boolean
+    plugins?: Array<object> // postcss plugins
+    options?: object // postcss options
+    onWarn?: MessageHandler
+  }
+
+  type MessageHandler = (message: Message) => void
+
+  type Message = {
+    type: string
+    text?: string
+  }
+
+  type CompilerOutput = {
+    code: string,
+    map?: object
+  }
+
+  type StyleCompilerOutput = CompilerOutput & {}
+
+  type TemplateCompilerSource = CompilerSource & {
+    descriptor: TemplateDescriptor
+  }
+
+  type TemplateCompilerConfig = {
+    scopeId: string
+    isHot?: boolean // false
+    isServer?: boolean // false
+    isProduction?: boolean // true
+    esModule?: boolean // true
+    optimizeSSR?: boolean // true
+    buble: object // see https://github.com/vuejs/vue-template-es2015-compiler/blob/master/index.js#L6
+    options?: {
+      preserveWhitspace?: boolean // true
+    }
+    transformToRequire?: object
+    plugins?: Array<Function>
+  }
+
+  type TemplateCompilerOutput = CompilerOutput & {
+    errors: Array<object>
+    tips: Array<object>
+  }
+
+  type AssemblerSource = {
+    script: {
+      id: string,
+      descriptor: ScriptDescriptor
+    }
+    styles: Array<{
+      id: string
+      hotPath: string
+      descriptor: StyleDescriptor
+    }>
+    render: {
+      id: string
+      descriptor: TemplateDescriptor
+    }
+    customBlocks: Array<{
+      id: string
+      descriptor: BlockDescriptor
+    }>
+  }
+
+  type AssemblerConfig = {
+    hashKey?: string
+    esModule?: boolean // true
+    shortFilePath?: string // = filename
+    require?: {
+      vueHotReloadAPI?: string // vue-hot-reload-api
+      normalizeComponent?: string // vue-component-compiler/src/normalize-component.js
+    }
+    scopeId: string // same as scopeId of style compiler.
+    moduleIdentifier?: string // ~ used in SSR
+    isHot?: boolean // false
+    isServer?: boolean // false
+    isProduction?: boolean // true
+    isInjectable?: boolean // false
+    hasStyleInjectFn?: boolean // false
+    onWarn?: MessageHandler // console.warn
+  }
+}
diff --git a/index.js b/index.js
deleted file mode 100644
index e69de29..0000000
diff --git a/package-lock.json b/package-lock.json
index 4ad2f7f..08954a7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,11 @@
       "integrity": "sha1-uB3l9ydOxOdW15fNg08wNkJyTl0=",
       "dev": true
     },
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+    },
     "acorn": {
       "version": "4.0.13",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
@@ -25,6 +30,23 @@
         "acorn": "4.0.13"
       }
     },
+    "acorn-jsx": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
+      "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
+      "dev": true,
+      "requires": {
+        "acorn": "3.3.0"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+          "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
+          "dev": true
+        }
+      }
+    },
     "ajv": {
       "version": "4.11.8",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
@@ -61,14 +83,12 @@
     "ansi-regex": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
-      "dev": true
+      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
     },
     "ansi-styles": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
       "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
-      "dev": true,
       "requires": {
         "color-convert": "1.9.0"
       }
@@ -122,6 +142,21 @@
       "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
       "dev": true
     },
+    "array-union": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+      "dev": true,
+      "requires": {
+        "array-uniq": "1.0.3"
+      }
+    },
+    "array-uniq": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+      "dev": true
+    },
     "array-unique": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
@@ -251,6 +286,14 @@
         "private": "0.1.7",
         "slash": "1.0.0",
         "source-map": "0.5.7"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
       }
     },
     "babel-generator": {
@@ -267,6 +310,14 @@
         "lodash": "4.17.4",
         "source-map": "0.5.7",
         "trim-right": "1.0.1"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
       }
     },
     "babel-helpers": {
@@ -413,6 +464,16 @@
         "tweetnacl": "0.14.5"
       }
     },
+    "big.js": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz",
+      "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q=="
+    },
+    "bluebird": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
+      "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw="
+    },
     "boom": {
       "version": "2.10.1",
       "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
@@ -467,6 +528,23 @@
       "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
       "dev": true
     },
+    "caller-path": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
+      "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
+      "dev": true,
+      "requires": {
+        "callsites": "0.2.0"
+      },
+      "dependencies": {
+        "callsites": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
+          "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
+          "dev": true
+        }
+      }
+    },
     "callsites": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
@@ -501,7 +579,6 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz",
       "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==",
-      "dev": true,
       "requires": {
         "ansi-styles": "3.2.0",
         "escape-string-regexp": "1.0.5",
@@ -514,6 +591,18 @@
       "integrity": "sha512-vHDDF/bP9RYpTWtUhpJRhCFdvvp3iDWvEbuDbWgvjUrNGV1MXJrE0MPcwGtEled04m61iwdBLUIHZtDgzWS4ZQ==",
       "dev": true
     },
+    "circular-json": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz",
+      "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==",
+      "dev": true
+    },
+    "cli-width": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+      "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+      "dev": true
+    },
     "cliui": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
@@ -551,7 +640,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz",
       "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=",
-      "dev": true,
       "requires": {
         "color-name": "1.1.3"
       }
@@ -559,8 +647,7 @@
     "color-name": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
-      "dev": true
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
     },
     "combined-stream": {
       "version": "1.0.5",
@@ -571,12 +658,37 @@
         "delayed-stream": "1.0.0"
       }
     },
+    "commander": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+      "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ=="
+    },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
       "dev": true
     },
+    "concat-stream": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
+      "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
+      "dev": true,
+      "requires": {
+        "inherits": "2.0.3",
+        "readable-stream": "2.3.3",
+        "typedarray": "0.0.6"
+      }
+    },
+    "config-chain": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz",
+      "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=",
+      "requires": {
+        "ini": "1.3.4",
+        "proto-list": "1.2.4"
+      }
+    },
     "content-type-parser": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.1.tgz",
@@ -621,6 +733,96 @@
         "boom": "2.10.1"
       }
     },
+    "css-modules-loader-core": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz",
+      "integrity": "sha1-WQhmgpShvs0mGuCkziGwtVHyHRY=",
+      "requires": {
+        "icss-replace-symbols": "1.1.0",
+        "postcss": "6.0.1",
+        "postcss-modules-extract-imports": "1.1.0",
+        "postcss-modules-local-by-default": "1.2.0",
+        "postcss-modules-scope": "1.1.0",
+        "postcss-modules-values": "1.3.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "requires": {
+            "ansi-styles": "2.2.1",
+            "escape-string-regexp": "1.0.5",
+            "has-ansi": "2.0.0",
+            "strip-ansi": "3.0.1",
+            "supports-color": "2.0.0"
+          },
+          "dependencies": {
+            "supports-color": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+              "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+            }
+          }
+        },
+        "has-flag": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+          "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo="
+        },
+        "postcss": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz",
+          "integrity": "sha1-AA29H47vIXqjaLmiEsX8QLKo8/I=",
+          "requires": {
+            "chalk": "1.1.3",
+            "source-map": "0.5.7",
+            "supports-color": "3.2.3"
+          }
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "requires": {
+            "ansi-regex": "2.1.1"
+          }
+        },
+        "supports-color": {
+          "version": "3.2.3",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+          "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
+          "requires": {
+            "has-flag": "1.0.0"
+          }
+        }
+      }
+    },
+    "css-selector-tokenizer": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
+      "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=",
+      "requires": {
+        "cssesc": "0.1.0",
+        "fastparse": "1.1.1",
+        "regexpu-core": "1.0.0"
+      }
+    },
+    "cssesc": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
+      "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q="
+    },
     "cssom": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz",
@@ -636,6 +838,15 @@
         "cssom": "0.3.2"
       }
     },
+    "d": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
+      "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
+      "dev": true,
+      "requires": {
+        "es5-ext": "0.10.30"
+      }
+    },
     "dashdash": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -653,6 +864,11 @@
         }
       }
     },
+    "de-indent": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+      "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
+    },
     "debug": {
       "version": "2.6.8",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
@@ -683,6 +899,29 @@
         "strip-bom": "2.0.0"
       }
     },
+    "del": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
+      "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
+      "dev": true,
+      "requires": {
+        "globby": "5.0.0",
+        "is-path-cwd": "1.0.0",
+        "is-path-in-cwd": "1.0.0",
+        "object-assign": "4.1.1",
+        "pify": "2.3.0",
+        "pinkie-promise": "2.0.1",
+        "rimraf": "2.6.2"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        }
+      }
+    },
     "delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -704,6 +943,59 @@
       "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==",
       "dev": true
     },
+    "doctrine": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
+      "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
+      "dev": true,
+      "requires": {
+        "esutils": "2.0.2",
+        "isarray": "1.0.0"
+      }
+    },
+    "dom-serializer": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+      "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
+      "dev": true,
+      "requires": {
+        "domelementtype": "1.1.3",
+        "entities": "1.1.1"
+      },
+      "dependencies": {
+        "domelementtype": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
+          "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
+          "dev": true
+        }
+      }
+    },
+    "domelementtype": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
+      "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=",
+      "dev": true
+    },
+    "domhandler": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz",
+      "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=",
+      "dev": true,
+      "requires": {
+        "domelementtype": "1.3.0"
+      }
+    },
+    "domutils": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz",
+      "integrity": "sha1-GVjMC0yUJuntNn+xyOhUiRsPo/8=",
+      "dev": true,
+      "requires": {
+        "dom-serializer": "0.1.0",
+        "domelementtype": "1.3.0"
+      }
+    },
     "ecc-jsbn": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
@@ -714,6 +1006,39 @@
         "jsbn": "0.1.1"
       }
     },
+    "editorconfig": {
+      "version": "0.13.3",
+      "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz",
+      "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==",
+      "requires": {
+        "bluebird": "3.5.0",
+        "commander": "2.11.0",
+        "lru-cache": "3.2.0",
+        "semver": "5.4.1",
+        "sigmund": "1.0.1"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
+          "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=",
+          "requires": {
+            "pseudomap": "1.0.2"
+          }
+        }
+      }
+    },
+    "emojis-list": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+      "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
+    },
+    "entities": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
+      "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
+      "dev": true
+    },
     "errno": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz",
@@ -732,11 +1057,80 @@
         "is-arrayish": "0.2.1"
       }
     },
+    "es5-ext": {
+      "version": "0.10.30",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.30.tgz",
+      "integrity": "sha1-cUGhaDZpfbq/qq7uQUlc4p9SyTk=",
+      "dev": true,
+      "requires": {
+        "es6-iterator": "2.0.1",
+        "es6-symbol": "3.1.1"
+      }
+    },
+    "es6-iterator": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz",
+      "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
+      "dev": true,
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.30",
+        "es6-symbol": "3.1.1"
+      }
+    },
+    "es6-map": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
+      "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
+      "dev": true,
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.30",
+        "es6-iterator": "2.0.1",
+        "es6-set": "0.1.5",
+        "es6-symbol": "3.1.1",
+        "event-emitter": "0.3.5"
+      }
+    },
+    "es6-set": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
+      "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
+      "dev": true,
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.30",
+        "es6-iterator": "2.0.1",
+        "es6-symbol": "3.1.1",
+        "event-emitter": "0.3.5"
+      }
+    },
+    "es6-symbol": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
+      "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
+      "dev": true,
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.30"
+      }
+    },
+    "es6-weak-map": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
+      "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
+      "dev": true,
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.30",
+        "es6-iterator": "2.0.1",
+        "es6-symbol": "3.1.1"
+      }
+    },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
-      "dev": true
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
     },
     "escodegen": {
       "version": "1.9.0",
@@ -756,6 +1150,305 @@
           "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
           "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
           "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true,
+          "optional": true
+        }
+      }
+    },
+    "escope": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
+      "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
+      "dev": true,
+      "requires": {
+        "es6-map": "0.1.5",
+        "es6-weak-map": "2.0.2",
+        "esrecurse": "4.2.0",
+        "estraverse": "4.2.0"
+      }
+    },
+    "eslint": {
+      "version": "3.19.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz",
+      "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=",
+      "dev": true,
+      "requires": {
+        "babel-code-frame": "6.26.0",
+        "chalk": "1.1.3",
+        "concat-stream": "1.6.0",
+        "debug": "2.6.8",
+        "doctrine": "2.0.0",
+        "escope": "3.6.0",
+        "espree": "3.5.1",
+        "esquery": "1.0.0",
+        "estraverse": "4.2.0",
+        "esutils": "2.0.2",
+        "file-entry-cache": "2.0.0",
+        "glob": "7.1.2",
+        "globals": "9.18.0",
+        "ignore": "3.3.5",
+        "imurmurhash": "0.1.4",
+        "inquirer": "0.12.0",
+        "is-my-json-valid": "2.16.1",
+        "is-resolvable": "1.0.0",
+        "js-yaml": "3.10.0",
+        "json-stable-stringify": "1.0.1",
+        "levn": "0.3.0",
+        "lodash": "4.17.4",
+        "mkdirp": "0.5.1",
+        "natural-compare": "1.4.0",
+        "optionator": "0.8.2",
+        "path-is-inside": "1.0.2",
+        "pluralize": "1.2.1",
+        "progress": "1.1.8",
+        "require-uncached": "1.0.3",
+        "shelljs": "0.7.8",
+        "strip-bom": "3.0.0",
+        "strip-json-comments": "2.0.1",
+        "table": "3.8.3",
+        "text-table": "0.2.0",
+        "user-home": "2.0.0"
+      },
+      "dependencies": {
+        "ajv-keywords": {
+          "version": "1.5.1",
+          "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz",
+          "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=",
+          "dev": true
+        },
+        "ansi-escapes": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
+          "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "2.2.1",
+            "escape-string-regexp": "1.0.5",
+            "has-ansi": "2.0.0",
+            "strip-ansi": "3.0.1",
+            "supports-color": "2.0.0"
+          }
+        },
+        "cli-cursor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
+          "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
+          "dev": true,
+          "requires": {
+            "restore-cursor": "1.0.1"
+          }
+        },
+        "figures": {
+          "version": "1.7.0",
+          "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+          "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+          "dev": true,
+          "requires": {
+            "escape-string-regexp": "1.0.5",
+            "object-assign": "4.1.1"
+          }
+        },
+        "inquirer": {
+          "version": "0.12.0",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
+          "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=",
+          "dev": true,
+          "requires": {
+            "ansi-escapes": "1.4.0",
+            "ansi-regex": "2.1.1",
+            "chalk": "1.1.3",
+            "cli-cursor": "1.0.2",
+            "cli-width": "2.2.0",
+            "figures": "1.7.0",
+            "lodash": "4.17.4",
+            "readline2": "1.0.1",
+            "run-async": "0.1.0",
+            "rx-lite": "3.1.2",
+            "string-width": "1.0.2",
+            "strip-ansi": "3.0.1",
+            "through": "2.3.8"
+          }
+        },
+        "onetime": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+          "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
+          "dev": true
+        },
+        "pluralize": {
+          "version": "1.2.1",
+          "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz",
+          "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=",
+          "dev": true
+        },
+        "progress": {
+          "version": "1.1.8",
+          "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+          "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
+          "dev": true
+        },
+        "restore-cursor": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
+          "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
+          "dev": true,
+          "requires": {
+            "exit-hook": "1.1.1",
+            "onetime": "1.1.0"
+          }
+        },
+        "run-async": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
+          "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=",
+          "dev": true,
+          "requires": {
+            "once": "1.4.0"
+          }
+        },
+        "rx-lite": {
+          "version": "3.1.2",
+          "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz",
+          "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=",
+          "dev": true
+        },
+        "slice-ansi": {
+          "version": "0.0.4",
+          "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+          "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "1.1.0",
+            "is-fullwidth-code-point": "1.0.0",
+            "strip-ansi": "3.0.1"
+          }
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "2.1.1"
+          }
+        },
+        "strip-bom": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+          "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        },
+        "table": {
+          "version": "3.8.3",
+          "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
+          "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=",
+          "dev": true,
+          "requires": {
+            "ajv": "4.11.8",
+            "ajv-keywords": "1.5.1",
+            "chalk": "1.1.3",
+            "lodash": "4.17.4",
+            "slice-ansi": "0.0.4",
+            "string-width": "2.1.1"
+          },
+          "dependencies": {
+            "ansi-regex": {
+              "version": "3.0.0",
+              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+              "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+              "dev": true
+            },
+            "is-fullwidth-code-point": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+              "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+              "dev": true
+            },
+            "string-width": {
+              "version": "2.1.1",
+              "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+              "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+              "dev": true,
+              "requires": {
+                "is-fullwidth-code-point": "2.0.0",
+                "strip-ansi": "4.0.0"
+              }
+            },
+            "strip-ansi": {
+              "version": "4.0.0",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+              "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+              "dev": true,
+              "requires": {
+                "ansi-regex": "3.0.0"
+              }
+            }
+          }
+        }
+      }
+    },
+    "eslint-plugin-html": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-2.0.3.tgz",
+      "integrity": "sha1-fImIOrDIX6XSi2ZqFKTpBqqQuJc=",
+      "dev": true,
+      "requires": {
+        "htmlparser2": "3.9.2"
+      }
+    },
+    "eslint-plugin-vue-libs": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vue-libs/-/eslint-plugin-vue-libs-1.2.1.tgz",
+      "integrity": "sha512-GSmpfPqSX4d1Etnuh/w98qLw1voc1HYAF9nqROUTwKVz/d3mJEnuR9rJn8XDzBG1ddErBgO11kLI3TUVMNdJ+A==",
+      "dev": true,
+      "requires": {
+        "eslint-plugin-html": "2.0.3"
+      }
+    },
+    "espree": {
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.1.tgz",
+      "integrity": "sha1-DJiLirRttTEAoZVK5LqZXd0n2H4=",
+      "dev": true,
+      "requires": {
+        "acorn": "5.1.2",
+        "acorn-jsx": "3.0.1"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz",
+          "integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==",
+          "dev": true
         }
       }
     },
@@ -765,6 +1458,25 @@
       "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
       "dev": true
     },
+    "esquery": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
+      "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
+      "dev": true,
+      "requires": {
+        "estraverse": "4.2.0"
+      }
+    },
+    "esrecurse": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz",
+      "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=",
+      "dev": true,
+      "requires": {
+        "estraverse": "4.2.0",
+        "object-assign": "4.1.1"
+      }
+    },
     "estraverse": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
@@ -777,6 +1489,16 @@
       "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
       "dev": true
     },
+    "event-emitter": {
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+      "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
+      "dev": true,
+      "requires": {
+        "d": "1.0.0",
+        "es5-ext": "0.10.30"
+      }
+    },
     "exec-sh": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.1.tgz",
@@ -801,6 +1523,12 @@
         "strip-eof": "1.0.0"
       }
     },
+    "exit-hook": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
+      "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
+      "dev": true
+    },
     "expand-brackets": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
@@ -860,6 +1588,11 @@
       "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
       "dev": true
     },
+    "fastparse": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
+      "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg="
+    },
     "fb-watchman": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz",
@@ -869,6 +1602,16 @@
         "bser": "2.0.0"
       }
     },
+    "file-entry-cache": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
+      "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
+      "dev": true,
+      "requires": {
+        "flat-cache": "1.3.0",
+        "object-assign": "4.1.1"
+      }
+    },
     "filename-regex": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
@@ -907,6 +1650,23 @@
         "locate-path": "2.0.0"
       }
     },
+    "flat-cache": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz",
+      "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=",
+      "dev": true,
+      "requires": {
+        "circular-json": "0.3.3",
+        "del": "2.2.2",
+        "graceful-fs": "4.1.11",
+        "write": "0.2.1"
+      }
+    },
+    "flatten": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz",
+      "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I="
+    },
     "for-in": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -1714,22 +2474,22 @@
             }
           }
         },
-        "string-width": {
-          "version": "1.0.2",
+        "string_decoder": {
+          "version": "1.0.1",
           "bundled": true,
           "dev": true,
           "requires": {
-            "code-point-at": "1.1.0",
-            "is-fullwidth-code-point": "1.0.0",
-            "strip-ansi": "3.0.1"
+            "safe-buffer": "5.0.1"
           }
         },
-        "string_decoder": {
-          "version": "1.0.1",
+        "string-width": {
+          "version": "1.0.2",
           "bundled": true,
           "dev": true,
           "requires": {
-            "safe-buffer": "5.0.1"
+            "code-point-at": "1.1.0",
+            "is-fullwidth-code-point": "1.0.0",
+            "strip-ansi": "3.0.1"
           }
         },
         "stringstream": {
@@ -1844,6 +2604,29 @@
         }
       }
     },
+    "generate-function": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+      "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
+      "dev": true
+    },
+    "generate-object-property": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+      "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+      "dev": true,
+      "requires": {
+        "is-property": "1.0.2"
+      }
+    },
+    "generic-names": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-1.0.2.tgz",
+      "integrity": "sha1-4lt/7OtbWo8o9flyp8z+V+Virc0=",
+      "requires": {
+        "loader-utils": "0.2.17"
+      }
+    },
     "get-caller-file": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
@@ -1912,6 +2695,28 @@
       "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
       "dev": true
     },
+    "globby": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
+      "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
+      "dev": true,
+      "requires": {
+        "array-union": "1.0.2",
+        "arrify": "1.0.1",
+        "glob": "7.1.2",
+        "object-assign": "4.1.1",
+        "pify": "2.3.0",
+        "pinkie-promise": "2.0.1"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        }
+      }
+    },
     "graceful-fs": {
       "version": "4.1.11",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
@@ -1973,7 +2778,6 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
       "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
-      "dev": true,
       "requires": {
         "ansi-regex": "2.1.1"
       }
@@ -1981,8 +2785,12 @@
     "has-flag": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
-      "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
-      "dev": true
+      "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE="
+    },
+    "hash-sum": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
+      "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ="
     },
     "hawk": {
       "version": "3.1.3",
@@ -1996,6 +2804,11 @@
         "sntp": "1.0.9"
       }
     },
+    "he": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+      "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0="
+    },
     "hoek": {
       "version": "2.16.3",
       "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
@@ -2027,6 +2840,20 @@
         "whatwg-encoding": "1.0.1"
       }
     },
+    "htmlparser2": {
+      "version": "3.9.2",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz",
+      "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=",
+      "dev": true,
+      "requires": {
+        "domelementtype": "1.3.0",
+        "domhandler": "2.4.1",
+        "domutils": "1.6.2",
+        "entities": "1.1.1",
+        "inherits": "2.0.3",
+        "readable-stream": "2.3.3"
+      }
+    },
     "http-signature": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
@@ -2044,12 +2871,28 @@
       "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=",
       "dev": true
     },
+    "icss-replace-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
+      "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0="
+    },
+    "ignore": {
+      "version": "3.3.5",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.5.tgz",
+      "integrity": "sha512-JLH93mL8amZQhh/p6mfQgVBH3M6epNq3DfsXsTSuSrInVjwyYlFE1nv2AgfRCC8PoOhM0jwQ5v8s9LgbK7yGDw==",
+      "dev": true
+    },
     "imurmurhash": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
       "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
       "dev": true
     },
+    "indexes-of": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
+      "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc="
+    },
     "inflight": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -2066,6 +2909,17 @@
       "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
       "dev": true
     },
+    "ini": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
+      "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4="
+    },
+    "interpret": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.4.tgz",
+      "integrity": "sha1-ggzdWIuGj/sZGoCVBtbJyPISsbA=",
+      "dev": true
+    },
     "invariant": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
@@ -2165,6 +3019,18 @@
         "is-extglob": "1.0.0"
       }
     },
+    "is-my-json-valid": {
+      "version": "2.16.1",
+      "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz",
+      "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==",
+      "dev": true,
+      "requires": {
+        "generate-function": "2.0.0",
+        "generate-object-property": "1.2.0",
+        "jsonpointer": "4.0.1",
+        "xtend": "4.0.1"
+      }
+    },
     "is-number": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
@@ -2174,6 +3040,30 @@
         "kind-of": "3.2.2"
       }
     },
+    "is-path-cwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
+      "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+      "dev": true
+    },
+    "is-path-in-cwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
+      "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
+      "dev": true,
+      "requires": {
+        "is-path-inside": "1.0.0"
+      }
+    },
+    "is-path-inside": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
+      "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=",
+      "dev": true,
+      "requires": {
+        "path-is-inside": "1.0.2"
+      }
+    },
     "is-posix-bracket": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
@@ -2186,6 +3076,21 @@
       "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
       "dev": true
     },
+    "is-property": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+      "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
+      "dev": true
+    },
+    "is-resolvable": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
+      "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=",
+      "dev": true,
+      "requires": {
+        "tryit": "1.0.3"
+      }
+    },
     "is-stream": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
@@ -2320,6 +3225,14 @@
         "mkdirp": "0.5.1",
         "rimraf": "2.6.2",
         "source-map": "0.5.7"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
       }
     },
     "istanbul-reports": {
@@ -2628,6 +3541,17 @@
         "pretty-format": "21.1.0"
       }
     },
+    "js-beautify": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.3.tgz",
+      "integrity": "sha512-mT9skIu0OWfBQPXOGJ4CgpPBgo3tj9gxi7weQdeaxxmpKIADK2g0xS0qCtQml7Ny3Ick5Cno093LKGZTzDd2UQ==",
+      "requires": {
+        "config-chain": "1.1.11",
+        "editorconfig": "0.13.3",
+        "mkdirp": "0.5.1",
+        "nopt": "3.0.6"
+      }
+    },
     "js-tokens": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
@@ -2708,8 +3632,7 @@
     "json5": {
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
-      "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
-      "dev": true
+      "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
     },
     "jsonify": {
       "version": "0.0.0",
@@ -2717,6 +3640,12 @@
       "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
       "dev": true
     },
+    "jsonpointer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
+      "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
+      "dev": true
+    },
     "jsprim": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -2799,6 +3728,17 @@
         }
       }
     },
+    "loader-utils": {
+      "version": "0.2.17",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
+      "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
+      "requires": {
+        "big.js": "3.2.0",
+        "emojis-list": "2.1.0",
+        "json5": "0.5.1",
+        "object-assign": "4.1.1"
+      }
+    },
     "locate-path": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@@ -2815,6 +3755,11 @@
       "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=",
       "dev": true
     },
+    "lodash.defaultsdeep": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz",
+      "integrity": "sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E="
+    },
     "longest": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
@@ -2834,7 +3779,6 @@
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
       "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
-      "dev": true,
       "requires": {
         "pseudomap": "1.0.2",
         "yallist": "2.1.2"
@@ -2918,14 +3862,12 @@
     "minimist": {
       "version": "0.0.8",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
-      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
-      "dev": true
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
     },
     "mkdirp": {
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
-      "dev": true,
       "requires": {
         "minimist": "0.0.8"
       }
@@ -2967,6 +3909,14 @@
         "which": "1.3.0"
       }
     },
+    "nopt": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+      "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+      "requires": {
+        "abbrev": "1.1.1"
+      }
+    },
     "normalize-package-data": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@@ -3018,8 +3968,7 @@
     "object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
-      "dev": true
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
     },
     "object.omit": {
       "version": "2.0.1",
@@ -3161,6 +4110,12 @@
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
       "dev": true
     },
+    "path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+      "dev": true
+    },
     "path-key": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
@@ -3219,6 +4174,79 @@
         "pinkie": "2.0.4"
       }
     },
+    "postcss": {
+      "version": "6.0.12",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.12.tgz",
+      "integrity": "sha512-K6SLofXEK43FBSyZ6/ExQV7ji24OEw4tEY6x1CAf7+tcoMWJoO24Rf3rVFVpk+5IQL1e1Cy3sTKfg7hXuLzafg==",
+      "requires": {
+        "chalk": "2.1.0",
+        "source-map": "0.5.7",
+        "supports-color": "4.4.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+        }
+      }
+    },
+    "postcss-modules": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-0.8.0.tgz",
+      "integrity": "sha1-qdAYBt/+GcJgfe6I6hy9gNwFmZI=",
+      "requires": {
+        "css-modules-loader-core": "1.1.0",
+        "generic-names": "1.0.2",
+        "postcss": "6.0.12",
+        "string-hash": "1.1.3"
+      }
+    },
+    "postcss-modules-extract-imports": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz",
+      "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=",
+      "requires": {
+        "postcss": "6.0.12"
+      }
+    },
+    "postcss-modules-local-by-default": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz",
+      "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=",
+      "requires": {
+        "css-selector-tokenizer": "0.7.0",
+        "postcss": "6.0.12"
+      }
+    },
+    "postcss-modules-scope": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz",
+      "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=",
+      "requires": {
+        "css-selector-tokenizer": "0.7.0",
+        "postcss": "6.0.12"
+      }
+    },
+    "postcss-modules-values": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz",
+      "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=",
+      "requires": {
+        "icss-replace-symbols": "1.1.0",
+        "postcss": "6.0.12"
+      }
+    },
+    "postcss-selector-parser": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz",
+      "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=",
+      "requires": {
+        "flatten": "1.0.2",
+        "indexes-of": "1.0.1",
+        "uniq": "1.0.1"
+      }
+    },
     "prelude-ls": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@@ -3255,6 +4283,17 @@
       "integrity": "sha1-aM5eih7woju1cMwoU3tTMqumPvE=",
       "dev": true
     },
+    "process-nextick-args": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+      "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
+      "dev": true
+    },
+    "proto-list": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+      "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk="
+    },
     "prr": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz",
@@ -3264,8 +4303,7 @@
     "pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
-      "dev": true
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
     },
     "punycode": {
       "version": "1.4.1",
@@ -3362,6 +4400,54 @@
         }
       }
     },
+    "readable-stream": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
+      "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
+      "dev": true,
+      "requires": {
+        "core-util-is": "1.0.2",
+        "inherits": "2.0.3",
+        "isarray": "1.0.0",
+        "process-nextick-args": "1.0.7",
+        "safe-buffer": "5.1.1",
+        "string_decoder": "1.0.3",
+        "util-deprecate": "1.0.2"
+      }
+    },
+    "readline2": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz",
+      "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=",
+      "dev": true,
+      "requires": {
+        "code-point-at": "1.1.0",
+        "is-fullwidth-code-point": "1.0.0",
+        "mute-stream": "0.0.5"
+      },
+      "dependencies": {
+        "mute-stream": {
+          "version": "0.0.5",
+          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
+          "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
+          "dev": true
+        }
+      }
+    },
+    "rechoir": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
+      "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
+      "dev": true,
+      "requires": {
+        "resolve": "1.1.7"
+      }
+    },
+    "regenerate": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
+      "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg=="
+    },
     "regenerator-runtime": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz",
@@ -3377,6 +4463,36 @@
         "is-equal-shallow": "0.1.3"
       }
     },
+    "regexpu-core": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
+      "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
+      "requires": {
+        "regenerate": "1.3.3",
+        "regjsgen": "0.2.0",
+        "regjsparser": "0.1.5"
+      }
+    },
+    "regjsgen": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+      "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc="
+    },
+    "regjsparser": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+      "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+      "requires": {
+        "jsesc": "0.5.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
+        }
+      }
+    },
     "remove-trailing-separator": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -3446,12 +4562,28 @@
       "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
       "dev": true
     },
+    "require-uncached": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
+      "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
+      "dev": true,
+      "requires": {
+        "caller-path": "0.1.0",
+        "resolve-from": "1.0.1"
+      }
+    },
     "resolve": {
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
       "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
       "dev": true
     },
+    "resolve-from": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
+      "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
+      "dev": true
+    },
     "right-align": {
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
@@ -3510,8 +4642,7 @@
     "semver": {
       "version": "5.4.1",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
-      "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
-      "dev": true
+      "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
     },
     "set-blocking": {
       "version": "2.0.0",
@@ -3534,12 +4665,28 @@
       "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
       "dev": true
     },
+    "shelljs": {
+      "version": "0.7.8",
+      "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz",
+      "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
+      "dev": true,
+      "requires": {
+        "glob": "7.1.2",
+        "interpret": "1.0.4",
+        "rechoir": "0.6.2"
+      }
+    },
     "shellwords": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
       "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
       "dev": true
     },
+    "sigmund": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
+      "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA="
+    },
     "signal-exit": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@@ -3562,10 +4709,9 @@
       }
     },
     "source-map": {
-      "version": "0.5.7",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
-      "dev": true
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
     },
     "source-map-support": {
       "version": "0.4.18",
@@ -3574,6 +4720,14 @@
       "dev": true,
       "requires": {
         "source-map": "0.5.7"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
       }
     },
     "spdx-correct": {
@@ -3627,6 +4781,20 @@
         }
       }
     },
+    "string_decoder": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+      "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "5.1.1"
+      }
+    },
+    "string-hash": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
+      "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs="
+    },
     "string-length": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz",
@@ -3693,11 +4861,16 @@
       "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
       "dev": true
     },
+    "strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+      "dev": true
+    },
     "supports-color": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
       "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
-      "dev": true,
       "requires": {
         "has-flag": "2.0.0"
       }
@@ -3721,12 +4894,24 @@
         "require-main-filename": "1.0.1"
       }
     },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+      "dev": true
+    },
     "throat": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz",
       "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=",
       "dev": true
     },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
     "tmpl": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
@@ -3760,6 +4945,12 @@
       "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
       "dev": true
     },
+    "tryit": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
+      "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
+      "dev": true
+    },
     "tunnel-agent": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -3785,6 +4976,12 @@
         "prelude-ls": "1.1.2"
       }
     },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "dev": true
+    },
     "uglify-js": {
       "version": "2.8.29",
       "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
@@ -3797,6 +4994,13 @@
         "yargs": "3.10.0"
       },
       "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true,
+          "optional": true
+        },
         "yargs": {
           "version": "3.10.0",
           "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
@@ -3819,6 +5023,26 @@
       "dev": true,
       "optional": true
     },
+    "uniq": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
+      "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8="
+    },
+    "user-home": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz",
+      "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=",
+      "dev": true,
+      "requires": {
+        "os-homedir": "1.0.2"
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+      "dev": true
+    },
     "uuid": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
@@ -3854,6 +5078,25 @@
         }
       }
     },
+    "vue-hot-reload-api": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.1.0.tgz",
+      "integrity": "sha1-nKWKbg35B4VUzhcIaItleHVNht4="
+    },
+    "vue-template-compiler": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.4.4.tgz",
+      "integrity": "sha512-XdHsNi8Z5WqwuFl/Z5eLKgE2DOEEOdMk1aA459uSgvwyy+pjKLBlQWsUpAtoR6o6Wmpujw6NtinAUGuqSTituQ==",
+      "requires": {
+        "de-indent": "1.0.2",
+        "he": "1.1.1"
+      }
+    },
+    "vue-template-es2015-compiler": {
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.5.3.tgz",
+      "integrity": "sha512-j3TBDtjqz7pC9XUzeSeqF5oekqPahxyUHsdG+kZKDH/V/DTexq5inYdLGstnqCUljoLC9LTj3H/5hmyToeSd1A=="
+    },
     "walker": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
@@ -3978,6 +5221,15 @@
       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
       "dev": true
     },
+    "write": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
+      "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
+      "dev": true,
+      "requires": {
+        "mkdirp": "0.5.1"
+      }
+    },
     "write-file-atomic": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz",
@@ -4010,8 +5262,7 @@
     "yallist": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
-      "dev": true
+      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
     },
     "yargs": {
       "version": "9.0.1",
diff --git a/package.json b/package.json
index 5dc1343..bfe52ba 100644
--- a/package.json
+++ b/package.json
@@ -2,9 +2,11 @@
   "name": "vue-component-compiler",
   "version": "3.0.0",
   "description": "bundler agnostic API for compiling Vue SFC",
-  "main": "index.js",
+  "main": "src/index.js",
   "scripts": {
-    "test": "jest"
+    "test": "jest",
+    "lint": "eslint src test",
+    "lint:fix": "eslint src test --fix"
   },
   "repository": {
     "type": "git",
@@ -20,8 +22,27 @@
   "bugs": {
     "url": "https://github.com/vuejs/vue-component-compiler/issues"
   },
+  "typings": "index.d.ts",
   "homepage": "https://github.com/vuejs/vue-component-compiler#readme",
   "devDependencies": {
+    "eslint": "^3.19.0",
+    "eslint-plugin-vue-libs": "^1.2.0",
     "jest": "^21.1.0"
+  },
+  "peerDependencies": {
+    "vue-template-compiler": "*"
+  },
+  "dependencies": {
+    "hash-sum": "^1.0.2",
+    "js-beautify": "^1.7.3",
+    "lodash.defaultsdeep": "^4.6.0",
+    "lru-cache": "^4.1.1",
+    "postcss": "^6.0.12",
+    "postcss-modules": "^0.8.0",
+    "postcss-selector-parser": "^2.2.3",
+    "source-map": "^0.6.1",
+    "vue-hot-reload-api": "^2.1.0",
+    "vue-template-compiler": "^2.4.4",
+    "vue-template-es2015-compiler": "^1.5.3"
   }
 }
diff --git a/src/assemble.js b/src/assemble.js
new file mode 100644
index 0000000..6ebd4f4
--- /dev/null
+++ b/src/assemble.js
@@ -0,0 +1,253 @@
+const defaults = require('lodash.defaultsdeep')
+const hash = require('hash-sum')
+
+const DISPOSED = 'disposed'
+const INJECT_STYLE_FN = 'injectStyle'
+const CSS_MODULES = 'cssModules'
+
+function _s (any) {
+  return JSON.stringify(any)
+}
+
+// eslint-disable-next-line camelcase
+function __vue_type__ (type, id, esModule, addPrefix = true) {
+  let output = addPrefix ? `\n/* ${type} */\n` : ''
+  if (id) {
+    if (esModule) {
+      output += `import __vue_${type}__ from ${_s(id)}\n`
+    } else {
+      output += `var __vue_${type}__ = require(${_s(id)})\n`
+    }
+  } else {
+    output += `var __vue_${type}__ = null\n`
+  }
+  return output
+}
+
+module.exports = function assemble (source, filename, config) {
+  config = defaults({}, config, {
+    esModule: true,
+    shortFilePath: filename,
+    require: {
+      vueHotReloadAPI: 'vue-hot-reload-api',
+      normalizeComponent: 'vue-component-compiler/src/runtime/normalize-component'
+    },
+    scopeId: null,
+    moduleIdentifier: null,
+    isHot: false,
+    isServer: false,
+    isProduction: true,
+    isInjectable: false,
+    hasStyleInjectFn: false,
+    onWarn: message => console.warn(message)
+  })
+
+  let output = ''
+  const { script, render, styles, customBlocks } = source
+  const needsHotReload = !config.isProduction && config.isHot
+  const hasScoped = styles.some(style => style.descriptor.scoped)
+
+  if (config.isInjectable) config.esModule = false
+
+  if (needsHotReload) output += `var ${DISPOSED} = false\n`
+
+  let cssModules
+  if (styles.length) {
+    let styleInjectionCode = ''
+
+    if (needsHotReload) styleInjectionCode += `if (${DISPOSED}) return\n`
+
+    styles.forEach((style, i) => {
+      const IMPORT_NAME = `__vue_style_${i}__`
+      const IMPORT_STRING = _s(style.id)
+      const moduleName = (style.descriptor.module === true) ? '$style' : style.descriptor.module
+      const needsStyleInjection = config.hasStyleInjectFn
+      const needsNamedImport = needsStyleInjection || typeof moduleName === 'string'
+      const runInjection = needsStyleInjection ? `${IMPORT_NAME} && ${IMPORT_NAME}.__inject__ && ${IMPORT_NAME}.__inject__(ssrContext)\n` : ''
+
+      if (needsNamedImport) {
+        output += config.esModule
+          ? `import ${IMPORT_NAME} from ${IMPORT_STRING}\n`
+          : `const ${IMPORT_NAME} = require(${IMPORT_STRING})\n`
+      } else {
+        output += config.esModule
+          ? `import ${IMPORT_STRING}\n`
+          : `require(${IMPORT_STRING})\n`
+      }
+
+      if (moduleName) {
+        if (!cssModules) {
+          cssModules = {}
+          if (needsHotReload) {
+            output += `var ${CSS_MODULES} = {}\n`
+          }
+        }
+        if (moduleName in cssModules) {
+          config.onWarn({
+            message: 'CSS module name "' + moduleName + '" is not unique!'
+          })
+          styleInjectionCode += runInjection
+        } else {
+          cssModules[moduleName] = true
+          const MODULE_KEY = _s(moduleName)
+
+          if (!needsHotReload) {
+            styleInjectionCode += runInjection + `this[${MODULE_KEY}] = ${IMPORT_NAME}\n`
+          } else {
+            styleInjectionCode += runInjection +
+              `${CSS_MODULES}[${MODULE_KEY}] = ${IMPORT_NAME}\n` +
+              `Object.defineProperty(this, ${MODULE_KEY}, { get: function () { return ${CSS_MODULES}[${MODULE_KEY}] }})\n`
+
+            output +=
+              `module.hot && module.hot.accept([${_s(style.hotPath || style.id)}], function () {\n` +
+              // 1. check if style has been injected
+              `  var oldLocals = ${CSS_MODULES}[${MODULE_KEY}]\n` +
+              `  if (!oldLocals) return\n` +
+              // 2. re-import (side effect: updates the <style>)
+              `  var newLocals = require(${IMPORT_STRING})\n` +
+              // 3. compare new and old locals to see if selectors changed
+              `  if (JSON.stringify(newLocals) === JSON.stringify(oldLocals)) return\n` +
+              // 4. locals changed. Update and force re-render.
+              `  ${CSS_MODULES}[${MODULE_KEY}] = newLocals\n` +
+              `  require(${_s(config.require.vueHotReloadAPI)}).rerender(${_s(config.moduleId)})\n` +
+              `})\n`
+          }
+        }
+      } else {
+        styleInjectionCode += runInjection
+      }
+    })
+    output += `function ${INJECT_STYLE_FN} (ssrContext) {\n` + pad(styleInjectionCode) + `}\n`
+  }
+
+  // we require the component normalizer function, and call it like so:
+  // normalizeComponent(
+  //   scriptExports,
+  //   compiledTemplate,
+  //   injectStyles,
+  //   scopeId,
+  //   moduleIdentifier (server only)
+  // )
+  output += config.esModule
+    ? `import normalizeComponent from ${_s(config.require.normalizeComponent)}\n`
+    : `var normalizeComponent = require(${_s(config.require.normalizeComponent)})\n`
+  // <script>
+  output += __vue_type__('script', script.id, config.esModule)
+  if (config.isInjectable) {
+    output +=
+      `if (__vue_script__) { __vue_script__ = __vue_script__(injections) }\n`
+  }
+
+  // <template>
+  output += __vue_type__('template', render.id, config.esModule)
+
+  // style
+  output += '\n/* styles */\n'
+  output += 'var __vue_styles__ = ' + (styles.length ? 'injectStyle' : 'null') + '\n'
+
+  // scopeId
+  output += '\n/* scopeId */\n'
+  output += 'var __vue_scopeId__ = ' + (hasScoped ? _s(config.scopeId) : 'null') + '\n'
+
+  // moduleIdentifier (server only)
+  output += '\n/* moduleIdentifier (server only) */\n'
+  output += 'var __vue_module_identifier__ = ' + (config.isServer ? _s(config.moduleIdentifier) : 'null') + '\n'
+
+  // close normalizeComponent call
+  output += '\nvar Component = normalizeComponent(\n' +
+  '  __vue_script__,\n' +
+  '  __vue_template__,\n' +
+  '  __vue_styles__,\n' +
+  '  __vue_scopeId__,\n' +
+  '  __vue_module_identifier__\n' +
+  ')\n'
+
+  // development-only code
+  if (!config.isProduction) {
+    // add filename in dev
+    output += `Component.options.__file = ${_s(config.shortFilePath)}\n`
+    // check named exports
+    output +=
+      `if (Component.esModule && Object.keys(Component.esModule).some(function (key) {\n` +
+      `  return key !== "default" && key.substr(0, 2) !== "__"\n` +
+      `})) {\n` +
+      `  console.error("named exports are not supported in *.vue files.")\n` +
+      `}\n`
+    // check functional components used with templates
+    if (render.id) {
+      output +=
+        'if (Component.options.functional) {\n' +
+        '  console.error("' +
+          '[vue-component-compiler] ' + filename + ': functional components are not ' +
+          'supported with templates, they should use render functions.' +
+        '")\n}\n'
+    }
+  }
+
+  if (customBlocks.length) {
+    let addedPrefix = false
+    customBlocks.forEach((customBlock, i) => {
+      const TYPE = `customBlock_${customBlock.descriptor.type}_${i}`
+      const BLOCK = `__vue_${TYPE}__`
+      if (!addedPrefix) output += `\n/* Custom Blocks */\n`
+      output += __vue_type__(TYPE, customBlock.id, config.esModule, false)
+      output += `if (typeof ${BLOCK} === 'function') { ${BLOCK}(Component) }\n`
+      addedPrefix = true
+    })
+  }
+
+  if (!config.isInjectable) {
+    if (needsHotReload) {
+      output +=
+        `\n/* hot reload */\n` +
+        `if (module.hot) { (function () {\n` +
+        `  var hotAPI = require(${_s(config.require.vueHotReloadAPI)})\n` +
+        `  hotAPI.install(require('vue'), false)\n` +
+        `  if (!hotAPI.compatible) return\n` +
+        `  module.hot.accept()\n` +
+        `  if (!module.hot.data) {\n` +
+        // initial insert
+        `    hotAPI.createRecord(${_s(config.moduleId)}, Component.options)\n` +
+        `  } else {\n`
+        // update
+      if (cssModules) {
+        output +=
+        `    if (module.hot.data.cssModules && Object.keys(module.hot.data.cssModules) !== Object.keys(cssModules)) {\n` +
+        `      delete Component.options._Ctor\n` +
+        `    }\n`
+      }
+
+      output +=
+        `    hotAPI.reload(${_s(config.moduleId)}, Component.options)\n` +
+        `  }\n`
+
+      // dispose
+      output +=
+        `  module.hot.dispose(function (data) {\n` +
+        (cssModules ? `    data.cssModules = cssModules\n` : '') +
+        `    disposed = true\n` +
+        `  })\n`
+
+      output +=
+        `})()}\n`
+    }
+
+    if (config.esModule) {
+      output += `\nexport default Component.options\n`
+    } else {
+      output += `\nmodule.exports = Component.exports\n`
+    }
+  } else {
+    output =
+      `\n/* dependency injection */\n` +
+      `module.exports = function (injections) {\n${pad(output)}\n` +
+      `  return Component.exports\n` +
+      `}\n`
+  }
+
+  return output
+}
+
+function pad (content) {
+  return content.trim().split('\n').map(line => '  ' + line).join('\n') + '\n'
+}
diff --git a/src/gen-id.js b/src/gen-id.js
new file mode 100644
index 0000000..747cc8d
--- /dev/null
+++ b/src/gen-id.js
@@ -0,0 +1,14 @@
+// utility for generating a uid for each component file
+// used in scoped CSS rewriting
+const path = require('path')
+const hash = require('hash-sum')
+const cache = Object.create(null)
+const sepRE = new RegExp(path.sep.replace('\\', '\\\\'), 'g')
+
+module.exports = function genId (file, context, key) {
+  const contextPath = context.split(path.sep)
+  const rootId = contextPath[contextPath.length - 1]
+  file = rootId + '/' + path.relative(context, file).replace(sepRE, '/') + (key || '')
+
+  return cache[file] || (cache[file] = hash(file))
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..f22386c
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,7 @@
+module.exports = {
+  parse: require('./parser'),
+  compileStyle: require('./style-compiler'),
+  compileTemplate: require('./template-compiler'),
+  assemble: require('./assemble'),
+  generateScopeId: require('./gen-id')
+}
diff --git a/src/parser.js b/src/parser.js
new file mode 100644
index 0000000..2b7e7f6
--- /dev/null
+++ b/src/parser.js
@@ -0,0 +1,67 @@
+const compiler = require('vue-template-compiler')
+const defaults = require('lodash.defaultsdeep')
+const LRU = require('lru-cache')
+const hash = require('hash-sum')
+const { SourceMapGenerator } = require('source-map')
+
+const cache = LRU(100)
+const splitRE = /\r?\n/g
+const emptyRE = /^(?:\/\/)?\s*$/
+
+module.exports = function (content, filename, config) {
+  config = defaults(config, { needMap: true })
+
+  const cacheKey = hash(filename + content)
+  const filenameWithHash = filename + '?' + cacheKey // source-map cache busting for hot-reloadded modules
+
+  if (cache.has(cacheKey)) return cache.get(cacheKey)
+
+  const output = compiler.parseComponent(content, { pad: 'line' })
+  if (config.needMap) {
+    if (output.script && !output.script.src) {
+      output.script.map = generateSourceMap(
+        filenameWithHash,
+        content,
+        output.script.content
+      )
+    }
+    if (output.styles) {
+      output.styles.forEach(style => {
+        if (!style.src) {
+          style.map = generateSourceMap(
+            filenameWithHash,
+            content,
+            style.content
+          )
+        }
+      })
+    }
+  }
+  cache.set(cacheKey, output)
+
+  return output
+}
+
+function generateSourceMap (filename, source, generated) {
+  const map = new SourceMapGenerator()
+
+  map.setSourceContent(filename, source)
+
+  generated.split(splitRE).forEach((line, index) => {
+    if (!emptyRE.test(line)) {
+      map.addMapping({
+        source: filename,
+        original: {
+          line: index + 1,
+          column: 0
+        },
+        generated: {
+          line: index + 1,
+          column: 0
+        }
+      })
+    }
+  })
+
+  return map.toJSON()
+}
diff --git a/src/runtime/inject-style-client.js b/src/runtime/inject-style-client.js
new file mode 100644
index 0000000..deeb8b8
--- /dev/null
+++ b/src/runtime/inject-style-client.js
@@ -0,0 +1,212 @@
+/*
+  MIT License http://www.opensource.org/licenses/mit-license.php
+  Author Tobias Koppers @sokra
+  Modified by Evan You @yyx990803, Rahul Kadyan @znck
+*/
+import listToStyles from './list-to-styles'
+
+/*
+type StyleObject = {
+  id: number;
+  parts: Array<StyleObjectPart>
+}
+
+type StyleObjectPart = {
+  css: string;
+  media: string;
+  sourceMap: ?string
+}
+*/
+
+const stylesInDom = {
+/*
+  [id: number]: {
+    id: number,
+    refs: number,
+    parts: Array<(obj?: StyleObjectPart) => void>
+  }
+*/
+}
+
+const head = (document.head || document.getElementsByTagName('head')[0])
+const noop = function () {}
+let singletonElement = null
+let singletonCounter = 0
+let isProduction = false
+
+// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
+// tags it will allow on a page
+const isOldIE = typeof navigator !== 'undefined' && /msie [6-9]\b/.test(navigator.userAgent.toLowerCase())
+
+export default (parentId, list, _isProduction) => {
+  isProduction = _isProduction
+
+  let styles = listToStyles(parentId, list)
+  addStylesToDom(styles)
+
+  return function update (newList) {
+    const mayRemove = []
+    for (let i = 0; i < styles.length; i++) {
+      const item = styles[i]
+      const domStyle = stylesInDom[item.id]
+      domStyle.refs--
+      mayRemove.push(domStyle)
+    }
+
+    if (newList) {
+      styles = listToStyles(parentId, newList)
+      addStylesToDom(styles)
+    } else {
+      styles = []
+    }
+
+    for (let i = 0; i < mayRemove.length; i++) {
+      const domStyle = mayRemove[i]
+      if (domStyle.refs === 0) {
+        for (let j = 0; j < domStyle.parts.length; j++) {
+          domStyle.parts[j]()
+        }
+        delete stylesInDom[domStyle.id]
+      }
+    }
+  }
+}
+
+function addStylesToDom (styles /* Array<StyleObject> */) {
+  for (let i = 0; i < styles.length; i++) {
+    var item = styles[i]
+    var domStyle = stylesInDom[item.id]
+    if (domStyle) {
+      domStyle.refs++
+      let j = 0
+      for (; j < domStyle.parts.length; j++) {
+        domStyle.parts[j](item.parts[j])
+      }
+
+      for (; j < item.parts.length; j++) {
+        domStyle.parts.push(addStyle(item.parts[j]))
+      }
+
+      if (domStyle.parts.length > item.parts.length) {
+        domStyle.parts.length = item.parts.length
+      }
+    } else {
+      const parts = []
+      for (var j = 0; j < item.parts.length; j++) {
+        parts.push(addStyle(item.parts[j]))
+      }
+      stylesInDom[item.id] = { id: item.id, refs: 1, parts: parts }
+    }
+  }
+}
+
+function createStyleElement () {
+  const styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  head.appendChild(styleElement)
+
+  return styleElement
+}
+
+function addStyle (obj /* StyleObjectPart */) {
+  let update, remove
+  let styleElement = document.querySelector('style[data-vue-ssr-id~="' + obj.id + '"]')
+
+  if (styleElement) {
+    if (isProduction) {
+      // has SSR styles and in production mode.
+      // simply do nothing.
+      return noop
+    } else {
+      // has SSR styles but in dev mode.
+      // for some reason Chrome can't handle source map in server-rendered
+      // style tags - source maps in <style> only works if the style tag is
+      // created and inserted dynamically. So we remove the server rendered
+      // styles and inject new ones.
+      styleElement.parentNode.removeChild(styleElement)
+    }
+  }
+
+  if (isOldIE) {
+    // use singleton mode for IE9.
+    const styleIndex = singletonCounter++
+    styleElement = singletonElement || (singletonElement = createStyleElement())
+    update = applyToSingletonTag.bind(null, styleElement, styleIndex, false)
+    remove = applyToSingletonTag.bind(null, styleElement, styleIndex, true)
+  } else {
+    // use multi-style-tag mode in all other cases
+    styleElement = createStyleElement()
+    update = applyToTag.bind(null, styleElement)
+    remove = function () {
+      styleElement.parentNode.removeChild(styleElement)
+    }
+  }
+
+  update(obj)
+
+  return function updateStyle (newObj /* StyleObjectPart */) {
+    if (newObj) {
+      if (newObj.css === obj.css &&
+          newObj.media === obj.media &&
+          newObj.sourceMap === obj.sourceMap) {
+        return
+      }
+      update(obj = newObj)
+    } else {
+      remove()
+    }
+  }
+}
+
+var replaceText = (function () {
+  const textStore = []
+
+  return function (index, replacement) {
+    textStore[index] = replacement
+    return textStore.filter(Boolean).join('\n')
+  }
+})()
+
+function applyToSingletonTag (styleElement, index, remove, obj) {
+  const css = remove ? '' : obj.css
+
+  if (styleElement.styleSheet) {
+    styleElement.styleSheet.cssText = replaceText(index, css)
+  } else {
+    const cssNode = document.createTextNode(css)
+    const childNodes = styleElement.childNodes
+    if (childNodes[index]) styleElement.removeChild(childNodes[index])
+    if (childNodes.length) {
+      styleElement.insertBefore(cssNode, childNodes[index])
+    } else {
+      styleElement.appendChild(cssNode)
+    }
+  }
+}
+
+function applyToTag (styleElement, obj) {
+  let css = obj.css
+  const media = obj.media
+  const sourceMap = obj.sourceMap
+
+  if (media) {
+    styleElement.setAttribute('media', media)
+  }
+
+  if (sourceMap) {
+    // https://developer.chrome.com/devtools/docs/javascript-debugging
+    // this makes source maps inside style tags work properly in Chrome
+    css += '\n/*# sourceURL=' + sourceMap.sources[0] + ' */'
+    // http://stackoverflow.com/a/26603875
+    css += '\n/*# sourceMappingURL=data:application/json;base64,' + window.btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + ' */'
+  }
+
+  if (styleElement.styleSheet) {
+    styleElement.styleSheet.cssText = css
+  } else {
+    while (styleElement.firstChild) {
+      styleElement.removeChild(styleElement.firstChild)
+    }
+    styleElement.appendChild(document.createTextNode(css))
+  }
+}
diff --git a/src/runtime/inject-style-server.js b/src/runtime/inject-style-server.js
new file mode 100644
index 0000000..0686df1
--- /dev/null
+++ b/src/runtime/inject-style-server.js
@@ -0,0 +1,79 @@
+/* globals __VUE_SSR_CONTEXT__ */
+import listToStyles from './list-to-styles'
+
+export default function (parentId, list, isProduction, context) {
+  if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
+    context = __VUE_SSR_CONTEXT__
+  }
+  if (context) {
+    if (!context.hasOwnProperty('styles')) {
+      Object.defineProperty(context, 'styles', {
+        enumerable: true,
+        get: () => renderStyles(context._styles)
+      })
+      // expose renderStyles for vue-server-renderer (vuejs/#6353)
+      context._renderStyles = renderStyles
+    }
+
+    const styles = context._styles || (context._styles = {})
+    list = listToStyles(parentId, list)
+    if (isProduction) {
+      addStyleProd(styles, list)
+    } else {
+      addStyleDev(styles, list)
+    }
+  }
+}
+
+// In production, render as few style tags as possible.
+// (mostly because IE9 has a limit on number of style tags)
+function addStyleProd (styles, list) {
+  for (let i = 0; i < list.length; i++) {
+    const parts = list[i].parts
+    for (var j = 0; j < parts.length; j++) {
+      const part = parts[j]
+      // group style tags by media types.
+      const id = part.media || 'default'
+      const style = styles[id]
+      if (style) {
+        if (style.ids.indexOf(part.id) < 0) {
+          style.ids.push(part.id)
+          style.css += '\n' + part.css
+        }
+      } else {
+        styles[id] = {
+          ids: [part.id],
+          css: part.css,
+          media: part.media
+        }
+      }
+    }
+  }
+}
+
+// In dev we use individual style tag for each module for hot-reload
+// and source maps.
+function addStyleDev (styles, list) {
+  for (let i = 0; i < list.length; i++) {
+    const parts = list[i].parts
+    for (let j = 0; j < parts.length; j++) {
+      const part = parts[j]
+      styles[part.id] = {
+        ids: [part.id],
+        css: part.css,
+        media: part.media
+      }
+    }
+  }
+}
+
+function renderStyles (styles) {
+  var css = ''
+  for (const key in styles) {
+    const style = styles[key]
+    css += '<style data-vue-ssr-id="' + style.ids.join(' ') + '"' +
+        (style.media ? (' media="' + style.media + '"') : '') + '>' +
+        style.css + '</style>'
+  }
+  return css
+}
diff --git a/src/runtime/list-to-styles.js b/src/runtime/list-to-styles.js
new file mode 100644
index 0000000..bc86bb4
--- /dev/null
+++ b/src/runtime/list-to-styles.js
@@ -0,0 +1,27 @@
+/**
+ * Translates the list format produced by css-loader into something
+ * easier to manipulate.
+ */
+export default function listToStyles (parentId, list) {
+  var styles = []
+  var newStyles = {}
+  for (var i = 0; i < list.length; i++) {
+    var item = list[i]
+    var id = item[0]
+    var css = item[1]
+    var media = item[2]
+    var sourceMap = item[3]
+    var part = {
+      id: parentId + ':' + i,
+      css: css,
+      media: media,
+      sourceMap: sourceMap
+    }
+    if (!newStyles[id]) {
+      styles.push(newStyles[id] = { id: id, parts: [part] })
+    } else {
+      newStyles[id].parts.push(part)
+    }
+  }
+  return styles
+}
diff --git a/src/runtime/normalize-component.js b/src/runtime/normalize-component.js
new file mode 100644
index 0000000..53f1edd
--- /dev/null
+++ b/src/runtime/normalize-component.js
@@ -0,0 +1,90 @@
+/* globals __VUE_SSR_CONTEXT__ */
+
+// this module is a runtime utility for cleaner component module output.
+
+export default function normalizeComponent (
+  rawScriptExports,
+  compiledTemplate,
+  injectStyles,
+  scopeId,
+  moduleIdentifier /* server only */
+) {
+  var esModule
+  var scriptExports = rawScriptExports = rawScriptExports || {}
+
+  // ES6 modules interop
+  var type = typeof rawScriptExports.default
+  if (type === 'object' || type === 'function') {
+    esModule = rawScriptExports
+    scriptExports = rawScriptExports.default
+  }
+
+  // Vue.extend constructor export interop
+  var options = typeof scriptExports === 'function'
+    ? scriptExports.options
+    : scriptExports
+
+  // render functions
+  if (compiledTemplate) {
+    options.render = compiledTemplate.render
+    options.staticRenderFns = compiledTemplate.staticRenderFns
+  }
+
+  // scopedId
+  if (scopeId) {
+    options._scopeId = scopeId
+  }
+
+  var hook
+  if (moduleIdentifier) { // server build
+    hook = function (context) {
+      // 2.3 injection
+      context =
+        context || // cached call
+        (this.$vnode && this.$vnode.ssrContext) || // stateful
+        (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
+      // 2.2 with runInNewContext: true
+      if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
+        context = __VUE_SSR_CONTEXT__
+      }
+      // inject component styles
+      if (injectStyles) {
+        injectStyles.call(this, context)
+      }
+      // register component module identifier for async chunk inferrence
+      if (context && context._registeredComponents) {
+        context._registeredComponents.add(moduleIdentifier)
+      }
+    }
+    // used by ssr in case component is cached and beforeCreate
+    // never gets called
+    options._ssrRegister = hook
+  } else if (injectStyles) {
+    hook = injectStyles
+  }
+
+  if (hook) {
+    var functional = options.functional
+    var existing = functional
+      ? options.render
+      : options.beforeCreate
+    if (!functional) {
+      // inject component registration as beforeCreate hook
+      options.beforeCreate = existing
+        ? [].concat(existing, hook)
+        : [hook]
+    } else {
+      // register for functioal component in vue file
+      options.render = function renderWithStyleInjection (h, context) {
+        hook.call(context)
+        return existing(h, context)
+      }
+    }
+  }
+
+  return {
+    esModule: esModule,
+    exports: scriptExports,
+    options: options
+  }
+}
diff --git a/src/style-compiler/index.js b/src/style-compiler/index.js
new file mode 100644
index 0000000..77cc3f9
--- /dev/null
+++ b/src/style-compiler/index.js
@@ -0,0 +1,62 @@
+const postcss = require('postcss')
+const cssModules = require('postcss-modules')
+const defaults = require('lodash.defaultsdeep')
+
+const trim = require('./plugins/trim')
+const scopeId = require('./plugins/scope-id')
+
+module.exports = function compileStyle (style, filename, config) {
+  config = defaults(config, {
+    async: false,
+    needMap: true,
+    plugins: [],
+    options: {},
+    onWarn: message => console.warn(message)
+  })
+
+  const plugins = [trim].concat(config.plugins)
+  const options = Object.assign({
+    to: filename,
+    from: filename
+  }, config.options)
+
+  // source map
+  if (config.needMap) {
+    options.map = {
+      inline: false,
+      annotation: false,
+      prev: style.map
+    }
+  }
+
+  if (!style.descriptor) {
+    throw Error('SFC block descriptor is missing.')
+  }
+
+  // add plugin for scoped css rewrite
+  if (style.descriptor.scoped) {
+    if (typeof (config.scopeId) !== 'string') {
+      throw Error(`'scopeId' is required to compile scoped style.`)
+    }
+    plugins.push(scopeId({ id: config.scopeId }))
+  }
+
+  let modules
+  if (style.descriptor.module) {
+    plugins.push(cssModules({
+      getJSON: (_, output) => { modules = output }
+    }))
+  }
+
+  const output = postcss(plugins).process(style.code, options)
+  const prepare = result => {
+    const output = { code: result.css, modules }
+
+    if (config.needMap) { output.map = result.map }
+    result.warnings().forEach(warning => config.onWarn(warning))
+
+    return output
+  }
+
+  return (config.async) ? output.then(prepare) : prepare(output)
+}
diff --git a/src/style-compiler/plugins/scope-id.js b/src/style-compiler/plugins/scope-id.js
new file mode 100644
index 0000000..8c51b80
--- /dev/null
+++ b/src/style-compiler/plugins/scope-id.js
@@ -0,0 +1,80 @@
+const postcss = require('postcss')
+const selectorParser = require('postcss-selector-parser')
+
+module.exports = postcss.plugin('add-id', function (opts) {
+  return function (root) {
+    const keyframes = Object.create(null)
+
+    root.each(function rewriteSelector (node) {
+      if (!node.selector) {
+        // handle media queries
+        if (node.type === 'atrule') {
+          if (node.name === 'media') {
+            node.each(rewriteSelector)
+          } else if (node.name === 'keyframes') {
+            // register keyframes
+            keyframes[node.params] = node.params = node.params + '-' + opts.id
+          }
+        }
+        return
+      }
+      node.selector = selectorParser(function (selectors) {
+        selectors.each(function (selector) {
+          var node = null
+          selector.each(function (n) {
+            // ">>>" combinator
+            if (n.type === 'combinator' && n.value === '>>>') {
+              n.value = ' '
+              n.spaces.before = n.spaces.after = ''
+              return false
+            }
+            // /deep/ alias for >>>, since >>> doesn't work in SASS
+            if (n.type === 'tag' && n.value === '/deep/') {
+              var next = n.next()
+              if (next.type === 'combinator' && next.value === ' ') {
+                next.remove()
+              }
+              n.remove()
+              return false
+            }
+            if (n.type !== 'pseudo' && n.type !== 'combinator') {
+              node = n
+            }
+          })
+          selector.insertAfter(node, selectorParser.attribute({
+            attribute: opts.id
+          }))
+        })
+      }).process(node.selector).result
+    })
+
+    // If keyframes are found in this <style>, find and rewrite animation names
+    // in declarations.
+    // Caveat: this only works for keyframes and animation rules in the same
+    // <style> element.
+    if (Object.keys(keyframes).length) {
+      root.walkDecls(decl => {
+        // individual animation-name declaration
+        if (/-?animation-name$/.test(decl.prop)) {
+          decl.value = decl.value.split(',')
+            .map(v => keyframes[v.trim()] || v.trim())
+            .join(',')
+        }
+        // shorthand
+        if (/-?animation$/.test(decl.prop)) {
+          decl.value = decl.value.split(',')
+            .map(v => {
+              var vals = v.split(/\s+/)
+              var name = vals[0]
+              if (keyframes[name]) {
+                return [keyframes[name]].concat(vals.slice(1)).join(' ')
+              } else {
+                return v
+              }
+            })
+            .join(',')
+        }
+      })
+    }
+  }
+})
diff --git a/src/style-compiler/plugins/trim.js b/src/style-compiler/plugins/trim.js
new file mode 100644
index 0000000..1e3aa80
--- /dev/null
+++ b/src/style-compiler/plugins/trim.js
@@ -0,0 +1,11 @@
+const postcss = require('postcss')
+
+module.exports = postcss.plugin('trim', function (opts) {
+  return function (css) {
+    css.walk(function (node) {
+      if (node.type === 'rule' || node.type === 'atrule') {
+        node.raws.before = node.raws.after = '\n'
+      }
+    })
+  }
+})
diff --git a/src/template-compiler/index.js b/src/template-compiler/index.js
new file mode 100644
index 0000000..01298e0
--- /dev/null
+++ b/src/template-compiler/index.js
@@ -0,0 +1,85 @@
+const compiler = require('vue-template-compiler')
+const transpile = require('vue-template-es2015-compiler')
+const defaults = require('lodash.defaultsdeep')
+const { js_beautify: beautify } = require('js-beautify')
+
+const transformRequire = require('./modules/transform-require')
+
+module.exports = function compileTemplate (template, filename, config) {
+  config = defaults(
+    config,
+    {
+      isHot: false,
+      isServer: false,
+      esModule: true,
+      isProduction: true,
+      optimizeSSR: true,
+      buble: {
+        transforms: {
+          stripWith: true
+        }
+      },
+      options: {
+        preserveWhitespace: true,
+        modules: []
+      },
+      plugins: []
+    }
+  )
+
+  const options = config.options
+
+  options.modules = options.modules.concat(config.plugins)
+  options.modules.push(transformRequire(config.transformToRequire))
+
+  const compile = (config.isServer && config.optimizeSSR !== false && compiler.ssrCompile) ? compiler.ssrCompile : compiler.compile
+  const compiled = compile(template.code, options)
+  const output = {
+    errors: compiled.errors,
+    tips: compiled.tips
+  }
+  const vueHotReloadAPI = (config.require && config.require.vueHotReloadAPI) || 'vue-hot-reload-api'
+
+  if (output.errors && output.errors.length) {
+    output.code = config.esModule === true
+      ? `export function render () {}\nexport var staticRenderFns = []`
+      : 'module.exports={render:function(){},staticRenderFns:[]}'
+  } else {
+    output.code = transpile(
+      'var render = ' + toFunction(compiled.render) + '\n' +
+      'var staticRenderFns = [' + compiled.staticRenderFns.map(toFunction).join(',') + ']',
+      config.buble
+    ) + '\n'
+
+    // mark with stripped (this enables Vue to use correct runtime proxy detection)
+    if (
+      !config.isProduction &&
+      config.buble.transforms.stripWith !== false
+    ) {
+      output.code += `render._withStripped = true\n`
+    }
+
+    const __exports__ = `{ render: render, staticRenderFns: staticRenderFns }`
+    output.code += config.esModule === true
+      ? `export default ${__exports__}`
+      : `module.exports = ${__exports__}`
+
+    if (!config.isProduction && config.isHot) {
+      output.code +=
+        '\nif (module.hot) {\n' +
+        '  module.hot.accept()\n' +
+        '  if (module.hot.data) {\n' +
+        `     require(${JSON.stringify(vueHotReloadAPI)}).rerender(${JSON.stringify(options.scopeId)}, module.exports)\n` +
+        '  }\n' +
+        '}'
+    }
+  }
+
+  return output
+}
+
+function toFunction (code) {
+  return 'function () {' + beautify(code, {
+    indent_size: 2 // eslint-disable-line camelcase
+  }) + '}'
+}
diff --git a/src/template-compiler/modules/transform-require.js b/src/template-compiler/modules/transform-require.js
new file mode 100644
index 0000000..edf7f47
--- /dev/null
+++ b/src/template-compiler/modules/transform-require.js
@@ -0,0 +1,48 @@
+// vue compiler module for transforming `<tag>:<attribute>` to `require`
+const defaultOptions = {
+  img: 'src',
+  image: 'xlink:href'
+}
+
+module.exports = userOptions => {
+  const options = userOptions
+    ? Object.assign({}, defaultOptions, userOptions)
+    : defaultOptions
+
+  return {
+    postTransformNode: node => {
+      transform(node, options)
+    }
+  }
+}
+
+function transform (node, options) {
+  for (const tag in options) {
+    if (node.tag === tag && node.attrs) {
+      const attributes = options[tag]
+      if (typeof attributes === 'string') {
+        node.attrs.some(attr => rewrite(attr, attributes))
+      } else if (Array.isArray(attributes)) {
+        attributes.forEach(item => node.attrs.some(attr => rewrite(attr, item)))
+      }
+    }
+  }
+}
+
+function rewrite (attr, name) {
+  if (attr.name === name) {
+    let value = attr.value
+    const isStatic = value.charAt(0) === '"' && value.charAt(value.length - 1) === '"'
+    if (!isStatic) {
+      return
+    }
+    const firstChar = value.charAt(1)
+    if (firstChar === '.' || firstChar === '~') {
+      if (firstChar === '~') {
+        value = '"' + value.slice(2)
+      }
+      attr.value = `require(${value})`
+    }
+    return true
+  }
+}
diff --git a/test/.eslintrc b/test/.eslintrc
new file mode 100644
index 0000000..55f121d
--- /dev/null
+++ b/test/.eslintrc
@@ -0,0 +1,5 @@
+{
+  "env": {
+    "jest": true
+  }
+}
diff --git a/test/assemble.test.js b/test/assemble.test.js
new file mode 100644
index 0000000..d396998
--- /dev/null
+++ b/test/assemble.test.js
@@ -0,0 +1,91 @@
+const assemble = require('../src/assemble')
+
+const descriptors = {
+  script: {
+    type: 'script',
+    content: '\nexport default {}\n',
+    start: 10,
+    end: 12,
+    attrs: []
+  },
+
+  styles: [
+    {
+      type: 'style',
+      content: '\n.foo { color: red }\n',
+      start: 14,
+      end: 16,
+      attrs: [{ name: 'scoped' }],
+      scoped: true
+    },
+    {
+      type: 'style',
+      content: `\n\$red: 'red';\n.bar { color: \$red }\n`,
+      start: 18,
+      end: 20,
+      attrs: [{ name: 'lang', value: 'scss' }],
+      lang: 'scss',
+      module: 'one'
+    }
+  ],
+
+  template: {
+    type: 'style',
+    content: '\n<div class="foo bar"></div>\n',
+    start: 1,
+    end: 3,
+    attrs: []
+  },
+
+  customBlocks: [
+    {
+      type: 'markdown',
+      content: '\n## Readme\nThis is markdown.\n',
+      start: 5,
+      end: 8,
+      attrs: []
+    }
+  ]
+}
+
+const source = {
+  script: { id: 'foo.vue?type=script', descriptor: descriptors.script },
+  styles: [
+    { id: 'foo.vue?type=style&index=0', descriptor: descriptors.styles[0] },
+    { id: 'foo.vue?type=style&index=1', descriptor: descriptors.styles[1] }
+  ],
+  render: { id: 'foo.vue?type=template', descriptor: descriptors.template },
+  customBlocks: [
+    { id: 'foo.vue?type=custom&index=0', descriptor: descriptors.customBlocks[0] }
+  ]
+}
+
+test('assemble code for SSR and HMR', () => {
+  const result = assemble(source, 'foo.vue', { scopeId: 'data-v-xxx', isServer: true, hasStyleInjectFn: true, isHot: true, isProduction: false })
+
+  console.log(result)
+})
+
+test('assemble code for non-SSR production', () => {
+  const result = assemble(source, 'foo.vue', { scopeId: 'data-v-xxx' })
+
+  console.log(result)
+})
+
+test('assemble code for SSR production', () => {
+  const result = assemble(source, 'foo.vue', { scopeId: 'data-v-xxx', isServer: true, hasStyleInjectFn: true })
+
+  console.log(result)
+})
+
+test('assemble code for non-SSR production as node module', () => {
+  const result = assemble(source, 'foo.vue', { scopeId: 'data-v-xxx', esModule: false })
+
+  console.log(result)
+})
+
+test('assemble code for non-SSR production with injections enabled', () => {
+  const result = assemble(source, 'foo.vue', { scopeId: 'data-v-xxx', isInjectable: true })
+
+  console.log(result)
+})
diff --git a/test/parser.test.js b/test/parser.test.js
new file mode 100644
index 0000000..22c157f
--- /dev/null
+++ b/test/parser.test.js
@@ -0,0 +1,25 @@
+const { SourceMapConsumer } = require('source-map')
+const parser = require('../src/parser')
+
+test('generated map should match to correct position in original source', () => {
+  const descriptor = parser(
+    `<template>
+      <div>Foo</div>
+    </template>
+
+    <script>
+    export default {}
+    </script>
+    `, 'foo.vue')
+  const consumer = new SourceMapConsumer(descriptor.script.map)
+  const position = consumer.originalPositionFor({ line: 6, column: 0 })
+
+  expect(position.line).toEqual(6)
+  expect(position.source.startsWith('foo.vue')).toBeTruthy()
+})
+
+test('should not generate source map', () => {
+  const descriptor = parser(`<script>export default {}</script>`, 'foo.vue', { needMap: false })
+
+  expect(descriptor.script.map).toBeUndefined()
+})
diff --git a/test/style-compiler.test.js b/test/style-compiler.test.js
new file mode 100644
index 0000000..3de9730
--- /dev/null
+++ b/test/style-compiler.test.js
@@ -0,0 +1,23 @@
+const compiler = require('../src/style-compiler')
+
+test('should rewrite scoped style', () => {
+  const style = {
+    code: '.foo { color: red }',
+    descriptor: {
+      scoped: true
+    }
+  }
+  const compiled = compiler(style, 'foo.vue', { scopeId: 'xxx', needMap: false })
+  expect(compiled.code.indexOf('.foo[xxx]')).toBeGreaterThan(-1)
+})
+
+test('should generate sourcemap', () => {
+  const style = {
+    code: '.foo { color: red }',
+    descriptor: {
+      scoped: true
+    }
+  }
+  const compiled = compiler(style, 'foo.vue', { scopeId: 'xxx' })
+  expect(compiled.map).toBeTruthy()
+})
diff --git a/test/template-compiler.test.js b/test/template-compiler.test.js
new file mode 100644
index 0000000..f6b5c01
--- /dev/null
+++ b/test/template-compiler.test.js
@@ -0,0 +1,32 @@
+const compiler = require('../src/template-compiler')
+
+test('should compile template to esModule', () => {
+  const template = {
+    code: '<div>{{foo}}</div>\n'
+  }
+  const compiled = compiler(template, 'foo.vue', { scopeId: 'xxx', isProduction: false })
+
+  expect(compiled.code.indexOf('export default')).toBeGreaterThan(-1)
+  expect(compiled.code.indexOf('render._withStripped')).toBeGreaterThan(-1)
+  expect(compiled.code.indexOf('module.hot.accept')).toBe(-1)
+})
+
+test('should compile template to node module', () => {
+  const template = {
+    code: '<div>{{foo}}</div>\n'
+  }
+  const compiled = compiler(template, 'foo.vue', { scopeId: 'xxx', esModule: false, isProduction: false })
+
+  expect(compiled.code.indexOf('export default')).toBe(-1)
+  expect(compiled.code.indexOf('render._withStripped')).toBeGreaterThan(-1)
+  expect(compiled.code.indexOf('module.hot.accept')).toBe(-1)
+})
+
+test('should compile with HMR', () => {
+  const template = {
+    code: '<div>{{foo}}</div>\n'
+  }
+  const compiled = compiler(template, 'foo.vue', { scopeId: 'xxx', isHot: true, isProduction: false })
+
+  expect(compiled.code.indexOf('module.hot.accept')).toBeGreaterThan(-1)
+})
diff --git a/test/test.js b/test/test.js
deleted file mode 100644
index 779f949..0000000
--- a/test/test.js
+++ /dev/null
@@ -1,3 +0,0 @@
-it('should pass', () => {
-
-})