Skip to content

Commit

Permalink
fix: normalize sources in source maps
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi committed Sep 3, 2020
1 parent 62e268a commit 877d99a
Show file tree
Hide file tree
Showing 10 changed files with 555 additions and 102 deletions.
8 changes: 4 additions & 4 deletions src/LessError.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ class LessError extends Error {
this.hideStack = true;
}

static getFileExcerptIfPossible(lessErr) {
if (typeof lessErr.extract === 'undefined') {
static getFileExcerptIfPossible(lessError) {
if (typeof lessError.extract === 'undefined') {
return [];
}

const excerpt = lessErr.extract.slice(0, 2);
const column = Math.max(lessErr.column - 1, 0);
const excerpt = lessError.extract.slice(0, 2);
const column = Math.max(lessError.column - 1, 0);

if (typeof excerpt[0] === 'undefined') {
excerpt.shift();
Expand Down
72 changes: 46 additions & 26 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';

import schema from './options.json';
import { getLessOptions, isUnsupportedUrl } from './utils';
import { getLessOptions, isUnsupportedUrl, normalizeSourceMap } from './utils';
import LessError from './LessError';

function lessLoader(source) {
async function lessLoader(source) {
const options = getOptions(this);

validateOptions(schema, options, {
Expand All @@ -19,6 +19,14 @@ function lessLoader(source) {

const callback = this.async();
const lessOptions = getLessOptions(this, options);
const useSourceMap =
typeof options.sourceMap === 'boolean' ? options.sourceMap : this.sourceMap;

if (useSourceMap) {
lessOptions.sourceMap = {
outputSourceFiles: true,
};
}

let data = source;

Expand All @@ -29,30 +37,42 @@ function lessLoader(source) {
: `${options.additionalData}\n${data}`;
}

less
.render(data, lessOptions)
.then(({ css, map, imports }) => {
imports.forEach((item) => {
if (isUnsupportedUrl(item)) {
return;
}

// `less` return forward slashes on windows when `webpack` resolver return an absolute windows path in `WebpackFileManager`
// Ref: https://github.com/webpack-contrib/less-loader/issues/357
this.addDependency(path.normalize(item));
});

callback(null, css, typeof map === 'string' ? JSON.parse(map) : map);
})
.catch((lessError) => {
if (lessError.filename) {
// `less` return forward slashes on windows when `webpack` resolver return an absolute windows path in `WebpackFileManager`
// Ref: https://github.com/webpack-contrib/less-loader/issues/357
this.addDependency(path.normalize(lessError.filename));
}

callback(new LessError(lessError));
});
let result;

try {
result = await less.render(data, lessOptions);
} catch (error) {
if (error.filename) {
// `less` return forward slashes on windows when `webpack` resolver return an absolute windows path in `WebpackFileManager`
// Ref: https://github.com/webpack-contrib/less-loader/issues/357
this.addDependency(path.normalize(error.filename));
}

callback(new LessError(error));

return;
}

const { css, imports } = result;

imports.forEach((item) => {
if (isUnsupportedUrl(item)) {
return;
}

// `less` return forward slashes on windows when `webpack` resolver return an absolute windows path in `WebpackFileManager`
// Ref: https://github.com/webpack-contrib/less-loader/issues/357
this.addDependency(path.normalize(item));
});

let map =
typeof result.map === 'string' ? JSON.parse(result.map) : result.map;

if (map && useSourceMap) {
map = normalizeSourceMap(map, this.rootContext);
}

callback(null, css, map);
}

export default lessLoader;
45 changes: 27 additions & 18 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ const trailingSlash = /[/\\]$/;
// This somewhat changed in Less 3.x. Now the file name comes without the
// automatically added extension whereas the extension is passed in as `options.ext`.
// So, if the file name matches this regexp, we simply ignore the proposed extension.
const isModuleImport = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;
const IS_SPECIAL_MODULE_IMPORT = /^~[^/]+$/;

// `[drive_letter]:\` + `\\[server]\[sharename]\`
const isNativeWin32Path = /^[a-zA-Z]:[/\\]|^\\\\/i;
const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;

/**
* Creates a Less plugin that uses webpack's resolving engine that is provided by the loaderContext.
Expand All @@ -32,7 +32,7 @@ function createWebpackLessPlugin(loaderContext) {

class WebpackFileManager extends less.FileManager {
supports(filename) {
if (filename[0] === '/' || isNativeWin32Path.test(filename)) {
if (filename[0] === '/' || IS_NATIVE_WIN32_PATH.test(filename)) {
return true;
}

Expand Down Expand Up @@ -89,7 +89,7 @@ function createWebpackLessPlugin(loaderContext) {
let result;

try {
if (isModuleImport.test(filename)) {
if (IS_SPECIAL_MODULE_IMPORT.test(filename)) {
const error = new Error();

error.type = 'Next';
Expand Down Expand Up @@ -171,23 +171,12 @@ function getLessOptions(loaderContext, loaderOptions) {
},
});

const useSourceMap =
typeof loaderOptions.sourceMap === 'boolean'
? loaderOptions.sourceMap
: loaderContext.sourceMap;

if (useSourceMap) {
lessOptions.sourceMap = {
outputSourceFiles: true,
};
}

return lessOptions;
}

function isUnsupportedUrl(url) {
// Is Windows paths `c:\`
if (/^[a-zA-Z]:\\/.test(url)) {
// Is Windows path
if (IS_NATIVE_WIN32_PATH.test(url)) {
return false;
}

Expand All @@ -196,4 +185,24 @@ function isUnsupportedUrl(url) {
return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url);
}

export { getLessOptions, isUnsupportedUrl };
function normalizeSourceMap(map) {
const newMap = map;

// map.file is an optional property that provides the output filename.
// Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
// eslint-disable-next-line no-param-reassign
delete newMap.file;

// eslint-disable-next-line no-param-reassign
newMap.sourceRoot = '';

// `less` returns POSIX paths, that's why we need to transform them back to native paths.
// eslint-disable-next-line no-param-reassign
newMap.sources = newMap.sources.map((source) => {
return path.normalize(source);
});

return newMap;
}

export { getLessOptions, isUnsupportedUrl, normalizeSourceMap };
12 changes: 6 additions & 6 deletions test/__snapshots__/additionalData-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`additionalData option should work additionalData data as function: css 1`] = `
exports[`"additionalData" option should work additionalData data as function: css 1`] = `
"/* RelativePath: additional-data.less; */
.background {
color: coral;
Expand All @@ -11,17 +11,17 @@ exports[`additionalData option should work additionalData data as function: css
"
`;

exports[`additionalData option should work additionalData data as function: errors 1`] = `Array []`;
exports[`"additionalData" option should work additionalData data as function: errors 1`] = `Array []`;

exports[`additionalData option should work additionalData data as function: warnings 1`] = `Array []`;
exports[`"additionalData" option should work additionalData data as function: warnings 1`] = `Array []`;

exports[`additionalData option should work additionalData data as string: css 1`] = `
exports[`"additionalData" option should work additionalData data as string: css 1`] = `
".background {
color: coral;
}
"
`;

exports[`additionalData option should work additionalData data as string: errors 1`] = `Array []`;
exports[`"additionalData" option should work additionalData data as string: errors 1`] = `Array []`;

exports[`additionalData option should work additionalData data as string: warnings 1`] = `Array []`;
exports[`"additionalData" option should work additionalData data as string: warnings 1`] = `Array []`;
Loading

0 comments on commit 877d99a

Please sign in to comment.