From 4c42e1b82ba412a248406f3bc3ceefc4bc2577c4 Mon Sep 17 00:00:00 2001 From: Vamsi Kalyan Date: Thu, 10 Jan 2019 21:44:59 +0530 Subject: [PATCH 1/2] add pre & post css transform callbacks to customize css transformation behavior Update lib/compilers/helpers/module-name-mapper-helper.js typo Co-Authored-By: yohodopo Update lib/compilers/helpers/module-name-mapper-helper.js typo Co-Authored-By: yohodopo --- .../babel-config/__snapshots__/test.js.snap | 4 ++ .../babel-config/components/Sass.vue | 5 ++ .../babel-config/components/Scss.vue | 11 ++- .../babel-config/cssPostTransform.js | 17 +++++ .../babel-config/cssPreTransform.js | 4 ++ e2e/__projects__/babel-config/package.json | 6 ++ e2e/__projects__/babel-config/test.js | 16 +++++ .../helpers/module-name-mapper-helper.js | 25 ++++--- lib/compilers/sass-compiler.js | 3 +- lib/compilers/scss-compiler.js | 17 +---- lib/process-style.js | 71 ++++++++++++++++--- yarn.lock | 38 +--------- 12 files changed, 147 insertions(+), 70 deletions(-) create mode 100644 e2e/__projects__/babel-config/cssPostTransform.js create mode 100644 e2e/__projects__/babel-config/cssPreTransform.js diff --git a/e2e/__projects__/babel-config/__snapshots__/test.js.snap b/e2e/__projects__/babel-config/__snapshots__/test.js.snap index 78f47729..a23c6547 100644 --- a/e2e/__projects__/babel-config/__snapshots__/test.js.snap +++ b/e2e/__projects__/babel-config/__snapshots__/test.js.snap @@ -140,3 +140,7 @@ __options__.staticRenderFns = staticRenderFns //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkJhc2ljU3JjLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUhBO0FBS0E7QUFQQTtBQVNBO0FBQ0E7QUFDQTtBQUNBO0FBRkE7QUFJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBSEE7QUFqQkEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCB7XG4gIG5hbWU6ICdiYXNpYycsXG4gIGNvbXB1dGVkOiB7XG4gICAgaGVhZGluZ0NsYXNzZXM6IGZ1bmN0aW9uIGhlYWRpbmdDbGFzc2VzKCkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgcmVkOiB0aGlzLmlzQ3JhenksXG4gICAgICAgIGJsdWU6ICF0aGlzLmlzQ3JhenksXG4gICAgICAgIHNoYWRvdzogdGhpcy5pc0NyYXp5XG4gICAgICB9XG4gICAgfVxuICB9LFxuICBkYXRhOiBmdW5jdGlvbiBkYXRhKCkge1xuICAgIHJldHVybiB7XG4gICAgICBtc2c6ICdXZWxjb21lIHRvIFlvdXIgVnVlLmpzIEFwcCcsXG4gICAgICBpc0NyYXp5OiBmYWxzZVxuICAgIH1cbiAgfSxcbiAgbWV0aG9kczoge1xuICAgIHRvZ2dsZUNsYXNzOiBmdW5jdGlvbiB0b2dnbGVDbGFzcygpIHtcbiAgICAgIHRoaXMuaXNDcmF6eSA9ICF0aGlzLmlzQ3JhenlcbiAgICB9XG4gIH1cbn1cbiJdfQ==" `; + +exports[`processes SCSS using user specified post transforms 1`] = `"
"`; + +exports[`processes SCSS using user specified pre transforms 1`] = `"
"`; diff --git a/e2e/__projects__/babel-config/components/Sass.vue b/e2e/__projects__/babel-config/components/Sass.vue index 877d76f6..a76ec392 100644 --- a/e2e/__projects__/babel-config/components/Sass.vue +++ b/e2e/__projects__/babel-config/components/Sass.vue @@ -13,3 +13,8 @@ .d background-color: blue + + diff --git a/e2e/__projects__/babel-config/components/Scss.vue b/e2e/__projects__/babel-config/components/Scss.vue index 5e2eab57..18daa220 100644 --- a/e2e/__projects__/babel-config/components/Scss.vue +++ b/e2e/__projects__/babel-config/components/Scss.vue @@ -1,5 +1,8 @@ + + diff --git a/e2e/__projects__/babel-config/cssPostTransform.js b/e2e/__projects__/babel-config/cssPostTransform.js new file mode 100644 index 00000000..c2b2cc8a --- /dev/null +++ b/e2e/__projects__/babel-config/cssPostTransform.js @@ -0,0 +1,17 @@ +const cssExtract = require('extract-from-css') +module.exports = function (cssContent, config, attrs) { + const cssNames = cssExtract.extractClasses(cssContent) + const obj = {} + for (let i = 0, l = cssNames.length; i < l; i++) { + obj[cssNames[i]] = cssNames[i] + } + + if (attrs.themed) { + return { + light: obj, + dark: obj + } + } + return obj; + +} \ No newline at end of file diff --git a/e2e/__projects__/babel-config/cssPreTransform.js b/e2e/__projects__/babel-config/cssPreTransform.js new file mode 100644 index 00000000..d5e6b6c2 --- /dev/null +++ b/e2e/__projects__/babel-config/cssPreTransform.js @@ -0,0 +1,4 @@ +module.exports = function (cssContent, config, attrs) { + return `${cssContent}\n .g{width: 10px}`; + +} \ No newline at end of file diff --git a/e2e/__projects__/babel-config/package.json b/e2e/__projects__/babel-config/package.json index f1a7e5ea..d454f667 100644 --- a/e2e/__projects__/babel-config/package.json +++ b/e2e/__projects__/babel-config/package.json @@ -40,6 +40,12 @@ "vue-jest": { "pug": { "basedir": "./" + }, + "cssTransform": { + "scss": { + "post": "./cssPostTransform.js", + "pre": "./cssPreTransform.js" + } } } } diff --git a/e2e/__projects__/babel-config/test.js b/e2e/__projects__/babel-config/test.js index 3d7eca79..aec9a682 100644 --- a/e2e/__projects__/babel-config/test.js +++ b/e2e/__projects__/babel-config/test.js @@ -145,6 +145,7 @@ it('processes Sass', () => { expect(wrapper.vm.$style.a).toEqual('a') expect(wrapper.vm.$style.b).toEqual('b') expect(wrapper.vm.$style.c).toEqual('c') + expect(wrapper.vm.$style.light).toBeUndefined() }) it('processes SCSS', () => { @@ -154,6 +155,21 @@ it('processes SCSS', () => { expect(wrapper.vm.$style.c).toEqual('c') }) +test('processes SCSS using user specified post transforms', () => { + const wrapper = mount(Scss) + expect(wrapper.vm.$style.light.a).toBeUndefined() + expect(wrapper.vm.$style.light.f).toEqual('f') + expect(wrapper.vm.$style.dark.f).toEqual('f') + expect(wrapper.vm.$style.dark.g).toEqual('g') + expect(wrapper.html()).toMatchSnapshot() +}) + +test('processes SCSS using user specified pre transforms', () => { + const wrapper = mount(Scss) + expect(wrapper.vm.$style.g).toEqual('g') + expect(wrapper.html()).toMatchSnapshot() +}) + test('process Stylus', () => { const wrapper = mount(Stylus) expect(wrapper.vm).toBeTruthy() diff --git a/lib/compilers/helpers/module-name-mapper-helper.js b/lib/compilers/helpers/module-name-mapper-helper.js index 207edb19..05f78706 100644 --- a/lib/compilers/helpers/module-name-mapper-helper.js +++ b/lib/compilers/helpers/module-name-mapper-helper.js @@ -1,17 +1,22 @@ const path = require('path') - +const matchModuleImport = /^[^?]*~/ /** - * Resolves the path to the file locally. + * Resolves the path to the file/module. * * @param {String} to - the name of the file to resolve to - * @param {String} localPath - the local path + * @param {String} importPath - the local path + * @param {String} fileType - extension of the file to be resolved * @returns {String} path - path to the file to import */ -function localResolve(to, localPath) { - if (localPath.startsWith('/')) { - return localPath +function resolve(to, importPath, fileType) { + importPath = + path.extname(importPath) === '' ? `${importPath}.${fileType}` : importPath + if (importPath.startsWith('/')) { + return importPath + } else if (matchModuleImport.test(importPath)) { + return require.resolve(importPath.replace(matchModuleImport, '')) } - return path.join(path.dirname(to), localPath) + return path.join(path.dirname(to), importPath) } /** @@ -20,12 +25,14 @@ function localResolve(to, localPath) { * @param {String} source - the original string * @param {String} filePath - the path of the current file (where the source originates) * @param {Object} jestConfig - the jestConfig holding the moduleNameMapper settings + * @param {Object} fileType - extn of the file to be resolved * @returns {String} path - the final path to import (including replacements via moduleNameMapper) */ module.exports = function applyModuleNameMapper( source, filePath, - jestConfig = {} + jestConfig = {}, + fileType = '' ) { if (!jestConfig.moduleNameMapper) return source @@ -47,5 +54,5 @@ module.exports = function applyModuleNameMapper( ) }, source) - return localResolve(filePath, importPath) + return resolve(filePath, importPath, fileType) } diff --git a/lib/compilers/sass-compiler.js b/lib/compilers/sass-compiler.js index 3827c14f..f993bc1d 100644 --- a/lib/compilers/sass-compiler.js +++ b/lib/compilers/sass-compiler.js @@ -28,7 +28,8 @@ module.exports = (content, filePath, jestConfig = {}) => { file: applyModuleNameMapper( url, prev === 'stdin' ? filePath : prev, - jestConfig + jestConfig, + 'sass' ) }) }) diff --git a/lib/compilers/scss-compiler.js b/lib/compilers/scss-compiler.js index 76b4b8be..4832305b 100644 --- a/lib/compilers/scss-compiler.js +++ b/lib/compilers/scss-compiler.js @@ -1,5 +1,3 @@ -const path = require('path') -const fs = require('fs') const ensureRequire = require('../ensure-require') const getVueJestConfig = require('../utils').getVueJestConfig const warn = require('../utils').warn @@ -19,26 +17,17 @@ module.exports = (content, filePath, jestConfig = {}) => { ensureRequire('scss', ['node-sass']) const sass = require('node-sass') - - let scssResources = '' - if (vueJestConfig.resources && vueJestConfig.resources.scss) { - scssResources = vueJestConfig.resources.scss - .map(scssResource => path.resolve(process.cwd(), scssResource)) - .filter(scssResourcePath => fs.existsSync(scssResourcePath)) - .map(scssResourcePath => fs.readFileSync(scssResourcePath).toString()) - .join('\n') - } - try { return sass .renderSync({ - data: scssResources + content, + data: content, outputStyle: 'compressed', importer: (url, prev, done) => ({ file: applyModuleNameMapper( url, prev === 'stdin' ? filePath : prev, - jestConfig + jestConfig, + 'scss' ) }) }) diff --git a/lib/process-style.js b/lib/process-style.js index d074d01b..28e635b7 100644 --- a/lib/process-style.js +++ b/lib/process-style.js @@ -1,19 +1,65 @@ +const path = require('path') +const fs = require('fs') const getVueJestConfig = require('./utils').getVueJestConfig +const warn = require('./utils').warn const cssExtract = require('extract-from-css') module.exports = function processStyle(stylePart, filePath, jestConfig = {}) { const vueJestConfig = getVueJestConfig(jestConfig) + let cssTransform = + vueJestConfig['cssTransform'] && + vueJestConfig['cssTransform'][stylePart.lang] + const validTransforms = cssTransform && (cssTransform.pre || cssTransform.post) + cssTransform = cssTransform || {} if (!stylePart || vueJestConfig.experimentalCSSCompile === false) { return {} } - const processStyleByLang = lang => - require('./compilers/' + lang + '-compiler')( - stylePart.content, + const globaResources = lang => { + let globalResources = '' + if (vueJestConfig.resources && vueJestConfig.resources[lang]) { + globalResources = vueJestConfig.resources[lang] + .map(resource => path.resolve(process.cwd(), resource)) + .filter(resourcePath => fs.existsSync(resourcePath)) + .map(resourcePath => fs.readFileSync(resourcePath).toString()) + .join('\n') + } + return globalResources + } + + const cssTransfomer = (transform, content) => { + if (!validTransforms || !transform || typeof transform !== 'string') { + return content + } + + const transformPath = /^(\.\.\/|\.\/|\/)/.test(transform) ? path.resolve(process.cwd(), transform) : transform + return require(transformPath)( + content, + vueJestConfig, + stylePart.attrs + ) + } + + const processStyleByLang = lang => { + const content = globaResources(lang) + stylePart.content + const preProcessedContent = cssTransfomer(cssTransform.pre, content) + return require('./compilers/' + lang + '-compiler')( + preProcessedContent, filePath, jestConfig ) + } + + const extractClassMap = cssCode => { + const cssNames = cssExtract.extractClasses(cssCode) + const obj = {} + for (let i = 0, l = cssNames.length; i < l; i++) { + obj[cssNames[i]] = cssNames[i] + } + + return obj + } let cssCode = stylePart.content switch (stylePart.lang) { @@ -29,12 +75,17 @@ module.exports = function processStyle(stylePart, filePath, jestConfig = {}) { break } - const cssNames = cssExtract.extractClasses(cssCode) - - const obj = {} - for (let i = 0, l = cssNames.length; i < l; i++) { - obj[cssNames[i]] = cssNames[i] + if (validTransforms) { + let locals = cssTransfomer(cssTransform.post, cssCode) + if (typeof locals !== 'object') { + !vueJestConfig.hideStyleWarn && + warn( + 'post-transformers are expected to return an object with key value pair as class names of the component' + ) + locals = extractClassMap(locals) + } + return locals + } else { + return extractClassMap(cssCode) } - - return obj } diff --git a/yarn.lock b/yarn.lock index 51db0ebc..ab525da1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2402,7 +2402,7 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debuglog@*, debuglog@^1.0.1: +debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -3902,7 +3902,7 @@ import-local@^1.0.0: pkg-dir "^2.0.0" resolve-cwd "^2.0.0" -imurmurhash@*, imurmurhash@^0.1.4: +imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -5225,11 +5225,6 @@ lockfile@^1.0.4: dependencies: signal-exit "^3.0.2" -lodash._baseindexof@*: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" - integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= - lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -5238,33 +5233,11 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@*: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" - integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= - -lodash._cacheindexof@*: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" - integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= - -lodash._createcache@*: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" - integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= - dependencies: - lodash._getnative "^3.0.0" - lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= -lodash._getnative@*, lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - lodash._reinterpolate@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -5320,11 +5293,6 @@ lodash.pick@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= -lodash.restparam@*: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" @@ -7260,7 +7228,7 @@ readable-stream@~1.1.10: isarray "0.0.1" string_decoder "~0.10.x" -readdir-scoped-modules@*, readdir-scoped-modules@^1.0.0: +readdir-scoped-modules@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747" integrity sha1-n6+jfShr5dksuuve4DDcm19AZ0c= From ad1ebcf683a9459431abc3f8f1b78f2c27eca1d6 Mon Sep 17 00:00:00 2001 From: Vamsi Kalyan Date: Thu, 17 Jan 2019 22:00:48 +0530 Subject: [PATCH 2/2] support for custom transformers --- .../babel-config/__snapshots__/test.js.snap | 2 + .../babel-config/babel-transformer.js | 5 + .../babel-config/components/PostCss.vue | 17 +- .../babel-config/cssPostTransform.js | 17 -- .../babel-config/cssPreTransform.js | 4 - e2e/__projects__/babel-config/package.json | 11 +- .../babel-config/pcssTransform.js | 7 + .../babel-config/scssTransform.js | 21 ++ e2e/__projects__/babel-config/test.js | 5 +- e2e/test-runner.js | 2 +- lib/compilers/typescript-compiler.js | 18 +- lib/process-style.js | 184 +++++++++++------- lib/process.js | 104 ++++++++-- lib/utils.js | 83 ++++++-- 14 files changed, 335 insertions(+), 145 deletions(-) create mode 100644 e2e/__projects__/babel-config/babel-transformer.js delete mode 100644 e2e/__projects__/babel-config/cssPostTransform.js delete mode 100644 e2e/__projects__/babel-config/cssPreTransform.js create mode 100644 e2e/__projects__/babel-config/pcssTransform.js create mode 100644 e2e/__projects__/babel-config/scssTransform.js diff --git a/e2e/__projects__/babel-config/__snapshots__/test.js.snap b/e2e/__projects__/babel-config/__snapshots__/test.js.snap index a23c6547..253c5213 100644 --- a/e2e/__projects__/babel-config/__snapshots__/test.js.snap +++ b/e2e/__projects__/babel-config/__snapshots__/test.js.snap @@ -141,6 +141,8 @@ __options__.staticRenderFns = staticRenderFns //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkJhc2ljU3JjLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUhBO0FBS0E7QUFQQTtBQVNBO0FBQ0E7QUFDQTtBQUNBO0FBRkE7QUFJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBSEE7QUFqQkEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCB7XG4gIG5hbWU6ICdiYXNpYycsXG4gIGNvbXB1dGVkOiB7XG4gICAgaGVhZGluZ0NsYXNzZXM6IGZ1bmN0aW9uIGhlYWRpbmdDbGFzc2VzKCkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgcmVkOiB0aGlzLmlzQ3JhenksXG4gICAgICAgIGJsdWU6ICF0aGlzLmlzQ3JhenksXG4gICAgICAgIHNoYWRvdzogdGhpcy5pc0NyYXp5XG4gICAgICB9XG4gICAgfVxuICB9LFxuICBkYXRhOiBmdW5jdGlvbiBkYXRhKCkge1xuICAgIHJldHVybiB7XG4gICAgICBtc2c6ICdXZWxjb21lIHRvIFlvdXIgVnVlLmpzIEFwcCcsXG4gICAgICBpc0NyYXp5OiBmYWxzZVxuICAgIH1cbiAgfSxcbiAgbWV0aG9kczoge1xuICAgIHRvZ2dsZUNsYXNzOiBmdW5jdGlvbiB0b2dnbGVDbGFzcygpIHtcbiAgICAgIHRoaXMuaXNDcmF6eSA9ICF0aGlzLmlzQ3JhenlcbiAgICB9XG4gIH1cbn1cbiJdfQ==" `; +exports[`processes PostCSS 1`] = `"
"`; + exports[`processes SCSS using user specified post transforms 1`] = `"
"`; exports[`processes SCSS using user specified pre transforms 1`] = `"
"`; diff --git a/e2e/__projects__/babel-config/babel-transformer.js b/e2e/__projects__/babel-config/babel-transformer.js new file mode 100644 index 00000000..91670a97 --- /dev/null +++ b/e2e/__projects__/babel-config/babel-transformer.js @@ -0,0 +1,5 @@ +const { createTransformer } = require('babel-jest') +module.exports = createTransformer({ + presets: ['@babel/preset-env'], + plugins: ['transform-vue-jsx'] +}) diff --git a/e2e/__projects__/babel-config/components/PostCss.vue b/e2e/__projects__/babel-config/components/PostCss.vue index e147826b..7563b947 100644 --- a/e2e/__projects__/babel-config/components/PostCss.vue +++ b/e2e/__projects__/babel-config/components/PostCss.vue @@ -1,21 +1,18 @@ diff --git a/e2e/__projects__/babel-config/cssPostTransform.js b/e2e/__projects__/babel-config/cssPostTransform.js deleted file mode 100644 index c2b2cc8a..00000000 --- a/e2e/__projects__/babel-config/cssPostTransform.js +++ /dev/null @@ -1,17 +0,0 @@ -const cssExtract = require('extract-from-css') -module.exports = function (cssContent, config, attrs) { - const cssNames = cssExtract.extractClasses(cssContent) - const obj = {} - for (let i = 0, l = cssNames.length; i < l; i++) { - obj[cssNames[i]] = cssNames[i] - } - - if (attrs.themed) { - return { - light: obj, - dark: obj - } - } - return obj; - -} \ No newline at end of file diff --git a/e2e/__projects__/babel-config/cssPreTransform.js b/e2e/__projects__/babel-config/cssPreTransform.js deleted file mode 100644 index d5e6b6c2..00000000 --- a/e2e/__projects__/babel-config/cssPreTransform.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = function (cssContent, config, attrs) { - return `${cssContent}\n .g{width: 10px}`; - -} \ No newline at end of file diff --git a/e2e/__projects__/babel-config/package.json b/e2e/__projects__/babel-config/package.json index d454f667..6dee783e 100644 --- a/e2e/__projects__/babel-config/package.json +++ b/e2e/__projects__/babel-config/package.json @@ -21,6 +21,8 @@ "babel-plugin-transform-vue-jsx": "^3.7.0", "jest": "^23.6.0", "node-sass": "^4.11.0", + "postcss": "^7.0.13", + "postcss-color-function": "^4.0.1", "vue-jest": "file:../../../" }, "jest": { @@ -41,11 +43,10 @@ "pug": { "basedir": "./" }, - "cssTransform": { - "scss": { - "post": "./cssPostTransform.js", - "pre": "./cssPreTransform.js" - } + "transform": { + "^scss$": "./scssTransform.js", + "^pcss|postcss$": "./pcssTransform.js", + "^js$": "./babel-transformer.js" } } } diff --git a/e2e/__projects__/babel-config/pcssTransform.js b/e2e/__projects__/babel-config/pcssTransform.js new file mode 100644 index 00000000..3fc383f0 --- /dev/null +++ b/e2e/__projects__/babel-config/pcssTransform.js @@ -0,0 +1,7 @@ +const postcss = require('postcss') +var colorFunction = require('postcss-color-function') +module.exports = { + process: function(content, filepath, config, attrs) { + return postcss([colorFunction()]).process(content).css + } +} diff --git a/e2e/__projects__/babel-config/scssTransform.js b/e2e/__projects__/babel-config/scssTransform.js new file mode 100644 index 00000000..608a4ef8 --- /dev/null +++ b/e2e/__projects__/babel-config/scssTransform.js @@ -0,0 +1,21 @@ +const cssExtract = require('extract-from-css') +module.exports = { + postProcess: function postProcess(src, filepath, config, attrs) { + const cssNames = cssExtract.extractClasses(src) + const obj = {} + for (let i = 0, l = cssNames.length; i < l; i++) { + obj[cssNames[i]] = cssNames[i] + } + + if (attrs.themed) { + return { + light: obj, + dark: obj + } + } + return obj + }, + preProcess: function postProcess(src, filepath, config, attrs) { + return `${src}\n .g{width: 10px}` + } +} diff --git a/e2e/__projects__/babel-config/test.js b/e2e/__projects__/babel-config/test.js index aec9a682..dc099d24 100644 --- a/e2e/__projects__/babel-config/test.js +++ b/e2e/__projects__/babel-config/test.js @@ -25,6 +25,7 @@ import Constructor from './components/Constructor.vue' test('processes .vue files', () => { const wrapper = mount(Basic) + expect(wrapper.vm.msg).toEqual('Welcome to Your Vue.js App') wrapper.vm.toggleClass() }) @@ -119,7 +120,9 @@ it('processes Less', () => { it('processes PostCSS', () => { const wrapper = mount(PostCss) - expect(wrapper.is('div')).toBeTruthy() + expect(wrapper.is('section')).toBeTruthy() + expect(wrapper.vm.$style.red).toEqual('red') + expect(wrapper.html()).toMatchSnapshot() }) test('processes pug templates', () => { diff --git a/e2e/test-runner.js b/e2e/test-runner.js index 384dd6b0..d9f562c8 100644 --- a/e2e/test-runner.js +++ b/e2e/test-runner.js @@ -43,7 +43,7 @@ function runTest(dir) { run('npm install --silent') log('Running tests') - run('npm run test') + run('npm run test') success(`(${dir}) Complete`) } diff --git a/lib/compilers/typescript-compiler.js b/lib/compilers/typescript-compiler.js index c1760c1e..d4ff8cbc 100644 --- a/lib/compilers/typescript-compiler.js +++ b/lib/compilers/typescript-compiler.js @@ -3,12 +3,16 @@ 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 { tsconfig } = getTsJestConfig(config) + const vueJestConfig = getVueJestConfig(config) + const { + tsconfig + } = getTsJestConfig(config) const babelOptions = getBabelOptions(filePath) const res = typescript.transpileModule(scriptContent, tsconfig) @@ -26,10 +30,12 @@ module.exports = function compileTypescript(scriptContent, filePath, config) { plugins: [require('@babel/plugin-transform-modules-commonjs')] } } - - const transformer = babelJest.createTransformer( - Object.assign(inlineBabelOptions, { inputSourceMap }) + const customTransformer = getCustomTransformer(vueJestConfig['transform'], 'js'); + const transformer = customTransformer.process ? customTransformer : babelJest.createTransformer( + Object.assign(inlineBabelOptions, { + inputSourceMap + }) ) return transformer.process(res.outputText, filePath, config) -} +} \ No newline at end of file diff --git a/lib/process-style.js b/lib/process-style.js index 28e635b7..440c9753 100644 --- a/lib/process-style.js +++ b/lib/process-style.js @@ -1,91 +1,131 @@ const path = require('path') const fs = require('fs') const getVueJestConfig = require('./utils').getVueJestConfig -const warn = require('./utils').warn +const transformContent = require('./utils').transformContent const cssExtract = require('extract-from-css') +const isUnassistedLang = require('./utils').isUnassistedLang +const getCssTransformer = require('./utils').getCustomTransformer -module.exports = function processStyle(stylePart, filePath, jestConfig = {}) { - const vueJestConfig = getVueJestConfig(jestConfig) - let cssTransform = - vueJestConfig['cssTransform'] && - vueJestConfig['cssTransform'][stylePart.lang] - const validTransforms = cssTransform && (cssTransform.pre || cssTransform.post) - cssTransform = cssTransform || {} - - if (!stylePart || vueJestConfig.experimentalCSSCompile === false) { - return {} +/** + * Validates and returns custom css transformers. + * + * @param {Object} cssTransform - object containing module/path of custom css transformers + * @param {String} lang - language + * @returns {Object} transformer - custom css transformer object + */ +function isValidTransformer(transformer = {}, lang) { + const langSupported = isUnassistedLang(lang) + const validTransformer = transformer.process || transformer.postProcess || transformer.preProcess + if ((transformer && (langSupported && transformer.process)) || (!langSupported && validTransformer)) { + return transformer } + return {} +} - const globaResources = lang => { - let globalResources = '' - if (vueJestConfig.resources && vueJestConfig.resources[lang]) { - globalResources = vueJestConfig.resources[lang] - .map(resource => path.resolve(process.cwd(), resource)) - .filter(resourcePath => fs.existsSync(resourcePath)) - .map(resourcePath => fs.readFileSync(resourcePath).toString()) - .join('\n') - } - return globalResources +/** + * Resolves content of css resources from file path + * + * @param {Object} resources - global resource files included vue-jest configuration + * @param {String} lang - language + * @returns {String} content - content of css resources + */ +function getGlobalResources(resources, lang) { + let globalResources = '' + if (resources && resources[lang]) { + globalResources = resources[lang] + .map(resource => path.resolve(process.cwd(), resource)) + .filter(resourcePath => fs.existsSync(resourcePath)) + .map(resourcePath => fs.readFileSync(resourcePath).toString()) + .join('\n') } + return globalResources +} - const cssTransfomer = (transform, content) => { - if (!validTransforms || !transform || typeof transform !== 'string') { - return content - } - - const transformPath = /^(\.\.\/|\.\/|\/)/.test(transform) ? path.resolve(process.cwd(), transform) : transform - return require(transformPath)( - content, - vueJestConfig, - stylePart.attrs - ) +/** + * Extract cass names from CSS code. + * + * @param {String} cssCode - css code + * @returns {Object} obj - object with key & value as class names of the input css code + */ +function extractClassMap(cssCode) { + const cssNames = cssExtract.extractClasses(cssCode) + const cssMap = {} + for (let i = 0, l = cssNames.length; i < l; i++) { + cssMap[cssNames[i]] = cssNames[i] } + return cssMap +} - const processStyleByLang = lang => { - const content = globaResources(lang) + stylePart.content - const preProcessedContent = cssTransfomer(cssTransform.pre, content) - return require('./compilers/' + lang + '-compiler')( - preProcessedContent, - filePath, - jestConfig - ) - } +function processStyleByLang( + content, + config, + filePath, + lang, + transformer = {}, + attrs +) { + const cssContent = getGlobalResources(config.resources, lang) + content + const preProcessedContent = transformContent( + cssContent, + filePath, + config, + transformer.preProcess, + attrs + ) + const processedContent = require('./compilers/' + lang + '-compiler')( + preProcessedContent, + filePath, + config + ) + return transformContent(processedContent, filePath, config, transformer.postProcess, attrs) +} - const extractClassMap = cssCode => { - const cssNames = cssExtract.extractClasses(cssCode) - const obj = {} - for (let i = 0, l = cssNames.length; i < l; i++) { - obj[cssNames[i]] = cssNames[i] +function transformStyles(content, filePath, config, lang, transformer = {}, attrs) { + if (transformer.process) { + return transformContent(content, filePath, config, transformer.process, attrs) + } else if (!isUnassistedLang(lang)) { + let cssCode = content + switch (lang) { + case 'styl': + case 'stylus': + cssCode = processStyleByLang(content, config, filePath, 'stylus', transformer, attrs) + break + case 'scss': + cssCode = processStyleByLang(content, config, filePath, lang, transformer, attrs) + break + case 'sass': + cssCode = processStyleByLang(content, config, filePath, lang, transformer, attrs) + break } - - return obj + return cssCode } + return {} +} - let cssCode = stylePart.content - switch (stylePart.lang) { - case 'styl': - case 'stylus': - cssCode = processStyleByLang('stylus') - break - case 'scss': - cssCode = processStyleByLang('scss') - break - case 'sass': - cssCode = processStyleByLang('sass') - break +module.exports = function processStyle(stylePart, filePath, jestConfig = {}) { + const vueJestConfig = getVueJestConfig(jestConfig) + const { + content, + lang, + attrs + } = stylePart + if (!stylePart || vueJestConfig.experimentalCSSCompile === false) { + return {} } + + const cssTransformer = isValidTransformer(getCssTransformer(vueJestConfig['transform'], lang), lang) - if (validTransforms) { - let locals = cssTransfomer(cssTransform.post, cssCode) - if (typeof locals !== 'object') { - !vueJestConfig.hideStyleWarn && - warn( - 'post-transformers are expected to return an object with key value pair as class names of the component' - ) - locals = extractClassMap(locals) - } - return locals - } else { + const cssCode = transformStyles( + content, + filePath, + jestConfig, + lang, + cssTransformer, + attrs + ) + + if (typeof cssCode === 'string') { return extractClassMap(cssCode) } -} + return cssCode +} \ No newline at end of file diff --git a/lib/process.js b/lib/process.js index 511fcff3..c055e8c2 100644 --- a/lib/process.js +++ b/lib/process.js @@ -10,32 +10,101 @@ const join = path.join const getVueJestConfig = require('./utils').getVueJestConfig const throwError = require('./utils').throwError const warn = require('./utils').warn +const isUnassistedLang = require('./utils').isUnassistedLang +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 compilerUtils = require('@vue/component-compiler-utils') const chalk = require('chalk') const convertSourceMap = require('convert-source-map') + +/** + * 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 processScript(scriptPart, filePath, config) { if (!scriptPart) { - return { code: '' } - } - - if (/^typescript|tsx?$/.test(scriptPart.lang)) { - return compileTypescript(scriptPart.content, filePath, config) + return { + code: '' + } } - - if (scriptPart.lang === 'coffee' || scriptPart.lang === 'coffeescript') { - return compileCoffeeScript(scriptPart.content, filePath, config) + 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 + ) } - return babelJest.process(scriptPart.content, filePath, config) + return processScriptBytransforms(content, filePath, config, babelJest, attrs) } let shouldLogStyleWarn = true -module.exports = function(src, filePath, config) { +module.exports = function (src, filePath, config) { const vueJestConfig = getVueJestConfig(config) const parts = compilerUtils.parse({ source: src, @@ -99,7 +168,7 @@ module.exports = function(src, filePath, config) { }) if (templateResult.errors.length) { - templateResult.errors.forEach(function(msg) { + templateResult.errors.forEach(function (msg) { console.error('\n' + chalk.red(msg) + '\n') }) throwError('Vue template compilation failed') @@ -123,7 +192,7 @@ module.exports = function(src, filePath, config) { if (Array.isArray(parts.styles) && parts.styles.length > 0) { if ( - parts.styles.some(ast => /^less|pcss|postcss/.test(ast.lang)) && + parts.styles.some(ast => isUnassistedLang(ast.lang)) && shouldLogStyleWarn ) { !vueJestConfig.hideStyleWarn && @@ -134,9 +203,7 @@ module.exports = function(src, filePath, config) { const styleStr = parts.styles .filter(ast => ast.module) .map(ast => { - const styleObj = /^less|pcss|postcss/.test(ast.lang) - ? {} - : processStyle(ast, filePath, config) + const styleObj = processStyle(ast, filePath, config) const moduleName = ast.module === true ? '$style' : ast.module @@ -177,5 +244,8 @@ module.exports = function(src, filePath, config) { output += '\n' + convertSourceMap.fromJSON(map.toString()).toComment() } - return { code: output, map } -} + return { + code: output, + map + } +} \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js index 3287b6c8..2591860f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,14 +1,50 @@ const loadPartialConfig = require('@babel/core').loadPartialConfig const createTransformer = require('ts-jest').createTransformer const chalk = require('chalk') +const path = require('path') -module.exports.getVueJestConfig = function getVueJestConfig(jestConfig) { +const fetchTransformer = function fetchTransformer(key, obj) { + for (const exp in obj) { + const matchKey = new RegExp(exp) + if (matchKey.test(key)) { + return obj[exp] + } + } + return null; +} + +const resolvePath = function isUnassistedLang(pathToResolve) { + return /^(\.\.\/|\.\/|\/)/.test(pathToResolve) ? + path.resolve(process.cwd(), pathToResolve) : + pathToResolve +} + +const info = function info(msg) { + console.info(chalk.blue('\n[vue-jest]: ' + msg + '\n')) +} + +const warn = function warn(msg) { + console.warn(chalk.red('\n[vue-jest]: ' + msg + '\n')) +} + +const transformContent = function transformContent(content, filePath, config, transformer, attrs) { + if (!transformer) { + return content + } + try { + return transformer(content, filePath, config, attrs) + } catch (err) { + warn(`There was an error while compiling ${filePath} ${err}`) + } + return content +} + +const getVueJestConfig = function getVueJestConfig(jestConfig) { return ( (jestConfig && jestConfig.globals && jestConfig.globals['vue-jest']) || {} ) } - -module.exports.getBabelOptions = function loadBabelOptions( +const getBabelOptions = function loadBabelOptions( filename, options = {} ) { @@ -23,23 +59,46 @@ module.exports.getBabelOptions = function loadBabelOptions( return loadPartialConfig(opts).options } -module.exports.getTsJestConfig = function getTsJestConfig(config) { - const tr = createTransformer() - return tr.configsFor(config) +const isUnassistedLang = function isUnassistedLang(lang) { + return /^less|pcss|postcss/.test(lang) } -module.exports.info = function info(msg) { - console.info(chalk.blue('\n[vue-jest]: ' + msg + '\n')) +const getTsJestConfig = function getTsJestConfig(config) { + const tr = createTransformer() + return tr.configsFor(config) } -module.exports.warn = function warn(msg) { - console.warn(chalk.red('\n[vue-jest]: ' + msg + '\n')) +const getCustomTransformer = function getCustomTransformer(transform = {}, lang) { + let transformerPath = fetchTransformer(lang, transform) + if (transformerPath) { + const transformer = require(resolvePath(transformerPath)) + const validTransformer = transformer.process || transformer.postProcess || transformer.preProcess + if (transformer && validTransformer) { + return transformer + } + } + return {} } -module.exports.throwError = function error(msg) { +const throwError = function error(msg) { throw new Error('\n[vue-jest] Error: ' + msg + '\n') } -module.exports.stripInlineSourceMap = function(str) { +const stripInlineSourceMap = function (str) { return str.slice(0, str.indexOf('//# sourceMappingURL')) } + +module.exports = { + stripInlineSourceMap, + throwError, + getCustomTransformer, + getTsJestConfig, + isUnassistedLang, + getBabelOptions, + getVueJestConfig, + transformContent, + info, + warn, + resolvePath, + fetchTransformer +} \ No newline at end of file