UNPKG

6.89 kBJavaScriptView Raw
1"use strict";
2const fs = require('fs-extra');
3
4const Utils = require('./utils.js');
5const Variables = require('./variables.js');
6const Selectors = require('./selectors.js');
7const Path = require('path');
8const CSS = require('css');
9
10const MinifyCSS = require('./zc-minify-css.js');
11const MinifyHTML = require('./zc-minify-html.js');
12const MinifyXML = require('./zc-minify-xml.js');
13const MinifyJS = require('./zc-minify-js.js');
14const MinifyJSON = require('./zc-minify-json.js');
15
16module.exports = function(settings) {
17 settings = Utils.makeSettings({
18 minifySelectors: {
19 imports: [],
20 prefix: '',
21 exportTo: null,
22 },
23 minifyCSS: true,
24 minifyJS: {
25 harmony: false,
26 passes: 1,
27 debug: false,
28 },
29 minifyHTML: {
30 minifyInlineJS: false,
31 minifyInlineCSS: false,
32 eventAttributes: [ /^(dom|child|mutation|dispatch)?on/ ],
33 },
34 minifyXML: true,
35 minifyJSON: true,
36
37 onloadjs: code => code,
38 onloadcss: code => code,
39 onloadhtml: code => code,
40 onloadxml: code => code,
41 onloadjson: code => code,
42
43 onloadfile: (data, extension, path) => data,
44 onminifyfile: (data, extension, path) => data,
45
46 recursivelyImport: false,
47
48 source: './src/',
49 destination: './dist/',
50 extensions: {
51 js: ['js'],
52 css: ['css'],
53 html: ['html'],
54 xml: ['xml'],
55 json: ['json'],
56 },
57 files: [],
58 copy: [],
59 }, settings);
60
61 Utils.reportSettings(settings);
62
63 let configuredMinifyJS = function(code) {
64 for (let i = 0; i < settings.minifyJS.passes; i++) {
65 code = MinifyJS(code, {
66 debug: settings.minifyJS.debug,
67 harmony: settings.minifyJS.harmony,
68 // passes: settings.minifyJS.passes,
69 });
70 }
71 return code;
72 };
73
74 let selectors = settings.minifySelectors && new Selectors({
75 prepend: settings.minifySelectors.prefix,
76 externalSelectors: (() => {
77 let ret = { c: [], i: [] };
78 settings.minifySelectors.imports.forEach(file => {
79 let json = fs.readFileSync(file, 'utf8'),
80 pkg = JSON.parse(json);
81
82 [].push.apply(ret.c, pkg.c);
83 [].push.apply(ret.i, pkg.i);
84
85 console.log(`Imported selectors from ${file}`);
86 });
87 return ret;
88 })()
89 });
90
91 // Clean paths and add slash at the end
92 // NOTE: Undefined behaviour if source path doesn't exist; destination path doesn't have to exist
93 const SRC_DIR = fs.realpathSync(settings.source) + '/';
94 const DST_DIR = Path.normalize(settings.destination + '/');
95
96 console.log(`Source: ${SRC_DIR}`);
97 console.log(`Destination: ${DST_DIR}`);
98
99 // For files that only need to be copied and not altered, just copy them
100 settings.copy.forEach(fileToCopy => {
101 fs.copySync(SRC_DIR + fileToCopy, DST_DIR + fileToCopy);
102 console.log(`Copied ${fileToCopy}`);
103 });
104
105 var codeToMinify = [];
106
107 // First, get and replace all the selectors from all files
108 settings.files.forEach(path => {
109
110 /*
111 WARNING: File paths are relative to source and destination dirs
112 */
113 let extension = path.slice(path.lastIndexOf('.') + 1 || path.length); // js, html, css ...
114
115 let data = fs.readFileSync(SRC_DIR + path, 'utf8'); // The actual contents of the file
116
117 data = Utils.loadImports(data, SRC_DIR, path, settings.recursivelyImport);
118
119 let variables = new Variables();
120 data = data.replace(/<ZC-SET\[(.+?)\]\[(.+?)\]>/g, (_, name, value) => {
121 variables.set(name, value);
122 return '';
123 });
124 data = data.replace(/<ZC-USE\[(.+?)\]>/g, (_, name) => {
125 return variables.get(name);
126 });
127 variables.end();
128
129 data = settings.onloadfile(data, extension, path);
130
131 switch (Utils.getType(extension, settings.extensions)) {
132 case Utils.TYPE.JS:
133 data = settings.onloadjs(data);
134 if (settings.minifySelectors) {
135 data = data.replace(/['"]([#.])([a-z-]+)['"]/g, (_, prefix, sel) => {
136 let minifiedSel = selectors.minifySelector(sel, prefix == '.');
137 return `"${prefix}${minifiedSel}"`;
138 });
139 }
140 break;
141
142 case Utils.TYPE.CSS:
143 data = settings.onloadcss(data);
144 if (settings.minifySelectors) {
145 let cssAst = CSS.parse(data),
146 rulesToProcess = cssAst.stylesheet.rules.slice(),
147 rule;
148
149 while (rule = rulesToProcess.shift()) {
150 if (rule.type == 'media') {
151 rulesToProcess = rulesToProcess.concat(rule.rules);
152 }
153
154 if (rule.type == 'rule') {
155 rule.selectors.forEach((ruleSelector, idx) => {
156 rule.selectors[idx] = ruleSelector.replace(/([.#])([a-z-]+)(?=[ \[:,.#)]|$)/g, (_, prefix, sel) => {
157 let minifiedSel = selectors.minifySelector(sel, prefix == '.');
158 return `${prefix}${minifiedSel}`;
159 });
160 });
161 }
162 }
163 data = CSS.stringify(cssAst, {
164 compress: true,
165 });
166 }
167 break;
168
169 case Utils.TYPE.HTML:
170 data = settings.onloadhtml(data);
171 if (settings.minifySelectors) {
172 data = data.replace(/ (class|for|id)="([a-z- ]+)"/g, (_, type, sel) => {
173 let ret = ` ${type}="`;
174
175 if (type == 'class') {
176 sel.split(/\s+/).forEach(klass => {
177 klass = klass.trim();
178 if (klass) ret += selectors.minifySelector(klass, true) + ' ';
179 });
180 } else {
181 sel = sel.trim();
182 if (sel) ret += selectors.minifySelector(sel, false);
183 }
184
185 ret += '"';
186 return ret;
187 });
188 }
189 break;
190
191 case Utils.TYPE.XML:
192 data = settings.onloadxml(data);
193 break;
194
195 case Utils.TYPE.JSON:
196 data = settings.onloadjson(data);
197 break;
198 }
199
200 codeToMinify.push({
201 extension: extension,
202 file: path,
203 data: data
204 });
205 });
206
207 if (settings.minifySelectors && settings.minifySelectors.exportTo) {
208 let json = JSON.stringify(selectors.export()),
209 file = settings.minifySelectors.exportTo;
210 fs.writeFileSync(file, json);
211 console.log(`Exported selectors to ${file}`);
212 }
213
214 // Then, minify all the files and write them to their destinations
215 codeToMinify.forEach(({ extension, file, data }) => {
216 let folder = Utils.getFolder(file);
217
218 switch (Utils.getType(extension, settings.extensions)) {
219 case Utils.TYPE.JS:
220 if (settings.minifyJS) {
221 data = configuredMinifyJS(data);
222 }
223 break;
224
225 case Utils.TYPE.CSS:
226 if (settings.minifyCSS) {
227 data = MinifyCSS(data);
228 }
229 break;
230
231 case Utils.TYPE.HTML:
232 if (settings.minifyHTML) {
233 data = MinifyHTML(data, {
234 minifyJS: settings.minifyHTML.minifyInlineJS && function(data) {
235 data = settings.onloadjs(data);
236 return configuredMinifyJS(data);
237 },
238 minifyCSS: settings.minifyHTML.minifyInlineCSS && function(data) {
239 return MinifyCSS(data);
240 },
241 eventAttributes: settings.minifyHTML.eventAttributes,
242 });
243 }
244 break;
245
246 case Utils.TYPE.XML:
247 if (settings.minifyXML) {
248 data = MinifyXML(data);
249 }
250 break;
251
252 case Utils.TYPE.JSON:
253 if (settings.minifyJSON) {
254 data = MinifyJSON(data);
255 }
256 break;
257 }
258
259 data = settings.onminifyfile(data, extension, file);
260
261 fs.outputFileSync(DST_DIR + file, data);
262 console.log(`Processed ${file}`);
263 });
264};