diff --git a/README.md b/README.md index 59fa76f..5e3ecf7 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ export default { ### Options +#### `css` + ```js vue({ // Filename to write all styles to @@ -95,6 +97,15 @@ vue({ }) ``` +#### `compileTemplate` + +```js +vue({ + // Compile templates to render functions (Vue 2 only) + compileTemplate: true, +}) +``` + ## Change log Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. diff --git a/package.json b/package.json index 373b1c1..3e7d36c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "html-minifier": "latest", "parse5": "latest", "rollup-pluginutils": "latest", + "vue-template-compiler": "^2.0.0-rc.4", "vue-template-validator": "latest" }, "devDependencies": { diff --git a/src/index.js b/src/index.js index b4d9502..3647e6d 100644 --- a/src/index.js +++ b/src/index.js @@ -7,6 +7,7 @@ export default function vue(options = {}) { const filter = createFilter(options.include, options.exclude); const styles = {}; let dest = options.css; + const compileTemplate = !!options.compileTemplate; return { name: 'vue', @@ -19,7 +20,7 @@ export default function vue(options = {}) { return null; } - const { js, css } = vueTransform(source, id); + const { js, css } = vueTransform(source, id, { compileTemplate }); // Map of every stylesheet styles[id] = css || {}; @@ -27,7 +28,11 @@ export default function vue(options = {}) { // Component javascript with inlined html template return js; }, - ongenerate(opts) { + ongenerate(opts, rendered) { + // Put with statements back + /* eslint-disable no-param-reassign */ + rendered.code = rendered.code.replace(/if\s*\(""__VUE_WITH_STATEMENT__"\)/g, + 'with(this)'); if (options.css === false) { return; } diff --git a/src/options.js b/src/options.js index 2c39717..b05e800 100644 --- a/src/options.js +++ b/src/options.js @@ -12,4 +12,5 @@ export default { removeEmptyAttributes: true, removeOptionalTags: true, }, + VUE_WITH_STATEMENT: '__VUE_WITH_STATEMENT__', }; diff --git a/src/vueTransform.js b/src/vueTransform.js index 4d20df2..c0f68e7 100644 --- a/src/vueTransform.js +++ b/src/vueTransform.js @@ -1,5 +1,6 @@ import deIndent from 'de-indent'; import htmlMinifier from 'html-minifier'; +import { compile as compileTemplate } from 'vue-template-compiler'; import parse5 from 'parse5'; import validateTemplate from 'vue-template-validator'; import { relative } from 'path'; @@ -38,6 +39,39 @@ function padContent(content) { .join('\n'); } +/** + * Wrap code inside a with statement inside a function + * This is necessary for Vue 2 template compilation + * + * @param {string} code + * @returns {string} + */ +function wrapRenderFunction(code) { + // Replace with(this) by something that works on strict mode + // https://github.com/vuejs/vue-template-es2015-compiler/blob/master/index.js + return `function(){${code.replace(/with\(this\)/g, 'if("__VUE_WITH_STATEMENT__")')}}`; +} + +/** + * Only support for es5 modules + * + * @param script + * @param render + * @returns {string} + */ +function injectRender(script, render) { + const matches = /(export default[^{]*\{)/g.exec(script); + if (matches) { + return script.split(matches[1]) + .join(`${matches[1]}` + + `render: ${wrapRenderFunction(render.render)},` + + 'staticRenderFns: [' + + `${render.staticRenderFns.map(wrapRenderFunction).join(',')}],` + ); + } + throw new Error('[rollup-plugin-vue] could not find place to inject template in script.'); +} + /** * Only support for es5 modules * @@ -78,19 +112,24 @@ function processTemplate(node, filePath, content) { * @param {string} content * @param {string} template */ -function processScript(node, filePath, content, template) { +function processScript(node, filePath, content, { template, render }) { const lang = checkLang(node) || 'js'; let script = parse5.serialize(node); // pad the script to ensure correct line number for syntax errors const location = content.indexOf(script); const before = padContent(content.slice(0, location)); script = before + script; - script = injectTemplate(script, template, lang); + if (template) { + script = injectTemplate(script, template, lang); + } else if (render) { + script = injectRender(script, render, lang); + } script = deIndent(script); + return script; } -export default function vueTransform(code, filePath) { +export default function vueTransform(code, filePath, transformOptions) { // 1. Parse the file into an HTML tree const fragment = parse5.parseFragment(code, { locationInfo: true }); @@ -108,10 +147,17 @@ export default function vueTransform(code, filePath) { // 4. Process template const template = processTemplate(nodes.template, filePath, code); + let js; + if (transformOptions.compileTemplate) { + const render = compileTemplate(template); + js = processScript(nodes.script, filePath, code, { render }); + } else { + js = processScript(nodes.script, filePath, code, { template }); + } // 5. Process script & style return { - js: processScript(nodes.script, filePath, code, template), + js, css: nodes.style && { content: parse5.serialize(nodes.style), lang: checkLang(nodes.style), diff --git a/test/expects/compileTemplate.js b/test/expects/compileTemplate.js new file mode 100755 index 0000000..95a5d7a --- /dev/null +++ b/test/expects/compileTemplate.js @@ -0,0 +1,9 @@ +var compileTemplate = {render: function(){if("__VUE_WITH_STATEMENT__"){return _h('div',[_h('p',[_s(msg)])])}},staticRenderFns: [], + data() { + return { + msg: 'Compile Template' + } + } +} + +export default compileTemplate; \ No newline at end of file diff --git a/test/fixtures/compileTemplate.vue b/test/fixtures/compileTemplate.vue new file mode 100755 index 0000000..d5306ff --- /dev/null +++ b/test/fixtures/compileTemplate.vue @@ -0,0 +1,15 @@ + + + diff --git a/test/test.js b/test/test.js index 5c3f651..912edef 100644 --- a/test/test.js +++ b/test/test.js @@ -23,7 +23,8 @@ function test(name) { plugins: [vuePlugin({ css (css) { actualCss = css - } + }, + compileTemplate: name === 'compileTemplate' })] }).then(function (bundle) { var result = bundle.generate()