UNPKG

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