diff --git a/e2e/__projects__/basic/__snapshots__/test.js.snap b/e2e/__projects__/basic/__snapshots__/test.js.snap index 78f47729..1125cbfd 100644 --- a/e2e/__projects__/basic/__snapshots__/test.js.snap +++ b/e2e/__projects__/basic/__snapshots__/test.js.snap @@ -54,13 +54,12 @@ var _default = { }; exports.default = _default; ; -var __options__ = typeof exports.default === 'function' -? exports.default.options -: exports.default +var __options__ = typeof exports.default === 'function' ? exports.default.options : exports.default var render = function() { var _vm = this var _h = _vm.$createElement - var _c = _vm._self._c || _h + /* istanbul ignore next */ +var _c = _vm._self._c || _h return _c(\\"div\\", { staticClass: \\"hello\\" }, [ _c(\\"h1\\", { class: _vm.headingClasses }, [_vm._v(_vm._s(_vm.msg))]) ]) @@ -86,7 +85,7 @@ this['$style'], {\\"testB\\":\\"testB\\"}); __options__.beforeCreate = beforeCreate ? [].concat(beforeCreate, styleFn) : [styleFn] })() -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkJhc2ljLnZ1ZSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUhBO0FBS0E7QUFQQTtBQVNBO0FBQ0E7QUFDQTtBQUNBO0FBRkE7QUFJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBSEE7QUFqQkEiLCJzb3VyY2VzQ29udGVudCI6WyI8dGVtcGxhdGU+XG4gIDxkaXYgY2xhc3M9XCJoZWxsb1wiPlxuICAgIDxoMSA6Y2xhc3M9XCJoZWFkaW5nQ2xhc3Nlc1wiPnt7IG1zZyB9fTwvaDE+XG4gIDwvZGl2PlxuPC90ZW1wbGF0ZT5cblxuPHN0eWxlIG1vZHVsZT1cImNzc1wiPlxuLnRlc3RBIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogcmVkO1xufVxuPC9zdHlsZT5cbjxzdHlsZSBtb2R1bGU+XG4udGVzdEIge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiBibHVlO1xufVxuPC9zdHlsZT5cbjxzdHlsZT5cbi50ZXN0QyB7XG4gIGJhY2tncm91bmQtY29sb3I6IGJsdWU7XG59XG48L3N0eWxlPlxuXG48c2NyaXB0PlxuZXhwb3J0IGRlZmF1bHQge1xuICBuYW1lOiAnYmFzaWMnLFxuICBjb21wdXRlZDoge1xuICAgIGhlYWRpbmdDbGFzc2VzOiBmdW5jdGlvbiBoZWFkaW5nQ2xhc3NlcygpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHJlZDogdGhpcy5pc0NyYXp5LFxuICAgICAgICBibHVlOiAhdGhpcy5pc0NyYXp5LFxuICAgICAgICBzaGFkb3c6IHRoaXMuaXNDcmF6eVxuICAgICAgfVxuICAgIH1cbiAgfSxcbiAgZGF0YTogZnVuY3Rpb24gZGF0YSgpIHtcbiAgICByZXR1cm4ge1xuICAgICAgbXNnOiAnV2VsY29tZSB0byBZb3VyIFZ1ZS5qcyBBcHAnLFxuICAgICAgaXNDcmF6eTogZmFsc2VcbiAgICB9XG4gIH0sXG4gIG1ldGhvZHM6IHtcbiAgICB0b2dnbGVDbGFzczogZnVuY3Rpb24gdG9nZ2xlQ2xhc3MoKSB7XG4gICAgICB0aGlzLmlzQ3JhenkgPSAhdGhpcy5pc0NyYXp5XG4gICAgfVxuICB9XG59XG48L3NjcmlwdD5cbiJdfQ==" +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkJhc2ljLnZ1ZSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUhBO0FBS0E7QUFQQTtBQVNBO0FBQ0E7QUFDQTtBQUNBO0FBRkE7QUFJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBSEE7QUFqQkE7Ozs7QUF2QkE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBIiwic291cmNlc0NvbnRlbnQiOlsiPHRlbXBsYXRlPlxuICA8ZGl2IGNsYXNzPVwiaGVsbG9cIj5cbiAgICA8aDEgOmNsYXNzPVwiaGVhZGluZ0NsYXNzZXNcIj57eyBtc2cgfX08L2gxPlxuICA8L2Rpdj5cbjwvdGVtcGxhdGU+XG5cbjxzdHlsZSBtb2R1bGU9XCJjc3NcIj5cbi50ZXN0QSB7XG4gIGJhY2tncm91bmQtY29sb3I6IHJlZDtcbn1cbjwvc3R5bGU+XG48c3R5bGUgbW9kdWxlPlxuLnRlc3RCIHtcbiAgYmFja2dyb3VuZC1jb2xvcjogYmx1ZTtcbn1cbjwvc3R5bGU+XG48c3R5bGU+XG4udGVzdEMge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiBibHVlO1xufVxuPC9zdHlsZT5cblxuPHNjcmlwdD5cbmV4cG9ydCBkZWZhdWx0IHtcbiAgbmFtZTogJ2Jhc2ljJyxcbiAgY29tcHV0ZWQ6IHtcbiAgICBoZWFkaW5nQ2xhc3NlczogZnVuY3Rpb24gaGVhZGluZ0NsYXNzZXMoKSB7XG4gICAgICByZXR1cm4ge1xuICAgICAgICByZWQ6IHRoaXMuaXNDcmF6eSxcbiAgICAgICAgYmx1ZTogIXRoaXMuaXNDcmF6eSxcbiAgICAgICAgc2hhZG93OiB0aGlzLmlzQ3JhenlcbiAgICAgIH1cbiAgICB9XG4gIH0sXG4gIGRhdGE6IGZ1bmN0aW9uIGRhdGEoKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIG1zZzogJ1dlbGNvbWUgdG8gWW91ciBWdWUuanMgQXBwJyxcbiAgICAgIGlzQ3Jhenk6IGZhbHNlXG4gICAgfVxuICB9LFxuICBtZXRob2RzOiB7XG4gICAgdG9nZ2xlQ2xhc3M6IGZ1bmN0aW9uIHRvZ2dsZUNsYXNzKCkge1xuICAgICAgdGhpcy5pc0NyYXp5ID0gIXRoaXMuaXNDcmF6eVxuICAgIH1cbiAgfVxufVxuPC9zY3JpcHQ+XG4iXX0=" `; exports[`generates source maps using src attributes 1`] = ` @@ -121,13 +120,12 @@ var _default = { }; exports.default = _default; ; -var __options__ = typeof exports.default === 'function' -? exports.default.options -: exports.default +var __options__ = typeof exports.default === 'function' ? exports.default.options : exports.default var render = function() { var _vm = this var _h = _vm.$createElement - var _c = _vm._self._c || _h + /* istanbul ignore next */ +var _c = _vm._self._c || _h return _c(\\"div\\", { staticClass: \\"hello\\" }, [ _c(\\"h1\\", { class: _vm.headingClasses }, [_vm._v(_vm._s(_vm.msg))]) ]) @@ -138,5 +136,5 @@ render._withStripped = true __options__.render = render __options__.staticRenderFns = staticRenderFns -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkJhc2ljU3JjLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUhBO0FBS0E7QUFQQTtBQVNBO0FBQ0E7QUFDQTtBQUNBO0FBRkE7QUFJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBSEE7QUFqQkEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCB7XG4gIG5hbWU6ICdiYXNpYycsXG4gIGNvbXB1dGVkOiB7XG4gICAgaGVhZGluZ0NsYXNzZXM6IGZ1bmN0aW9uIGhlYWRpbmdDbGFzc2VzKCkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgcmVkOiB0aGlzLmlzQ3JhenksXG4gICAgICAgIGJsdWU6ICF0aGlzLmlzQ3JhenksXG4gICAgICAgIHNoYWRvdzogdGhpcy5pc0NyYXp5XG4gICAgICB9XG4gICAgfVxuICB9LFxuICBkYXRhOiBmdW5jdGlvbiBkYXRhKCkge1xuICAgIHJldHVybiB7XG4gICAgICBtc2c6ICdXZWxjb21lIHRvIFlvdXIgVnVlLmpzIEFwcCcsXG4gICAgICBpc0NyYXp5OiBmYWxzZVxuICAgIH1cbiAgfSxcbiAgbWV0aG9kczoge1xuICAgIHRvZ2dsZUNsYXNzOiBmdW5jdGlvbiB0b2dnbGVDbGFzcygpIHtcbiAgICAgIHRoaXMuaXNDcmF6eSA9ICF0aGlzLmlzQ3JhenlcbiAgICB9XG4gIH1cbn1cbiJdfQ==" +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIlNvdXJjZU1hcHNTcmMudnVlIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUhBO0FBS0E7QUFQQTtBQVNBO0FBQ0E7QUFDQTtBQUNBO0FBRkE7QUFJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBSEE7QUFqQkEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCB7XG4gIG5hbWU6ICdiYXNpYycsXG4gIGNvbXB1dGVkOiB7XG4gICAgaGVhZGluZ0NsYXNzZXM6IGZ1bmN0aW9uIGhlYWRpbmdDbGFzc2VzKCkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgcmVkOiB0aGlzLmlzQ3JhenksXG4gICAgICAgIGJsdWU6ICF0aGlzLmlzQ3JhenksXG4gICAgICAgIHNoYWRvdzogdGhpcy5pc0NyYXp5XG4gICAgICB9XG4gICAgfVxuICB9LFxuICBkYXRhOiBmdW5jdGlvbiBkYXRhKCkge1xuICAgIHJldHVybiB7XG4gICAgICBtc2c6ICdXZWxjb21lIHRvIFlvdXIgVnVlLmpzIEFwcCcsXG4gICAgICBpc0NyYXp5OiBmYWxzZVxuICAgIH1cbiAgfSxcbiAgbWV0aG9kczoge1xuICAgIHRvZ2dsZUNsYXNzOiBmdW5jdGlvbiB0b2dnbGVDbGFzcygpIHtcbiAgICAgIHRoaXMuaXNDcmF6eSA9ICF0aGlzLmlzQ3JhenlcbiAgICB9XG4gIH1cbn1cbiJdfQ==" `; diff --git a/lib/add-template-mapping.js b/lib/add-template-mapping.js deleted file mode 100644 index 57ea8e20..00000000 --- a/lib/add-template-mapping.js +++ /dev/null @@ -1,26 +0,0 @@ -const splitRE = /\r?\n/g - -module.exports = function addTemplateMapping( - content, - parts, - output, - map, - beforeLines -) { - const afterLines = output.split(splitRE).length - const templateLine = content.slice(0, parts.template.start).split(splitRE) - .length - for (; beforeLines < afterLines; beforeLines++) { - map.addMapping({ - source: map._filename, - generated: { - line: beforeLines, - column: 0 - }, - original: { - line: templateLine, - column: 0 - } - }) - } -} diff --git a/lib/coffee-transformer.js b/lib/coffee-transformer.js new file mode 100644 index 00000000..79c55dae --- /dev/null +++ b/lib/coffee-transformer.js @@ -0,0 +1,26 @@ +const ensureRequire = require('./ensure-require.js') +const throwError = require('./utils').throwError +const getBabelOptions = require('./utils').getBabelOptions + +module.exports = { + process(src, filename, config) { + ensureRequire('coffee', ['coffeescript']) + const coffee = require('coffeescript') + const babelOptions = getBabelOptions(filename) + let compiled + try { + compiled = coffee.compile(src, { + filename, + bare: true, + sourceMap: true, + transpile: babelOptions + }) + } catch (err) { + throwError(err) + } + return { + code: compiled.js, + map: compiled.v3SourceMap + } + } +} diff --git a/lib/compilers/coffee-compiler.js b/lib/compilers/coffee-compiler.js deleted file mode 100644 index 45e53afd..00000000 --- a/lib/compilers/coffee-compiler.js +++ /dev/null @@ -1,24 +0,0 @@ -const ensureRequire = require('../ensure-require.js') -const throwError = require('../utils').throwError -const getBabelOptions = require('../utils').getBabelOptions - -module.exports = function(src, filename, config) { - ensureRequire('coffee', ['coffeescript']) - const coffee = require('coffeescript') - const babelOptions = getBabelOptions(filename) - let compiled - try { - compiled = coffee.compile(src, { - filename, - bare: true, - sourceMap: true, - transpile: babelOptions - }) - } catch (err) { - throwError(err) - } - return { - code: compiled.js, - map: compiled.v3SourceMap - } -} diff --git a/lib/compilers/typescript-compiler.js b/lib/compilers/typescript-compiler.js deleted file mode 100644 index c2d47834..00000000 --- a/lib/compilers/typescript-compiler.js +++ /dev/null @@ -1,44 +0,0 @@ -const ensureRequire = require('../ensure-require') -const babelJest = require('babel-jest') -const getBabelOptions = require('../utils').getBabelOptions -const getTsJestConfig = require('../utils').getTsJestConfig -const stripInlineSourceMap = require('../utils').stripInlineSourceMap -const getCustomTransformer = require('../utils').getCustomTransformer -const getVueJestConfig = require('../utils').getVueJestConfig - -module.exports = function compileTypescript(scriptContent, filePath, config) { - ensureRequire('typescript', ['typescript']) - const typescript = require('typescript') - const vueJestConfig = getVueJestConfig(config) - const { tsconfig } = getTsJestConfig(config) - const babelOptions = getBabelOptions(filePath) - - const res = typescript.transpileModule(scriptContent, tsconfig) - - res.outputText = stripInlineSourceMap(res.outputText) - - const inputSourceMap = - res.sourceMapText !== undefined ? JSON.parse(res.sourceMapText) : '' - - // handle ES modules in TS source code in case user uses non commonjs module - // output and there is no .babelrc. - let inlineBabelOptions = {} - if (tsconfig.compilerOptions.module !== 'commonjs' && !babelOptions) { - inlineBabelOptions = { - plugins: [require('@babel/plugin-transform-modules-commonjs')] - } - } - const customTransformer = getCustomTransformer( - vueJestConfig['transform'], - 'js' - ) - const transformer = customTransformer.process - ? customTransformer - : babelJest.createTransformer( - Object.assign(inlineBabelOptions, { - inputSourceMap - }) - ) - - return transformer.process(res.outputText, filePath, config) -} diff --git a/lib/generate-code.js b/lib/generate-code.js new file mode 100644 index 00000000..e86e1090 --- /dev/null +++ b/lib/generate-code.js @@ -0,0 +1,83 @@ +const splitRE = /\r?\n/g + +module.exports = function generateCode( + scriptResult, + templateResult, + stylesResult, + isFunctional +) { + let output = '' + let renderFnStartLine + let renderFnEndLine + + if (scriptResult) { + output += `${scriptResult.code};\n` + } else { + output += + `Object.defineProperty(exports, "__esModule", {\n` + + ` value: true\n` + + `});\n` + + 'module.exports.default = {};\n' + } + + output += + `var __options__ = typeof exports.default === 'function' ` + + `? exports.default.options ` + + `: exports.default\n` + + if (templateResult) { + renderFnStartLine = output.split(splitRE).length + templateResult.code = templateResult.code.replace( + 'var _c = _vm._self._c || _h', + '/* istanbul ignore next */\nvar _c = _vm._self._c || _h' + ) + output += `${templateResult.code}\n` + + renderFnEndLine = output.split(splitRE).length + + output += + `__options__.render = render\n` + + `__options__.staticRenderFns = staticRenderFns\n` + + if (isFunctional) { + output += '__options__.functional = true\n' + output += '__options__._compiled = true\n' + } + } + + if (stylesResult) { + const styleStr = stylesResult + .map( + ({ code, moduleName }) => + `if(!this['${moduleName}']) {\n` + + ` this['${moduleName}'] = {};\n` + + `}\n` + + `this['${moduleName}'] = Object.assign(\n` + + `this['${moduleName}'], ${code});\n` + ) + .join('') + if (isFunctional) { + output += + `;(function() {\n` + + ` var originalRender = __options__.render\n` + + ` var styleFn = function () { ${styleStr} }\n` + + ` __options__.render = function renderWithStyleInjection (h, context) {\n` + + ` styleFn.call(context)\n` + + ` return originalRender(h, context)\n` + + ` }\n` + + `})()\n` + } else { + output += + `;(function() {\n` + + ` var beforeCreate = __options__.beforeCreate\n` + + ` var styleFn = function () { ${styleStr} }\n` + + ` __options__.beforeCreate = beforeCreate ? [].concat(beforeCreate, styleFn) : [styleFn]\n` + + `})()\n` + } + } + return { + code: output, + renderFnStartLine, + renderFnEndLine + } +} diff --git a/lib/generate-source-map.js b/lib/generate-source-map.js index 94c22d4f..ec9ec089 100644 --- a/lib/generate-source-map.js +++ b/lib/generate-source-map.js @@ -3,37 +3,67 @@ const sourceMap = require('source-map') const splitRE = /\r?\n/g module.exports = function generateSourceMap( - script, - filePath, - content, - inputMap + scriptResult, + src, + filename, + renderFnStartLine, + renderFnEndLine, + templateLine ) { - const filename = path.basename(filePath) - + const file = path.basename(filename) const map = new sourceMap.SourceMapGenerator() - map.setSourceContent(filename, content) - // check input source map from babel/coffee etc - let inputMapConsumer = inputMap && new sourceMap.SourceMapConsumer(inputMap) - script.split(splitRE).forEach(function(line, index) { - let ln = index + 1 - let originalLine = inputMapConsumer - ? inputMapConsumer.originalPositionFor({ line: ln, column: 0 }).line - : ln - if (originalLine) { - map.addMapping({ - source: filename, - generated: { - line: ln, - column: 0 - }, - original: { - line: originalLine, - column: 0 - } - }) - } - }) - map._filename = filename + const scriptFromExternalSrc = scriptResult && scriptResult.externalSrc + + // If script uses external file for content (using the src attribute) then + // map result to this file, instead of original. + const srcContent = scriptFromExternalSrc ? scriptResult.externalSrc : src + + map.setSourceContent(file, srcContent) + if (scriptResult) { + let inputMapConsumer = + scriptResult.map && new sourceMap.SourceMapConsumer(scriptResult.map) + scriptResult.code.split(splitRE).forEach(function(line, index) { + let ln = index + 1 + let originalLine = inputMapConsumer + ? inputMapConsumer.originalPositionFor({ line: ln, column: 0 }).line + : ln + if (originalLine) { + map.addMapping({ + source: file, + generated: { + line: ln, + column: 0 + }, + original: { + line: originalLine, + column: 0 + } + }) + } + }) + } + + // If script is from external src then the source map will only show the + // script section. We won't map the generated render function to this file, + // because the coverage report would be confusing + if (scriptFromExternalSrc) { + return map + } + + for (; renderFnStartLine < renderFnEndLine; renderFnStartLine++) { + map.addMapping({ + source: file, + generated: { + line: renderFnStartLine, + column: 0 + }, + original: { + line: templateLine, + column: 0 + } + }) + } + return map } diff --git a/lib/process.js b/lib/process.js index e228f049..55a846aa 100644 --- a/lib/process.js +++ b/lib/process.js @@ -1,245 +1,147 @@ +const splitRE = /\r?\n/g + const VueTemplateCompiler = require('vue-template-compiler') const generateSourceMap = require('./generate-source-map') -const addTemplateMapping = require('./add-template-mapping') -const compileTypescript = require('./compilers/typescript-compiler') -const compileCoffeeScript = require('./compilers/coffee-compiler') -const processStyle = require('./process-style') +const typescriptTransformer = require('./typescript-transformer') +const coffeescriptTransformer = require('./coffee-transformer') +const _processStyle = require('./process-style') const fs = require('fs') const path = require('path') -const join = path.join const getVueJestConfig = require('./utils').getVueJestConfig const logResultErrors = require('./utils').logResultErrors -const getCustomTransformer = require('./utils').getCustomTransformer const stripInlineSourceMap = require('./utils').stripInlineSourceMap -const transformContent = require('./utils').transformContent -const splitRE = /\r?\n/g -const babelJest = require('babel-jest') +const throwError = require('./utils').throwError +const babelTransformer = require('babel-jest') const compilerUtils = require('@vue/component-compiler-utils') const convertSourceMap = require('convert-source-map') +const generateCode = require('./generate-code') -/** - * Transforms content using custom transformer. - * - * @param {Object} content - content to be transformed - * @param {String} filePath - absolute path to the file - * @param {Object} config- jest configuration - * @param {Function} transformer - custom transformer function - * @param {Object} attrs - attributes - * @returns {Object|string} content - Transformed content - */ -function processScriptBytransforms( - content, - filePath, - config, - transforms = {}, - attrs -) { - const preprocessedContent = transformContent( - content, - filePath, - config, - transforms.preprocess, - attrs - ) - const processedContent = transformContent( - preprocessedContent, - filePath, - config, - transforms.process, - attrs - ) - return transformContent( - processedContent, - filePath, - config, - transforms.postprocess, - attrs - ) +function resolveTransformer(lang, vueJestConfig) { + if (/^typescript$|tsx?$/.test(lang)) { + return typescriptTransformer + } else if (/^coffee$|coffeescript$/.test(lang)) { + return coffeescriptTransformer + } else { + return babelTransformer + } +} + +function loadSrc(src, filePath) { + var dir = path.dirname(filePath) + var srcPath = path.resolve(dir, src) + try { + return fs.readFileSync(srcPath, 'utf-8') + } catch (e) { + throwError( + 'Failed to load src: "' + src + '" from file: "' + filePath + '"' + ) + } } function processScript(scriptPart, filePath, config) { if (!scriptPart) { - return { - code: '' - } + return null } - const { content, lang = 'js', attrs } = scriptPart - const vueJestConfig = getVueJestConfig(config) - const transforms = getCustomTransformer(vueJestConfig['transform'], lang) - // script has custom transformer - if (transforms.process) { - return transformContent( - content, - filePath, - config, - transforms.process, - attrs - ) - } else if (/^typescript$|tsx?$/.test(lang)) { - return processScriptBytransforms( - content, - filePath, - config, - { - process: compileTypescript - }, - attrs - ) - } else if (/^coffee$|coffeescript$/.test(lang)) { - return processScriptBytransforms( - content, - filePath, - config, - { - process: compileCoffeeScript - }, - attrs - ) + + let externalSrc = null + if (scriptPart.src) { + scriptPart.content = loadSrc(scriptPart.src, filePath) + externalSrc = scriptPart.content } - return processScriptBytransforms(content, filePath, config, babelJest, attrs) + const vueJestConfig = getVueJestConfig(config) + const transformer = resolveTransformer(scriptPart.lang, vueJestConfig) + + const result = transformer.process(scriptPart.content, filePath, config) + result.code = stripInlineSourceMap(result.code) + result.externalSrc = externalSrc + return result } -module.exports = function(src, filePath, config) { +function processTemplate(template, filename, config) { + if (!template) { + return null + } + const vueJestConfig = getVueJestConfig(config) - const descriptor = compilerUtils.parse({ - source: src, + + if (template.src) { + template.content = loadSrc(template.src, filename) + } + + const result = compilerUtils.compileTemplate({ + source: template.content, compiler: VueTemplateCompiler, - filename: filePath + filename: filename, + compilerOptions: { + optimize: false + }, + isFunctional: template.attrs.functional, + preprocessLang: template.lang, + preprocessOptions: vueJestConfig[template.lang] }) - let scriptSrcContent = src - let sourceMapPath = filePath - if (descriptor.script && descriptor.script.src) { - const externalScrPath = join(filePath, '..', descriptor.script.src) + logResultErrors(result) + + return result +} - descriptor.script.content = fs.readFileSync(externalScrPath, 'utf8') - scriptSrcContent = descriptor.script.content - sourceMapPath = externalScrPath +function processStyle(styles, filename, config) { + if (!styles) { + return null } - const result = processScript(descriptor.script, filePath, config) - let compiledScriptContent = result.code - compiledScriptContent = stripInlineSourceMap(compiledScriptContent) - const inputMap = result.map + const filteredStyles = styles + .filter(style => style.module) + .map(style => ({ + code: _processStyle(style, filename, config), + moduleName: style.module === true ? '$style' : style.module + })) - const map = generateSourceMap( - compiledScriptContent, - sourceMapPath, - scriptSrcContent, - inputMap - ) + return filteredStyles.length ? filteredStyles : null +} - let output = '' +module.exports = function(src, filename, config) { + const descriptor = compilerUtils.parse({ + source: src, + compiler: VueTemplateCompiler, + filename + }) - if (compiledScriptContent) { - output += `${compiledScriptContent};\n` - } else { - output += - `Object.defineProperty(exports, "__esModule", {\n` + - ` value: true\n` + - `});\n` + - 'module.exports.default = {};\n' - } + const templateResult = processTemplate(descriptor.template, filename, config) + const scriptResult = processScript(descriptor.script, filename, config) + const stylesResult = processStyle(descriptor.styles, filename, config) - output += - `var __options__ = typeof exports.default === 'function'\n` + - `? exports.default.options\n` + - `: exports.default\n` - - if (descriptor.template) { - descriptor.template.filename = filePath - if (descriptor.template.src) { - descriptor.template.filename = join( - filePath, - '..', - descriptor.template.src - ) - descriptor.template.content = fs.readFileSync( - descriptor.template.filename, - 'utf8' - ) - } - - const templateResult = compilerUtils.compileTemplate({ - source: descriptor.template.content, - compiler: VueTemplateCompiler, - filename: descriptor.template.filename, - isFunctional: descriptor.template.attrs.functional, - preprocessLang: descriptor.template.lang, - preprocessOptions: vueJestConfig[descriptor.template.lang] - }) - - logResultErrors(templateResult) - - output += - `${templateResult.code}\n` + - `__options__.render = render\n` + - `__options__.staticRenderFns = staticRenderFns\n` - - if (descriptor.template.attrs.functional) { - output += '__options__.functional = true\n' - output += '__options__._compiled = true\n' - } - - if (map) { - const beforeLines = output.split(splitRE).length - addTemplateMapping( - compiledScriptContent, - descriptor, - output, - map, - beforeLines - ) - } - } + const isFunctional = + descriptor.template && + descriptor.template.attrs && + descriptor.template.attrs.functional - if (Array.isArray(descriptor.styles) && descriptor.styles.length > 0) { - const styleStr = descriptor.styles - .filter(ast => ast.module) - .map(ast => { - const styleObj = processStyle(ast, filePath, config) - const moduleName = ast.module === true ? '$style' : ast.module - - return ( - `if(!this['${moduleName}']) {\n` + - ` this['${moduleName}'] = {};\n` + - `}\n` + - `this['${moduleName}'] = Object.assign(\n` + - `this['${moduleName}'], ${styleObj});\n` - ) - }) - .filter(_ => _) - .join('') - - if (styleStr.length !== 0) { - if (descriptor.template && descriptor.template.attrs.functional) { - output += - `;(function() {\n` + - ` var originalRender = __options__.render\n` + - ` var styleFn = function () { ${styleStr} }\n` + - ` __options__.render = function renderWithStyleInjection (h, context) {\n` + - ` styleFn.call(context)\n` + - ` return originalRender(h, context)\n` + - ` }\n` + - `})()\n` - } else { - output += - `;(function() {\n` + - ` var beforeCreate = __options__.beforeCreate\n` + - ` var styleFn = function () { ${styleStr} }\n` + - ` __options__.beforeCreate = beforeCreate ? [].concat(beforeCreate, styleFn) : [styleFn]\n` + - `})()\n` - } - } - } + const templateStart = descriptor.template && descriptor.template.start + const templateLine = src.slice(0, templateStart).split(splitRE).length + + const output = generateCode( + scriptResult, + templateResult, + stylesResult, + isFunctional + ) + + const map = generateSourceMap( + scriptResult, + src, + filename, + output.renderFnStartLine, + output.renderFnEndLine, + templateLine + ) if (map) { - output += '\n' + convertSourceMap.fromJSON(map.toString()).toComment() + output.code += '\n' + convertSourceMap.fromJSON(map.toString()).toComment() } return { - code: output, + code: output.code, map } } diff --git a/lib/typescript-transformer.js b/lib/typescript-transformer.js new file mode 100644 index 00000000..7650234a --- /dev/null +++ b/lib/typescript-transformer.js @@ -0,0 +1,46 @@ +const ensureRequire = require('./ensure-require') +const babelJest = require('babel-jest') +const getBabelOptions = require('./utils').getBabelOptions +const getTsJestConfig = require('./utils').getTsJestConfig +const stripInlineSourceMap = require('./utils').stripInlineSourceMap +const getCustomTransformer = require('./utils').getCustomTransformer +const getVueJestConfig = require('./utils').getVueJestConfig + +module.exports = { + process(scriptContent, filePath, config) { + ensureRequire('typescript', ['typescript']) + const typescript = require('typescript') + const vueJestConfig = getVueJestConfig(config) + const { tsconfig } = getTsJestConfig(config) + const babelOptions = getBabelOptions(filePath) + + const res = typescript.transpileModule(scriptContent, tsconfig) + + res.outputText = stripInlineSourceMap(res.outputText) + + const inputSourceMap = + res.sourceMapText !== undefined ? JSON.parse(res.sourceMapText) : '' + + // handle ES modules in TS source code in case user uses non commonjs module + // output and there is no .babelrc. + let inlineBabelOptions = {} + if (tsconfig.compilerOptions.module !== 'commonjs' && !babelOptions) { + inlineBabelOptions = { + plugins: [require('@babel/plugin-transform-modules-commonjs')] + } + } + const customTransformer = getCustomTransformer( + vueJestConfig['transform'], + 'js' + ) + const transformer = customTransformer.process + ? customTransformer + : babelJest.createTransformer( + Object.assign(inlineBabelOptions, { + inputSourceMap + }) + ) + + return transformer.process(res.outputText, filePath, config) + } +}