1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const cli_utils_1 = require("@design-systems/cli-utils");
|
5 | const tapable_1 = require("tapable");
|
6 | const path_1 = tslib_1.__importDefault(require("path"));
|
7 | const dedent_1 = tslib_1.__importDefault(require("dedent"));
|
8 | const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
9 | const minimatch_1 = tslib_1.__importDefault(require("minimatch"));
|
10 | const chokidar_1 = require("chokidar");
|
11 | const fast_glob_1 = tslib_1.__importDefault(require("fast-glob"));
|
12 | const pretty_bytes_1 = tslib_1.__importDefault(require("pretty-bytes"));
|
13 | const pretty_ms_1 = tslib_1.__importDefault(require("pretty-ms"));
|
14 | const clean_css_1 = tslib_1.__importDefault(require("clean-css"));
|
15 | const utils_1 = require("./utils");
|
16 | const babel_1 = tslib_1.__importStar(require("./babel"));
|
17 | const postcss_1 = tslib_1.__importStar(require("./postcss"));
|
18 | const command_1 = require("./command");
|
19 | const typescript_1 = tslib_1.__importDefault(require("./typescript"));
|
20 | const POSTCSS_CONFIG = path_1.default.join(__dirname, './configs/postcss.config.js');
|
21 |
|
22 | function match(file, globs) {
|
23 | const globArr = Array.isArray(globs) ? globs : [globs];
|
24 | return globArr.find(currGlob => minimatch_1.default(file, currGlob));
|
25 | }
|
26 | var postcss_2 = require("./postcss");
|
27 | exports.getPostCssConfig = postcss_2.getPostCssConfig;
|
28 | exports.getPostCssConfigSync = postcss_2.getPostCssConfigSync;
|
29 | var babel_2 = require("./babel");
|
30 | exports.getBabelConfig = babel_2.getBabelConfig;
|
31 | exports.getBabelOptions = babel_2.getBabelOptions;
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | class BuildPlugin {
|
40 | constructor() {
|
41 | this.hooks = {
|
42 | processCSSFiles: new tapable_1.SyncBailHook(['cssFiles'])
|
43 | };
|
44 | this.logger = cli_utils_1.createLogger({ scope: 'build' });
|
45 | this.cssFiles = new Map();
|
46 | this.buildArgs = command_1.defaults;
|
47 | this.generateCSS = async () => {
|
48 | if (this.cssFiles.size === 0) {
|
49 | return;
|
50 | }
|
51 | this.logger.trace('Generating merged css...');
|
52 | const outFile = path_1.default.join(this.buildArgs.outputDirectory, this.buildArgs.cssMain);
|
53 | const clean = new clean_css_1.default({
|
54 | sourceMap: true,
|
55 | sourceMapInlineSources: true,
|
56 | level: 2,
|
57 | rebase: false,
|
58 | rebaseTo: this.buildArgs.outputDirectory
|
59 | });
|
60 | const cssToMinify = Array.from(this.cssFiles.entries()).map(([file, { css, map }]) => ({
|
61 | [file]: {
|
62 | styles: css,
|
63 | sourceMap: map
|
64 | .toString()
|
65 | .replace(/<input css (\d+)>/g, '../src/<input css $1>')
|
66 | }
|
67 | }));
|
68 | const minified = clean.minify(this.hooks.processCSSFiles.call(cssToMinify) || cssToMinify);
|
69 | if (minified.errors.length > 0) {
|
70 | this.logger.debug('Errors\n\n', minified.errors.join('\n '), '\n');
|
71 | }
|
72 | if (minified.warnings.length > 0) {
|
73 | this.logger.debug('Warnings\n\n', minified.warnings.join('\n '), '\n');
|
74 | }
|
75 | const duration = pretty_ms_1.default(minified.stats.timeSpent);
|
76 | const efficiency = minified.stats.efficiency.toPrecision(2);
|
77 | const difference = pretty_bytes_1.default(minified.stats.originalSize - minified.stats.minifiedSize);
|
78 | this.logger.debug(dedent_1.default `
|
79 | CSS minification results:
|
80 |
|
81 | Original: ${pretty_bytes_1.default(minified.stats.originalSize)}
|
82 | Minified: ${pretty_bytes_1.default(minified.stats.minifiedSize)}
|
83 |
|
84 | ${difference} -${efficiency}% ${duration}\n
|
85 | `);
|
86 | await fs_extra_1.default.outputFile(`${outFile}.map`, minified.sourceMap);
|
87 | await fs_extra_1.default.outputFile(outFile, `${minified.styles}\n/*# sourceMappingURL=${this.buildArgs.cssMain}.map */`);
|
88 | this.logger.complete('Generated merged css');
|
89 | };
|
90 | this.getFileList = async () => fast_glob_1.default(`${this.buildArgs.inputDirectory}/**/*.*`);
|
91 | this.transformFile = async (file) => {
|
92 | const { inputDirectory, outputDirectory } = this.buildArgs;
|
93 | if (this.isIgnored(file)) {
|
94 | return;
|
95 | }
|
96 | switch (path_1.default.extname(file)) {
|
97 | case '.css':
|
98 | this.logger.pending(`CSS -> ${file}`);
|
99 | return (postcss_1.default({
|
100 | inFile: file,
|
101 | inDir: inputDirectory,
|
102 | outDir: outputDirectory,
|
103 | configFile: POSTCSS_CONFIG,
|
104 | watch: this.buildArgs.watch
|
105 | })
|
106 |
|
107 | .then(css => css && this.cssFiles.set(path_1.default.resolve(file), css)));
|
108 | case '.js':
|
109 | case '.jsx':
|
110 | case '.ts':
|
111 | case '.tsx':
|
112 | this.logger.pending(`transpiling -> ${file}`);
|
113 | return babel_1.default(file, inputDirectory, outputDirectory, babel_1.BABEL_CONFIG);
|
114 | default:
|
115 | this.logger.pending(`copy -> ${file}`);
|
116 | await fs_extra_1.default.copy(file, path_1.default.join(utils_1.getOutPath(inputDirectory, file, path_1.default.join(outputDirectory, 'cjs')), path_1.default.basename(file)));
|
117 | await fs_extra_1.default.copy(file, path_1.default.join(utils_1.getOutPath(inputDirectory, file, path_1.default.join(outputDirectory, 'esm')), path_1.default.basename(file)));
|
118 | break;
|
119 | }
|
120 | };
|
121 | this.onAddOrChanged = async (file) => {
|
122 | this.logger.info(`File changed: ${file}`);
|
123 | const extname = path_1.default.extname(file);
|
124 | try {
|
125 | const result = await this.transformFile(file);
|
126 | if (result &&
|
127 | 'success' in result &&
|
128 | result.success &&
|
129 | (extname === '.ts' || extname === '.tsx')) {
|
130 | await this.typescriptCompiler.buildTypes(true);
|
131 | }
|
132 | }
|
133 | catch (error) {
|
134 | this.logger.trace(error);
|
135 | }
|
136 | if (file.includes('theme.')) {
|
137 | await Promise.all([...this.cssFiles.keys()].map(async (cssFile) => this.transformFile(cssFile)));
|
138 | }
|
139 | if (extname === '.css' || file.includes('theme.')) {
|
140 | await this.generateCSS();
|
141 | await this.typescriptCompiler.buildTypes(true);
|
142 | }
|
143 | if (file.includes('tsconfig.json')) {
|
144 | await this.typescriptCompiler.buildTypes(true);
|
145 | }
|
146 | this.logger.watch('Watching for changes');
|
147 | };
|
148 | this.onDelete = async (file) => {
|
149 | const { inputDirectory, outputDirectory } = this.buildArgs;
|
150 | this.logger.info(`File deleted: ${file}`);
|
151 | switch (path_1.default.extname(file)) {
|
152 | case '.css': {
|
153 | const fName = path_1.default.resolve(file);
|
154 | if (this.cssFiles.has(fName)) {
|
155 | this.cssFiles.delete(fName);
|
156 | }
|
157 | const cssPath = postcss_1.getCSSPath(file, inputDirectory, outputDirectory);
|
158 | await Promise.all([
|
159 | this.generateCSS(),
|
160 | fs_extra_1.default.remove(cssPath),
|
161 | fs_extra_1.default.remove(`${cssPath}.json`)
|
162 | ]);
|
163 | return;
|
164 | }
|
165 | case '.js':
|
166 | case '.jsx':
|
167 | case '.ts':
|
168 | case '.tsx': {
|
169 | const { cjsFile, mjsFile } = babel_1.getJSOutputFiles(file, inputDirectory, outputDirectory);
|
170 | await Promise.all([fs_extra_1.default.remove(cjsFile), fs_extra_1.default.remove(mjsFile)]);
|
171 | return;
|
172 | }
|
173 | default:
|
174 | await fs_extra_1.default.remove(path_1.default.join(utils_1.getOutPath(inputDirectory, file, outputDirectory), path_1.default.basename(file)));
|
175 | }
|
176 | };
|
177 | this.isIgnored = (filename) => match(`./${filename}`, this.buildArgs.ignore);
|
178 | this.watch = async () => {
|
179 | const watcher = chokidar_1.watch([this.buildArgs.inputDirectory, 'tsconfig.json'], {
|
180 | awaitWriteFinish: true,
|
181 | ignoreInitial: false
|
182 | });
|
183 |
|
184 | const withIgnore = (fn) => (file) => {
|
185 | if (this.isIgnored(file)) {
|
186 | return;
|
187 | }
|
188 | fn(file);
|
189 | };
|
190 | return new Promise((resolve, reject) => {
|
191 | watcher.on('error', reject);
|
192 | watcher.on('ready', () => {
|
193 | this.logger.watch('Watching for changes');
|
194 | watcher.on('add', withIgnore(this.onAddOrChanged));
|
195 | watcher.on('change', withIgnore(this.onAddOrChanged));
|
196 | watcher.on('unlink', withIgnore(this.onDelete));
|
197 | });
|
198 | });
|
199 | };
|
200 | }
|
201 | async run(args) {
|
202 | this.buildArgs = Object.assign(Object.assign({}, this.buildArgs), args);
|
203 | this.typescriptCompiler = new typescript_1.default(this.buildArgs);
|
204 | const startTime = Date.now();
|
205 | const { watch, outputDirectory, ignore } = this.buildArgs;
|
206 |
|
207 |
|
208 | if (!watch && !fs_extra_1.default.existsSync(outputDirectory)) {
|
209 | await fs_extra_1.default.remove('tsconfig.tsbuildinfo');
|
210 | }
|
211 | const files = await this.getFileList();
|
212 |
|
213 | const transformed = files
|
214 | .map(nextFile => !match(nextFile, ignore) && this.transformFile(nextFile))
|
215 | .filter((i) => typeof i !== 'boolean');
|
216 |
|
217 | const results = await Promise.all(transformed);
|
218 | await this.generateCSS();
|
219 | const hasTSFiles = files.some(file => path_1.default.extname(file).startsWith('.ts'));
|
220 | if (hasTSFiles) {
|
221 | if (results.find(result => result && result.success !== undefined && !result.success)) {
|
222 | this.logger.info('Skipping type build. Build errors found.');
|
223 | }
|
224 | else {
|
225 | await this.typescriptCompiler.buildTypes(watch);
|
226 | }
|
227 | }
|
228 | else {
|
229 | this.logger.info('Skipping type build. No .ts(x) files found.');
|
230 | }
|
231 | const runtime = pretty_ms_1.default(Date.now() - startTime);
|
232 | this.logger.done(`Transformed ${transformed.length} files in ${runtime} seconds`);
|
233 | if (watch) {
|
234 | return this.watch();
|
235 | }
|
236 | }
|
237 | }
|
238 | exports.default = BuildPlugin;
|
239 |
|
\ | No newline at end of file |