[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