1 | var List = require('css-tree').List;
|
---|
2 | var clone = require('css-tree').clone;
|
---|
3 | var usageUtils = require('./usage');
|
---|
4 | var clean = require('./clean');
|
---|
5 | var replace = require('./replace');
|
---|
6 | var restructure = require('./restructure');
|
---|
7 | var walk = require('css-tree').walk;
|
---|
8 |
|
---|
9 | function readChunk(children, specialComments) {
|
---|
10 | var buffer = new List();
|
---|
11 | var nonSpaceTokenInBuffer = false;
|
---|
12 | var protectedComment;
|
---|
13 |
|
---|
14 | children.nextUntil(children.head, function(node, item, list) {
|
---|
15 | if (node.type === 'Comment') {
|
---|
16 | if (!specialComments || node.value.charAt(0) !== '!') {
|
---|
17 | list.remove(item);
|
---|
18 | return;
|
---|
19 | }
|
---|
20 |
|
---|
21 | if (nonSpaceTokenInBuffer || protectedComment) {
|
---|
22 | return true;
|
---|
23 | }
|
---|
24 |
|
---|
25 | list.remove(item);
|
---|
26 | protectedComment = node;
|
---|
27 | return;
|
---|
28 | }
|
---|
29 |
|
---|
30 | if (node.type !== 'WhiteSpace') {
|
---|
31 | nonSpaceTokenInBuffer = true;
|
---|
32 | }
|
---|
33 |
|
---|
34 | buffer.insert(list.remove(item));
|
---|
35 | });
|
---|
36 |
|
---|
37 | return {
|
---|
38 | comment: protectedComment,
|
---|
39 | stylesheet: {
|
---|
40 | type: 'StyleSheet',
|
---|
41 | loc: null,
|
---|
42 | children: buffer
|
---|
43 | }
|
---|
44 | };
|
---|
45 | }
|
---|
46 |
|
---|
47 | function compressChunk(ast, firstAtrulesAllowed, num, options) {
|
---|
48 | options.logger('Compress block #' + num, null, true);
|
---|
49 |
|
---|
50 | var seed = 1;
|
---|
51 |
|
---|
52 | if (ast.type === 'StyleSheet') {
|
---|
53 | ast.firstAtrulesAllowed = firstAtrulesAllowed;
|
---|
54 | ast.id = seed++;
|
---|
55 | }
|
---|
56 |
|
---|
57 | walk(ast, {
|
---|
58 | visit: 'Atrule',
|
---|
59 | enter: function markScopes(node) {
|
---|
60 | if (node.block !== null) {
|
---|
61 | node.block.id = seed++;
|
---|
62 | }
|
---|
63 | }
|
---|
64 | });
|
---|
65 | options.logger('init', ast);
|
---|
66 |
|
---|
67 | // remove redundant
|
---|
68 | clean(ast, options);
|
---|
69 | options.logger('clean', ast);
|
---|
70 |
|
---|
71 | // replace nodes for shortened forms
|
---|
72 | replace(ast, options);
|
---|
73 | options.logger('replace', ast);
|
---|
74 |
|
---|
75 | // structure optimisations
|
---|
76 | if (options.restructuring) {
|
---|
77 | restructure(ast, options);
|
---|
78 | }
|
---|
79 |
|
---|
80 | return ast;
|
---|
81 | }
|
---|
82 |
|
---|
83 | function getCommentsOption(options) {
|
---|
84 | var comments = 'comments' in options ? options.comments : 'exclamation';
|
---|
85 |
|
---|
86 | if (typeof comments === 'boolean') {
|
---|
87 | comments = comments ? 'exclamation' : false;
|
---|
88 | } else if (comments !== 'exclamation' && comments !== 'first-exclamation') {
|
---|
89 | comments = false;
|
---|
90 | }
|
---|
91 |
|
---|
92 | return comments;
|
---|
93 | }
|
---|
94 |
|
---|
95 | function getRestructureOption(options) {
|
---|
96 | if ('restructure' in options) {
|
---|
97 | return options.restructure;
|
---|
98 | }
|
---|
99 |
|
---|
100 | return 'restructuring' in options ? options.restructuring : true;
|
---|
101 | }
|
---|
102 |
|
---|
103 | function wrapBlock(block) {
|
---|
104 | return new List().appendData({
|
---|
105 | type: 'Rule',
|
---|
106 | loc: null,
|
---|
107 | prelude: {
|
---|
108 | type: 'SelectorList',
|
---|
109 | loc: null,
|
---|
110 | children: new List().appendData({
|
---|
111 | type: 'Selector',
|
---|
112 | loc: null,
|
---|
113 | children: new List().appendData({
|
---|
114 | type: 'TypeSelector',
|
---|
115 | loc: null,
|
---|
116 | name: 'x'
|
---|
117 | })
|
---|
118 | })
|
---|
119 | },
|
---|
120 | block: block
|
---|
121 | });
|
---|
122 | }
|
---|
123 |
|
---|
124 | module.exports = function compress(ast, options) {
|
---|
125 | ast = ast || { type: 'StyleSheet', loc: null, children: new List() };
|
---|
126 | options = options || {};
|
---|
127 |
|
---|
128 | var compressOptions = {
|
---|
129 | logger: typeof options.logger === 'function' ? options.logger : function() {},
|
---|
130 | restructuring: getRestructureOption(options),
|
---|
131 | forceMediaMerge: Boolean(options.forceMediaMerge),
|
---|
132 | usage: options.usage ? usageUtils.buildIndex(options.usage) : false
|
---|
133 | };
|
---|
134 | var specialComments = getCommentsOption(options);
|
---|
135 | var firstAtrulesAllowed = true;
|
---|
136 | var input;
|
---|
137 | var output = new List();
|
---|
138 | var chunk;
|
---|
139 | var chunkNum = 1;
|
---|
140 | var chunkChildren;
|
---|
141 |
|
---|
142 | if (options.clone) {
|
---|
143 | ast = clone(ast);
|
---|
144 | }
|
---|
145 |
|
---|
146 | if (ast.type === 'StyleSheet') {
|
---|
147 | input = ast.children;
|
---|
148 | ast.children = output;
|
---|
149 | } else {
|
---|
150 | input = wrapBlock(ast);
|
---|
151 | }
|
---|
152 |
|
---|
153 | do {
|
---|
154 | chunk = readChunk(input, Boolean(specialComments));
|
---|
155 | compressChunk(chunk.stylesheet, firstAtrulesAllowed, chunkNum++, compressOptions);
|
---|
156 | chunkChildren = chunk.stylesheet.children;
|
---|
157 |
|
---|
158 | if (chunk.comment) {
|
---|
159 | // add \n before comment if there is another content in output
|
---|
160 | if (!output.isEmpty()) {
|
---|
161 | output.insert(List.createItem({
|
---|
162 | type: 'Raw',
|
---|
163 | value: '\n'
|
---|
164 | }));
|
---|
165 | }
|
---|
166 |
|
---|
167 | output.insert(List.createItem(chunk.comment));
|
---|
168 |
|
---|
169 | // add \n after comment if chunk is not empty
|
---|
170 | if (!chunkChildren.isEmpty()) {
|
---|
171 | output.insert(List.createItem({
|
---|
172 | type: 'Raw',
|
---|
173 | value: '\n'
|
---|
174 | }));
|
---|
175 | }
|
---|
176 | }
|
---|
177 |
|
---|
178 | if (firstAtrulesAllowed && !chunkChildren.isEmpty()) {
|
---|
179 | var lastRule = chunkChildren.last();
|
---|
180 |
|
---|
181 | if (lastRule.type !== 'Atrule' ||
|
---|
182 | (lastRule.name !== 'import' && lastRule.name !== 'charset')) {
|
---|
183 | firstAtrulesAllowed = false;
|
---|
184 | }
|
---|
185 | }
|
---|
186 |
|
---|
187 | if (specialComments !== 'exclamation') {
|
---|
188 | specialComments = false;
|
---|
189 | }
|
---|
190 |
|
---|
191 | output.appendList(chunkChildren);
|
---|
192 | } while (!input.isEmpty());
|
---|
193 |
|
---|
194 | return {
|
---|
195 | ast: ast
|
---|
196 | };
|
---|
197 | };
|
---|