diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c73a0fe..4d1d57b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +# [0.27.1](https://github.com/webpack/css-loader/compare/v0.27.0...v0.27.1) (2017-03-10) + # [0.27.0](https://github.com/webpack/css-loader/compare/v0.26.2...v0.27.0) (2017-03-10) diff --git a/lib/compile-exports.js b/lib/compile-exports.js index 04f97b0d..41dcade3 100644 --- a/lib/compile-exports.js +++ b/lib/compile-exports.js @@ -17,21 +17,32 @@ module.exports = function compileExports(result, importItemMatcher, camelCaseKey function addEntry(k) { res.push("\t" + JSON.stringify(k) + ": " + valueAsString); } - if (camelCaseKeys !== 'only' && camelCaseKeys !== 'dashesOnly') { - addEntry(key); - } var targetKey; - if (camelCaseKeys === true || camelCaseKeys === 'only') { - targetKey = camelCase(key); - if (targetKey !== key) { - addEntry(targetKey); - } - } else if (camelCaseKeys === 'dashes' || camelCaseKeys === 'dashesOnly') { - targetKey = dashesCamelCase(key); - if (targetKey !== key) { - addEntry(targetKey); - } + switch(camelCaseKeys) { + case true: + addEntry(key); + targetKey = camelCase(key); + if (targetKey !== key) { + addEntry(targetKey); + } + break; + case 'dashes': + addEntry(key); + targetKey = dashesCamelCase(key); + if (targetKey !== key) { + addEntry(targetKey); + } + break; + case 'only': + addEntry(camelCase(key)); + break; + case 'dashesOnly': + addEntry(dashesCamelCase(key)); + break; + default: + addEntry(key); + break; } return res; }, []).join(",\n"); diff --git a/lib/convert-source-map.js b/lib/convert-source-map.js new file mode 100644 index 00000000..732f6213 --- /dev/null +++ b/lib/convert-source-map.js @@ -0,0 +1,143 @@ +/* eslint-disable */ +'use strict'; +// XXXXX: This file should not exist. Working around a core level bug +// that prevents using fs at loaders. +//var fs = require('fs'); // XXX +var path = require('path'); + +var commentRx = /^\s*\/(?:\/|\*)[@#]\s+sourceMappingURL=data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(?:.*)$/mg; +var mapFileCommentRx = + //Example (Extra space between slashes added to solve Safari bug. Exclude space in production): + // / /# sourceMappingURL=foo.js.map /*# sourceMappingURL=foo.js.map */ + /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/){1}[ \t]*$)/mg + +function decodeBase64(base64) { + return new Buffer(base64, 'base64').toString(); +} + +function stripComment(sm) { + return sm.split(',').pop(); +} + +function readFromFileMap(sm, dir) { + // NOTE: this will only work on the server since it attempts to read the map file + + mapFileCommentRx.lastIndex = 0; + var r = mapFileCommentRx.exec(sm); + + // for some odd reason //# .. captures in 1 and /* .. */ in 2 + var filename = r[1] || r[2]; + var filepath = path.resolve(dir, filename); + + try { + return fs.readFileSync(filepath, 'utf8'); + } catch (e) { + throw new Error('An error occurred while trying to read the map file at ' + filepath + '\n' + e); + } +} + +function Converter (sm, opts) { + opts = opts || {}; + + if (opts.isFileComment) sm = readFromFileMap(sm, opts.commentFileDir); + if (opts.hasComment) sm = stripComment(sm); + if (opts.isEncoded) sm = decodeBase64(sm); + if (opts.isJSON || opts.isEncoded) sm = JSON.parse(sm); + + this.sourcemap = sm; +} + +Converter.prototype.toJSON = function (space) { + return JSON.stringify(this.sourcemap, null, space); +}; + +Converter.prototype.toBase64 = function () { + var json = this.toJSON(); + return new Buffer(json).toString('base64'); +}; + +Converter.prototype.toComment = function (options) { + var base64 = this.toBase64(); + var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64; + return options && options.multiline ? '/*# ' + data + ' */' : '//# ' + data; +}; + +// returns copy instead of original +Converter.prototype.toObject = function () { + return JSON.parse(this.toJSON()); +}; + +Converter.prototype.addProperty = function (key, value) { + if (this.sourcemap.hasOwnProperty(key)) throw new Error('property %s already exists on the sourcemap, use set property instead'); + return this.setProperty(key, value); +}; + +Converter.prototype.setProperty = function (key, value) { + this.sourcemap[key] = value; + return this; +}; + +Converter.prototype.getProperty = function (key) { + return this.sourcemap[key]; +}; + +exports.fromObject = function (obj) { + return new Converter(obj); +}; + +exports.fromJSON = function (json) { + return new Converter(json, { isJSON: true }); +}; + +exports.fromBase64 = function (base64) { + return new Converter(base64, { isEncoded: true }); +}; + +exports.fromComment = function (comment) { + comment = comment + .replace(/^\/\*/g, '//') + .replace(/\*\/$/g, ''); + + return new Converter(comment, { isEncoded: true, hasComment: true }); +}; + +exports.fromMapFileComment = function (comment, dir) { + return new Converter(comment, { commentFileDir: dir, isFileComment: true, isJSON: true }); +}; + +// Finds last sourcemap comment in file or returns null if none was found +exports.fromSource = function (content) { + var m = content.match(commentRx); + return m ? exports.fromComment(m.pop()) : null; +}; + +// Finds last sourcemap comment in file or returns null if none was found +exports.fromMapFileSource = function (content, dir) { + var m = content.match(mapFileCommentRx); + return m ? exports.fromMapFileComment(m.pop(), dir) : null; +}; + +exports.removeComments = function (src) { + return src.replace(commentRx, ''); +}; + +exports.removeMapFileComments = function (src) { + return src.replace(mapFileCommentRx, ''); +}; + +exports.generateMapFileComment = function (file, options) { + var data = 'sourceMappingURL=' + file; + return options && options.multiline ? '/*# ' + data + ' */' : '//# ' + data; +}; + +Object.defineProperty(exports, 'commentRegex', { + get: function getCommentRegex () { + return commentRx; + } +}); + +Object.defineProperty(exports, 'mapFileCommentRegex', { + get: function getMapFileCommentRegex () { + return mapFileCommentRx; + } +}); diff --git a/lib/css-base.js b/lib/css-base.js index 1e240e59..71b9e7dc 100644 --- a/lib/css-base.js +++ b/lib/css-base.js @@ -53,7 +53,7 @@ function cssWithMappingToString(item) { if (!cssMapping) { return content; } - var convertSourceMap = require('convert-source-map'); + var convertSourceMap = require('./convert-source-map'); var sourceMapping = convertSourceMap.fromObject(cssMapping).toComment({multiline: true}); var sourceURLs = cssMapping.sources.map(function (source) { return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */' diff --git a/package.json b/package.json index c1ab06e7..665f3045 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "css-loader", - "version": "0.27.0", + "version": "0.27.1", "author": "Tobias Koppers @sokra", "description": "css loader module for webpack", "engines": { diff --git a/test/camelCaseTest.js b/test/camelCaseTest.js index 5c669e84..a9991b76 100644 --- a/test/camelCaseTest.js +++ b/test/camelCaseTest.js @@ -5,6 +5,7 @@ var testRaw = require("./helpers").testRaw; describe("camelCase", function() { var css = ".btn-info_is-disabled { color: blue; }"; + var mixedCss = ".btn-info_is-disabled { color: blue; } .simple { color: red; }"; var exports = { with: [ [1, "._1L-rnCOXCE_7H94L5XT4uB { color: blue; }", ""] @@ -16,24 +17,24 @@ describe("camelCase", function() { [1, "._1L-rnCOXCE_7H94L5XT4uB { color: blue; }", ""] ], withoutOnly: [ - [1, "._1L-rnCOXCE_7H94L5XT4uB { color: blue; }", ""] + [1, "._1L-rnCOXCE_7H94L5XT4uB { color: blue; } .KKtodWG-IuEaequFjAsoJ { color: red; }", ""] ], dashesOnly: [ - [1, "._1L-rnCOXCE_7H94L5XT4uB { color: blue; }", ""] + [1, "._1L-rnCOXCE_7H94L5XT4uB { color: blue; } .KKtodWG-IuEaequFjAsoJ { color: red; }", ""] ] }; exports.with.locals = {'btn-info_is-disabled': '_1L-rnCOXCE_7H94L5XT4uB'}; exports.without.locals = {btnInfoIsDisabled: '_1L-rnCOXCE_7H94L5XT4uB', 'btn-info_is-disabled': '_1L-rnCOXCE_7H94L5XT4uB'}; exports.dashes.locals = {btnInfo_isDisabled: '_1L-rnCOXCE_7H94L5XT4uB', 'btn-info_is-disabled': '_1L-rnCOXCE_7H94L5XT4uB'}; - exports.withoutOnly.locals = {btnInfoIsDisabled: '_1L-rnCOXCE_7H94L5XT4uB'}; - exports.dashesOnly.locals = {btnInfo_isDisabled: '_1L-rnCOXCE_7H94L5XT4uB'}; + exports.withoutOnly.locals = {btnInfoIsDisabled: '_1L-rnCOXCE_7H94L5XT4uB', simple: 'KKtodWG-IuEaequFjAsoJ'}; + exports.dashesOnly.locals = {btnInfo_isDisabled: '_1L-rnCOXCE_7H94L5XT4uB', simple: 'KKtodWG-IuEaequFjAsoJ'}; test("with", css, exports.with, "?modules"); test("without", css, exports.without, "?modules&camelCase"); test("dashes", css, exports.dashes, "?modules&camelCase=dashes"); // Remove this option in v1.0.0 and make the removal of the original classname the default behaviour. See #440. - test("withoutOnly", css, exports.withoutOnly, "?modules&camelCase=only"); + test("withoutOnly", mixedCss, exports.withoutOnly, "?modules&camelCase=only"); // Remove this option in v1.0.0 and make the removal of the original classname the default behaviour. See #440. - test("dashesOnly", css, exports.dashesOnly, "?modules&camelCase=dashesOnly"); + test("dashesOnly", mixedCss, exports.dashesOnly, "?modules&camelCase=dashesOnly"); testRaw("withoutRaw", '.a {}', 'exports.locals = {\n\t"a": "_1buUQJccBRS2-2i27LCoDf"\n};', "?modules&camelCase"); testRaw("dashesRaw", '.a {}', 'exports.locals = {\n\t"a": "_1buUQJccBRS2-2i27LCoDf"\n};', "?modules&camelCase=dashes");