From c4fc82595d1a5f23ec56a1ca1f90a541d69ea50f Mon Sep 17 00:00:00 2001 From: Rahul Kadyan Date: Tue, 3 Oct 2017 08:19:11 +0530 Subject: [PATCH] feat: template to render function compiler --- src/template-compiler/index.js | 68 +++++++++++++++++++ .../modules/transform-require.js | 48 +++++++++++++ test/template-compiler.test.js | 32 +++++++++ 3 files changed, 148 insertions(+) create mode 100644 src/template-compiler/index.js create mode 100644 src/template-compiler/modules/transform-require.js create mode 100644 test/template-compiler.test.js diff --git a/src/template-compiler/index.js b/src/template-compiler/index.js new file mode 100644 index 0000000..a796e05 --- /dev/null +++ b/src/template-compiler/index.js @@ -0,0 +1,68 @@ +const compiler = require('vue-template-compiler') +const transpile = require('vue-template-es2015-compiler') +const { js_beautify: beautify } = require('js-beautify') + +const transformRequire = require('./modules/transform-require') + +module.exports = function compileTemplate (template, filename, config) { + const options = Object.assign({ + preserveWhitespace: true + }, config.options, { + modules: [transformRequire(config.transformToRequire)].concat(config.plugins || []) + }) + 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 + } + + if (output.errors && output.errors.length) { + output.code = config.esModule !== false + ? `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 || + !config.buble.transforms || + config.buble.transforms.stripWith !== false + )) { + output.code += `render._withStripped = true\n` + } + + const __exports__ = `{ render: render, staticRenderFns: staticRenderFns }` + output.code += config.esModule !== false + ? `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('vue-hot-reload-api').rerender('${options.scopeId}', module.exports)\n` + + ' }\n' + + '}' + } + } + + return Promise.resolve(output) +} + +function toFunction (code) { + return 'function () {' + beautify(code, { + indent_size: 2 // eslint-disable-line camelcase + }) + '}' +} + +function pad (html) { + return html.split(/\r?\n/).map(line => ` ${line}`).join('\n') +} 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 `:` 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/template-compiler.test.js b/test/template-compiler.test.js new file mode 100644 index 0000000..9b905ca --- /dev/null +++ b/test/template-compiler.test.js @@ -0,0 +1,32 @@ +const compiler = require('../src/template-compiler') + +test('should compile template to esModule', async () => { + const template = { + code: '
{{foo}}
\n' + } + const compiled = await compiler(template, 'foo.vue', { scopeId: 'xxx' }) + + 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', async () => { + const template = { + code: '
{{foo}}
\n' + } + const compiled = await compiler(template, 'foo.vue', { scopeId: 'xxx', esModule: 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', async () => { + const template = { + code: '
{{foo}}
\n' + } + const compiled = await compiler(template, 'foo.vue', { scopeId: 'xxx', isHot: true }) + + expect(compiled.code.indexOf('module.hot.accept')).toBeGreaterThan(-1) +})