rework.js
4.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*
* MIT License http://opensource.org/licenses/MIT
* Author: Ben Holloway @bholloway
*/
'use strict';
var path = require('path'),
convert = require('convert-source-map');
var fileProtocol = require('../file-protocol');
var rework = requireOptionalPeerDependency('rework'),
visit = requireOptionalPeerDependency('rework-visit');
/**
* Process the given CSS content into reworked CSS content.
*
* @param {string} sourceFile The absolute path of the file being processed
* @param {string} sourceContent CSS content without source-map
* @param {{outputSourceMap: boolean, transformDeclaration:function, absSourceMap:object,
* sourceMapConsumer:object}} params Named parameters
* @return {{content: string, map: object}} Reworked CSS and optional source-map
*/
function process(sourceFile, sourceContent, params) {
// embed source-map in css
// prepend file protocol to all sources to avoid problems with source map
var contentWithMap = sourceContent + (
params.absSourceMap ?
convert.fromObject(fileProtocol.prepend(params.absSourceMap)).toComment({multiline: true}) :
''
);
// need to prepend file protocol to source as well to avoid problems with source map
var reworked = rework(contentWithMap, {source: fileProtocol.prepend(sourceFile)})
.use(reworkPlugin)
.toString({
sourcemap : params.outputSourceMap,
sourcemapAsObject: params.outputSourceMap
});
// complete with source-map
if (params.outputSourceMap) {
return {
content: reworked.code,
map : fileProtocol.remove(reworked.map)
};
}
// complete without source-map
else {
return {
content: reworked,
map : null
};
}
/**
* Plugin for css rework that follows SASS transpilation.
*
* @param {object} stylesheet AST for the CSS output from SASS
*/
function reworkPlugin(stylesheet) {
// visit each node (selector) in the stylesheet recursively using the official utility method
// each node may have multiple declarations
visit(stylesheet, function visitor(declarations) {
if (declarations) {
declarations.forEach(eachDeclaration);
}
});
/**
* Process a declaration from the syntax tree.
* @param declaration
*/
function eachDeclaration(declaration) {
var isValid = declaration.value && (declaration.value.indexOf('url') >= 0);
if (isValid) {
declaration.value = params.transformDeclaration(declaration.value, getPathsAtChar);
}
/**
* Create a hash of base path strings.
*
* Position in the declaration is not supported since rework does not refine sourcemaps to this detail.
*
* @throws Error on invalid source map
* @returns {{selector:string, property:string}} Hash of base path strings
*/
function getPathsAtChar() {
var posSelector = declaration.parent && declaration.parent.position.start,
posProperty = declaration.position.start;
var result = {
property: positionToOriginalDirectory(posProperty),
selector: positionToOriginalDirectory(posSelector)
};
var isValid = [result.property, result.selector].every(Boolean);
if (isValid) {
return result;
}
else if (params.sourceMapConsumer) {
throw new Error('source-map information is not available at url() declaration');
} else {
throw new Error('a valid source-map is not present (ensure preceding loaders output a source-map)');
}
}
}
}
/**
* Given an apparent position find the directory of the original file.
*
* @param startPosApparent {{line: number, column: number}}
* @returns {false|string} Directory of original file or false on invalid
*/
function positionToOriginalDirectory(startPosApparent) {
// reverse the original source-map to find the original source file before transpilation
var startPosOriginal =
!!params.sourceMapConsumer &&
params.sourceMapConsumer.originalPositionFor(startPosApparent);
// we require a valid directory for the specified file
var directory =
!!startPosOriginal &&
!!startPosOriginal.source &&
fileProtocol.remove(path.dirname(startPosOriginal.source));
return directory;
}
}
module.exports = process;
/**
* Require the given filename but fail with an error that `requireOptionalPeerDependencies` must be installed.
*
* @param moduleName The module to require
* @returns {*} The module
* @throws Error when module is not found
*/
function requireOptionalPeerDependency(moduleName) {
try {
return require(moduleName);
}
catch (error) {
if (error.message === 'Cannot find module \'' + moduleName + '\'') {
throw new Error('to use the "rework" engine you must install the optionalPeerDependencies');
}
else {
throw error;
}
}
}