diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..02a1ffbc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 3c3629e6..64006860 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +test/output +coverage diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 00000000..cb0fd846 --- /dev/null +++ b/.jscsrc @@ -0,0 +1,7 @@ +{ + "preset": "node-style-guide", + "fileExtensions": [ ".js", "jscs" ], + "excludeFiles": [ + "node_modules/**" + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..9bffa971 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing + + 1. Fork it! + 2. Create your feature branch: `git checkout -b my-new-feature` + 3. Commit your changes: `git commit -am 'Add some feature'` + 4. Push to the branch: `git push origin my-new-feature` + 5. Submit a pull request :D + +# Code Standards + + I'm not too strict with coding styles. + + While personally I'm using the [node-style-guide](https://github.com/felixge/node-style-guide), as long you don't to something too weird or fancy, that's probably ok. + + If, however, you want to ensure that you're following the node style guide, you can use [JSCS](https://github.com/jscs-dev/node-jscs). The rules are already set on the [.jscsrc](https://github.com/babel/babel-loader/blob/master/.jscsrc) file and there's most likely some [package](http://jscs.info/overview.html#friendly-packages) to your editor already. + + Documentation, wether in the state of JavaDoc or simple line comments are always welcome. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..29e3021e --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2014-2015 Luís Couto + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 6105f17b..4ac3b578 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,158 @@ # babel-loader + > Babel is a compiler for writing next generation JavaScript. -> Turn ES6 code into vanilla ES5 with no runtime required using [babel](https://github.com/babel/babel); + This package allows the use babel with [webpack](https://github.com/webpack/webpack) -## Install + __Notes:__ Issues with the output should be reported on the babel [issue tracker](https://github.com/babel/babel/issues); -``` -$ npm install --save-dev babel-loader +## Installation + +```bash +npm install babel-loader --save-dev ``` -## Usage +__Note:__ [npm](https://npmjs.com) will deprecate [peerDependencies](https://github.com/npm/npm/issues/6565) on the next major release, so required dependencies like babel-core and webpack will have to be installed manually. -```javascript -import Animal from 'babel!./Animal.js'; +## Usage + Within your webpack configuration object, you'll need to add the babel-loader to the list of modules, like so: -class Person extends Animal { - constructor(arg='default') { - this.eat = 'Happy Meal'; - } + ```javascript +module: { + loaders: [ + { + test: /\.jsx?$/, + exclude: /(node_modules|bower_components)/, + loader: 'babel' + } + ] } + ``` -export default Person; -``` +### Options -```javascript -var Person = require('babel!./Person.js').default; -new Person(); -``` +See the `babel` [options](http://babeljs.io/docs/usage/options/). -Or within the webpack config: +You can pass options to the loader by writting them as a [query string](https://github.com/webpack/loader-utils): -```javascript + ```javascript module: { - loaders: [ - { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'} - ] + loaders: [ + { + test: /\.jsx?$/, + exclude: /(node_modules|bower_components)/, + loader: 'babel?optional[]=runtime&stage=0' + } + ] } -``` + ``` -and then import normally: + or by using the query property: -```javascript -import Person from './Person.js'; -``` + ```javascript +module: { + loaders: [ + { + test: /\.jsx?$/, + exclude: /(node_modules|bower_components)/, + loader: 'babel', + query: { + optional: ['runtime'], + stage: 0 + } + } + ] +} + ``` + + This loader also supports the following loader-specific option: + + * `cacheDirectory`: When set, the given directory will be used to cache the results of the loader. Future webpack builds will attempt to read from the cache to avoid needing to run the potentially expensive Babel recompilation process on each run. A value of `true` will cause the loader to use the default OS temporary file directory. + + * `cacheIdentifier`: When set, it will add the given identifier to the cached files. This can be used to force cache busting if the identifier changes. By default the identifier is made by using the babel-core's version and the babel-loader's version. + + + __Note:__ The `sourceMap` option is ignored, instead sourceMaps are automatically enabled when webpack is configured to use them (via the `devtool` config option). ## Troubleshooting -#### babel-loader is slow! +### babel-loader is slow! -Make sure you are transforming as few files as possible. Because you are probably -matching `/\.js$/`, you might be transforming the `node_modules` folder or other unwanted -source. See the `exclude` option in the `loaders` config as documented above. + Make sure you are transforming as few files as possible. Because you are probably + matching `/\.js$/`, you might be transforming the `node_modules` folder or other unwanted + source. -#### babel is injecting helpers into each file and bloating my code! + See the `exclude` option in the `loaders` config as documented above. -babel uses very small helpers for common functions such as `_extend`. By default -this will be added to every file that requires it. +### babel is injecting helpers into each file and bloating my code! -You can instead require the babel runtime as a separate module to avoid the duplication. + babel uses very small helpers for common functions such as `_extend`. By default + this will be added to every file that requires it. -The following configuration disables automatic per-file runtime injection in babel, instead -requiring `babel-runtime` and making all helper references use it. + You can instead require the babel runtime as a separate module to avoid the duplication. -See the [docs](https://babeljs.io/docs/usage/runtime) for more information. + The following configuration disables automatic per-file runtime injection in babel, instead + requiring `babel-runtime` and making all helper references use it. -**NOTE:** You must run `npm install babel-runtime --save` to include this in your project. + See the [docs](https://babeljs.io/docs/usage/runtime) for more information. + + **NOTE:** You must run `npm install babel-runtime --save` to include this in your project. ```javascript loaders: [ - // the optional 'runtime' transformer tells babel to require the runtime instead of inlining it. - { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader?optional=runtime' } + // the optional 'runtime' transformer tells babel to require the runtime + // instead of inlining it. + { + test: /\.jsx?$/, + exclude: /(node_modules|bower_components)/, + loader: 'babel-loader?optional[]=runtime' + } ] ``` -## Options +#### custom polyfills (e.g. Promise library) + +Since Babel includes a polyfill that includes a custom [regenerator runtime](https://github.com/facebook/regenerator/blob/master/runtime.js) and [core.js](https://github.com/zloirock/core-js), the following usual shimming method using `webpack.ProvidePlugin` will not work: -See the `babel` [options](http://babeljs.io/docs/usage/options/) +```javascript +// ... + new webpack.ProvidePlugin({ + 'Promise': 'bluebird' + }), +// ... +``` -This loader also supports the following loader-specific option: +The following approach will not work either: -* `cacheDirectory`: When set, the given directory will be used to cache the results of the loader. - Future webpack builds will attempt to read from the cache to avoid needing to run the potentially - expensive Babel recompilation process on each run. A value of `true` will cause the loader to - use the default OS temporary file directory. +```javascript +require('babel-runtime/core-js/promise').default = require('bluebird'); + +var promise = new Promise; +``` + +which outputs to (using `runtime`): + +```javascript +'use strict'; -Note: The `sourceMap` option is ignored, instead sourceMaps are automatically enabled when webpack is configured to use them (via the `devtool` config option). +var _Promise = require('babel-runtime/core-js/promise')['default']; -## License +require('babel-runtime/core-js/promise')['default'] = require('bluebird'); + +var promise = new _Promise(); +``` + +The previous `Promise` library is referenced and used before it is overridden. + +One approach is to have a "bootstrap" step in your application that would first override the default globals before your application: + +```javascript +// bootstrap.js + +require('babel-runtime/core-js/promise').default = require('bluebird'); + +// ... + +require('./app'); +``` -MIT © Luis Couto +## [License](http://couto.mit-license.org/) diff --git a/index.js b/index.js index 33e4bc1f..3e7dd55d 100644 --- a/index.js +++ b/index.js @@ -1,125 +1,64 @@ -var loaderUtils = require('loader-utils'), - babel = require('babel-core'), - crypto = require('crypto'), - fs = require('fs'), - path = require('path'), - os = require('os'), - zlib = require('zlib'), - version = require('./package').version, - toBoolean = function (val) { - if (val === 'true') { return true; } - if (val === 'false') { return false; } - return val; - }; - -module.exports = function (source, inputSourceMap) { - - var options = loaderUtils.parseQuery(this.query), - callback = this.async(), - result, cacheDirectory; - - if (this.cacheable) { - this.cacheable(); - } - - // Convert 'true'/'false' to true/false - options = Object.keys(options).reduce(function (accumulator, key) { - accumulator[key] = toBoolean(options[key]); - return accumulator; - }, {}); - - options.sourceMap = this.sourceMap; - options.inputSourceMap = inputSourceMap; - options.filename = loaderUtils.getRemainingRequest(this); - - cacheDirectory = options.cacheDirectory; - delete options.cacheDirectory; - - if (cacheDirectory === true) cacheDirectory = os.tmpdir(); - - if (cacheDirectory){ - cachedTranspile(cacheDirectory, source, options, onResult); - } else { - onResult(null, transpile(source, options)); - } - - function onResult(err, result){ - if (err) return callback(err); - - callback(err, err ? null : result.code, err ? null : result.map); - } +var assign = require('object-assign'); +var babel = require('babel-core'); +var cache = require('./lib/fs-cache.js'); +var loaderUtils = require('loader-utils'); +var pkg = require('./package.json'); + +var transpile = function(source, options) { + var result = babel.transform(source, options); + var code = result.code; + var map = result.map; + + if (map) { + map.sourcesContent = [source]; + } + + return { + code: code, + map: map, + }; }; -function transpile(source, options){ - var result = babel.transform(source, options); - - var code = result.code; - var map = result.map; - if (map) { - map.sourcesContent = [source]; - } - - return { - code: code, - map: map - }; -} - -function cachedTranspile(cacheDirectory, source, options, callback){ - var cacheFile = path.join(cacheDirectory, buildCachePath(cacheDirectory, source, options)); - - readCache(cacheFile, function(err, result){ - if (err){ - try { - result = transpile(source, options); - } catch (e){ - return callback(e); - } - - writeCache(cacheFile, result, function(err){ - callback(err, result); - }); - } else { - callback(null, result); - } - }); -} - -function readCache(cacheFile, callback){ - fs.readFile(cacheFile, function(err, data){ - if (err) return callback(err); - - zlib.gunzip(data, function(err, content){ - if (err) return callback(err); - - try { - content = JSON.parse(content); - } catch (e){ - return callback(e); - } +module.exports = function(source, inputSourceMap) { + var callback = this.async(); + var result = {}; + // Handle options + var defaultOptions = { + inputSourceMap: inputSourceMap, + filename: loaderUtils.getRemainingRequest(this), + cacheIdentifier: JSON.stringify({ + 'babel-loader': pkg.version, + 'babel-core': babel.version, + }), + }; + var globalOptions = this.options.babel; + var loaderOptions = loaderUtils.parseQuery(this.query); + var options = assign({}, defaultOptions, globalOptions, loaderOptions); + + if (options.sourceMap === undefined) { + options.sourceMap = this.sourceMap; + } - callback(null, content); - }); - }); -} + cacheDirectory = options.cacheDirectory; + cacheIdentifier = options.cacheIdentifier; -function writeCache(cacheFile, result, callback){ - var content = JSON.stringify(result); + delete options.cacheDirectory; + delete options.cacheIdentifier; - zlib.gzip(content, function(err, data){ - if (err) return callback(err); + this.cacheable(); - fs.writeFile(cacheFile, data, callback); + if (cacheDirectory) { + cache({ + directory: cacheDirectory, + identifier: cacheIdentifier, + source: source, + options: options, + transform: transpile, + }, function(err, result) { + callback(err, result.code, result.map); }); -} - -function buildCachePath(dir, source, options){ - var hash = crypto.createHash('SHA1'); - hash.end(JSON.stringify({ - loaderVersion: version, - babelVersion: babel.version, - source: source, - options: options - })); - return 'babel-loader-cache-' + hash.read().toString('hex') + '.json.gzip'; -} + } else { + result = transpile(source, options); + callback(null, result.code, result.map); + } +}; diff --git a/lib/fs-cache.js b/lib/fs-cache.js new file mode 100644 index 00000000..011f6611 --- /dev/null +++ b/lib/fs-cache.js @@ -0,0 +1,143 @@ +/** + * Filesystem cache + * + * Given a file and a transform function, cache the result into files + * or retrieve the previously cached files if the given file is already known. + * + * @see https://github.com/babel/babel-loader/issues/34 + * @see https://github.com/babel/babel-loader/pull/41 + */ +var crypto = require('crypto'); +var fs = require('fs'); +var os = require('os'); +var path = require('path'); +var zlib = require('zlib'); + +/** + * read + * + * Read the contents from the compressed file. + * + * @async + * @params {String} filename + * @params {Function} callback + */ +var read = function(filename, callback) { + fs.readFile(filename, function(err, data) { + if (err) { return callback(err); } + + zlib.gunzip(data, function(err, content) { + var result = {}; + + if (err) { return callback(err); } + + try { + result = JSON.parse(content); + } catch (e) { + return callback(e); + } + + return callback(null, content); + }); + }); +}; + + +/** + * write + * + * Write contents into a compressed file. + * + * @async + * @params {String} filename + * @params {String} result + * @params {Function} callback + */ +var write = function(filename, result, callback) { + var content = JSON.stringify(result); + + zlib.gzip(content, function(err, data) { + if (err) { return callback(err); } + + fs.writeFile(filename, data, callback); + }); +}; + + +/** + * Build the filename for the cached file + * + * @params {String} source File source code + * @params {Object} options Options used + * + * @return {String} + */ +var filename = function(source, identifier, options) { + var hash = crypto.createHash('SHA1'); + var contents = JSON.stringify({ + source: source, + options: options, + identifier: identifier, + }); + + hash.end(contents); + + return hash.read().toString('hex') + '.json.gzip'; +}; + +/** + * cache + * + * @async + * @param {Object} params + * @param {String} params.directory Directory where cached files will be stored + * @param {String} params.identifier Unique identifier to help with cache busting + * @param {String} params.source Original contents of the file to be cached + * @param {Object} params.options Options to be given to the transform function + * @param {Function} params.transform Function that will transform the original + * file and whose result will be cached + * + * @param {Function} callback + * + * @example + * + * cache({ + * directory: '.tmp/cache', + * identifier: 'babel-loader-cachefile', + * source: *source code from file*, + * options: { + * experimental: true, + * runtime: true + * }, + * transform: function(source, options) { + * var content = *do what you need with the source* + * return content; + * } + * }, function(err, result) { + * + * }); + */ +var cache = module.exports = function(params, callback) { + // Spread params into named variables + // Forgive user whenever possible + var source = params.source; + var options = params.options || {}; + var transform = params.transform; + var identifier = params.identifier; + var directory = (typeof params.directory === 'string') ? + params.directory : + os.tmpdir(); + var file = path.join(directory, filename(source, identifier, options)); + + read(file, function(err, content) { + var result = {}; + + if (!err) { return callback(null, content); } + + result = transform(source, options); + + return write(file, result, function(err) { + callback(err, result); + }); + }); +}; diff --git a/package.json b/package.json index 56d34ac5..3d197c25 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,28 @@ { "name": "babel-loader", - "version": "5.0.0", + "version": "5.1.0", "description": "babel module loader for webpack", "main": "index.js", "dependencies": { - "babel-core": "^5.0.0", - "loader-utils": "^0.2.5" + "babel-core": "^5.4.0", + "object-assign": "^2.0.0", + "loader-utils": "^0.2.7" }, "peerDependencies": { "babel-core": "*", "webpack": "*" }, "devDependencies": { - "mocha": "^2.0.1", - "mocha-loader": "^0.7.0", - "should": "^4.3.1", - "webpack-dev-server": "^1.6.6" + "babel-core": "^5.4.2", + "expect.js": "^0.3.1", + "istanbul": "^0.3.14", + "mkdirp": "^0.5.1", + "mocha": "^2.2.5", + "rimraf": "^2.3.3", + "webpack": "^1.9.5" }, "scripts": { - "test": "webpack-dev-server --config=test/webpack.config.js" + "test": "istanbul cover ./node_modules/.bin/_mocha -- test/*.test.js" }, "repository": { "type": "git", diff --git a/test/all.js b/test/all.js deleted file mode 100644 index 1301f3ae..00000000 --- a/test/all.js +++ /dev/null @@ -1 +0,0 @@ -require('./basic.test.js'); diff --git a/test/basic.test.js b/test/basic.test.js deleted file mode 100644 index f845065f..00000000 --- a/test/basic.test.js +++ /dev/null @@ -1,11 +0,0 @@ -var should = require('should'); - -describe('basic', function () { - it('should compile a es6 module', function () { - var App = require('../!./fixtures/basic.js').default, - result = new App().result; - - result.should.be.type('string'); - result.should.equal('testtest'); - }); -}); diff --git a/test/cache.test.js b/test/cache.test.js new file mode 100644 index 00000000..91ef8555 --- /dev/null +++ b/test/cache.test.js @@ -0,0 +1,165 @@ +var fs = require('fs'); +var path = require('path'); +var assign = require('object-assign'); +var expect = require('expect.js'); +var mkdirp = require('mkdirp'); +var rimraf = require('rimraf'); +var webpack = require('webpack'); + +describe('Filesystem Cache', function() { + + var cacheDir = path.resolve(__dirname, 'output/cache/cachefiles'); + var outputDir = path.resolve(__dirname, './output/cache/'); + var babelLoader = path.resolve(__dirname, '../'); + var globalConfig = { + entry: './test/fixtures/basic.js', + output: { + path: outputDir, + filename: '[id].cache.js', + }, + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader, + exclude: /node_modules/, + }, + ], + }, + }; + + // Clean generated cache files before each test + // so that we can call each test with an empty state. + beforeEach(function(done) { + rimraf(outputDir, function(err) { + if (err) { return done(err); } + mkdirp(cacheDir, done); + }); + }); + + it('should output files to cache directory', function(done) { + + var config = assign({}, globalConfig, { + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader + '?cacheDirectory=' + cacheDir, + exclude: /node_modules/, + }, + ], + }, + }); + + webpack(config, function(err, stats) { + expect(err).to.be(null); + + fs.readdir(cacheDir, function(err, files) { + expect(err).to.be(null); + expect(files).to.not.be.empty(); + done(); + }); + }); + }); + + it('should read from cache directory if cached file exists', function(done) { + var config = assign({}, globalConfig, { + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader + '?cacheDirectory=' + cacheDir, + exclude: /node_modules/, + }, + ], + }, + }); + + // @TODO Find a way to know if the file as correctly read without relying on + // Istanbul for coverage. + webpack(config, function(err, stats) { + expect(err).to.be(null); + + webpack(config, function(err, stats) { + expect(err).to.be(null); + fs.readdir(cacheDir, function(err, files) { + expect(err).to.be(null); + expect(files).to.not.be.empty(); + done(); + }); + }); + }); + + }); + + it('should have one file per module', function(done) { + var config = assign({}, globalConfig, { + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader + '?cacheDirectory=' + cacheDir, + exclude: /node_modules/, + }, + ], + }, + }); + + webpack(config, function(err, stats) { + expect(err).to.be(null); + + fs.readdir(cacheDir, function(err, files) { + expect(err).to.be(null); + expect(files).to.have.length(3); + done(); + }); + }); + + }); + + + it('should generate a new file if the identifier changes', function(done) { + + var configs = [ + assign({}, globalConfig, { + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader + '?cacheDirectory=' + cacheDir + '&cacheIdentifier=a', + exclude: /node_modules/, + }, + ], + }, + }), + assign({}, globalConfig, { + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader + '?cacheDirectory=' + cacheDir + '&cacheIdentifier=b', + exclude: /node_modules/, + }, + ], + }, + }), + ]; + var counter = configs.length; + + configs.forEach(function(config) { + webpack(config, function(err, stats) { + expect(err).to.be(null); + counter -= 1; + + if (!counter) { + fs.readdir(cacheDir, function(err, files) { + expect(err).to.be(null); + expect(files).to.have.length(6); + done(); + }); + } + }); + }); + + }); +}); diff --git a/test/fixtures/basic.js b/test/fixtures/basic.js index 9aac6e2f..3fdbb5cc 100644 --- a/test/fixtures/basic.js +++ b/test/fixtures/basic.js @@ -1,4 +1,6 @@ -import test from '../../!./test.js'; +/*jshint esnext:true*/ + +import test from './import.js'; class App { constructor(arg='test') { diff --git a/test/fixtures/constant.js b/test/fixtures/constant.js new file mode 100644 index 00000000..5aac8576 --- /dev/null +++ b/test/fixtures/constant.js @@ -0,0 +1 @@ +const LOADER = true; diff --git a/test/fixtures/experimental.js b/test/fixtures/experimental.js new file mode 100644 index 00000000..15d9d76c --- /dev/null +++ b/test/fixtures/experimental.js @@ -0,0 +1,18 @@ +/*jshint esnext:true*/ + +let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; +let n = { x, y, ...z }; + +// Array comprehensions +var results = [ + for(c of customers) + if (c.city == "Seattle") + { name: c.name, age: c.age } +] + +// Generator comprehensions +var results = ( + for(c of customers) + if (c.city == "Seattle") + { name: c.name, age: c.age } +) diff --git a/test/fixtures/import.js b/test/fixtures/import.js new file mode 100644 index 00000000..efbb72c9 --- /dev/null +++ b/test/fixtures/import.js @@ -0,0 +1,7 @@ +/*jshint esnext:true*/ + +import loader from './constant.js'; + +var test = loader; + +export default test; diff --git a/test/fixtures/test.js b/test/fixtures/test.js deleted file mode 100644 index 1b1543fa..00000000 --- a/test/fixtures/test.js +++ /dev/null @@ -1,3 +0,0 @@ -var test = 'test'; - -export default test; diff --git a/test/options.test.js b/test/options.test.js new file mode 100644 index 00000000..255a5cb6 --- /dev/null +++ b/test/options.test.js @@ -0,0 +1,124 @@ +var fs = require('fs'); +var path = require('path'); +var assign = require('object-assign'); +var expect = require('expect.js'); +var mkdirp = require('mkdirp'); +var rimraf = require('rimraf'); +var webpack = require('webpack'); + +describe('Options', function() { + + var outputDir = path.resolve(__dirname, './output/options'); + var babelLoader = path.resolve(__dirname, '../'); + var globalConfig = { + entry: './test/fixtures/basic.js', + output: { + path: outputDir, + filename: '[id].options.js', + }, + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader, + exclude: /node_modules/, + }, + ], + }, + }; + + // Clean generated cache files before each test + // so that we can call each test with an empty state. + beforeEach(function(done) { + rimraf(outputDir, function(err) { + if (err) { return done(err); } + mkdirp(outputDir, done); + }); + }); + + it('should interpret options given to the loader', function(done) { + var config = assign({}, globalConfig, { + entry: './test/fixtures/experimental.js', + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader + '?stage=0', + exclude: /node_modules/, + }, + ], + }, + }); + + webpack(config, function(err, stats) { + expect(err).to.be(null); + + fs.readdir(outputDir, function(err, files) { + expect(err).to.be(null); + expect(files).to.not.be.empty(); + + done(); + }); + }); + }); + + it('should interpret options given globally', function(done) { + + var config = assign({}, globalConfig, { + entry: './test/fixtures/experimental.js', + babel: { + stage: 0, + }, + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader, + exclude: /node_modules/, + }, + ], + }, + }); + + webpack(config, function(err, stats) { + expect(err).to.be(null); + + fs.readdir(outputDir, function(err, files) { + expect(err).to.be(null); + expect(files).to.not.be.empty(); + + done(); + }); + }); + }); + + it('should give priority to loader options', function(done) { + var config = assign({}, globalConfig, { + entry: './test/fixtures/experimental.js', + babel: { + stage: 4, + }, + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader + '?stage=0', + exclude: /node_modules/, + }, + ], + }, + }); + + webpack(config, function(err, stats) { + expect(err).to.be(null); + + fs.readdir(outputDir, function(err, files) { + expect(err).to.be(null); + expect(files).to.not.be.empty(); + + done(); + }); + }); + }); + +}); diff --git a/test/sourcemaps.test.js b/test/sourcemaps.test.js new file mode 100644 index 00000000..a7f693ab --- /dev/null +++ b/test/sourcemaps.test.js @@ -0,0 +1,117 @@ +var fs = require('fs'); +var path = require('path'); +var assign = require('object-assign'); +var expect = require('expect.js'); +var mkdirp = require('mkdirp'); +var rimraf = require('rimraf'); +var webpack = require('webpack'); + +describe('Sourcemaps', function() { + + var outputDir = path.resolve(__dirname, './output/sourcemaps'); + var babelLoader = path.resolve(__dirname, '../'); + var globalConfig = { + entry: './test/fixtures/basic.js', + output: { + path: outputDir, + filename: '[id].options.js', + }, + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader, + exclude: /node_modules/, + }, + ], + }, + }; + + // Clean generated cache files before each test + // so that we can call each test with an empty state. + beforeEach(function(done) { + rimraf(outputDir, function(err) { + if (err) { return done(err); } + mkdirp(outputDir, done); + }); + }); + + it('should output webpack\'s sourcemap', function(done) { + + var config = assign({}, globalConfig, { + devtool: 'source-map', + entry: './test/fixtures/basic.js', + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader, + exclude: /node_modules/, + }, + ], + }, + }); + + webpack(config, function(err, stats) { + expect(err).to.be(null); + + fs.readdir(outputDir, function(err, files) { + expect(err).to.be(null); + + var map = files.filter(function(file) { + return (file.indexOf('.map') !== -1); + }); + + expect(map).to.not.be.empty(); + + fs.readFile(path.resolve(outputDir, map[0]), function(err, data) { + expect(err).to.be(null); + expect(data.toString().indexOf('webpack:///')).to.not.equal(-1); + done(); + }); + + }); + }); + }); + + it.skip('should output babel\'s sourcemap', function(done) { + + var config = assign({}, globalConfig, { + entry: './test/fixtures/basic.js', + babel: { + sourceMap: true, + sourceMapName: './output/sourcemaps/babel.map', + }, + module: { + loaders: [ + { + test: /\.jsx?/, + loader: babelLoader, + exclude: /node_modules/, + }, + ], + }, + }); + + webpack(config, function(err, stats) { + expect(err).to.be(null); + + fs.readdir(outputDir, function(err, files) { + expect(err).to.be(null); + + var map = files.filter(function(file) { + return (file.indexOf('.map') !== -1); + }); + + expect(map).to.not.be.empty(); + + fs.readFile(path.resolve(outputDir, map[0]), function(err, data) { + expect(err).to.be(null); + expect(data.toString().indexOf('webpack:///')).to.equal(-1); + done(); + }); + }); + }); + }); + +}); diff --git a/test/webpack.config.js b/test/webpack.config.js deleted file mode 100644 index c5f0e628..00000000 --- a/test/webpack.config.js +++ /dev/null @@ -1,5 +0,0 @@ -// Just run "webpack-dev-server" -module.exports = { - context: __dirname, - entry: "mocha-loader!./all.js" -};