diff --git a/.gitignore b/.gitignore index 40b878db..e47ca26d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules/ \ No newline at end of file +node_modules/ +.idea/ +*.iml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 65b38240..78a54b8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ language: node_js -node_js: '8.0' +node_js: +- '9' +- '8' +- '6' script: - npm run all \ No newline at end of file diff --git a/index.js b/index.js index 85823ce4..4406b8c9 100644 --- a/index.js +++ b/index.js @@ -1,16 +1,16 @@ const { basename, extname } = require('path'); -const { compile } = require('svelte'); +const { compile, preprocess } = require('svelte'); const { getOptions } = require('loader-utils'); const { statSync, utimesSync, writeFileSync } = require('fs'); const { fileSync } = require('tmp'); function sanitize(input) { - return basename(input) - .replace(extname(input), '') - .replace(/[^a-zA-Z_$0-9]+/g, '_') - .replace(/^_/, '') - .replace(/_$/, '') - .replace(/^(\d)/, '_$1'); + return basename(input). + replace(extname(input), ''). + replace(/[^a-zA-Z_$0-9]+/g, '_'). + replace(/^_/, ''). + replace(/_$/, ''). + replace(/^(\d)/, '_$1'); } function capitalize(str) { @@ -20,19 +20,20 @@ function capitalize(str) { module.exports = function(source, map) { this.cacheable(); - const options = getOptions(this) || {}; + const options = Object.assign({}, this.options, getOptions(this)); + const callback = this.async(); options.filename = this.resourcePath; options.format = this.version === 1 ? options.format || 'cjs' : 'es'; options.shared = - options.format === 'es' && require.resolve('svelte/shared.js'); + options.format === 'es' && require.resolve('svelte/shared.js'); if (options.emitCss) options.css = false; if (!options.name) options.name = capitalize(sanitize(options.filename)); - try { - let { code, map, css, cssMap } = compile(source, options); + preprocess(source, options).then(processed => { + let { code, map, css, cssMap } = compile(processed.toString(), options); if (options.emitCss && css) { const tmpobj = fileSync({ postfix: '.css' }); @@ -40,14 +41,14 @@ module.exports = function(source, map) { code = code + `\nrequire('${tmpobj.name}');\n`; writeFileSync(tmpobj.name, css); - const stats = statSync(tmpobj.name); - utimesSync(tmpobj.name, stats.atimeMs - 9999, stats.mtimeMs - 9999); + const { atime, mtime } = statSync(tmpobj.name); + utimesSync(tmpobj.name, atime.getTime() - 9999, mtime.getTime() - 9999); } - this.callback(null, code, map); - } catch (err) { + callback(null, code, map); + }, err => callback(err)).catch(err => { // wrap error to provide correct // context when logging to console - this.callback(new Error(err.toString() + '\n' + err.frame)); - } + callback(new Error(`${err.name}: ${err.toString()}`)); + }); }; diff --git a/package-lock.json b/package-lock.json index 0773a9bf..4532cd73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte-loader", - "version": "2.1.0", + "version": "2.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1277,6 +1277,15 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -1288,15 +1297,6 @@ "strip-ansi": "3.0.1" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -1325,9 +1325,9 @@ "dev": true }, "svelte": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-1.28.1.tgz", - "integrity": "sha512-/RloCDdGKA9yUXpfQ6ebQuj3syS0wbDJJrGCxxD8HdpEOASAvq+pF7ByttV+wwOkcH59tafChlLWgzqVUUpUcA==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-1.47.2.tgz", + "integrity": "sha512-yOoN1J618NB4NYuGKMh+SAt60dL8jZl2ug5rloCdHPfKfhs4Qmctt+U9CjK4NDtdaEk9maSsRCd7oEWbkUomIw==", "dev": true }, "table": { diff --git a/package.json b/package.json index 4577818f..30686107 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,10 @@ "mocha": "^3.2.0", "sinon": "^1.17.6", "sinon-chai": "^2.8.0", - "svelte": "^1.6.0" + "svelte": "^1.47.2" }, "peerDependencies": { - "svelte": "^1.0.7" + "svelte": "^1.44.0" }, "repository": { "type": "git", diff --git a/test/fixtures/style-valid.html b/test/fixtures/style-valid.html new file mode 100644 index 00000000..f06bb022 --- /dev/null +++ b/test/fixtures/style-valid.html @@ -0,0 +1,8 @@ + + + diff --git a/test/loader.spec.js b/test/loader.spec.js index 495955e1..1f910ae1 100644 --- a/test/loader.spec.js +++ b/test/loader.spec.js @@ -14,244 +14,329 @@ function d([str]) { describe('loader', () => { function testLoader(fileName, callback, query, version = 2) { - return () => { + return (done) => { + function cb() { + try { + callback(...[].slice.call(arguments)); + } catch (err) { + expect(callbackSpy).to.have.been.called; + return done(err); + } + expect(callbackSpy).to.have.been.called; + done(); + } + const fileContents = readFile(fileName); - const cacheableSpy = spy(() => {}); - const callbackSpy = spy(callback); - - loader.call( - { - cacheable: cacheableSpy, - callback: callbackSpy, - resourcePath: fileName, - version, - query - }, - fileContents, - null - ); - expect(callbackSpy).to.have.been.called; + const cacheableSpy = spy(function() { + }); + + const callbackSpy = spy(cb); + + loader.call({ + cacheable: cacheableSpy, + async: () => callbackSpy, + resourcePath: fileName, + version, + query, + }, fileContents, null); + expect(cacheableSpy).to.have.been.called; }; } it( - 'should compile', - testLoader('test/fixtures/good.html', function(err, code, map) { - expect(err).not.to.exist; - expect(code).to.exist; - expect(map).to.exist; - }) + 'should compile', + testLoader('test/fixtures/good.html', function(err, code, map) { + expect(err).not.to.exist; + expect(code).to.exist; + expect(map).to.exist; + }) ); describe('error handling', () => { it( - 'should handle parse error', - testLoader('test/fixtures/parse-error.html', function( - err, - code, - map, - context - ) { - expect(err).to.exist; - - expect(err.message).to.eql(d` - ParseError: Expected }}} + 'should handle parse error', + testLoader('test/fixtures/parse-error.html', function( + err, + code, + map, + context) { + expect(err).to.exist; + + expect(err.message).to.eql(d` + ParseError: Expected }}} (1:18) 1:
Count: {{{count}}
^ 2: ` - ); + ); - expect(code).not.to.exist; - expect(map).not.to.exist; - }) + expect(code).not.to.exist; + expect(map).not.to.exist; + }) ); it( - 'should handle wrong export', - testLoader('test/fixtures/export-error.html', function( - err, - code, - map, - context - ) { - expect(err).to.exist; - - expect(err.message).to.eql(d` - ParseError: Unexpected token + 'should handle wrong export', + testLoader('test/fixtures/export-error.html', function( + err, + code, + map, + context) { + expect(err).to.exist; + + expect(err.message).to.eql(d` + ParseError: Unexpected token (5:7) 3: ` - ); + ); - expect(code).not.to.exist; - expect(map).not.to.exist; - }) + expect(code).not.to.exist; + expect(map).not.to.exist; + }) ); it( - 'should validation error', - testLoader('test/fixtures/validation-error.html', function( - err, - code, - map, - context - ) { - expect(err).to.exist; - - expect(err.message).to.eql(d` - ValidationError: Computed properties can be function expressions or arrow function expressions + 'should validation error', + testLoader('test/fixtures/validation-error.html', function( + err, + code, + map, + context) { + expect(err).to.exist; + + expect(err.message).to.eql(d` + ValidationError: Computed properties can be function expressions or arrow function expressions (6:11) 4: export default { 5: computed: { 6: foo: 'BAR' ^ 7: } 8: };` - ); + ); - expect(code).not.to.exist; - expect(map).not.to.exist; - }) + expect(code).not.to.exist; + expect(map).not.to.exist; + }) ); }); describe('ES2015 features', () => { it( - 'should keep imports / methods', - testLoader('test/fixtures/es2015.html', function(err, code, map) { - expect(err).not.to.exist; + 'should keep imports / methods', + testLoader('test/fixtures/es2015.html', function(err, code, map) { + expect(err).not.to.exist; - expect(code).to.exist; - expect(map).to.exist; + expect(code).to.exist; + expect(map).to.exist; - // es2015 statements remain - expect(code).to.contain(`import { hello } from './utils';`); - expect(code).to.contain('data() {'); - }) + // es2015 statements remain + expect(code).to.contain(`import { hello } from './utils';`); + expect(code).to.contain('data() {'); + }) ); it( - 'should keep nested Component import', - testLoader('test/fixtures/parent.html', function(err, code, map) { - expect(err).not.to.exist; + 'should keep nested Component import', + testLoader('test/fixtures/parent.html', function(err, code, map) { + expect(err).not.to.exist; - // es2015 statements remain - expect(code).to.contain(`import Nested from './nested';`); + // es2015 statements remain + expect(code).to.contain(`import Nested from './nested';`); - expect(code).to.exist; - expect(map).to.exist; - }) + expect(code).to.exist; + expect(map).to.exist; + }) ); }); describe('configuration via query', () => { describe('css', () => { it( - 'should configure css (default)', - testLoader('test/fixtures/css.html', function(err, code, map) { - expect(err).not.to.exist; - expect(code).to.contain('function add_css()'); - }) + 'should configure css (default)', + testLoader('test/fixtures/css.html', function(err, code, map) { + expect(err).not.to.exist; + expect(code).to.contain('function add_css()'); + }) ); it( - 'should configure no css', - testLoader( - 'test/fixtures/css.html', - function(err, code, map) { - expect(err).not.to.exist; - expect(code).not.to.contain('function add_css()'); - }, - { css: false } - ) + 'should configure no css', + testLoader( + 'test/fixtures/css.html', + function(err, code, map) { + expect(err).not.to.exist; + expect(code).not.to.contain('function add_css()'); + }, + { css: false } + ) ); }); describe('shared', () => { it( - 'should configure shared=false (default)', - testLoader( - 'test/fixtures/good.html', - function(err, code, map) { - expect(err).not.to.exist; - - expect(code).not.to.contain('import {'); - expect(code).not.to.contain('svelte/shared.js'); - }, - {}, - 1 - ) + 'should configure shared=false (default)', + testLoader( + 'test/fixtures/good.html', + function(err, code, map) { + expect(err).not.to.exist; + + expect(code).not.to.contain('import {'); + expect(code).not.to.contain('svelte/shared.js'); + }, + {}, + 1 + ) ); it( - 'should configure shared=true', - testLoader( - 'test/fixtures/good.html', - function(err, code, map) { - expect(err).not.to.exist; - - expect(code).to.contain('import {'); - expect(code).to.contain('svelte/shared.js'); - }, - { shared: true } - ) + 'should configure shared=true', + testLoader( + 'test/fixtures/good.html', + function(err, code, map) { + expect(err).not.to.exist; + + expect(code).to.contain('import {'); + expect(code).to.contain('svelte/shared.js'); + }, + { shared: true } + ) ); }); describe('generate', () => { it( - 'should configure generate=undefined (default)', - testLoader('test/fixtures/good.html', function(err, code, map) { - expect(err).not.to.exist; + 'should configure generate=undefined (default)', + testLoader('test/fixtures/good.html', function(err, code, map) { + expect(err).not.to.exist; - expect(code).not.to.contain('.render = function(state, options = {}) {'); - }) + expect(code). + not. + to. + contain('.render = function(state, options = {}) {'); + }) ); it( - 'should configure generate=ssr', - testLoader( - 'test/fixtures/good.html', - function(err, code, map) { - expect(err).not.to.exist; - - expect(code).to.contain('.render = function(state, options = {}) {'); - }, - { generate: 'ssr' } - ) + 'should configure generate=ssr', + testLoader( + 'test/fixtures/good.html', + function(err, code, map) { + expect(err).not.to.exist; + + expect(code). + to. + contain('.render = function(state, options = {}) {'); + }, + { generate: 'ssr' } + ) ); }); describe('emitCss', function() { it( - 'should configure emitCss=false (default)', - testLoader( - 'test/fixtures/css.html', - function(err, code, map) { - expect(err).not.to.exist; - - expect(code).not.to.match(/require\('.+\.css'\);/); - }, - {} - ) + 'should configure emitCss=false (default)', + testLoader( + 'test/fixtures/css.html', + function(err, code, map) { + expect(err).not.to.exist; + + expect(code).not.to.match(/require\('.+\.css'\);/); + }, + {} + ) ); it( - 'should configure emitCss=true', - testLoader( - 'test/fixtures/css.html', - function(err, code, map) { - expect(err).not.to.exist; + 'should configure emitCss=true', + testLoader( + 'test/fixtures/css.html', + function(err, code, map) { + expect(err).not.to.exist; + + expect(code).to.match(/require\('.+\.css'\);/); + }, + { emitCss: true } + ) + ); + }); - expect(code).to.match(/require\('.+\.css'\);/); + describe('preprocess', () => { + it('should preprocess successfully', (done) => { + function callback(err, code, map) { + expect(err).not.to.exist; + expect(code).to.exist; + expect(code).to.contain('button{width:50px;height:50px}'); + expect(map).to.exist; + } + + function cb() { + try { + callback(...[].slice.call(arguments)); + } catch (err) { + expect(callbackSpy).to.have.been.called; + return done(err); + } + expect(callbackSpy).to.have.been.called; + done(); + } + + const fileContents = readFileSync('test/fixtures/style-valid.html', + 'utf-8'); + const cacheableSpy = spy(() => { + }); + const callbackSpy = spy(cb); + const options = { + style: ({ content }) => { + return { + code: content.replace(/\$size/gi, '50px'), + }; }, - { emitCss: true } - ) - ); + }; + + loader.call( + { + cacheable: cacheableSpy, + async: () => callbackSpy, + resourcePath: 'test/fixtures/style-valid.html', + options, + }, + fileContents, + null + ); + + expect(cacheableSpy).to.have.been.called; + }); + + it('should not preprocess successfully', () => { + const fileContents = readFileSync('test/fixtures/style-valid.html', + 'utf-8'); + const cacheableSpy = spy(() => { + }); + const options = { + style: () => { + throw new Error('Error while preprocessing'); + }, + }; + + loader.call( + { + cacheable: cacheableSpy, + async: () => (err) => { + expect(err).to.exist; + }, + resourcePath: 'test/fixtures/style-valid.html', + options, + }, + fileContents, + null + ); + + }); }); }); });