[feature] add
diff --git a/src/html-loader.js b/src/html-loader.js
new file mode 100644
index 0000000..20ae43f
--- /dev/null
+++ b/src/html-loader.js
@@ -0,0 +1,61 @@
+import loaderUtils from 'loader-utils'
+import blocker from 'weex-transformer/lib/blocker'
+import templater from 'weex-templater'
+
+import {
+    FUNC_START_REG,
+    FUNC_END_REG,
+    stringifyFunction
+} from './util'
+
+function extrackBlock (source, type) {
+  return new Promise((resolve, reject) => {
+    blocker.format(source, (err, ret) => {
+      if (err) {
+        reject(err)
+      } else {
+        resolve(ret[type])
+      }
+    })
+  })
+}
+
+function parseTemplate (source) {
+  return new Promise((resolve, reject) => {
+    templater.parse(source, (err, obj) => {
+      if (err) {
+        reject(err)
+      } else {
+        // parse json to string and treat function specially
+        let target = JSON.stringify(obj.jsonTemplate, stringifyFunction, '  ')
+        target = target.replace(FUNC_START_REG, '').replace(FUNC_END_REG, '')
+        resolve(target)
+      }
+    })
+  })
+}
+
+module.exports = function(source) {
+    this.cacheable && this.cacheable()
+    const callback = this.async()
+
+    const params = {
+        loaderQuery: loaderUtils.parseQuery(this.query),
+        resourceQuery: loaderUtils.parseQuery(this.resourceQuery),
+        resourcePath: this.resourcePath
+    }
+
+    extrackBlock(source, 'template')
+        .then(template => {
+            if (params.loaderQuery.extract) {
+                return parseTemplate(template.content)
+            } else if (params.loaderQuery.raw) {
+                callback(null, template.content)
+            }
+        }).then(result => {
+            result = `module.exports = ${result}\n`
+            callback(null, result)
+        }).catch(e => {
+            callback(e, '')
+        })
+}
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..fbec04e
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,70 @@
+import loaderUtils from 'loader-utils'
+
+import {
+  parseScript,
+  parseStyle,
+  parseTemplate,
+  parseWeexFile
+} from './parser'
+import * as config from './config'
+import * as legacy from './legacy'
+
+function partedLoader (type, loader, params, source) {
+  let promise
+  switch (type) {
+    case 'js':
+    case 'script':
+      const transformerVersion = config.transformerVersion
+      promise = parseScript(loader, params, source,
+        { config: JSON.stringify({ transformerVersion }) })
+      break
+    case 'css':
+    case 'style':
+      promise = parseStyle(loader, params, source)
+      break
+    case 'html':
+    case 'tpl':
+    case 'template':
+      promise = parseTemplate(loader, params, source)
+      break
+    case 'we':
+    default:
+      promise = parseWeexFile(loader, params, source)
+      break
+  }
+  return promise
+}
+
+function loader (source) {
+  this.cacheable && this.cacheable()
+
+  const callback = this.async()
+  const params = {
+    loaderQuery: loaderUtils.parseQuery(this.query),
+    resourceQuery: loaderUtils.parseQuery(this.resourceQuery),
+    resourcePath: this.resourcePath
+  }
+  const type = params.loaderQuery.type || 'we'
+  const promise = partedLoader(type, this, params, source)
+
+  promise.then(result => {
+    if (type === 'style' || type === 'css' ||
+      type === 'html' || type === 'tpl' || type === 'template') {
+      result = 'module.exports=' + result
+    }
+    callback(null, result)
+  }).catch(err => {
+    this.emitError(err.toString())
+    callback(err.toString(), '')
+  })
+}
+
+loader.setLogLevel = level => {
+  config.logLevel = level
+}
+
+for (const key in legacy) {
+  loader[key] = legacy[key]
+}
+
+module.exports = loader
diff --git a/src/loader.js b/src/loader.js
new file mode 100644
index 0000000..a6f03cf
--- /dev/null
+++ b/src/loader.js
@@ -0,0 +1,120 @@
+import loaderUtils from 'loader-utils'
+import path from 'path'
+import parse5 from 'parse5'
+import md5 from 'md5'
+
+import {
+    getNameByPath
+} from './util'
+
+const loaderPath = __dirname
+
+function getRequire(loaderContext, loader, filepath) {
+    return 'require(' +
+                loaderUtils.stringifyRequest(
+                    loaderContext,
+                    `!!${loader}!${filepath}`
+                ) +
+           ')\n'
+}
+
+function getAttribute (node, name) {
+  if (node.attrs) {
+    var i = node.attrs.length
+    var attr
+    while (i--) {
+      attr = node.attrs[i]
+      if (attr.name === name) {
+        return attr.value
+      }
+    }
+  }
+}
+
+function parse(source) {
+
+  const fragment = parse5.parseFragment(source, {
+    locationInfo: true
+  })
+
+  const output = {
+    template: [],
+    style: [],
+    script: []
+  }
+
+  fragment.childNodes.forEach(node => {
+    const type = node.tagName
+
+    if (!output[type]) {
+        return
+    }
+
+    const lang = getAttribute(node, 'lang')
+    const src = getAttribute(node, 'src')
+
+    output[type].push({
+        lang,
+        src
+    })
+  })
+
+  return output
+}
+
+module.exports = function(source) {
+    this.cacheable && this.cacheable()
+
+    const defaultLoaders = {
+        main: path.resolve(loaderPath, 'loader.js'),
+        template: path.resolve(loaderPath, 'html-loader.js?extract'),
+        style: path.resolve(loaderPath, 'style-loader.js?extract'),
+        script: path.resolve(loaderPath, 'script-loader.js?extract')
+    }
+    const options = this.options.weex || {}
+    const loaders = Object.assign({}, defaultLoaders, options.loaders || {})
+    const loaderQuery = loaderUtils.parseQuery(this.query)
+    const resourceQuery = loaderUtils.parseQuery(this.resourceQuery)
+    const resourcePath = this.resourcePath
+    const isEntry = resourceQuery.entry
+    const name =  isEntry ? md5(resourcePath) :
+                    resourceQuery.name || getNameByPath(resourcePath)
+
+    let output = '';
+
+    const parts = parse(source)
+
+    if (parts.template.length) {
+        const template = parts.template[0]
+        output += 'var __weex_template__ = ' + getRequire(this, loaders['template'], resourcePath)
+    }
+
+    if (parts.style.length) {
+        const style = parts.style[0]
+        output += 'var __weex_style__ = ' + getRequire(this, loaders['style'], resourcePath)
+    }
+
+    if (parts.script.length) {
+        const script = parts.script[0]
+        output += 'var __weex_script__ = ' + getRequire(this, loaders['script'], resourcePath)
+    }
+
+
+
+    output += `
+__weex_define__('@weex-component/${name}', [], function(__weex_require__, __weex_exports__, __weex_module__) {
+    __weex_module__.exports = Object.assign({}, __weex_script__, {
+        template: __weex_template__,
+        style: __weex_style__
+    })
+})\n
+`
+    if (isEntry) {
+        output += `
+__weex_bootstrap__('@weex-component/${name}')\n
+`
+    }
+
+    return output;
+
+}
\ No newline at end of file
diff --git a/src/parser.js b/src/parser.js
new file mode 100644
index 0000000..ac1dd76
--- /dev/null
+++ b/src/parser.js
@@ -0,0 +1,211 @@
+import blocker from 'weex-transformer/lib/blocker'
+import templater from 'weex-templater'
+import styler from 'weex-styler'
+import scripter from 'weex-scripter'
+
+import md5 from 'md5'
+
+import { transformerVersion } from './config'
+import {
+  MODULE_EXPORTS_REG,
+  REQUIRE_REG,
+  FUNC_START_REG,
+  FUNC_END_REG,
+  getNameByPath,
+  checkFileExist,
+  depHasRequired,
+  stringifyFunction,
+  appendToWarn
+} from './util'
+
+export function parseWeexFile (loader, params, source, deps, elementName) {
+  return new Promise(
+    // separate source into <element>s, <template>, <style>s and <script>s
+    separateBlocks(source, deps || []))
+    // pre-parse non-javascript parts
+    .then(preParseBlocks(loader, params, elementName))
+    // join blocks together and parse as javascript finally
+    .then(parseBlocks(loader, params, elementName))
+}
+
+function separateBlocks (source, deps) {
+  return (resolve, reject) => {
+    blocker.format(source, (err, ret) => {
+      if (err) {
+        reject(err)
+      }
+      else {
+        ret.deps = deps
+        resolve(ret)
+      }
+    })
+  }
+}
+
+function preParseBlocks (loader, params) {
+  return (blocks) => {
+    const { deps, elements, template, styles, scripts, config, data } = blocks
+    const promises = [
+      Promise.resolve(),
+      Promise.resolve(),
+      Promise.resolve(),
+      Promise.resolve(scripts),
+      Promise.resolve(deps),
+      Promise.resolve(config),
+      Promise.resolve(data)
+    ]
+    let content
+    // pre-parse sub elements
+    if (elements) {
+      const elPromises = []
+      Object.keys(elements).forEach(key => {
+        const el = elements[key]
+        elPromises.push(parseWeexFile(loader, params, el.content, deps, el.name))
+      })
+      promises[0] = Promise.all(elPromises)
+    }
+    // pre-parse template
+    if (template) {
+      content = template.content
+      promises[1] = parseTemplate(loader, params, content, deps)
+    }
+    // pre-parse styles
+    if (styles) {
+      content = styles.reduce((pre, cur) => {
+        return pre + '\n' + cur.content
+      }, '')
+      promises[2] = parseStyle(loader, params, content)
+    }
+    return Promise.all(promises)
+  }
+}
+
+function parseBlocks (loader, params, elementName) {
+  return (results) => {
+    const elements = results[0] || []
+    const template = results[1]
+    const style = results[2]
+    const scripts = results[3]
+    const deps = results[4] || []
+    const configResult = results[5]
+    const dataResult = results[6]
+
+    let content = ''
+    let config = {}
+    let data
+
+    if (scripts) {
+      content += scripts.reduce((pre, cur) => {
+        return pre + '\n;' + cur.content
+      }, '')
+    }
+
+    let requireContent = ''
+    if (deps.length) {
+      requireContent += deps.map(dep =>
+        depHasRequired(content, dep) ? 'require("' + dep + '");' : ''
+      ).join('\n')
+      content = requireContent + '\n' + content
+    }
+
+    if (template) {
+      content += '\n;module.exports.template = module.exports.template || {}' +
+        '\n;Object.assign(module.exports.template, ' + template + ')'
+    }
+
+    if (style) {
+      content += '\n;module.exports.style = module.exports.style || {}' +
+        '\n;Object.assign(module.exports.style, ' + style + ')'
+    }
+
+    if (configResult) {
+      config = new Function('return ' + configResult.content.replace(/\n/g, ''))()
+    }
+    config.transformerVersion = transformerVersion
+    config = JSON.stringify(config, null, 2)
+
+    if (dataResult) {
+      data = new Function('return ' + dataResult.content.replace(/\n/g, ''))()
+      data = JSON.stringify(data, null, 2)
+    }
+
+    return parseScript(loader, params, content, { config, data, elementName, elements })
+  }
+}
+
+export function parseTemplate (loader, params, source, deps) {
+  return new Promise((resolve, reject) => {
+    templater.parse(source, (err, obj) => {
+      if (err) {
+        reject(err)
+      }
+      else {
+        appendToWarn(loader, obj.log)
+        // push valid obj.deps to deps
+        if (deps && obj.deps) {
+          obj.deps.map(
+            dep => checkFileExist(dep, params.resourcePath)
+          ).forEach(dep => {
+            if (dep) {
+              deps.push(dep)
+            }
+          })
+        }
+        // parse json to string and treat function specially
+        let target = JSON.stringify(obj.jsonTemplate, stringifyFunction, '  ')
+        target = target.replace(FUNC_START_REG, '').replace(FUNC_END_REG, '')
+        resolve(target)
+      }
+    })
+  })
+}
+
+export function parseStyle (loader, params, source) {
+  return new Promise((resolve, reject) => {
+    styler.parse(source, (err, obj) => {
+      if (err) {
+        reject(err)
+      }
+      else {
+        appendToWarn(loader, obj.log)
+        resolve(JSON.stringify(obj.jsonStyle, null, 2))
+      }
+    })
+  })
+}
+
+export function parseScript (loader, params, source, env) {
+  const { config, data, elementName, elements } = env
+
+  // the entry component has a special resource query and not a sub element tag
+  const isEntry = params.resourceQuery.entry === true && !elementName
+
+  // resolve component name
+  const name = isEntry
+    ? md5(source)
+    : (elementName || params.resourceQuery.name || getNameByPath(params.resourcePath))
+
+  // fix data option from an object to a function
+  let target = scripter.fix(source)
+
+  // wrap with __weex_define__(name, [], (r, e, m) {...})
+  target = target
+      .replace(MODULE_EXPORTS_REG, '__weex_module__.exports')
+      .replace(REQUIRE_REG, '__weex_require__($1$2$1)')
+  target = ';__weex_define__("@weex-component/' + name + '", [], ' +
+      'function(__weex_require__, __weex_exports__, __weex_module__)' +
+      '{\n' + target + '\n})'
+
+  // append __weex_bootstrap__ for entry component
+  if (isEntry) {
+    target += '\n;__weex_bootstrap__("@weex-component/' + name + '", ' +
+        String(config) + ',' +
+        String(data) + ')'
+  }
+
+  // join with elements deps
+  target = (elements || []).concat(target).join(';\n\n')
+
+  return Promise.resolve(target)
+}
+
diff --git a/src/script-loader.js b/src/script-loader.js
new file mode 100644
index 0000000..8874f4a
--- /dev/null
+++ b/src/script-loader.js
@@ -0,0 +1,51 @@
+import loaderUtils from 'loader-utils'
+import blocker from 'weex-transformer/lib/blocker'
+import scripter from 'weex-scripter'
+
+import {
+    FUNC_START_REG,
+    FUNC_END_REG,
+    stringifyFunction
+} from './util'
+
+function extrackBlock (source, type) {
+  return new Promise((resolve, reject) => {
+    blocker.format(source, (err, ret) => {
+      if (err) {
+        reject(err)
+      } else {
+        resolve(ret[type])
+      }
+    })
+  })
+}
+
+function parseScript(source) {
+  return new Promise((resolve, reject) => {
+    resolve(scripter.fix(source))
+  })
+}
+
+module.exports = function(source) {
+    this.cacheable && this.cacheable()
+    const callback = this.async()
+
+    const params = {
+        loaderQuery: loaderUtils.parseQuery(this.query),
+        resourceQuery: loaderUtils.parseQuery(this.resourceQuery),
+        resourcePath: this.resourcePath
+    }
+
+    extrackBlock(source, 'scripts')
+        .then(scripts => {
+            if (params.loaderQuery.extract) {
+                return parseScript(scripts[0].content)
+            } else if (params.loaderQuery.raw) {
+                callback(null, scripts[0].content)
+            }
+        }).then(result => {
+            callback(null, result)
+        }).catch(e => {
+            callback(e, '')
+        })
+}
\ No newline at end of file
diff --git a/src/style-loader.js b/src/style-loader.js
new file mode 100644
index 0000000..fb3e4f4
--- /dev/null
+++ b/src/style-loader.js
@@ -0,0 +1,58 @@
+import loaderUtils from 'loader-utils'
+import blocker from 'weex-transformer/lib/blocker'
+import styler from 'weex-styler'
+
+import {
+    FUNC_START_REG,
+    FUNC_END_REG,
+    stringifyFunction
+} from './util'
+
+function extrackBlock (source, type) {
+  return new Promise((resolve, reject) => {
+    blocker.format(source, (err, ret) => {
+      if (err) {
+        reject(err)
+      } else {
+        resolve(ret[type])
+      }
+    })
+  })
+}
+
+function parseStyle(source) {
+  return new Promise((resolve, reject) => {
+    styler.parse(source, (err, obj) => {
+      if (err) {
+        reject(err)
+      } else {
+        resolve(JSON.stringify(obj.jsonStyle, null, 2))
+      }
+    })
+  })
+}
+
+module.exports = function(source) {
+    this.cacheable && this.cacheable()
+    const callback = this.async()
+
+    const params = {
+        loaderQuery: loaderUtils.parseQuery(this.query),
+        resourceQuery: loaderUtils.parseQuery(this.resourceQuery),
+        resourcePath: this.resourcePath
+    }
+
+    extrackBlock(source, 'styles')
+        .then(styles => {
+            if (params.loaderQuery.extract) {
+                return parseStyle(styles[0].content)
+            } else if (params.loaderQuery.raw) {
+                callback(null, styles[0].content)
+            }
+        }).then(result => {
+            result = `module.exports = ${result}\n`
+            callback(null, result)
+        }).catch(e => {
+            callback(e, '')
+        })
+}
\ No newline at end of file
diff --git a/test/spec/a.we b/test/spec/a.we
new file mode 100644
index 0000000..62ac6f9
--- /dev/null
+++ b/test/spec/a.we
@@ -0,0 +1,24 @@
+<template>
+    <div>
+        <text class="hello">Hello {{name}}</text>
+    </div>
+</template>
+
+
+<style>
+.hello {
+    font-size: 26px;
+    color: red;
+}
+</style>
+
+
+<script>
+module.exports = {
+    data: function() {
+        return {
+            name: 'Weex'
+        }
+    }
+}
+</script>
\ No newline at end of file