UNPKG

11.4 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const cli_utils_1 = require("@design-systems/cli-utils");
5const tapable_1 = require("tapable");
6const path_1 = tslib_1.__importDefault(require("path"));
7const dedent_1 = tslib_1.__importDefault(require("dedent"));
8const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
9const minimatch_1 = tslib_1.__importDefault(require("minimatch"));
10const chokidar_1 = require("chokidar");
11const fast_glob_1 = tslib_1.__importDefault(require("fast-glob"));
12const pretty_bytes_1 = tslib_1.__importDefault(require("pretty-bytes"));
13const pretty_ms_1 = tslib_1.__importDefault(require("pretty-ms"));
14const clean_css_1 = tslib_1.__importDefault(require("clean-css"));
15const utils_1 = require("./utils");
16const babel_1 = tslib_1.__importStar(require("./babel"));
17const postcss_1 = tslib_1.__importStar(require("./postcss"));
18const command_1 = require("./command");
19const typescript_1 = tslib_1.__importDefault(require("./typescript"));
20const POSTCSS_CONFIG = path_1.default.join(__dirname, './configs/postcss.config.js');
21/** Find all matching globs. */
22function match(file, globs) {
23 const globArr = Array.isArray(globs) ? globs : [globs];
24 return globArr.find(currGlob => minimatch_1.default(file, currGlob));
25}
26var postcss_2 = require("./postcss");
27exports.getPostCssConfig = postcss_2.getPostCssConfig;
28exports.getPostCssConfigSync = postcss_2.getPostCssConfigSync;
29var babel_2 = require("./babel");
30exports.getBabelConfig = babel_2.getBabelConfig;
31exports.getBabelOptions = babel_2.getBabelOptions;
32/**
33 * Build looks for js and css files in src/ and outputs a CJS, ESM, and CSS builds to /dist.
34 * By default the same folder structure is maintained.
35 *
36 * - css files are replaced with their js equivalent.
37 * - ESM builds use the .mjs extension, CJS uses .js.
38 */
39class 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 // Save the CSS output for merging later
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 /** Only run a function for non-ignored files. */
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 } = this.buildArgs;
206 // Since watching happens across everything in parallel,
207 // leave any old build there for now
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 // Kick off all of the transforms in parallel
213 const transformed = files
214 .map(nextFile => !this.isIgnored(nextFile) && this.transformFile(nextFile))
215 .filter((i) => typeof i !== 'boolean');
216 // We need to wait until all the CSS is done before merging them
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}
238exports.default = BuildPlugin;
239//# sourceMappingURL=index.js.map
\No newline at end of file