1 | "use strict";
|
2 | const fs = require('fs-extra');
|
3 |
|
4 | const Utils = require('./utils.js');
|
5 | const Variables = require('./variables.js');
|
6 | const Selectors = require('./selectors.js');
|
7 | const Path = require('path');
|
8 | const CSS = require('css');
|
9 |
|
10 | const MinifyCSS = require('./zc-minify-css.js');
|
11 | const MinifyHTML = require('./zc-minify-html.js');
|
12 | const MinifyXML = require('./zc-minify-xml.js');
|
13 | const MinifyJS = require('./zc-minify-js.js');
|
14 | const MinifyJSON = require('./zc-minify-json.js');
|
15 |
|
16 | module.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 |
|
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 |
|
92 |
|
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 |
|
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 |
|
108 | settings.files.forEach(path => {
|
109 |
|
110 | |
111 |
|
112 |
|
113 | let extension = path.slice(path.lastIndexOf('.') + 1 || path.length);
|
114 |
|
115 | let data = fs.readFileSync(SRC_DIR + path, 'utf8');
|
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 |
|
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 | };
|